123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396 |
- /*
- * Copyright 2018 Scytl Secure Electronic Voting SA
- *
- * All rights reserved
- *
- * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
- */
- cryptolib.modules.asymmetric = cryptolib.modules.asymmetric || {};
- /**
- * @namespace asymmetric/cipher
- */
- cryptolib.modules.asymmetric.cipher = function(box) {
- 'use strict';
- box.asymmetric = box.asymmetric || {};
- box.asymmetric.cipher = {};
- /**
- * A module that holds asymmetric cipher functionalities.
- *
- * @exports asymmetric/cipher/factory
- */
- box.asymmetric.cipher.factory = {};
- var policies = {
- cipher: {
- algorithm: box.policies.asymmetric.cipher.algorithm,
- secretKeyLengthBytes: box.policies.asymmetric.cipher.secretKeyLengthBytes,
- ivLengthBytes: box.policies.asymmetric.cipher.ivLengthBytes,
- tagLengthBytes: box.policies.asymmetric.cipher.tagLengthBytes,
- deriver: box.policies.asymmetric.cipher.deriver,
- hash: box.policies.asymmetric.cipher.hash,
- symmetricCipher: box.policies.asymmetric.cipher.symmetricCipher,
- provider: box.policies.asymmetric.cipher.provider
- }
- };
- var utils, converters, exceptions, randomFactory;
- var f = function(box) {
- utils = box.commons.utils;
- converters = new box.commons.utils.Converters();
- exceptions = box.commons.exceptions;
- randomFactory =
- new box.primitives.securerandom.factory.SecureRandomFactory();
- };
- f.policies = {
- primitives: {
- secureRandom:
- {provider: box.policies.asymmetric.cipher.secureRandom.provider}
- }
- };
- cryptolib('commons', 'primitives.securerandom', f);
- /** @class */
- box.asymmetric.cipher.factory.AsymmetricCipherFactory = function() {};
- box.asymmetric.cipher.factory.AsymmetricCipherFactory.prototype = {
- /**
- * @function Gets an asymmetric cipher.
- * @returns {asymmetric/cipher.CryptoForgeAsymmetricCipher}
- */
- getCryptoAsymmetricCipher: function() {
- try {
- if (policies.cipher.provider ===
- Config.asymmetric.cipher.provider.FORGE) {
- var secureRandomBytes = randomFactory.getCryptoRandomBytes();
- return this.getCryptoForgeAsymmetricCipher(secureRandomBytes);
- } else {
- throw new exceptions.CryptoLibException(
- 'No suitable provider for the asymmetric cipher was provided.');
- }
- } catch (error) {
- throw new exceptions.CryptoLibException(
- 'A CryptoAsymmetricCipher could not be obtained.', error);
- }
- },
- /**
- * @function Gets a Forge asymmetric cipher.
- * @param secureRandom
- * {CryptoScytlRandomBytes} a source of random bytes.
- * @returns {asymmetric/cipher.CryptoForgeAsymmetricCipher}
- */
- getCryptoForgeAsymmetricCipher: function(secureRandom) {
- return new CryptoForgeAsymmetricCipher(secureRandom);
- }
- };
- /**
- * Defines a Forge asymmetric cipher
- *
- * @class
- * @param secureRandomBytes
- * {CryptoScytlRandomBytes} a source of random bytes.
- * @memberof asymmetric/cipher
- */
- function CryptoForgeAsymmetricCipher(secureRandomBytes) {
- if (!secureRandomBytes) {
- throw new exceptions.CryptoLibException(
- 'The received PRNG was not valid');
- }
- this.bitOperators = new utils.BitOperators();
- this.secureRandomBytes = secureRandomBytes;
- // We would like to use the SCYTL PRNG directly in the ciphers that are
- // created. However it is currently not possible to pass a PRNG to the
- // Forge library when creating or using a cipher. However, in some cases,
- // it is possible to pass a seed (created using the SCYTL PRNG) to FORGE
- // that is then used to seed the FORGE internal PRNG.
- this.encodingSeedLength = 32;
- try {
- if ((policies.cipher.algorithm.name !==
- Config.asymmetric.cipher.algorithm.RSA_OAEP.name) &&
- (policies.cipher.algorithm.name !==
- Config.asymmetric.cipher.algorithm.RSA_KEM.name)) {
- throw new exceptions.CryptoLibException(
- 'The specified algorithm is not supported.');
- }
- } catch (error) {
- throw new exceptions.CryptoLibException(
- 'CryptoForgeAsymmetricCipher could not be created.', error);
- }
- /**
- * Parses the four parts of the data that is produced by the encrypt
- * function, and that is received by the decrypt function.
- * <p>
- * We know the length of three of these parts, and we know the total
- * length of the data, therefore we can parse out all four of the parts.
- */
- this._parseParts = function(privateKey, encryptedData) {
- var ivLengthBytes = policies.cipher.ivLengthBytes;
- var tagLengthBytes = policies.cipher.tagLengthBytes;
- var encapsulationLengthBytes = privateKey.n.bitLength() / 8;
- var totalLength = encryptedData.length;
- var totalKnownLength =
- encapsulationLengthBytes + ivLengthBytes + tagLengthBytes;
- var encryptedDataLength = totalLength - totalKnownLength;
- var startIndexOfSecondPart = encapsulationLengthBytes;
- var startIndexOfThirdPart = startIndexOfSecondPart + ivLengthBytes;
- var startIndexOfFourthPart = startIndexOfThirdPart + encryptedDataLength;
- var encapsulation =
- this.bitOperators.extract(encryptedData, 0, startIndexOfSecondPart);
- var iv = this.bitOperators.extract(
- encryptedData, startIndexOfSecondPart, startIndexOfThirdPart);
- var data = this.bitOperators.extract(
- encryptedData, startIndexOfThirdPart, startIndexOfFourthPart);
- var tag =
- this.bitOperators.extract(encryptedData, startIndexOfFourthPart);
- return {encapsulation: encapsulation, iv: iv, data: data, tag: tag};
- };
- this._validateInputs = function(keyPem, data) {
- if (!keyPem) {
- throw new exceptions.CryptoLibException(
- 'The received key was not initialized.');
- }
- if (!data) {
- throw new exceptions.CryptoLibException(
- 'The received data was not initialized.');
- }
- };
- // Note: at the moment, the options that are being used with the RSA_OAEP
- // algorithm are hardcoded (for encrypting and decrypting). This could be
- // improved so that these are read from the properties file. Doing this will
- // mean any that existing properties files (used by consumers of the
- // library) will become invalid (if the consumer uses RSA_OAEP) as they wont
- // have the mandatory new properties.
- this._getRsaOaepHash = function() {
- return box.forge.md.sha256.create();
- };
- //
- this._getRsaOaepMaskHash = function() {
- // For interoperability purposes, the MGF1 hash function must
- // remain as SHA-1.
- return box.forge.md.sha1.create();
- };
- this._getRsaOaepEncodingOptions = function() {
- var encodingOptions = {
- md: this._getRsaOaepHash(),
- mgf1: {md: this._getRsaOaepMaskHash()},
- seed: this.secureRandomBytes.nextRandom(this.encodingSeedLength)
- };
- return encodingOptions;
- };
- this._getRsaOaepDecodingOptions = function() {
- var decodingOptions = {
- md: this._getRsaOaepHash(),
- mgf1: {md: this._getRsaOaepMaskHash()}
- };
- return decodingOptions;
- };
- this._determineAndCreateHash = function(requestedHash) {
- if (requestedHash ===
- Config.asymmetric.cipher.algorithm.RSA_KEM.deriver.messagedigest
- .algorithm.SHA256) {
- return forge.md.sha256.create();
- } else {
- throw new exceptions.CryptoLibException(
- 'Unsupported hash function specified.');
- }
- };
- this._determineAndCreateDeriver = function(requestedDeriver) {
- var hash = this._determineAndCreateHash(policies.cipher.hash);
- if (requestedDeriver ===
- Config.asymmetric.cipher.algorithm.RSA_KEM.deriver.name.KDF1) {
- return new forge.kem.kdf1(hash);
- } else if (
- requestedDeriver ===
- Config.asymmetric.cipher.algorithm.RSA_KEM.deriver.name.KDF2) {
- return new forge.kem.kdf2(hash);
- } else if (
- requestedDeriver ===
- Config.asymmetric.cipher.algorithm.RSA_KEM.deriver.name.MGF1) {
- return new forge.mgf.mgf1.create(hash);
- } else {
- throw new exceptions.CryptoLibException(
- 'Unsupported deriver function specified.');
- }
- };
- }
- CryptoForgeAsymmetricCipher.prototype = {
- /**
- * Encrypts some data.
- * <p>
- * If the algorithm is RSA-KEM, then the output from this function will
- * be the base64 encoding of the following data:
- * <p>
- * [Encapsulation][IV][Encrypted Data][Tag]
- *
- * @function
- * @param publicKeyPem
- * {string} public key, as string in PEM format.
- * @param dataBase64
- * {string} data to be encrypted, as string in Base64 encoded
- * format.
- * @returns encrypted data, as string in Base 64 encoded format..
- */
- encrypt: function(publicKeyPem, dataBase64) {
- this._validateInputs(publicKeyPem, dataBase64);
- try {
- var publicKey = box.forge.pki.publicKeyFromPem(publicKeyPem);
- var data = converters.base64Decode(dataBase64);
- var output;
- if (policies.cipher.algorithm.name ===
- Config.asymmetric.cipher.algorithm.RSA_OAEP.name) {
- var encodingOptions = this._getRsaOaepEncodingOptions();
- output = publicKey.encrypt(
- data, policies.cipher.algorithm.name, encodingOptions);
- } else if (
- policies.cipher.algorithm.name ===
- Config.asymmetric.cipher.algorithm.RSA_KEM.name) {
- var secretKeyLengthBytes = policies.cipher.secretKeyLengthBytes;
- var ivLengthBytes = policies.cipher.ivLengthBytes;
- var symmetricCipher = policies.cipher.symmetricCipher;
- var deriver =
- this._determineAndCreateDeriver(policies.cipher.deriver);
- // generate and encapsulate secret key
- var kem = forge.kem.rsa.create(deriver);
- var result = kem.encrypt(publicKey, secretKeyLengthBytes);
- var iv = forge.random.getBytesSync(ivLengthBytes);
- var cipher = forge.cipher.createCipher(symmetricCipher, result.key);
- cipher.start({iv: iv});
- cipher.update(forge.util.createBuffer(data));
- cipher.finish();
- var encryptedData = cipher.output.getBytes();
- var tag = cipher.mode.tag.getBytes();
- output = result.encapsulation.toString() + iv.toString() +
- encryptedData.toString() + tag.toString();
- } else {
- throw new exceptions.CryptoLibException(
- 'The specified algorithm is not supported.');
- }
- return converters.base64Encode(output);
- } catch (error) {
- throw new exceptions.CryptoLibException(
- 'CryptoForgeAsymmetricCipher, data could not be encrypted.', error);
- }
- },
- /**
- * Decrypts some encrypted data.
- *
- * @function
- * @param privateKeyPem
- * {string} private key, as string in PEM format.
- * @param encryptedDataB64
- * {string} encrypted data, as string in Base64 encoded
- * format.
- * @returns decrypted data, as string in Base64 encoded format.
- */
- decrypt: function(privateKeyPem, encryptedDataBase64) {
- this._validateInputs(privateKeyPem, encryptedDataBase64);
- try {
- var privateKey = box.forge.pki.privateKeyFromPem(privateKeyPem);
- var encryptedData = converters.base64Decode(encryptedDataBase64);
- var decryptedData;
- if (policies.cipher.algorithm.name ===
- Config.asymmetric.cipher.algorithm.RSA_OAEP.name) {
- var decodingOptions = this._getRsaOaepDecodingOptions();
- decryptedData = privateKey.decrypt(
- encryptedData, policies.cipher.algorithm.name, decodingOptions);
- } else if (
- policies.cipher.algorithm.name ===
- Config.asymmetric.cipher.algorithm.RSA_KEM.name) {
- var symmetricCipher = policies.cipher.symmetricCipher;
- var secretKeyLengthBytes = policies.cipher.secretKeyLengthBytes;
- var encryptedDataParts = this._parseParts(privateKey, encryptedData);
- var deriver =
- this._determineAndCreateDeriver(policies.cipher.deriver);
- // decrypt encapsulated secret key
- var kem = forge.kem.rsa.create(deriver);
- var key = kem.decrypt(
- privateKey, encryptedDataParts.encapsulation,
- secretKeyLengthBytes);
- // decrypt some bytes
- var decipher = forge.cipher.createDecipher(symmetricCipher, key);
- decipher.start(
- {iv: encryptedDataParts.iv, tag: encryptedDataParts.tag});
- decipher.update(forge.util.createBuffer(encryptedDataParts.data));
- var result = decipher.finish();
- // result will be false if there was a failure
- if (result) {
- decryptedData = decipher.output.getBytes();
- } else {
- throw new exceptions.CryptoLibException(
- 'Error while decrypting data.');
- }
- } else {
- throw new exceptions.CryptoLibException(
- 'The specified algorithm is not supported.');
- }
- return converters.base64Encode(decryptedData);
- } catch (error) {
- throw new exceptions.CryptoLibException(
- 'CryptoForgeAsymmetricCipher, data could not be decrypted.', error);
- }
- }
- };
- };
|