UNPKG

ndn-js

Version:

A JavaScript client library for Named Data Networking

883 lines (795 loc) 33.2 kB
/** * Copyright (C) 2017-2019 Regents of the University of California. * @author: Jeff Thompson <jefft0@remap.ucla.edu> * @author: From https://github.com/named-data/ndn-cxx/blob/master/ndn-cxx/security/transform/private-key.cpp * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * A copy of the GNU Lesser General Public License is in the file COPYING. */ // Use capitalized Crypto to not clash with the browser's crypto.subtle. /** @ignore */ var cryptoConstants = require('crypto').constants; /** @ignore */ var Crypto = require('../../crypto.js'); /** @ignore */ var KeyType = require('../security-types').KeyType; /** @ignore */ var EncryptAlgorithmType = require('../../encrypt/algo/encrypt-params.js').EncryptAlgorithmType; /** @ignore */ var DigestAlgorithm = require('../security-types.js').DigestAlgorithm; /** @ignore */ var DataUtils = require('../../encoding/data-utils.js').DataUtils; /** @ignore */ var SyncPromise = require('../../util/sync-promise.js').SyncPromise; /** @ignore */ var DerNode = require('../../encoding/der/der-node.js').DerNode; /** @ignore */ var DerSequence = require('../../encoding/der/der-node.js').DerNode.DerSequence; /** @ignore */ var DerInteger = require('../../encoding/der/der-node.js').DerNode.DerInteger; /** @ignore */ var OID = require('../../encoding/oid.js').OID; /** @ignore */ var Blob = require('../../util/blob.js').Blob; /** @ignore */ var UseSubtleCrypto = require('../../use-subtle-crypto-node.js').UseSubtleCrypto; /** @ignore */ var RsaKeypair = null; try { // This should be installed with: sudo npm install rsa-keypair RsaKeypair = require('rsa-keypair'); } catch (e) {} /** * A TpmPrivateKey holds an in-memory private key and provides cryptographic * operations such as for signing by the in-memory TPM. * * Create an uninitialized TpmPrivateKey. You must call a load method to * initialize it, such as loadPkcs1. * @constructor */ var TpmPrivateKey = function TpmPrivateKey() { this.keyType_ = null; // number from KeyType this.privateKey_ = null; // The PEM-encoded private key. this.subtleKey_ = null; // The internal Crypto.subtle form of the key. this.decryptSubtleKey_ = null; // The internal Crypto.subtle form of the key for decryption. }; exports.TpmPrivateKey = TpmPrivateKey; /** * Create a new TpmPrivateKey.Error to report an error in private key processing. * Call with: throw new TpmPrivateKey.Error(new Error("message")). * @constructor * @param {Error} error The exception created with new Error. */ TpmPrivateKey.Error = function TpmPrivateKeyError(error) { if (error) { error.__proto__ = TpmPrivateKey.Error.prototype; return error; } }; TpmPrivateKey.Error.prototype = new Error(); TpmPrivateKey.Error.prototype.name = "TpmPrivateKeyError"; /** * Load the unencrypted private key from a buffer with the PKCS #1 encoding. * This replaces any existing private key in this object. * @param {Buffer} encoding The byte buffer with the private key encoding. * @param {number} keyType (optional) The KeyType, such as KeyType.RSA. If * omitted or null, then partially decode the private key to determine the key * type. * @throws TpmPrivateKey.Error for errors decoding the key. */ TpmPrivateKey.prototype.loadPkcs1 = function(encoding, keyType) { if (encoding instanceof Blob) encoding = encoding.buf(); if (keyType == undefined) { // Try to determine the key type. try { var parsedNode = DerNode.parse(encoding); var children = parsedNode.getChildren(); // An RsaPrivateKey has integer version 0 and 8 integers. if (children.length == 9 && (children[0] instanceof DerInteger) && children[0].toVal() == 0 && (children[1] instanceof DerInteger) && (children[2] instanceof DerInteger) && (children[3] instanceof DerInteger) && (children[4] instanceof DerInteger) && (children[5] instanceof DerInteger) && (children[6] instanceof DerInteger) && (children[7] instanceof DerInteger) && (children[8] instanceof DerInteger)) keyType = KeyType.RSA; else // Assume it is an EC key. Try decoding it below. keyType = KeyType.EC; } catch (ex) { // Assume it is an EC key. Try decoding it below. keyType = KeyType.EC; } } if (keyType == KeyType.EC) { // Encode the DER as PEM. var keyBase64 = encoding.toString('base64'); var keyPem = "-----BEGIN EC PRIVATE KEY-----\n"; for (var i = 0; i < keyBase64.length; i += 64) keyPem += (keyBase64.substr(i, 64) + "\n"); keyPem += "-----END EC PRIVATE KEY-----"; this.privateKey_ = keyPem; } else if (keyType == KeyType.RSA) { // Encode the DER as PEM. var keyBase64 = encoding.toString('base64'); var keyPem = "-----BEGIN RSA PRIVATE KEY-----\n"; for (var i = 0; i < keyBase64.length; i += 64) keyPem += (keyBase64.substr(i, 64) + "\n"); keyPem += "-----END RSA PRIVATE KEY-----"; this.privateKey_ = keyPem; } else throw new TpmPrivateKey.Error(new Error ("loadPkcs1: Unrecognized keyType: " + keyType)); this.keyType_ = keyType; this.subtleKey_ = null; this.decryptSubtleKey_ = null; }; /** * Load the unencrypted private key from a buffer with the PKCS #8 encoding. * This replaces any existing private key in this object. * @param {Buffer} encoding The byte buffer with the private key encoding. * @param {number} keyType (optional) The KeyType, such as KeyType.RSA. If * omitted or null, then partially decode the private key to determine the key * type. * @throws TpmPrivateKey.Error for errors decoding the key. */ TpmPrivateKey.prototype.loadPkcs8 = function(encoding, keyType) { if (encoding instanceof Blob) encoding = encoding.buf(); var privateKeyDer; if (keyType == undefined) { // Decode the PKCS #8 private key to find the algorithm OID and the inner // private key DER. var oidString, algorithmParameters, privateKeyDer; try { var parsedNode = DerNode.parse(encoding); var pkcs8Children = parsedNode.getChildren(); // Get the algorithm OID and parameters. var algorithmIdChildren = DerNode.getSequence(pkcs8Children, 1).getChildren(); oidString = algorithmIdChildren[0].toVal(); algorithmParameters = algorithmIdChildren[1]; // Get the value of the 3rd child which is the octet string. privateKeyDer = pkcs8Children[2].toVal(); } catch (ex) { // Error decoding as PKCS #8. Try PKCS #1 for backwards compatibility. try { this.loadPkcs1(encoding); return; } catch (ex) { throw new TpmPrivateKey.Error(new Error ("loadPkcs8: Error decoding private key: " + ex)); } } if (oidString == TpmPrivateKey.EC_ENCRYPTION_OID) keyType = KeyType.EC; else if (oidString == TpmPrivateKey.RSA_ENCRYPTION_OID) keyType = KeyType.RSA; else throw new TpmPrivateKey.Error(new Error ("loadPkcs8: Unrecognized private key OID: " + oidString)); } else { // Decode the PKCS #8 key to get the inner private key DER. var parsedNode = DerNode.parse(encoding); // Get the value of the 3rd child which is the octet string. privateKeyDer = parsedNode.getChildren()[2].toVal(); } this.loadPkcs1(privateKeyDer, keyType); }; /** * Load the encrypted private key from a buffer with the PKCS #8 encoding of * the EncryptedPrivateKeyInfo. * This replaces any existing private key in this object. This partially * decodes the private key to determine the key type. * @param {Buffer} encoding The byte buffer with the private key encoding. * @param {Buffer} password The password for decrypting the private key, which * should have characters in the range of 1 to 127. * @throws TpmPrivateKey.Error for errors decoding the key. */ TpmPrivateKey.prototype.loadEncryptedPkcs8 = function(encoding, password) { if (encoding instanceof Blob) encoding = encoding.buf(); if (password instanceof Blob) password = password.buf(); // Decode the PKCS #8 EncryptedPrivateKeyInfo. // See https://tools.ietf.org/html/rfc5208. var oidString; var parameters; var encryptedKey; try { var parsedNode = DerNode.parse(encoding, 0); var encryptedPkcs8Children = parsedNode.getChildren(); var algorithmIdChildren = DerNode.getSequence (encryptedPkcs8Children, 0).getChildren(); oidString = algorithmIdChildren[0].toVal(); parameters = algorithmIdChildren[1]; encryptedKey = encryptedPkcs8Children[1].toVal(); } catch (ex) { throw new TpmPrivateKey.Error(new Error ("Cannot decode the PKCS #8 EncryptedPrivateKeyInfo: " + ex)); } // Use the password to get the unencrypted pkcs8Encoding. var pkcs8Encoding; if (oidString == TpmPrivateKey.PBES2_OID) { // Decode the PBES2 parameters. See https://www.ietf.org/rfc/rfc2898.txt . var keyDerivationOidString; var keyDerivationParameters; var encryptionSchemeOidString; var encryptionSchemeParameters; try { var parametersChildren = parameters.getChildren(); var keyDerivationAlgorithmIdChildren = DerNode.getSequence (parametersChildren, 0).getChildren(); keyDerivationOidString = keyDerivationAlgorithmIdChildren[0].toVal(); keyDerivationParameters = keyDerivationAlgorithmIdChildren[1]; var encryptionSchemeAlgorithmIdChildren = DerNode.getSequence (parametersChildren, 1).getChildren(); encryptionSchemeOidString = encryptionSchemeAlgorithmIdChildren[0].toVal(); encryptionSchemeParameters = encryptionSchemeAlgorithmIdChildren[1]; } catch (ex) { throw new TpmPrivateKey.Error(new Error ("Cannot decode the PBES2 parameters: " + ex)); } // Get the derived key from the password. var derivedKey = null; if (keyDerivationOidString == TpmPrivateKey.PBKDF2_OID) { // Decode the PBKDF2 parameters. var salt; var nIterations; try { var pbkdf2ParametersChildren = keyDerivationParameters.getChildren(); salt = pbkdf2ParametersChildren[0].toVal(); nIterations = pbkdf2ParametersChildren[1].toVal(); } catch (ex) { throw new TpmPrivateKey.Error(new Error ("Cannot decode the PBES2 parameters: " + ex)); } // Check the encryption scheme here to get the needed result length. var resultLength; if (encryptionSchemeOidString == TpmPrivateKey.DES_EDE3_CBC_OID) resultLength = TpmPrivateKey.DES_EDE3_KEY_LENGTH; else throw new TpmPrivateKey.Error(new Error ("Unrecognized PBES2 encryption scheme OID: " + encryptionSchemeOidString)); try { // TODO: Support Crypto.subtle. derivedKey = Crypto.pbkdf2Sync (password, salt.buf(), nIterations, resultLength, 'sha1'); } catch (ex) { throw new TpmPrivateKey.Error(new Error ("Error computing the derived key using PBKDF2 with HMAC SHA1: " + ex)); } } else throw new TpmPrivateKey.Error(new Error ("Unrecognized PBES2 key derivation OID: " + keyDerivationOidString)); // Use the derived key to get the unencrypted pkcs8Encoding. if (encryptionSchemeOidString == TpmPrivateKey.DES_EDE3_CBC_OID) { // Decode the DES-EDE3-CBC parameters. var initialVector; try { initialVector = encryptionSchemeParameters.toVal(); } catch (ex) { throw new TpmPrivateKey.Error(new Error ("Cannot decode the DES-EDE3-CBC parameters: " + ex)); } try { // TODO: Support Crypto.subtle. var cipher = Crypto.createDecipheriv ("des-ede3-cbc", derivedKey, initialVector.buf()); pkcs8Encoding = Buffer.concat ([cipher.update(encryptedKey.buf()), cipher.final()]); } catch (ex) { throw new TpmPrivateKey.Error(new Error ("Error decrypting PKCS #8 key with DES-EDE3-CBC: " + ex)); } } else throw new TpmPrivateKey.Error(new Error ("Unrecognized PBES2 encryption scheme OID: " + encryptionSchemeOidString)); } else throw new TpmPrivateKey.Error(new Error ("Unrecognized PKCS #8 EncryptedPrivateKeyInfo OID: " + oidString)); this.loadPkcs8(pkcs8Encoding); }; /** * Get the encoded public key for this private key. * @return {Blob} The public key encoding Blob. * @throws TpmPrivateKey.Error if no private key is loaded, or error converting * to a public key. */ TpmPrivateKey.prototype.derivePublicKey = function() { if (this.keyType_ != KeyType.RSA) throw new TpmPrivateKey.Error(new Error ("derivePublicKey: The private key is not loaded")); try { var rsaPrivateKeyDer = DataUtils.privateKeyPemToDer(this.privateKey_); // Decode the PKCS #1 RSAPrivateKey. var parsedNode = DerNode.parse(rsaPrivateKeyDer, 0); var rsaPrivateKeyChildren = parsedNode.getChildren(); var modulus = rsaPrivateKeyChildren[1]; var publicExponent = rsaPrivateKeyChildren[2]; // Encode the PKCS #1 RSAPublicKey. var rsaPublicKey = new DerNode.DerSequence(); rsaPublicKey.addChild(modulus); rsaPublicKey.addChild(publicExponent); var rsaPublicKeyDer = rsaPublicKey.encode(); // Encode the SubjectPublicKeyInfo. var algorithmIdentifier = new DerNode.DerSequence(); algorithmIdentifier.addChild(new DerNode.DerOid(new OID (TpmPrivateKey.RSA_ENCRYPTION_OID))); algorithmIdentifier.addChild(new DerNode.DerNull()); var publicKey = new DerNode.DerSequence(); publicKey.addChild(algorithmIdentifier); publicKey.addChild(new DerNode.DerBitString(rsaPublicKeyDer.buf(), 0)); return publicKey.encode(); } catch (ex) { // We don't expect this to happen since the key was encoded here. throw new TpmPrivateKey.Error(new Error ("derivePublicKey: Error decoding private key " + ex)); } }; /** * Decrypt the cipherText using this private key according the encryption * algorithmType. Only RSA encryption is supported for now. * @param {Buffer} cipherText The cipher text byte buffer. * @param {number} algorithmType (optional) This decrypts according to * algorithmType which is an int from the EncryptAlgorithmType enum. If omitted, * use RsaOaep. * @param {boolean} useSync (optional) If true then return a SyncPromise which * is already fulfilled. If omitted or false, this may return a SyncPromise or * an async Promise. * @return {Promise|SyncPromise} A promise which returns the decrypted data Blob, * or a promise rejected with TpmPrivateKey.Error if the private key is not * loaded, if decryption is not supported for this key type, or for error * decrypting. */ TpmPrivateKey.prototype.decryptPromise = function (cipherText, algorithmType, useSync) { if (typeof algorithmType === 'boolean') { // algorithmType is omitted, so shift. useSync = algorithmType; algorithmType = undefined; } if (algorithmType == undefined) algorithmType = EncryptAlgorithmType.RsaOaep; if (this.keyType_ == null) return SyncPromise.reject(new TpmPrivateKey.Error(new Error ("decrypt: The private key is not loaded"))); if (UseSubtleCrypto() && !useSync && // Crypto.subtle doesn't implement PKCS1 padding. algorithmType != EncryptAlgorithmType.RsaPkcs) { if (algorithmType == EncryptAlgorithmType.RsaOaep) { return this.getDecryptSubtleKeyPromise_() .then(function(subtleKey) { return crypto.subtle.decrypt ({ name: "RSA-OAEP" }, subtleKey, cipherText); }) .then(function(result) { return Promise.resolve(new Blob(new Uint8Array(result), false)); }); } else return Promise.reject(new Error("Unsupported padding scheme")); } else { var padding; if (algorithmType == EncryptAlgorithmType.RsaPkcs) padding = cryptoConstants.RSA_PKCS1_PADDING; else if (algorithmType == EncryptAlgorithmType.RsaOaep) padding = cryptoConstants.RSA_PKCS1_OAEP_PADDING; else return SyncPromise.reject(new TpmPrivateKey.Error(new Error ("Unsupported padding scheme"))); try { // In Node.js, privateDecrypt requires version v0.12. return SyncPromise.resolve(new Blob (Crypto.privateDecrypt ({ key: this.privateKey_, padding: padding }, cipherText), false)); } catch (err) { return SyncPromise.reject(new TpmPrivateKey.Error(err)); } } }; /** * Sign the data with this private key, returning a signature Blob. * @param {Buffer} data The input byte buffer. * @param {number} digestAlgorithm The digest algorithm as an int from the * DigestAlgorithm enum. * @param {boolean} useSync (optional) If true then return a SyncPromise which * is already fulfilled. If omitted or false, this may return a SyncPromise or * an async Promise. * @return {Promise|SyncPromise} A promise which returns the signature Blob (or * an isNull Blob if this private key is not initialized), or a promise rejected * with TpmPrivateKey.Error for unrecognized digestAlgorithm or an error in * signing. */ TpmPrivateKey.prototype.signPromise = function(data, digestAlgorithm, useSync) { if (this.keyType_ == null) return SyncPromise.resolve(new Blob()); if (digestAlgorithm != DigestAlgorithm.SHA256) return SyncPromise.reject(new TpmPrivateKey.Error(new Error ("TpmPrivateKey.sign: Unsupported digest algorithm"))); if (UseSubtleCrypto() && !useSync) { var algorithm; if (this.keyType_ === KeyType.RSA) algorithm = { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" }}; else return SyncPromise.reject(new TpmPrivateKey.Error(new Error ("signPromise: Unrecognized key type " + this.keyType_))); return this.getSubtleKeyPromise_() .then(function(subtleKey) { return crypto.subtle.sign(algorithm, subtleKey, data); }) .then(function(signature) { var result = new Blob(new Uint8Array(signature), true); return Promise.resolve(result); }); } else { var signer; if (this.keyType_ === KeyType.RSA) signer = Crypto.createSign("RSA-SHA256"); else if (this.keyType === KeyType.EC) // Just create a "sha256". The Crypto library will infer ECDSA from the key. signer = Crypto.createSign("sha256"); else return SyncPromise.reject(new TpmPrivateKey.Error(new Error ("signPromise: Unrecognized key type " + this.keyType_))); signer.update(data); var signature = new Buffer (DataUtils.toNumbersIfString(signer.sign(this.privateKey_))); var result = new Blob(signature, false); return SyncPromise.resolve(result); } }; /** * Get the encoded unencrypted private key in PKCS #1. * @return {Blob} The private key encoding Blob. * @throws {TpmPrivateKey.Error} If no private key is loaded, or error encoding. */ TpmPrivateKey.prototype.toPkcs1 = function() { if (this.keyType_ == null) throw new TpmPrivateKey.Error(new Error ("toPkcs1: The private key is not loaded")); // this.privateKey_ is already the base64-encoded PKCS #1 key. return new Blob(DataUtils.privateKeyPemToDer(this.privateKey_), false); }; /** * Get the encoded unencrypted private key in PKCS #8. * @return {Blob} The private key encoding Blob. * @throws {TpmPrivateKey.Error} If no private key is loaded, or error encoding. */ TpmPrivateKey.prototype.toPkcs8 = function() { if (this.keyType_ == null) throw new TpmPrivateKey.Error(new Error ("toPkcs8: The private key is not loaded")); var oid; if (this.keyType_ === KeyType.RSA) oid = new OID(TpmPrivateKey.RSA_ENCRYPTION_OID); else if (this.keyType === KeyType.EC) oid = new OID(TpmPrivateKey.EC_ENCRYPTION_OID); else // We don't expect this to happen. throw new TpmPrivateKey.Error(new Error ("toPkcs8: Unrecognized key type " + this.keyType_)); return TpmPrivateKey.encodePkcs8PrivateKey (this.toPkcs1().buf(), oid, new DerNode.DerNull()); }; /** * Get the encoded encrypted private key in PKCS #8. * @param {Buffer} password The password for encrypting the private key, which * should have characters in the range of 1 to 127. * @return {Blob} The encoding Blob of the EncryptedPrivateKeyInfo. * @throws {TpmPrivateKey.Error} If no private key is loaded, or error encoding. */ TpmPrivateKey.prototype.toEncryptedPkcs8 = function(password) { if (this.keyType_ == null) throw new TpmPrivateKey.Error(new Error ("toPkcs8: The private key is not loaded")); if (password instanceof Blob) password = password.buf(); // Create the derivedKey from the password. var nIterations = 2048; var salt = Crypto.randomBytes(8); var derivedKey; try { // TODO: Support Crypto.subtle. derivedKey = Crypto.pbkdf2Sync (password, salt, nIterations, TpmPrivateKey.DES_EDE3_KEY_LENGTH, 'sha1'); } catch (ex) { throw new TpmPrivateKey.Error(new Error ("Error computing the derived key using PBKDF2 with HMAC SHA1: " + ex)); } // Use the derived key to get the encrypted pkcs8Encoding. var encryptedEncoding; var initialVector = Crypto.randomBytes(8); try { // TODO: Support Crypto.subtle. var cipher = Crypto.createCipheriv ("des-ede3-cbc", derivedKey, initialVector); encryptedEncoding = Buffer.concat ([cipher.update(this.toPkcs8().buf()), cipher.final()]); } catch (ex) { throw new TpmPrivateKey.Error(new Error ("Error encrypting PKCS #8 key with DES-EDE3-CBC: " + ex)); } try { // Encode the PBES2 parameters. See https://www.ietf.org/rfc/rfc2898.txt . var keyDerivationParameters = new DerSequence(); keyDerivationParameters.addChild(new DerNode.DerOctetString(salt)); keyDerivationParameters.addChild(new DerNode.DerInteger(nIterations)); var keyDerivationAlgorithmIdentifier = new DerSequence(); keyDerivationAlgorithmIdentifier.addChild(new DerNode.DerOid (TpmPrivateKey.PBKDF2_OID)); keyDerivationAlgorithmIdentifier.addChild(keyDerivationParameters); var encryptionSchemeAlgorithmIdentifier = new DerSequence(); encryptionSchemeAlgorithmIdentifier.addChild(new DerNode.DerOid (TpmPrivateKey.DES_EDE3_CBC_OID)); encryptionSchemeAlgorithmIdentifier.addChild(new DerNode.DerOctetString (initialVector)); var encryptedKeyParameters = new DerSequence(); encryptedKeyParameters.addChild(keyDerivationAlgorithmIdentifier); encryptedKeyParameters.addChild(encryptionSchemeAlgorithmIdentifier); var encryptedKeyAlgorithmIdentifier = new DerSequence(); encryptedKeyAlgorithmIdentifier.addChild(new DerNode.DerOid (TpmPrivateKey.PBES2_OID)); encryptedKeyAlgorithmIdentifier.addChild(encryptedKeyParameters); // Encode the PKCS #8 EncryptedPrivateKeyInfo. // See https://tools.ietf.org/html/rfc5208. var encryptedKey = new DerSequence(); encryptedKey.addChild(encryptedKeyAlgorithmIdentifier); encryptedKey.addChild(new DerNode.DerOctetString(encryptedEncoding)); return encryptedKey.encode(); } catch (ex) { throw new TpmPrivateKey.Error(new Error ("Error encoding the encryped PKCS #8 private key: " + ex)); } }; /** * Generate a key pair according to keyParams and return a new TpmPrivateKey * with the private key. You can get the public key with derivePublicKey. * @param {KeyParams} keyParams The parameters of the key. * @param {boolean} useSync (optional) If true then return a SyncPromise which * is already fulfilled. If omitted or false, this may return a SyncPromise or * an async Promise. * @return {Promise|SyncPromise} A promise which returns the new TpmPrivateKey, * or a promise rejected with Error if the key type is not supported, or a * promise rejected with TpmPrivateKey.Error for an invalid key size, or an * error generating. */ TpmPrivateKey.generatePrivateKeyPromise = function(keyParams, useSync) { // TODO: Check for RSAKey in the browser. if (UseSubtleCrypto() && !useSync) { if (keyParams.getKeyType() === KeyType.RSA) { var privateKey = null; return crypto.subtle.generateKey ({ name: "RSASSA-PKCS1-v1_5", modulusLength: keyParams.getKeySize(), publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: {name: "SHA-256"} }, true, ["sign"]) .then(function(key) { privateKey = key.privateKey; // Export the private key to DER. return crypto.subtle.exportKey("pkcs8", key.privateKey); }) .then(function(pkcs8Der) { var result = new TpmPrivateKey(); result.loadPkcs8(new Blob(new Uint8Array(pkcs8Der), false), KeyType.RSA); // Cache the crypto.subtle private key. result.subtleKey_ = privateKey; return SyncPromise.resolve(result); }); } else return Promise.reject(new Error ("Cannot generate a key pair of type " + keyParams.getKeyType())); } else { // Assume we are in Node.js. var privateKeyPem; if (keyParams.getKeyType() === KeyType.RSA) { if (!RsaKeypair) return SyncPromise.reject(new TpmPrivateKey.Error(new Error ("Need to install rsa-keypair: sudo npm install rsa-keypair"))); var keyPair = RsaKeypair.generate(keyParams.getKeySize()); privateKeyPem = keyPair.privateKey.toString(); } else return SyncPromise.reject(new Error ("Cannot generate a key pair of type " + keyParams.getKeyType())); var result = new TpmPrivateKey(); result.privateKey_ = privateKeyPem; result.keyType_ = keyParams.getKeyType(); return SyncPromise.resolve(result); } }; /** * Encode the private key to a PKCS #8 private key. We do this explicitly here * to avoid linking to extra OpenSSL libraries. * @param {Buffer} privateKeyDer The input private key DER. * @param {OID} oid The OID of the privateKey. * @param {DerNode} parameters The DerNode of the parameters for the OID. * @return {Blob} The PKCS #8 private key DER. */ TpmPrivateKey.encodePkcs8PrivateKey = function(privateKeyDer, oid, parameters) { var algorithmIdentifier = new DerNode.DerSequence(); algorithmIdentifier.addChild(new DerNode.DerOid(oid)); algorithmIdentifier.addChild(parameters); var result = new DerNode.DerSequence(); result.addChild(new DerNode.DerInteger(0)); result.addChild(algorithmIdentifier); result.addChild(new DerNode.DerOctetString(privateKeyDer)); return result.encode(); }; /** * Encode the RSAKey private key as a PKCS #1 private key. * @param {RSAKey} rsaKey The RSAKey private key. * @return {Blob} The PKCS #1 private key DER. */ TpmPrivateKey.encodePkcs1PrivateKeyFromRSAKey = function(rsaKey) { // Imitate KJUR getEncryptedPKCS5PEMFromRSAKey. var result = new DerNode.DerSequence(); result.addChild(new DerNode.DerInteger(0)); result.addChild(new DerNode.DerInteger(TpmPrivateKey.bigIntegerToBuffer(rsaKey.n))); result.addChild(new DerNode.DerInteger(rsaKey.e)); result.addChild(new DerNode.DerInteger(TpmPrivateKey.bigIntegerToBuffer(rsaKey.d))); result.addChild(new DerNode.DerInteger(TpmPrivateKey.bigIntegerToBuffer(rsaKey.p))); result.addChild(new DerNode.DerInteger(TpmPrivateKey.bigIntegerToBuffer(rsaKey.q))); result.addChild(new DerNode.DerInteger(TpmPrivateKey.bigIntegerToBuffer(rsaKey.dmp1))); result.addChild(new DerNode.DerInteger(TpmPrivateKey.bigIntegerToBuffer(rsaKey.dmq1))); result.addChild(new DerNode.DerInteger(TpmPrivateKey.bigIntegerToBuffer(rsaKey.coeff))); return result.encode(); }; /** * Encode the public key values in the RSAKey private key as a * SubjectPublicKeyInfo. * @param {RSAKey} rsaKey The RSAKey private key with the public key values. * @return {Blob} The SubjectPublicKeyInfo DER. */ TpmPrivateKey.encodePublicKeyFromRSAKey = function(rsaKey) { var rsaPublicKey = new DerNode.DerSequence(); rsaPublicKey.addChild(new DerNode.DerInteger (TpmPrivateKey.bigIntegerToBuffer(rsaKey.n))); rsaPublicKey.addChild(new DerNode.DerInteger(rsaKey.e)); var algorithmIdentifier = new DerNode.DerSequence(); algorithmIdentifier.addChild (new DerNode.DerOid(new OID(TpmPrivateKey.RSA_ENCRYPTION_OID))); algorithmIdentifier.addChild(new DerNode.DerNull()); var result = new DerNode.DerSequence(); result.addChild(algorithmIdentifier); result.addChild(new DerNode.DerBitString(rsaPublicKey.encode().buf(), 0)); return result.encode(); }; /** * Convert a BigInteger to a Buffer. * @param {BigInteger} bigInteger The BigInteger. * @return {Buffer} The Buffer. */ TpmPrivateKey.bigIntegerToBuffer = function(bigInteger) { // Imitate KJUR.asn1.ASN1Util.bigIntToMinTwosComplementsHex. var hex = bigInteger.toString(16); if (hex.substr(0, 1) == "-") throw new Error ("TpmPrivateKey.bigIntegerToBuffer: Negative integers are not currently supported"); if (hex.length % 2 == 1) // Odd number of characters. hex = "0" + hex; else { if (! hex.match(/^[0-7]/)) // The first byte is >= 0x80, so prepend a zero to keep it positive. hex = "00" + hex; } return new Buffer(hex, 'hex'); }; /** * A private method to get the cached crypto.subtle key (for signing), importing * it from this.privateKey_ if needed. This means we only have to do this once per * session, giving us a small but not insignificant performance boost. * @return {Promise} A promise which returns the cached crypto.subtle key. */ TpmPrivateKey.prototype.getSubtleKeyPromise_ = function() { if (!this.subtleKey_) { // This is the first time in the session that we're using crypto subtle // with this key so we have to convert to pkcs8 and import it. if (this.keyType_ === KeyType.RSA) { var algorithm = { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" }}; var privateDER = DataUtils.privateKeyPemToDer(this.privateKey_); var pkcs8 = TpmPrivateKey.encodePkcs8PrivateKey (privateDER, new OID(TpmPrivateKey.RSA_ENCRYPTION_OID), new DerNode.DerNull()).buf(); var thisKey = this; return crypto.subtle.importKey ("pkcs8", pkcs8, algorithm, true, ["sign"]) .then(function(subtleKey) { // Cache the crypto.subtle key object. thisKey.subtleKey_ = subtleKey; return Promise.resolve(thisKey.subtleKey_); }); } else return SyncPromise.reject(new TpmPrivateKey.Error(new Error ("Unrecognized key type " + this.keyType_))); } else // The crypto.subtle key has been cached on a previous call or from keygen. return Promise.resolve(this.subtleKey_); }; /** * A private method to get the cached crypto.subtle key for decrypting, * importing it from this.privateKey_ if needed. This means we only have to do * this once per session, giving us a small but not insignificant performance * boost. This is separate from getSubtleKeyPromise_ because the import * parameters for decrypt are different. * @return {Promise} A promise which returns the cached crypto.subtle key for * decrypt. */ TpmPrivateKey.prototype.getDecryptSubtleKeyPromise_ = function() { if (!this.decryptSubtleKey_) { // This is the first time in the session that we're using crypto subtle // with this key so we have to convert to pkcs8 and import it. if (this.keyType_ === KeyType.RSA) { var algorithm = { name: "RSA-OAEP", hash: {name: "SHA-1"} }; var privateDER = DataUtils.privateKeyPemToDer(this.privateKey_); var pkcs8 = TpmPrivateKey.encodePkcs8PrivateKey (privateDER, new OID(TpmPrivateKey.RSA_ENCRYPTION_OID), new DerNode.DerNull()).buf(); var thisKey = this; return crypto.subtle.importKey ("pkcs8", pkcs8, algorithm, false, ["decrypt"]) .then(function(subtleKey) { // Cache the crypto.subtle key object. thisKey.decryptSubtleKey_ = subtleKey; return Promise.resolve(thisKey.decryptSubtleKey_); }); } else return SyncPromise.reject(new TpmPrivateKey.Error(new Error ("Unrecognized key type " + this.keyType_))); } else // The crypto.subtle key has been cached on a previous call. return Promise.resolve(this.decryptSubtleKey_); }; TpmPrivateKey.RSA_ENCRYPTION_OID = "1.2.840.113549.1.1.1"; TpmPrivateKey.EC_ENCRYPTION_OID = "1.2.840.10045.2.1"; TpmPrivateKey.PBES2_OID = "1.2.840.113549.1.5.13"; TpmPrivateKey.PBKDF2_OID = "1.2.840.113549.1.5.12"; TpmPrivateKey.DES_EDE3_CBC_OID = "1.2.840.113549.3.7"; TpmPrivateKey.DES_EDE3_KEY_LENGTH = 24;