From 283cdd4d120f47a488649b591f9ad88a3587ebe0 Mon Sep 17 00:00:00 2001 From: Jonathan Patchell Date: Thu, 21 Dec 2017 11:26:36 -0500 Subject: [PATCH] [FAB-7726] Fix PKCS11 implementation These changes provide support for using the PKCS11 implementation with external HSMs. * The bccsp pkcs11 was modified to support how the client calls the CryptoSuite to provide support for the e2e integration test. * The unit test util was modified to load keys from the CryptoSuite using GetKey calculating the SKI from cert if configured for using pkcs11 keys. Change-Id: I1fdd7a497c28d91022d09a873de80b65182d93f3 Signed-off-by: Jonathan Patchell Signed-off-by: Gari Singh --- build/tasks/ca.js | 2 + fabric-client/lib/impl/bccsp_pkcs11.js | 159 ++++++++++++++++++--- fabric-client/lib/impl/ecdsa/pkcs11_key.js | 69 ++++++++- test/unit/pkcs11.js | 16 +-- 4 files changed, 215 insertions(+), 31 deletions(-) diff --git a/build/tasks/ca.js b/build/tasks/ca.js index 61c594f3a6..6ca54da03e 100644 --- a/build/tasks/ca.js +++ b/build/tasks/ca.js @@ -16,8 +16,10 @@ const DEPS = [ 'fabric-client/lib/Config.js', 'fabric-client/lib/Remote.js', 'fabric-client/lib/User.js', + 'fabric-client/lib/impl/bccsp_pkcs11.js', 'fabric-client/lib/impl/CouchDBKeyValueStore.js', 'fabric-client/lib/impl/CryptoSuite_ECDSA_AES.js', + 'fabric-client/lib/impl/aes/*', 'fabric-client/lib/impl/ecdsa/*', 'fabric-client/lib/impl/CryptoKeyStore.js', 'fabric-client/lib/impl/FileKeyValueStore.js', diff --git a/fabric-client/lib/impl/bccsp_pkcs11.js b/fabric-client/lib/impl/bccsp_pkcs11.js index aa338dec21..4bc15d0c02 100644 --- a/fabric-client/lib/impl/bccsp_pkcs11.js +++ b/fabric-client/lib/impl/bccsp_pkcs11.js @@ -21,18 +21,44 @@ var utils = require('../utils'); var aesKey = require('./aes/pkcs11_key.js'); var ecdsaKey = require('./ecdsa/pkcs11_key.js'); +var elliptic = require('elliptic'); +var EC = elliptic.ec; + +var jsrsa = require('jsrsasign'); +var KEYUTIL = jsrsa.KEYUTIL; + var BN = require('bn.js'); var ecsig = require('elliptic/lib/elliptic/ec/signature.js'); var callsite = require('callsite'); var crypto = require('crypto'); var pkcs11js = require('pkcs11js'); var util = require('util'); +var ECDSAKey = require('./ecdsa/key.js'); +var hashPrimitives = require('../hash.js'); var logger = utils.getLogger('crypto_pkcs11'); const _pkcs11ParamsSizeToOid = { 256: '06082A8648CE3D030107', 384: '06052B81040022' }; const _pkcs11ParamsOidToSize = { '06082A8648CE3D030107': 256, '06052B81040022': 384 }; +var _pkcs11 = null; +var _initialized = false; + +function _preventMalleability(sig, curve) { + + var halfOrder = curve.n.shrn(1); + if (!halfOrder) { + throw new Error('Can not find the half order needed to calculate "s" value for immalleable signatures. Unsupported curve name: ' + curve); + } + + if (sig.s.cmp(halfOrder) == 1) { + var bigNum = curve.n; + sig.s = bigNum.sub(sig.s); + } + + return sig; +} + /* * Function name and line number for logger. */ @@ -88,7 +114,7 @@ var CryptoSuite_PKCS11 = class extends api.CryptoSuite { /* * If no slot specified, get it from env var or config file. */ - var pkcs11Slot = opts.slot; + var pkcs11Slot = opts ? opts.slot : null; if (typeof pkcs11Slot === 'undefined' || pkcs11Slot === null) pkcs11Slot = utils.getConfigSetting('crypto-pkcs11-slot'); if (typeof pkcs11Slot === 'undefined' || pkcs11Slot === null) @@ -100,21 +126,57 @@ var CryptoSuite_PKCS11 = class extends api.CryptoSuite { /* * If no pin specified, get it from env var or config file. */ - var pkcs11Pin = opts.pin; + var pkcs11Pin = opts ? opts.pin: null; if (typeof pkcs11Pin === 'undefined' || pkcs11Pin === null) pkcs11Pin = utils.getConfigSetting('crypto-pkcs11-pin'); if (typeof pkcs11Pin === 'undefined' || pkcs11Pin === null || typeof pkcs11Pin !== 'string') throw new Error(__func() + 'PKCS11 PIN must be set'); + super(); this._keySize = keySize; + if (this._keySize === 256) { + this._curveName = 'secp256r1'; + this._ecdsaCurve = elliptic.curves['p256']; + } else if (this._keySize === 384) { + this._curveName = 'secp384r1'; + this._ecdsaCurve = elliptic.curves['p384']; + } + + if (typeof hash === 'string' && hash !== null && hash !== '') { + this._hashAlgo = hash; + } else { + this._hashAlgo = utils.getConfigSetting('crypto-hash-algo'); + } + + switch (this._hashAlgo.toLowerCase() + '-' + this._keySize) { + case 'sha3-256': + this._hashFunction = hashPrimitives.sha3_256; + break; + case 'sha3-384': + this._hashFunction = hashPrimitives.sha3_384; + break; + case 'sha2-256': + this._hashFunction = hashPrimitives.sha2_256; + break; + case 'sha2-384': + this._hashFunction = hashPrimitives.sha2_384; + break; + default: + throw Error(util.format('Unsupported hash algorithm and key size pair: %s-%s', this._hashAlgo, this._keySize)); + } + /* * Load native PKCS11 library, open PKCS11 session and login. */ - this._pkcs11 = new pkcs11js.PKCS11(); + if (_pkcs11 == null) { + _pkcs11 = new pkcs11js.PKCS11(); + } + this._pkcs11 = _pkcs11; + this._pkcs11OpenSession(this._pkcs11, pkcs11Lib, pkcs11Slot, pkcs11Pin); /* * SKI to key cache for getKey(ski) function. @@ -147,12 +209,15 @@ var CryptoSuite_PKCS11 = class extends api.CryptoSuite { */ _fixEcpt(ecpt) { if ((ecpt.length & 1) == 0 && - (ecpt[0] == 0x04) && (ecpt[ecpt.length-1] == 0x04)) - { - logger.info(__func() + - 'workaround opencryptoki EC point wrong length: ' + - ecpt.length); - ecpt = ecpt.slice(0, ecpt.length-1); + (ecpt[0] == 0x04) && (ecpt[ecpt.length - 1] == 0x04)) { + console.log(__func() + + 'workaround opencryptoki EC point wrong length: ' + + ecpt.length); + ecpt = ecpt.slice(0, ecpt.length - 1); + } else if (ecpt[0] == 0x04 && ecpt[2] == 0x04) { + console.log('trimming leading 0x04 0xXX', ecpt); + ecpt = ecpt.slice(2); + console.log(ecpt); } return ecpt; } @@ -163,8 +228,11 @@ var CryptoSuite_PKCS11 = class extends api.CryptoSuite { _pkcs11OpenSession(pkcs11, pkcs11Lib, pkcs11Slot, pkcs11Pin) { logger.debug(__func() + 'parameters are pkcs11Slot %s pkcs11Pin %s pkcs11Lib %s', pkcs11Slot, pkcs11Pin, pkcs11Lib); - pkcs11.load(pkcs11Lib); - pkcs11.C_Initialize(); + if (!_initialized) { + pkcs11.load(pkcs11Lib); + pkcs11.C_Initialize(); + _initialized = true; + } try { // Getting info about PKCS11 Module @@ -217,6 +285,7 @@ var CryptoSuite_PKCS11 = class extends api.CryptoSuite { if (this._pkcs11Session != null) pkcs11.C_CloseSession(this._pkcs11Session); pkcs11.C_Finalize(); + _initialized = false; throw(e); } finally { @@ -357,13 +426,13 @@ var CryptoSuite_PKCS11 = class extends api.CryptoSuite { /* * Set CKA_ID of public and private key to be SKI. */ - var ski = crypto.createHash('sha1').update(ecpt).digest(); + var ski = Buffer.from(hashPrimitives.sha2_256(ecpt), 'hex'); this._pkcs11SetAttributeValue( pkcs11, pkcs11Session, handles.publicKey, - [{ type: pkcs11js.CKA_ID, value: ski }]); + [{ type: pkcs11js.CKA_ID, value: ski }, {type: pkcs11js.CKA_LABEL, value: ski.toString('hex')}]); this._pkcs11SetAttributeValue( pkcs11, pkcs11Session, handles.privateKey, - [{ type: pkcs11js.CKA_ID, value: ski }]); + [{ type: pkcs11js.CKA_ID, value: ski }, { type: pkcs11js.CKA_LABEL, value: ski.toString('hex')}]); logger.debug(__func() + 'pub ski: ' + util.inspect( (this._pkcs11GetAttributeValue( pkcs11, pkcs11Session, handles.publicKey, @@ -480,7 +549,8 @@ var CryptoSuite_PKCS11 = class extends api.CryptoSuite { */ var r = new BN(sig.slice(0, sig.length/2).toString('hex'), 16); var s = new BN(sig.slice(sig.length/2).toString('hex'), 16); - var der = (new ecsig({ r: r, s: s})).toDER(); + var sig = _preventMalleability({r: r, s: s}, this._ecdsaCurve); + var der = (new ecsig({ r: sig.r, s: sig.s})).toDER(); logger.debug(__func() + 'ECDSA DER signature: ' + util.inspect(Buffer.from(der), {depth: null})); return Buffer.from(der); @@ -678,6 +748,10 @@ var CryptoSuite_PKCS11 = class extends api.CryptoSuite { return objs; } + setCryptoKeyStore(cryptoKeyStore) { + this._cryptoKeyStore = cryptoKeyStore; + } + /********************************************************************************** * Public interface functions. * **********************************************************************************/ @@ -693,6 +767,9 @@ var CryptoSuite_PKCS11 = class extends api.CryptoSuite { * containing the private key and the public key. */ generateKey(opts) { + if (opts != null && (typeof opts.algorithm === 'undefined' || opts.algorithm === null)) { + opts.algorithm = 'ECDSA'; + } if (typeof opts === 'undefined' || opts === null || typeof opts.algorithm === 'undefined' || opts.algorithm === null || typeof opts.algorithm !== 'string') @@ -726,6 +803,7 @@ var CryptoSuite_PKCS11 = class extends api.CryptoSuite { }); break; case 'ECDSA': + var cryptoSuite = this; return new Promise(function(resolve, reject) { try { var attr = self._pkcs11GenerateECKeyPair( @@ -736,6 +814,7 @@ var CryptoSuite_PKCS11 = class extends api.CryptoSuite { */ var key = new ecdsaKey(attr, self._keySize); self._skiToKey[attr.ski.toString('hex')] = key; + key._cryptoSuite = cryptoSuite; return resolve(key); } catch(e) { @@ -753,7 +832,7 @@ var CryptoSuite_PKCS11 = class extends api.CryptoSuite { * Returns the key this CSP associates to the Subject Key Identifier ski. */ getKey(ski) { - if (typeof ski === 'undefined' || ski === null || !(ski instanceof Buffer)) + if (typeof ski === 'undefined' || ski === null || !(ski instanceof Buffer || typeof ski === 'string')) return Promise.reject(Error(__func() + 'ski must be Buffer type')); /* * Found the ski in the session key cache. @@ -764,6 +843,9 @@ var CryptoSuite_PKCS11 = class extends api.CryptoSuite { util.inspect(hit, { depth: null })); return Promise.resolve(hit); } + if (typeof ski == 'string') { + ski = Buffer.from(ski, 'hex'); + } var self = this; return new Promise(function(resolve, reject) { @@ -839,8 +921,8 @@ var CryptoSuite_PKCS11 = class extends api.CryptoSuite { */ verify(key, signature, digest) { if (typeof key === 'undefined' || key === null || - !(key instanceof ecdsaKey)) - throw new Error(__func() + 'key must be PKCS11_ECDSA_KEY type'); + !(key instanceof ecdsaKey || key instanceof ECDSAKey)) + throw new Error(__func() + 'key must be PKCS11_ECDSA_KEY type or ECDSA_KEY type'); if (typeof signature === 'undefined' || signature === null || !(signature instanceof Buffer)) throw new Error(__func() + 'signature must be Buffer type'); @@ -848,6 +930,12 @@ var CryptoSuite_PKCS11 = class extends api.CryptoSuite { !(digest instanceof Buffer)) throw new Error(__func() + 'digest must be Buffer type'); + if (key instanceof ECDSAKey) { + var ecdsa = new EC(this._ecdsaCurve); + var pubKey = ecdsa.keyFromPublic(key.getPublicKey()._key.pubKeyHex, 'hex'); + return pubKey.verify(this.hash(digest), signature); + } + return this._pkcs11Verify(this._pkcs11, this._pkcs11Session, key.getPublicKey(), digest, signature); } @@ -895,19 +983,33 @@ var CryptoSuite_PKCS11 = class extends api.CryptoSuite { * This is an implementation of {@link module:api.CryptoSuite#importKey} */ importKey(raw, opts) { - if (typeof raw === 'undefined' || raw === null || !(raw instanceof Buffer)) - return Promise.reject(Error(__func() + 'raw must be Buffer type')); + if (typeof opts === 'undefined') { + opts = {}; + } + if (typeof opts.algorithm === 'undefined') { + opts.algorithm = 'X509Certificate'; + } + if (typeof raw === 'undefined' || raw === null || !(raw instanceof Buffer || typeof raw === 'string')) + return Promise.reject(Error(__func() + 'raw must be Buffer type or String type')); if (typeof opts === 'undefined' || opts === null || typeof opts.algorithm === 'undefined' || opts.algorithm === null || typeof opts.algorithm !== 'string') return Promise.reject(Error(__func() + 'opts.algorithm must be String type')); - var token = (opts.ephemeral !== 'undefined' && - opts.ephemeral === false) ? true : false; + var token = (typeof opts.ephemeral !== 'undefined' && + opts.ephemeral) ? false : true; var self = this; switch (opts.algorithm.toUpperCase()) { + case 'X509CERTIFICATE': + var key = KEYUTIL.getKey(raw); + var theKey = new ECDSAKey(key); + if (token) { + return Promise.resolve(theKey); + } else { + return theKey; + } case 'AES': return new Promise(function(resolve, reject) { try { @@ -938,6 +1040,19 @@ var CryptoSuite_PKCS11 = class extends api.CryptoSuite { } } + hash(msg, opts) { + return this._hashFunction(msg); + } + + closeSession() { + this._pkcs11.C_CloseSession(this._pkcs11Session); + } + + finalize() { + this._pkcs11.C_Finalize(); + _initialized = false; + } + }; module.exports = CryptoSuite_PKCS11; diff --git a/fabric-client/lib/impl/ecdsa/pkcs11_key.js b/fabric-client/lib/impl/ecdsa/pkcs11_key.js index c60a36d5ab..9f101fa8f7 100644 --- a/fabric-client/lib/impl/ecdsa/pkcs11_key.js +++ b/fabric-client/lib/impl/ecdsa/pkcs11_key.js @@ -17,6 +17,12 @@ 'use strict'; var api = require('../../api.js'); +var jsrsa = require('jsrsasign'); +var asn1 = jsrsa.asn1; +var crypto = jsrsa.crypto; + +var elliptic = require('elliptic'); +var EC = elliptic.ec; const _spkiBase = { 256: '3059301306072A8648CE3D020106082A8648CE3D030107034200', 384: '', @@ -76,8 +82,69 @@ var PKCS11_ECDSA_KEY = class extends api.Key { } } + signCSR(csr, sigAlgName) { + + csr.asn1SignatureAlg = + new asn1.x509.AlgorithmIdentifier({'name': sigAlgName}); + + var digest = this._cryptoSuite.hash(Buffer.from(csr.asn1CSRInfo.getEncodedHex(), 'hex')); + var sig = this._cryptoSuite.sign(this, Buffer.from(digest, 'hex')); + csr.hexSig = sig.toString('hex'); + + csr.asn1Sig = new asn1.DERBitString({'hex': '00' + csr.hexSig}); + var seq = new asn1.DERSequence({'array': [csr.asn1CSRInfo, csr.asn1SignatureAlg, csr.asn1Sig]}); + csr.hTLV = seq.getEncodedHex(); + csr.isModified = false; + } + + newCSRPEM(param) { + var _KJUR_asn1_csr = asn1.csr; + if (param.subject === undefined) throw 'parameter subject undefined'; + if (param.sbjpubkey === undefined) throw 'parameter sbjpubkey undefined'; + if (param.sigalg === undefined) throw 'parameter sigalg undefined'; + if (param.sbjprvkey === undefined) throw 'parameter sbjpubkey undefined'; + var ecdsa = new EC(this._cryptoSuite._ecdsaCurve); + var pubKey = ecdsa.keyFromPublic(this._pub._ecpt); + var csri = new _KJUR_asn1_csr.CertificationRequestInfo(); + csri.setSubjectByParam(param.subject); + csri.setSubjectPublicKeyByGetKey({xy: pubKey.getPublic('hex'), curve: 'secp256r1'}); + if (param.ext !== undefined && param.ext.length !== undefined) { + for (var i = 0; i < param.ext.length; i++) { + for (key in param.ext[i]) { + csri.appendExtensionByName(key, param.ext[i][key]); + } + } + } + + var csr = new _KJUR_asn1_csr.CertificationRequest({'csrinfo': csri}); + this.signCSR(csr, param.sigalg); + + var pem = csr.getPEMString(); + return pem; + + } + + generateCSR(subjectDN) { + //check to see if this is a private key + if (!this.isPrivate()){ + throw new Error('A CSR cannot be generated from a public key'); + }; + + try { + var csr = this.newCSRPEM({ + subject: { str: asn1.x509.X500Name.ldapToOneline(subjectDN)}, + sbjpubkey: this._pub, + sigalg: 'SHA256withECDSA', + sbjprvkey: this + }); + return csr; + } catch (err) { + throw err; + } + } + getSKI() { - return this._ski; + return this._ski.toString('hex'); } isSymmetric() { diff --git a/test/unit/pkcs11.js b/test/unit/pkcs11.js index 5923c26417..2e83b5d331 100644 --- a/test/unit/pkcs11.js +++ b/test/unit/pkcs11.js @@ -136,13 +136,13 @@ test('\n\n**PKCS11 - generate a non-ephemeral key\n\n', (t) => { // re-construct a new instance of the CryptoSuite so that when "getKey()" // is called it'll not have the previously generated key in memory but // have to retrieve it from persistence store - existingCrypto._pkcs11.C_CloseSession(existingCrypto._pkcs11Session); - existingCrypto._pkcs11.C_Finalize(); + existingCrypto.closeSession(); + existingCrypto.finalize(); - var cryptoUtils = utils.newCryptoSuite({ + cryptoUtils = utils.newCryptoSuite({ lib: libpath, slot: 0, - pin: '98765432' }); + pin: pin }); return cryptoUtils.getKey(ski); }) @@ -253,13 +253,13 @@ test('\n\n**PKCS11 - Test sign and verify with non-ephemeral ECDSA key pair in t // re-construct a new instance of the CryptoSuite so that when "getKey()" // is called it'll not have the previously generated key in memory but // have to retrieve it from persistence store - existingCrypto._pkcs11.C_CloseSession(existingCrypto._pkcs11Session); - existingCrypto._pkcs11.C_Finalize(); + existingCrypto.closeSession(); + existingCrypto.finalize(); - var cryptoUtils = utils.newCryptoSuite({ + cryptoUtils = utils.newCryptoSuite({ lib: libpath, slot: 0, - pin: '98765432' }); + pin: pin }); return cryptoUtils.getKey(ski); })