/* * 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 */ /* jshint node:true */ 'use strict'; var Pkcs12KeyStore = require('./pkcs12-keystore'); var cryptoPolicy = require('scytl-cryptopolicy'); var elGamal = require('scytl-elgamal'); var symmetric = require('scytl-symmetric'); var pbkdf = require('scytl-pbkdf'); var constants = require('./constants'); var validator = require('./input-validator'); var codec = require('scytl-codec'); var forge = require('node-forge'); module.exports = ScytlKeyStore; var BigInteger = forge.jsbn.BigInteger; /** * @class ScytlKeyStore * @classdesc Encapsulates a Scytl key store. To instantiate this object, use * the method {@link KeyStoreService.newScytlKeyStore}. * @hideconstructor * @param {Object|string} * keyStore The provided Scytl key store, as an object with expected * properties OR its JSON string representation. * @param {string} * password The password to load the Scytl key store. * @param {Object} * [options] An object containing optional arguments. * @param {Policy} * [options.policy=Default policy] The cryptographic policy to use. * @param {PbkdfRandomService} * [options.pbkdfRandomService=Created internally] The PBKDF service * to use. * @param {SymmetricCryptographyService} * [options.symmetricCryptographyService=Created internally] The * symmetric cryptography service to use. * @param {ElGamalCryptographyService} * [options.elGamalCryptographyService=Created internally] The * ElGamal cryptography service to use. * @throws {Error} * If the input data validation fails or the underlying key store * could not be loaded. */ function ScytlKeyStore(keyStore, password, options) { var scytlKeyStore = keyStore; if (typeof keyStore === 'string') { validator.checkIsJsonString( keyStore, 'Scytl key store (in JSON format) to load'); scytlKeyStore = JSON.parse(keyStore); } checkScytlKeyStore(scytlKeyStore); validator.checkIsType(password, 'string', 'Password to load Scytl key store'); options = options || {}; var policy_; if (options.policy) { policy_ = options.policy; } else { policy_ = cryptoPolicy.newIntance(); } var pbkdfService; if (options.pbkdfService) { pbkdfService = options.pbkdfService; } else { pbkdfService = pbkdf.newService({policy: policy_}); } var symmetricService_; if (options.symmetricCryptographyService) { symmetricService_ = options.symmetricCryptographyService; } else { symmetricService_ = symmetric.newService({policy: policy_}); } var elGamalService_; if (options.elGamalCryptographyService) { elGamalService_ = options.elGamalCryptographyService; } else { elGamalService_ = elGamal.newService(); } var pbkdfDeriver_ = pbkdfService.newDeriver(); var symmetricCipher_ = symmetricService_.newCipher(); var derivedKeys_ = {}; var derivedPasswords_ = {}; var pkcs12KeyStore_; try { pkcs12KeyStore_ = new Pkcs12KeyStore(scytlKeyStore.store, getDerivedPassword(password)); } catch (error) { throw new Error('Could not load underlying PKCS12 key store; ' + error); } /** * Retrieves a private key stored inside the Scytl key store, given the * key's storage alias name and password. * * @function getPrivateKey * @memberof ScytlKeyStore * @param {string} * alias The storage alias name of the private key to retrieve. * @param {string} * password The storage password of the private key. * @returns {string} The private key, in PEM format. * @throws {Error} * If the input data validation fails or the private key could * not be retrieved. */ this.getPrivateKey = function(alias, password) { validator.checkIsNonEmptyString( alias, 'Storage alias name of private key to retrieve from Scytl key store'); validator.checkIsType( password, 'string', 'Password of private key to retrieve from Scytl key store'); try { return pkcs12KeyStore_.getPrivateKey(alias, getDerivedPassword(password)); } catch (error) { throw new Error( 'Could not retrieve private key with alias \'' + alias + '\' from Scytl key store; ' + error); } }; /** * Retrieves a certificate stored inside the Scytl key store, given the * storage alias name of the certificate or that of its associated private * key entry. * * @function getCertificate * @memberof ScytlKeyStore * @param {string} * alias The storage alias name of the certificate or that of its * associated private key entry. * @returns {string} The certificate, in PEM format. * @throws {Error} * If the input data validation fails or the certificate could * not be retrieved. */ this.getCertificate = function(alias) { validator.checkIsNonEmptyString( alias, 'Storage alias name of certificate to retrieve from Scytl key store,'); try { return pkcs12KeyStore_.getCertificate(alias); } catch (error) { throw new Error( 'Could not retrieve certificate with alias \'' + alias + '\' from Scytl key store; ' + error); } }; /** * Retrieves a certificate stored inside the Scytl key store, given the * certificate's subject common name. * * @function getCertificateBySubject * @memberof ScytlKeyStore * @param {string} * subjectCn The subject common name of the certificate. * @returns {string} The certificate, in PEM format. * @throws {Error} * If the input data validation fails or the certificate could * not be retrieved. */ this.getCertificateBySubject = function(subjectCn) { validator.checkIsNonEmptyString( subjectCn, 'Subject common name of certificate to retrieve from Scytl key store'); try { return pkcs12KeyStore_.getCertificateBySubject(subjectCn); } catch (error) { throw new Error( 'Could not retrieve certificate with subject common name \'' + subjectCn + '\' from Scytl key store; ' + error); } }; /** * Retrieves a certificate chain stored inside the Scytl key store, given * the storage alias name of the chain's associated private key entry. * * @function getCertificateChain * @memberof ScytlKeyStore * @param {string} * alias The storage alias name of the chain's associated private * key entry. * @returns {string[]} The certificate chain, as an array of strings in PEM * format. * @throws {Error} * If the input data validation fails or the certificate chain * could not be retrieved. */ this.getCertificateChain = function(alias) { validator.checkIsNonEmptyString( alias, 'Storage alias name of certificate chain to retrieve from Scytl key store'); try { return pkcs12KeyStore_.getCertificateChain(alias); } catch (error) { throw new Error( 'Could not retrieve certificate chain with alias \'' + alias + '\' from Scytl key store; ' + error); } }; /** * Retrieves a secret key stored inside the Scytl key store, given the key's * storage alias name and password. * * @function getSecretKey * @memberof ScytlKeyStore * @param {string} * alias The storage alias name of the secret key to retrieve. * @param {string} * password The storage password of the secret key. * @returns {Uint8Array} The secret key. * @throws {Error} * If the input data validation fails or the secret key could * not be retrieved. */ this.getSecretKey = function(alias, password) { validator.checkIsNonEmptyString( alias, 'Storage alias name of secret key to retrieve from Scytl key store'); validator.checkIsType( password, 'string', 'Password of secret key to retrieve from key store'); var encryptedKey = scytlKeyStore.secrets[alias]; checkEncryptedKey(encryptedKey, 'secret', alias); var secretKey = getKey(password, encryptedKey, alias, 'secret'); return codec.binaryDecode(secretKey); }; /** * Retrieves an ElGamal private key stored inside the Scytl key store, given * the key's storage alias name and password. * * @function getElGamalPrivateKey * @memberof ScytlKeyStore * @param {string} * alias The storage alias name of the ElGamal private key to * retrieve. * @param {string} * password The storage password of the ElGamal private key. * @returns {ElGamalPrivateKey} The ElGamal private key. * @throws {Error} * If the input data validation fails or the ElGamal private key * could not be retrieved. */ this.getElGamalPrivateKey = function(alias, password) { validator.checkIsNonEmptyString( alias, 'Storage alias name of ElGamal private key to retrieve from Scytl key store'); validator.checkIsType( password, 'string', 'Password of ElGamal private key to retrieve from Scytl key store'); var encryptedKey = scytlKeyStore.egPrivKeys[alias]; checkEncryptedKey(encryptedKey, 'ElGamal private', alias); var elGamalPrivateKeyJson = getKey(password, encryptedKey, alias, 'ElGamal private'); return elGamalService_.newPrivateKey(elGamalPrivateKeyJson); }; function getDerivedPassword(password) { if (!derivedPasswords_.hasOwnProperty(password)) { var derivedKeyBytes = getDerivedKey(password); var derivedKeyBigInteger = new BigInteger(derivedKeyBytes); var derivedPassword = derivedKeyBigInteger.toString(constants.CHARACTER_MAX_RADIX); derivedPasswords_[password] = derivedPassword; } return derivedPasswords_[password]; } function getDerivedKey(password) { if (!derivedKeys_.hasOwnProperty(password)) { derivedKeys_[password] = pbkdfDeriver_.derive( password, codec.base64Decode(scytlKeyStore.salt)); } return derivedKeys_[password]; } function checkEncryptedKey(key, keyType, alias) { if (!key) { throw new Error( 'Could not find ' + keyType + ' key with storge alias name \'' + alias + '\''); } } function getKey(password, encryptedKey, alias, keyType) { var aliasAndKey = symmetricCipher_.init(getDerivedKey(password)) .decrypt(codec.base64Decode(encryptedKey)); var aliasAndKeyBinaryEncoded = codec.binaryEncode(aliasAndKey); var decryptedAlias = aliasAndKeyBinaryEncoded.slice(0, alias.length); if (decryptedAlias !== alias) { throw new Error( 'Expected decrypted alias for ' + keyType + ' key to be \'' + alias + '\'; Found \'' + decryptedAlias + '\' Check password provided to retrieve key from store.'); } return aliasAndKeyBinaryEncoded.slice(alias.length, aliasAndKey.length); } function checkScytlKeyStore(scytlKeyStore) { validator.checkIsObjectWithProperties( scytlKeyStore, 'Scytl key store to load'); if (typeof scytlKeyStore.salt === 'undefined') { throw new Error('Field \'salt\' is undefined in Scytl key store to load'); } if (typeof scytlKeyStore.store === 'undefined') { throw new Error( 'Field \'store\' is undefined in Scytl key store to load'); } } }