Source: ptolemy/actor/lib/jjs/modules/crypto/crypto.js

// Copyright (c) 2016-2017 The Regents of the University of California.
// All rights reserved.
//
// Permission is hereby granted, without written agreement and without
// license or royalty fees, to use, copy, modify, and distribute this
// software and its documentation for any purpose, provided that the above
// copyright notice and the following two paragraphs appear in all copies
// of this software.
//
// IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
// FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
// ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
// THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
// THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
// PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
// CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
// ENHANCEMENTS, OR MODIFICATIONS.
//
//

/**
 * Module supporting crypto operations.
 *
 * @module crypto
 * @author Hokeun Kim
 * @version $$Id$$
 */

// Stop extra messages from jslint.  Note that there should be no
// space between the / and the * and global.
/*globals actor, console, exports, Java, require, util */
/*jshint globalstrict: true */
"use strict";

var CryptoHelper = Java.type('ptolemy.actor.lib.jjs.modules.crypto.CryptoHelper');
var EventEmitter = require('events').EventEmitter;

// FIXME: jshint warns: If a strict mode function is executed using function invocation, its 'this' value will be undefined.
this.helper = new CryptoHelper(actor, this);

function unsigendByteArrayEquals(a, b) {
    if (a.length != b.length) {
        return false;
    }
    for (var i = 0; i < a.length; i++) {
        // to compare unsigned byte values
        if ((a[i] & 0xff) != (b[i] & 0xff)) {
            return false;
        }
    }
    return true;
}


///////////////////////////////////////////////////////////////////
////                        Secure Hash                        ////

exports.getHashLength = function (hashAlgorithm) {
    return this.helper.getHashLength(hashAlgorithm);
};

exports.getKeySize = function (key) {
    return this.helper.getKeySize(key);
};

/** Calculate hash value using a secure hash function.
 */
exports.hash = function (input, hashAlgorithm) {
    return this.helper.hash(input, hashAlgorithm);
};

exports.hmac = function (input, key, hashAlgorithm) {
    return this.helper.hmac(input, key, hashAlgorithm);
};

///////////////////////////////////////////////////////////////////
////                 Public key cryptography                   ////

exports.parsePublicKeyCryptoSpec = function (cryptoSpec) {
    var cryptoSpecTokens = cryptoSpec.split(':');
    return {
        cipher: cryptoSpecTokens[0],
        sign: cryptoSpecTokens[1]
    };
};

exports.loadPrivateKey = function (filePath) {
    return this.helper.loadPrivateKey(filePath);
};

exports.loadPublicKey = function (filePath) {
    return this.helper.loadPublicKey(filePath);
};

exports.privateDecrypt = function (input, privateKey, cipherAlgorithm) {
    return this.helper.privateDecrypt(input, privateKey, cipherAlgorithm);
};

exports.publicEncrypt = function (input, publicKey, cipherAlgorithm) {
    return this.helper.publicEncrypt(input, publicKey, cipherAlgorithm);
};

exports.publicEncryptAndSign = function (input, publicKey, privateKey, cipherAlgorithm, signAlgorithm) {
    var encryptedData = exports.publicEncrypt(input, publicKey, cipherAlgorithm);
    var signature = exports.signWithPrivateKey(encryptedData, privateKey, signAlgorithm);
    return encryptedData.concat(signature);
};

/** Return a random byte array.
 */
exports.randomBytes = function (size) {
    return this.helper.randomBytes(size);
};

exports.signWithPrivateKey = function (input, privateKey, signAlgorithm) {
    return this.helper.signWithPrivateKey(input, privateKey, signAlgorithm);
};

exports.verifySignature = function (data, signature, publicKey, signAlgorithm) {
    return this.helper.verifySignature(data, signature, publicKey, signAlgorithm);
};

///////////////////////////////////////////////////////////////////
////                   Symmetric Cryptography                  ////

exports.parseSymmetricCryptoSpec = function (cryptoSpec) {
    var cryptoSpecTokens = cryptoSpec.split(':');
    return {
        cipher: cryptoSpecTokens[0],
        mac: cryptoSpecTokens[1]
    };
};

/** Return a symmetric decrypted bytes.
 */
exports.symmetricDecrypt = function (input, key, cipherAlgorithm) {
    return this.helper.symmetricDecrypt(input, key, cipherAlgorithm);
};

exports.symmetricDecryptWithHash = function (input, cipherKeyVal, macKeyVal,
    cipherAlgorithm, macAlgorithm) {
    var hashLength = this.helper.getMacLength(macAlgorithm);
    if (input.length < hashLength) {
        return {
            data: null,
            hashOk: false
        };
    }
    var enc = input.slice(0, input.length - hashLength);
    var receivedTag = input.slice(input.length - hashLength);
    var computedTag = this.helper.hmac(enc, macKeyVal, macAlgorithm);
    var hashOk = unsigendByteArrayEquals(receivedTag, computedTag);
    if (!hashOk) {
        return {
            data: null,
            hashOk: false
        };
    }
    var data = this.helper.symmetricDecrypt(enc, cipherKeyVal, cipherAlgorithm);
    return {
        data: data,
        hashOk: hashOk
    };
};

/** Return a symmetric encrypted bytes.
 */
exports.symmetricEncrypt = function (input, key, cipherAlgorithm) {
    return this.helper.symmetricEncrypt(input, key, cipherAlgorithm);
};

exports.symmetricEncryptWithHash = function (input, cipherKeyVal, macKeyVal,
    cipherAlgorithm, macAlgorithm) {
    var i;
    if (typeof input === 'string') {
        var temp = [];
        if (input.startsWith('0x')) {
            for (i = 2; i < (input.length - 1); i += 2) {
                temp.push(parseInt(input.substring(i, i + 2), 16));
            }
        } else {
            for (i = 0; i < input.length; i++) {
                temp.push(input.charCodeAt(i));
            }
        }
        input = temp;
    }
    var enc = this.helper.symmetricEncrypt(input, cipherKeyVal, cipherAlgorithm);
    var tag = this.helper.hmac(enc, macKeyVal, macAlgorithm);
    return enc.concat(tag);
};