/* * 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 */ /** * Scytl Key Store. It is a custom keystore. */ cryptolib.modules.stores.sks = function(box) { 'use strict'; box.stores = box.stores || {}; box.stores.sks = {}; var converters; var secretKeyGeneratorFactory; var cipher; var pkcs12Module; var exceptions; var homomorphicKeyFactory; var f = function(box) { converters = new box.commons.utils.Converters(); cipher = (new box.symmetric.cipher.factory.SymmetricCipherFactory()) .getCryptoSymmetricCipher(); secretKeyGeneratorFactory = new box.primitives.derivation.factory.SecretKeyGeneratorFactory(); pkcs12Module = box.stores.pkcs12; exceptions = box.commons.exceptions; homomorphicKeyFactory = new box.homomorphic.keypair.factory.KeyFactory(); }; f.policies = { symmetric: { secretkey: { encryption: {length: box.policies.symmetric.secretkey.encryption.length}, mac: {length: box.policies.symmetric.secretkey.mac.length}, secureRandom: {provider: box.policies.symmetric.secretkey.secureRandom.provider} }, cipher: { provider: box.policies.symmetric.cipher.provider, algorithm: box.policies.symmetric.cipher.algorithm, initializationVectorLengthBytes: box.policies.symmetric.cipher.initializationVectorLengthBytes, secureRandom: {provider: box.policies.symmetric.cipher.secureRandom.provider} } }, primitives: { derivation: { pbkdf: { provider: box.policies.primitives.derivation.pbkdf.provider, hash: box.policies.primitives.derivation.pbkdf.hash, saltLengthBytes: box.policies.primitives.derivation.pbkdf.saltLengthBytes, keyLengthBytes: box.policies.primitives.derivation.pbkdf.keyLengthBytes, iterations: box.policies.primitives.derivation.pbkdf.iterations } } } }; cryptolib( 'commons', 'primitives.derivation', 'symmetric.cipher', 'stores.pkcs12', 'homomorphic.keypair', f); /** * Defines a SksReader. * * @param data. */ box.stores.sks.SksReader = function(data) { // A cache of keys derived from store passwords, to prevent consecutive // operations on a keystore to derive keys each time. var bigintKeys = {}; var derivedKeys = {}; // Create the PBKDF generator. var cryptoPbkdfSecretKeyGenerator = secretKeyGeneratorFactory.createPBKDF(); try { var loadKeyStore = function(data) { var keyStore; if (data) { if (typeof data === 'string') { keyStore = JSON.parse(data); } else if (typeof data === 'object') { keyStore = data; } } return keyStore; }; var validateKeyStoreStructure = function(keyStore) { if (typeof keyStore.salt === 'undefined') { throw new Error('Missing salt'); } if (typeof keyStore.store === 'undefined') { throw new Error('Missing store'); } }; this.keyStore = loadKeyStore(data); validateKeyStoreStructure(this.keyStore); this.store = new pkcs12Module.Pkcs12(this.keyStore.store); } catch (error) { throw new Error('Invalid keyStore: ' + error.message); } function validateEncryptedKey(encryptedKey, keyType) { if (!encryptedKey) { throw new exceptions.CryptoLibException( 'The keyStore does not contain a key of type ' + keyType); } } /** * Derive a key from the keystore password. * * @param {string} passwordB64 the Base64-encoded keystore password * @returns the Base64-encoded key derived from the password */ this._deriveKey = function(passwordB64) { return cryptoPbkdfSecretKeyGenerator.generateSecret( passwordB64, this.keyStore.salt); }; /** * Get the Base64-encoded key derived from the store password. * * @param {string} passwordB64 the Base64-encoded store password * @return the Base64-encoded key derived from the store password */ this._getDerivedKey = function(passwordB64) { if (!derivedKeys.hasOwnProperty(passwordB64)) { derivedKeys[passwordB64] = this._deriveKey(passwordB64); } return derivedKeys[passwordB64]; }; /** * Get the Base64-encoded BigInteger representation of the key derived from * the store password. * * @param {string} passwordB64 the Base64-encoded store password * @returns the Base64-encoded BigInteger representation of the key derived * from the store password */ this._getBigIntKey = function(passwordB64) { if (!bigintKeys.hasOwnProperty(passwordB64)) { var derivedPasswordBase64 = this._getDerivedKey(passwordB64); var derivedPassword = converters.bytesFromString( converters.base64Decode(derivedPasswordBase64)); var derivedPasswordString = new box.forge.jsbn.BigInteger(derivedPassword); var longPasswordB64 = converters.base64Encode(derivedPasswordString.toString(36)); bigintKeys[passwordB64] = longPasswordB64; } return bigintKeys[passwordB64]; }; this._getKey = function(alias, encryptedKey, passwordB64, keyType) { validateEncryptedKey(encryptedKey, keyType); var aliasAndKeyBase64 = this.decryptKey(encryptedKey, passwordB64); var aliasAndKey = converters.base64Decode(aliasAndKeyBase64); var decryptedAlias = aliasAndKey.slice(0, alias.length); if (decryptedAlias !== alias) { throw new exceptions.CryptoLibException( 'The decrypted ' + keyType + ' key does not correspond to the given alias.'); } var key = aliasAndKey.slice(alias.length, aliasAndKey.length); var keyBase64 = converters.base64Encode(key); return keyBase64; }; }; box.stores.sks.SksReader.prototype = { getPrivateKeys: function(passwordB64) { var updateArguments = function(bigIntKey, args) { var argumentsCopy = Array.prototype.slice.call(args, 1); argumentsCopy.unshift(bigIntKey); return argumentsCopy; }; return this.store.getPrivateKey.apply( this.store, updateArguments(this._getBigIntKey(passwordB64), arguments)); }, getSecretKey: function(alias, passwordB64) { var encryptedKey = this.keyStore.secrets[alias]; return this._getKey(alias, encryptedKey, passwordB64, 'secret'); }, getElGamalPrivateKey: function(alias, passwordB64) { var encryptedKey = this.keyStore.egPrivKeys[alias]; var keyBase64 = this._getKey(alias, encryptedKey, passwordB64, 'ElGamal private'); return homomorphicKeyFactory.createPrivateKey(keyBase64); }, decryptKey: function(encryptedKey, passwordB64) { return cipher.decrypt(this._getDerivedKey(passwordB64), encryptedKey); }, /** * Retrieves the private key chain * * @param passwordB64 * Password as string in Base64 encoded format. * @return Private key chain of as PrivateKeyChain object, which * contains aliases and their related private keys in PEM * format. */ getPrivateKeyChain: function(passwordB64) { return this.store.getPrivateKeyChain(this._getBigIntKey(passwordB64)); }, /** * Retrieves the full certificate chain. * * @param passwordB64 * Password as string in Base64 encoded format. * @return Certificate chain as array of strings in PEM format. */ getCertificateChain: function(passwordB64) { return this.store.getCertificateChain(this._getBigIntKey(passwordB64)); }, /** * Retrieves a certificate chain by private key alias * * @param passwordB64 * Password as string in Base64 encoded format. * @param alias * Alias to filter * @return Certificate chain as array of strings in PEM format. */ getCertificateChainByAlias: function(passwordB64, alias) { return this.store.getCertificateChainByAlias( this._getBigIntKey(passwordB64), alias); }, /** * Retrieves a certificate by subject common name. * * @param passwordB64 * Password as string in Base64 encoded format. * @param subjectCn * Subject common name of certificate to retrieve, as string. * @return Certificate with given subject common name, as string in PEM * format. */ getCertificateBySubject: function(passwordB64, subjectCn) { return this.store.getCertificateBySubject( this._getBigIntKey(passwordB64), subjectCn); } }; };