UNPKG

node-gost-crypto

Version:

Node.js implementation of WebCrypto API interfaces and Public Key Infrastructure for GOST algorithms (Russian Cryptographic Standards)

1,206 lines (1,138 loc) 108 kB
/** * @file Key and Certificate Store methods * @version 1.76 * @copyright 2014-2016, Rudolf Nickolaev. All rights reserved. */ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ const gostCrypto = require('./gostCrypto'); // const GostASN1 = require('./gostASN1'); // const GostCert = require('./gostCert'); // const GostCMS = require('./gostCMS'); /* * Common tools and methods */ var root = global; var Promise = root.Promise; var Object = root.Object; var CryptoOperationData = root.ArrayBuffer; var Date = root.Date; var subtle = gostCrypto.subtle; var asn1 = gostCrypto.asn1; var coding = gostCrypto.coding; var providers = gostCrypto.security.providers; var cert = gostCrypto.cert; var cms = gostCrypto.cms; // Expand javascript object function expand() { var r = {}; for (var i = 0, n = arguments.length; i < n; i++) { var item = arguments[i]; if (typeof item === 'object') for (var name in item) if (item.hasOwnProperty(name)) r[name] = item[name]; } return r; } function defineProperty(object, name, descriptor, enumerable) { if (typeof descriptor !== 'object') descriptor = {value: descriptor}; if (enumerable !== undefined) descriptor.enumerable = enumerable; Object.defineProperty(object, name, descriptor); } function defineProperties(object, properties, enumerable) { for (var name in properties) defineProperty(object, name, properties[name], enumerable); } // Extend javascript class function extend(Super, Class, propertiesObject, propertiesClass) { // If constructor not defined if (typeof Class !== 'function') { propertiesClass = propertiesObject; propertiesObject = Class; Class = function () { Super.apply(this, arguments); }; } // Create prototype properties Class.prototype = Object.create(Super.prototype, { constructor: { value: Class }, superclass: { value: Super.prototype } }); if (propertiesObject) defineProperties(Class.prototype, propertiesObject, true); // Inherites super class properties if (Super !== Object) for (var name in Super) Class[name] = Super[name]; Class.super = Super; if (propertiesClass) defineProperties(Class, propertiesClass, true); return Class; } // Get random values function getSeed(length) { var seed = new Uint8Array(length); gostCrypto.getRandomValues(seed); return seed.buffer; } // Self resolver function call(callback) { try { callback(); } catch (e) { } } // Get buffer function buffer(d) { if (d instanceof CryptoOperationData) return d; else if (d && d.buffer && d.buffer instanceof CryptoOperationData) return d.byteOffset === 0 && d.byteLength === d.buffer.byteLength ? d.buffer : new Uint8Array(new Uint8Array(d, d.byteOffset, d.byteLength)).buffer; else throw new DataError('CryptoOperationData required'); } // Today date + n days with time function now(n) { var date = new Date(); if (n) date.setDate(date.getDate() + n); return date; } // Today date + n days function today(n) { var date = now(n); date.setHours(0, 0, 0, 0); return date; } // Check the buffers to equal function equalBuffers(r1, r2) { var s1 = new Uint8Array(r1), s2 = new Uint8Array(r2); if (s1.length !== s2.length) return false; for (var i = 0, n = s1.length; i < n; i++) if (s1[i] !== s2[i]) return false; return true; } // Generate new alias function generateUUID() { var r = new Uint8Array(getSeed(16)), s = ''; for (var i = 0; i < 16; i++) s += ('00' + r[i].toString(16)).slice(-2); return s.substr(0, 8) + '-' + s.substr(8, 4) + '-4' + s.substr(13, 3) + '-9' + s.substr(17, 3) + '-' + s.substr(20, 12); } // Return get32 from buffer function get32(buffer, offset) { var r = new Uint8Array(buffer, offset, 4); return (r[3] << 24) | (r[2] << 16) | (r[1] << 8) | r[0]; } function set32(buffer, offset, int) { var r = new Uint8Array(buffer, offset, 4); r[3] = int >>> 24; r[2] = int >>> 16 & 0xff; r[1] = int >>> 8 & 0xff; r[0] = int & 0xff; return r; } // Salt size function saltSize(algorithm) { switch (algorithm.id) { case 'pbeWithSHAAnd40BitRC2-CBC': case 'pbeWithSHAAnd128BitRC2-CBC': return 8; case 'pbeUnknownGost': return 16; case 'sha1': return 20; default: return 32; } } // Password to bytes function passwordData(derivation, password) { if (!password) return new CryptoOperationData(0); if (derivation.name.indexOf('CPKDF') >= 0) { // CryptoPro store password var r = []; for (var i = 0; i < password.length; i++) { var c = password.charCodeAt(i); r.push(c & 0xff); r.push(c >>> 8 & 0xff); r.push(0); r.push(0); } return new Uint8Array(r).buffer; } else if (derivation.name.indexOf('PFXKDF') >= 0) // PKCS#12 unicode password return coding.Chars.decode(password + '\0', 'unicode'); else // PKCS#5 password mode return coding.Chars.decode(password, 'utf8'); } /** * Key and Certificate Store methods * * @class GostKeys */ function GostKeys() { } /** * Key templates * <ul> * <li>providerName - provider name for key encryption, default 'CP-01'</li> * <li>days - validity of the key in days, default 7305</li> * </ul> * * @memberOf GostKeys * @instance */ var options = { providerName: 'CP-01', days: 7305 }; GostKeys.prototype.options = options; /** * A class for private keys in PKCS #8 format * * @class GostKeys.PKCS8 * @extends GostASN1.PrivateKeyInfo * @param {(FormatedData|GostASN1.PrivateKeyInfo)} keyInfo */ function PKCS8(keyInfo) { asn1.PrivateKeyInfo.call(this, keyInfo); } extend(asn1.PrivateKeyInfo, PKCS8, { /** * Get the private key * * @memberOf GostKeys.PKCS8 * @instance * @returns {Promise} Promise to return the {@link Key} */ getPrivateKey: function () { var keyUsages = (this.privateKeyAlgorithm.id === 'rsaEncryption') ? ['sign'] : ['sign', 'deriveKey', 'deriveBits']; return subtle.importKey('pkcs8', this.encode(), this.privateKeyAlgorithm, 'true', keyUsages); }, /** * Set the private key * * @memberOf GostKeys.PKCS8 * @instance * @param {Key} privateKey The Private Key * @returns {Promise} Promise to return the self object after set the key */ setPrivateKey: function (privateKey) { var self = global; return subtle.exportKey('pkcs8', privateKey).then(function (keyInfo) { asn1.PrivateKeyInfo.call(self, keyInfo); return self; }); }, /** * Generate private key and return certification request * * @memberOf GostKeys.PKCS8 * @instance * @param {(FormatedData|GostASN1.CertificationRequest)} req The request templates * @param {(AlgorithmIdentifier|string)} keyAlgorithm The name of provider or algorithm identifier * @returns {Promise} Promise to return the {@link GostCert.Request} after key generation */ generate: function (req, keyAlgorithm) { var self = global; return new Promise(call).then(function () { if (!(req instanceof cert.Request)) req = new cert.Request(req); // Generate request return req.generate(keyAlgorithm); }).then(function (key) { asn1.PrivateKeyInfo.call(self, key); ; return req; }); } }); /** * A class for private keys in PKCS #8 format * * @memberOf GostKeys * @type GostKeys.PKCS8 */ GostKeys.prototype.PKCS8 = PKCS8; /** * A class for PKCS #5 and PKCS #12 password-encrypted private keys in PKCS #8 format * * @class GostKeys.PKCS8Encrypted * @extends GostASN1.EncryptedPrivateKeyInfo * @param {(FormatedData|GostASN1.EncryptedPrivateKeyInfo)} encryptedKey */ function PKCS8Encrypted(encryptedKey) { asn1.EncryptedPrivateKeyInfo.call(this, encryptedKey); } extend(asn1.EncryptedPrivateKeyInfo, PKCS8Encrypted, { /** * Get the private key info * * @memberOf GostKeys.PKCS8Encrypted * @instance * @param {(Key|CryptoOperationData|string)} keyPassword The secret key or password for decryption * @returns {Promise} Promise to return decrypted {@link GostKeys.PKCS8} */ getKey: function (keyPassword) { var self = this, engine; return new Promise(call).then(function () { engine = new cms.EncryptedDataContentInfo({ contentType: 'encryptedData', version: 0, encryptedContentInfo: { contentType: 'data', contentEncryptionAlgorithm: self.encryptionAlgorithm, encryptedContent: self.encryptedData } }); return engine.getEnclosed(keyPassword); }).then(function (contentInfo) { // Create key object return PKCS8.decode(contentInfo.content); }); }, /** * Get the private key * * @memberOf GostKeys.PKCS8Encrypted * @instance * @param {(Key|CryptoOperationData|string)} keyPassword The secret key or password for decryption * @returns {Promise} Promise to return decrypted {@link Key} */ getPrivateKey: function (keyPassword) { return this.getKey(keyPassword).then(function (keyInfo) { return keyInfo.getPrivateKey(); }); }, /** * Sets and encrypt the private key info * * @memberOf GostKeys.PKCS8Encrypted * @instance * @param {(FormatedData|GostKeys.PKCS8)} keyInfo The private key info * @param {(Key|CryptoOperationData|string)} keyPassword The secret key or password for encryption * @param {(AlgorithmIdentifier|string)} encryptionAlgorithm The encryption algorithm or provider name * @returns {Promise} Promise to return self object after set key */ setKey: function (keyInfo, keyPassword, encryptionAlgorithm) { var self = this, engine; return new Promise(call).then(function () { keyInfo = new PKCS8(keyInfo); engine = new cms.EncryptedDataContentInfo(); return engine.encloseContent(keyInfo.encode(), keyPassword, encryptionAlgorithm || options.providerName); }).then(function () { self.encryptionAlgorithm = engine.encryptedContentInfo.contentEncryptionAlgorithm; self.encryptedData = engine.encryptedContentInfo.encryptedContent; return self; }); }, /** * Set the private key * * @memberOf GostKeys.PKCS8Encrypted * @instance * @param {Key} privateKey The private key * @param {(Key|CryptoOperationData|string)} keyPassword The secret key or password for decryption * @param {(AlgorithmIdentifier|string)} encryptionAlgorithm The encryption algorithm or provider name * @returns {Promise} Promise to return self object after set key */ setPrivateKey: function (privateKey, keyPassword, encryptionAlgorithm) { var self = global; return new PKCS8().setPrivateKey(privateKey).then(function (keyInfo) { return self.setKey(keyInfo, keyPassword, encryptionAlgorithm); }); }, /** * Generate private key and return certification request * * @memberOf GostKeys.PKCS8Encrypted * @instance * @param {(FormatedData|GostASN1.CertificationRequest)} req The request templates * @param {(Key|CryptoOperationData|string)} keyPassword The secret key or password for decryption * @param {(AlgorithmIdentifier|string)} keyAlgorithm The name of provider or algorithm * @param {(AlgorithmIdentifier|string)} encryptionAlgorithm The encryption algorithm or provider name * @returns {Promise} Promise to return {@link GostCert.Request} */ generate: function (req, keyPassword, keyAlgorithm, encryptionAlgorithm) { var self = global; return new Promise(call).then(function () { if (!(req instanceof cert.Request)) req = new cert.Request(req); // Generate request return req.generate(keyAlgorithm); }).then(function (key) { return self.setKey(key, keyPassword, encryptionAlgorithm); }).then(function () { return req; }); } }); /** * A class for PKCS #5 and PKCS #12 password-encrypted private keys in PKCS #8 format * * @memberOf GostKeys * @type GostKeys.PKCS8Encrypted */ GostKeys.prototype.PKCS8Encrypted = PKCS8Encrypted; /** * A class for password-encrypted private keys in SignalCom container<br><br> * * The container file list: * <ul> * <li>mk.db3 - master key data</li> * <li>masks.db3 - encrypted or decrypted masks</li> * <li>kek.opq - wrapped key encryption key</li> * <li>rand.opq - wrapped random data</li> * </ul> * * @class GostKeys.SignalComKeyContainer * @param {SignalComKeyContainer} container */ function SignalComKeyContainer(container) { if (container) { var self = global; ['mk.db3', 'masks.db3', 'kek.opq', 'rand.opq'].forEach(function (name) { self[name] = container[name]; }); } } extend(Object, SignalComKeyContainer, { /** * Get password-based encryption key * * @memberOf GostKeys.SignalComKeyContainer * @instance * @param {string} keyPassword * @returns {Promise} Promise to return {@link Key} */ getEncryptionKey: function (keyPassword) { var self = this, wrapping = providers['SC-01'].wrapping, encryption = providers['SC-01'].encryption, derivation = providers['SC-01'].derivation, masks = self['masks.db3'], mk = self['mk.db3'], kek = self['kek.opq']; // Decrypt key return new Promise(call).then(function () { if ((!masks || !mk || !kek)) throw new Error('Not enougth key container files'); // Check for encrypted key if (masks.byteLength > 32) { if (keyPassword) { // Extract password based encryption mask return subtle.importKey('raw', coding.Chars.decode(keyPassword, 'utf8'), derivation, false, ['deriveKey', 'deriveBits']).then(function (integrityKey) { return subtle.deriveKey(expand(derivation, {salt: new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0])}), integrityKey, encryption, false, ['decrypt']); }).then(function (encryptionKey) { var encrypted = new cms.EncryptedDataContentInfo(masks); return encrypted.getEnclosed(encryptionKey); }).then(function (digested) { return digested.verify(); }).then(function (data) { return data.content; }); } else throw new Error('Key password is required'); } else if (keyPassword) throw new Error('Key password is not required'); return masks; }).then(function (decrypedMasks) { // Combine masks masks = decrypedMasks; var mkm = new Uint8Array(mk.byteLength + masks.byteLength); mkm.set(new Uint8Array(mk), 0); mkm.set(new Uint8Array(masks), mk.byteLength); // Import master key return subtle.importKey('raw', mkm.buffer, wrapping, false, ['unwrapKey']); }).then(function (unwrappingKey) { // Unwrap kek return subtle.unwrapKey('raw', kek, unwrappingKey, wrapping, encryption, false, ['wrapKey', 'unwrapKey']); }); }, /** * Generate encryption key and container files * * @memberOf GostKeys.SignalComKeyContainer * @instance * @param {string} keyPassword * @returns {Promise} Promise to return {@link Key} */ generateContainer: function (keyPassword) { var self = this, wrapping = providers['SC-01'].wrapping, encryption = providers['SC-01'].encryption, derivation = providers['SC-01'].derivation, digest = providers['SC-01'].digest, encryptionKey, wrappingKey; return new Promise(call).then(function () { // Generate wrapping key return subtle.generateKey(wrapping, true, ['wrapKey']); }).then(function (key) { wrappingKey = key; // Split masks var len = wrappingKey.buffer.byteLength; self['mk.db3'] = new Uint8Array(new Uint8Array(wrappingKey.buffer, 0, len - 32)).buffer; var masks = new Uint8Array(new Uint8Array(wrappingKey.buffer, len - 32, 32)).buffer; if (keyPassword) { // Encrypt masks var encrypted = new cms.EncryptedDataContentInfo(), digested = new cms.DigestedDataContentInfo(); // Digest data return digested.encloseContent(masks, digest).then(function () { digested = {// Double wrapping - SignalCom mistake contentType: 'digestedData', content: digested.encode() }; return subtle.importKey('raw', coding.Chars.decode(keyPassword, 'utf8'), derivation, false, ['deriveKey', 'deriveBits']); }).then(function (integrityKey) { return subtle.deriveKey(expand(derivation, {salt: new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0])}), integrityKey, encryption, false, ['encrypt']); }).then(function (encryptionKey) { // Encrypt data with password return encrypted.encloseContent(digested, encryptionKey, encryption); }).then(function () { return encrypted.encode(); }); } return masks; }).then(function (masks) { self['masks.db3'] = masks; // Generate encryption key return subtle.generateKey(encryption, false, ['wrapKey', 'unwrapKey']); }).then(function (key) { encryptionKey = key; // Wrap encryption key return subtle.wrapKey('raw', key, wrappingKey, wrapping); }).then(function (data) { self['kek.opq'] = data; // Generate random seed return subtle.generateKey(encryption, false, ['wrapKey', 'unwrapKey']); }).then(function (key) { // Wrap random seed return subtle.wrapKey('raw', key, wrappingKey, wrapping); }).then(function (data) { self['rand.opq'] = data; return encryptionKey; }); } }); /** * A class for password-encrypted private keys in SignalCom container * * @memberOf GostKeys * @type GostKeys.SignalComKeyContainer */ GostKeys.prototype.SignalComKeyContainer = SignalComKeyContainer; /** * A class for password-encrypted SignalCom private keys * * @class GostKeys.SignalComPrivateKeyInfo * @extends GostASN1.GostWrappedPrivateKey * @extends GostKeys.SignalComKeyContainer * @param {GostASN1.PrivateKeyInfo} keyInfo * @param {GostKeys.SignalComKeyContainer} container */ function SignalComPrivateKeyInfo(keyInfo, container) { asn1.GostWrappedPrivateKey.call(this, keyInfo); SignalComKeyContainer.call(this, container); } extend(asn1.GostWrappedPrivateKey, SignalComPrivateKeyInfo, { /** * Get the private key info * * @memberOf GostKeys.SignalComPrivateKeyInfo * @param {string} keyPassword The password for decryption * @returns {Promise} Promise to return {@link GostKeys.PKCS8} */ getKey: function (keyPassword) { return this.getPrivateKey(keyPassword).then(function (privateKey) { return new PKCS8().setPrivateKey(privateKey); }); }, /** * Get the private key * * @memberOf GostKeys.SignalComPrivateKeyInfo * @instance * @param {string} keyPassword The password for decryption * @returns {Promise} Promise to return the {@link Key} */ getPrivateKey: function (keyPassword) { var self = this, wrapping = providers['SC-01'].wrapping, publicKeyData; // Decrypt key return new Promise(call).then(function () { // Get password key return self.getEncryptionKey(keyPassword, true); }).then(function (encryptionKey) { // Unwrap private key return subtle.unwrapKey('raw', self.privateKeyWrapped, encryptionKey, wrapping, self.privateKeyAlgorithm, true, ['sign', 'deriveKey', 'deriveBits']); }).then(function (privateKey) { publicKeyData = self.attributes && self.attributes['id-sc-gostR3410-2001-publicKey']; // Generate key pair if (publicKeyData) return subtle.generateKey(expand(privateKey.algorithm, {ukm: privateKey.buffer}), privateKey.extractable, privateKey.usages); else return {privateKey: privateKey}; }).then(function (keyPair) { // Compare public key if (publicKeyData && !equalBuffers(keyPair.publicKey.buffer, publicKeyData)) throw new Error('Check public key failed'); return keyPair.privateKey; }); }, /** * Sets and encrypt the private key info * * @memberOf GostKeys.SignalComPrivateKeyInfo * @instance * @param {(FormatedData|GostKeys.PKCS8)} keyInfo The private key info * @param {string} keyPassword The password for encryption * @returns {Promise} Promise to return self object after set the key */ setKey: function (keyInfo, keyPassword) { var self = global; return new PKCS8(keyInfo).getPrivateKey().then(function (privateKey) { return self.setPrivateKey(privateKey, keyPassword); }); }, /** * Set the private key * * @memberOf GostKeys.SignalComPrivateKeyInfo * @instance * @param {Key} privateKey The private key * @param {string} keyPassword The secret key encryption * @returns {Promise} Promise to return self object after set the key */ setPrivateKey: function (privateKey, keyPassword) { var self = this, wrapping = providers['SC-01'].wrapping, wrappedData; return new Promise(call).then(function () { // Get or generate encryption key return self.getEncryptionKey(keyPassword)['catch'](function () { return self.generateContainer(keyPassword); }); }).then(function (encryptionKey) { // Encrypt key buffer return subtle.wrapKey('raw', privateKey, encryptionKey, wrapping); }).then(function (data) { wrappedData = data; // Generate public key return subtle.generateKey(expand(privateKey.algorithm, {ukm: privateKey.buffer}), true, ['sign', 'verify']); }).then(function (keyPair) { self.object = { version: 0, privateKeyAlgorithm: privateKey.algorithm, privateKeyWrapped: wrappedData, attributes: { 'id-sc-gostR3410-2001-publicKey': keyPair.publicKey.buffer } }; return self; }); }, /** * Change key password * * @memberOf GostKeys.SignalComPrivateKeyInfo * @instance * @param {string} oldKeyPassword Old key password * @param {string} newKeyPassword New key password * @returns {Promise} Promise to return self object after change password */ changePassword: function (oldKeyPassword, newKeyPassword) { var self = global; return self.getPrivateKey(oldKeyPassword).then(function (privateKey) { return self.setPrivateKey(privateKey, newKeyPassword); }); }, /** * Generate private key, certificate and return certification request * * @memberOf GostKeys.SignalComPrivateKeyInfo * @instance * @param {(FormatedData|GostASN1.CertificationRequest)} req The request templates * @param {(Key|CryptoOperationData|string)} keyPassword The secret key or password for decryption * @param {(AlgorithmIdentifier|string)} keyAlgorithm The name of provider or algorithm * @returns {Promise} Promise to return {@link GostCert.Request} */ generate: function (req, keyPassword, keyAlgorithm) { var self = this, keyInfo; return new Promise(call).then(function () { if (!(req instanceof cert.Request)) req = new cert.Request(req); // Generate request return req.generate(keyAlgorithm); }).then(function (key) { keyInfo = key; return self.setKey(keyInfo, keyPassword); }).then(function () { return req; }); } }); defineProperties(SignalComPrivateKeyInfo.prototype, SignalComKeyContainer.prototype); /** * A class for password-encrypted SignalCom private keys * * @memberOf GostKeys * @type GostKeys.SignalComPrivateKeyInfo */ GostKeys.prototype.SignalComPrivateKeyInfo = SignalComPrivateKeyInfo; /** * A class for password-encrypted private keys in CryptoPro container * * The container file list: * <ul> * <li>header - container header @link GostASN1.GostKeyContainer</li> * <li>name - container name @link GostASN1.GostKeyContainerName</li> * <li>primary - private keys data @link GostASN1.GostPrivateKeys</li> * <li>masks - private key masks @link GostASN1.GostPrivateMasks</li> * <li>primary2 - reserve of private keys data @link GostASN1.GostPrivateKeys</li> * <li>masks2 - reserve of private key masks @link GostASN1.GostPrivateMasks</li> * </ul> * * @class GostKeys.CryptoProKeyContainer * @param {Object} container */ function CryptoProKeyContainer(container) { if (container) { this.header = asn1.GostKeyContainer.decode(container.header); this.name = asn1.GostKeyContainerName.decode(container.name); this.primary = asn1.GostPrivateKeys.decode(container.primary); this.masks = asn1.GostPrivateMasks.decode(container.masks); if (container.primary2 && container.masks2) { this.primary2 = asn1.GostPrivateKeys.decode(container.primary2); this.masks2 = asn1.GostPrivateMasks.decode(container.masks2); } } } extend(Object, CryptoProKeyContainer, (function () { // True if 512 bit function isKeySize512(algorithm) { return algorithm.name.indexOf('-512') >= 0 || algorithm.length === 512; } // Test version 2012 function isVersion2012(algorithm) { return !((algorithm.name.indexOf('-94') >= 0 || algorithm.name.indexOf('-2001') >= 0 || algorithm.version === 1994 || algorithm.version === 2001)); } // Derive password key function derivePasswordKey(algorithm, password, salt) { var hash = isVersion2012(algorithm) ? 'GOST R 34.11-256' : 'GOST R 34.11-94/' + (algorithm.sBox || 'D-A'), derivation = {name: 'CPKDF', hash: hash, salt: salt, iterations: password ? 2000 : 2}; // Import password return subtle.importKey('raw', passwordData(derivation, password), derivation, false, ['deriveKey', 'deriveBits']).then(function (baseKey) { // Derive key return subtle.deriveKey(derivation, baseKey, 'GOST 28147', false, ['sign', 'verify', 'encrypt', 'decrypt']); }); } // Compute password MAC function computePasswordMAC(algorithm, password, salt) { var mac = expand({name: 'GOST 28147-MAC'}, algorithm.encParams); // Derive password return derivePasswordKey(algorithm, password, salt).then(function (macKey) { // Mac for 16 zero bytes return subtle.sign(mac, macKey, new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])); }); } // var lastBuffer; // Compute container MAC function computeContainerMAC(algorithm, content) { var mac = expand({name: 'GOST 28147-MAC'}, algorithm.encParams), keyData = new Uint8Array([// 32 zero bytes 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); return subtle.importKey('raw', keyData, mac, false, ['sign']).then(function (macKey) { // var buffer = new Uint8Array(content.encode()); // console.log(coding.Hex.encode(buffer)); // if (lastBuffer && lastBuffer.length === buffer.length) { // for (var i = 0; i < buffer.length; i++) // if (lastBuffer[i] !== buffer[i]) // console.log('diff at ' + i); // } else // console.log('diff length'); // lastBuffer = buffer; // Mac for content return subtle.sign(mac, macKey, content.encode()); }); } // Compute mask MAC function computeMaskMAC(algorithm, mask, status) { // Import mask as key for MAC var mac = expand({name: 'GOST 28147-MAC'}, algorithm.encParams), keyData = mask.byteLength === 64 ? new Uint8Array(new Uint8Array(mask, 32, 32)).buffer : mask; return subtle.importKey('raw', keyData, mac, false, ['sign']).then(function (macKey) { // Verify MAC for maskStatus return subtle.sign(mac, macKey, status); }); } // Generate mask function generateMasks(algorithm) { var wrapAlgorithm = expand(algorithm, {mode: 'MASK'}), mask, status = getSeed(12); wrapAlgorithm.name = wrapAlgorithm.name.replace('-DH', ''); return subtle.generateKey(wrapAlgorithm, true, ['wrapKey', 'unwrapKey']).then(function (key) { return subtle.exportKey('raw', key); }).then(function (data) { mask = data; return computeMaskMAC(algorithm, mask, status); }).then(function (hmac) { return new asn1.GostPrivateMasks({ mask: mask, randomStatus: status, hmacRandom: hmac }); }); } // Compute FP function computeFP(privateKey) { // Generate key pair with predefined ukm for check public key return subtle.generateKey(expand(privateKey.algorithm, {ukm: privateKey.buffer}), true, ['sign', 'verify']).then(function (keyPair) { return new Uint8Array(new Uint8Array(keyPair.publicKey.buffer, 0, 8)).buffer; }); } // Unwrap private key function unwrapKey(algorithm, encryptionKey, key, mask, fp) { var encryption = {name: 'GOST 28147-ECB', sBox: algorithm.encParams && algorithm.encParams.sBox}, unwrapAlgorithm = expand(algorithm, {mode: 'MASK'}), privateKey; unwrapAlgorithm.name = unwrapAlgorithm.name.replace('-DH', ''); var wrappedKey; // Encrypt ukm data for private key return subtle.decrypt(encryption, encryptionKey, key).then(function (data) { wrappedKey = data; // Import mask key return subtle.importKey('raw', mask, unwrapAlgorithm, 'false', ['sign', 'unwrapKey']); }).then(function (unwrappingKey) { // Unwrap private key return subtle.unwrapKey('raw', wrappedKey, unwrappingKey, unwrapAlgorithm, algorithm, 'true', ['sign']); }).then(function (key) { privateKey = key; return computeFP(privateKey); }).then(function (computedFP) { // Check public key buffer if (!equalBuffers(computedFP, fp)) throw new Error('Incorrect fp'); return privateKey; }); } // Wrap private key function wrapKey(algorithm, encryptionKey, privateKey, mask) { var encryption = {name: 'GOST 28147-ECB', sBox: algorithm.encParams && algorithm.encParams.sBox}, wrapAlgorithm = expand(algorithm, {mode: 'MASK'}); wrapAlgorithm.name = wrapAlgorithm.name.replace('-DH', ''); // Import mask key return subtle.importKey('raw', mask, wrapAlgorithm, false, ['sign', 'wrapKey']).then(function (wrappingKey) { // Wrap private key return subtle.wrapKey('raw', privateKey, wrappingKey, wrapAlgorithm); }).then(function (wrappedKey) { // Encrypt key return subtle.encrypt(encryption, encryptionKey, wrappedKey); }); } // Decrypt private key function decryptKey(content, primary, masks, keyPassword, secondary) { var algorithm = content.primaryPrivateKeyParameters.privateKeyAlgorithm; return new Promise(call).then(function () { // Check format if (primary.hmacKey) throw new Error('Old key format'); if (masks.randomStatus.byteLength < 12) throw new Error("Invalid random status length"); // Import mask as key for MAC return computeMaskMAC(algorithm, masks.mask, masks.randomStatus); }).then(function (hmac) { if (!equalBuffers(hmac, masks.hmacRandom)) throw new Error("Imita for mask is invalid"); // Derive key return derivePasswordKey(algorithm, keyPassword, new Uint8Array(masks.randomStatus, 0, 12)); }).then(function (encryptionKey) { // Unwrap keys return secondary && primary.secondaryKey ? unwrapKey(content.secondaryPrivateKeyParameters.privateKeyAlgorithm, encryptionKey, primary.secondaryKey, masks.mask, content.secondaryFP) : unwrapKey(algorithm, encryptionKey, primary.primaryKey, masks.mask, content.primaryFP); }); } // Encrypt private key function encryptKey(algorithm, primary, masks, keyPassword, secondary, privateKey) { // Derive key return derivePasswordKey(algorithm, keyPassword, new Uint8Array(masks.randomStatus, 0, 12)).then(function (encryptionKey) { // Wrap keys return wrapKey(algorithm, encryptionKey, privateKey, masks.mask); }).then(function (encryptedKey) { if (!primary) primary = new asn1.GostPrivateKeys(); if (secondary) { primary.secondaryKey = encryptedKey; } else { primary.primaryKey = encryptedKey; } return primary; }); } return { /** * Get the private key info * * @memberOf GostKeys.CryptoProKeyContainer * @instance * @param {string} keyPassword The password for decryption * @param {boolean} secondary True if required secondary key * @returns {Promise} Promise to return {@link GostKeys.PKCS8} */ getKey: function (keyPassword, secondary) { return this.getPrivateKey(keyPassword, secondary).then(function (privateKey) { return new PKCS8().setPrivateKey(privateKey); }); }, /** * Get the private key * * @memberOf GostKeys.CryptoProKeyContainer * @instance * @param {string} keyPassword Rhe password for decryption * @param {boolean} secondary True if required secondary key * @returns {Promise} Promise to return the {@link Key} */ getPrivateKey: function (keyPassword, secondary) { var self = this, content = self.header.keyContainerContent; // Decrypt key return decryptKey(content, self.primary, self.masks, keyPassword, secondary)['catch'](function (e) { if (self.primary2 && self.masks2) return decryptKey(content, self.primary2, self.masks2, keyPassword, secondary); else throw e; }); }, /** * Get the certificate from the key container * * @memberOf GostKeys.CryptoProKeyContainer * @instance * @param {boolean} secondary True for set secondary certificate * @returns {Promise} Promise to return {@link GostCert.X509} */ getCertificate: function (secondary) { var self = this, content = self.header.keyContainerContent; return new Promise(call).then(function () { if (secondary) return new cert.X509(content.secondaryCertificate); else return new cert.X509(content.primaryCertificate); }); }, /** * Get the container name * * @memberOf GostKeys.CryptoProKeyContainer * @instance * @returns {string} Container name */ getContainerName: function () { return this.name.containerName; }, /** * Sets and encrypt the private key info * * @memberOf GostKeys.CryptoProKeyContainer * @instance * @param {(FormatedData|GostKeys.PKCS8)} keyInfo The private key info * @param {string} keyPassword The assword for encryption * @param {boolean} secondary True for set secondary key * @param {number} days Validity days. Default 7305 days (20 years) * @returns {Promise} Promise to return self object after set key */ setKey: function (keyInfo, keyPassword, secondary, days) { var self = global; return new PKCS8(keyInfo).getPrivateKey().then(function (privateKey) { return self.setPrivateKey(privateKey, keyPassword, secondary, days); }); }, /** * Set the private key * * @memberOf GostKeys.CryptoProKeyContainer * @instance * @param {Key} privateKey The private key * @param {string} keyPassword The secret key encryption * @param {boolean} secondary True for set secondary key * @param {number} days Validity days. Default 7305 days (20 years) * @returns {Promise} Promise to return self object after set key */ setPrivateKey: function (privateKey, keyPassword, secondary, days) { var self = this, content, algorithm; return new Promise(call).then(function () { self.header = self.header || new asn1.GostKeyContainer({ keyContainerContent: { containerAlgoritmIdentifier: { algorithm: 'id-CryptoPro-GostPrivateKeys-V2-Full' }, attributes: ['kccaReservePrimary', 'kccaPrimaryKeyAbsent'], extensions: { keyValidity: { notAfter: now(days || options.days) } } } }); content = self.header.keyContainerContent; // Set private key var keyParameters = secondary ? content.secondaryPrivateKeyParameters : content.primaryPrivateKeyParameters; if (!keyParameters) { algorithm = expand(privateKey.algorithm, { sBox: "D-A", encParams: { block: "CFB", sBox: "E-A", keyMeshing: "CP" } }); keyParameters = { attributes: ["pkaExportable", "pkaExchange", "pkaDhAllowed"], privateKeyAlgorithm: algorithm }; if (secondary) { if (!content.primaryPrivateKeyParameters) throw new Error('Primary key must be defined first'); content.secondaryPrivateKeyParameters = keyParameters; } else { content.primaryPrivateKeyParameters = keyParameters; var absent = content.attributes.indexOf('kccaPrimaryKeyAbsent'); if (absent >= 0) content.attributes.splice(absent, 1); } } else algorithm = keyParameters.privateKeyAlgorithm; // Generate masks var promises = []; [0, 1].forEach(function (i) { var name = 'masks' + (i > 0 ? '2' : ''); if (!self[name]) promises.push(generateMasks(algorithm).then(function (masks) { self[name] = masks; })); }); return Promise.all(promises); }).then(function () { // Encrypt key var promises = []; [0, 1].forEach(function (i) { var name = 'primary' + (i > 0 ? '2' : ''), maskname = 'masks' + (i > 0 ? '2' : ''); promises.push(encryptKey(algorithm, self[name], self[maskname], keyPassword, secondary, privateKey).then(function (primary) { self[name] = primary; })); }); return Promise.all(promises); }).then(function () { // Compute FP for a private key return computeFP(privateKey).then(function (FP) { if (secondary) content.secondaryFP = FP; else content.primaryFP = FP; }); }).then(function () { // Compute password MAC var softPassword = content.attributes.indexOf('kccaSoftPassword'); if (keyPassword) { if (softPassword < 0) content.attributes.push('kccaSoftPassword'); return computePasswordMAC(algorithm, keyPassword, content.primaryFP); } else { if (softPassword >= 0) content.attributes.splice(softPassword, 1); } }).then(function (hmac) { if (hmac) content.hmacPassword = hmac; // Calculate container MAC return computeContainerMAC(algorithm, content); }).then(function (hmac) { self.header.hmacKeyContainerContent = hmac; return self; }); }, /** * Set the certificate to the key container * * @memberOf GostKeys.CryptoProKeyContainer * @instance *