From 9c3e33ffdf6084def7d5610be823caaf80d8202e Mon Sep 17 00:00:00 2001 From: Jim Zhang Date: Sat, 17 Dec 2016 00:30:12 -0500 Subject: [PATCH] FAB-1408 enhance ecdsa/key.js for public key as part of supporting signature verification, we need public keys extracted from certificates to be represented by the standard api.Key interface. as such we need the lib/impl/ecdsa/key.js to be enhanced so it can be seeded with a public key object Patch 2: re-implemented key.getSKI() to use EC standard as described in X9.62-1988 section 4.3.6 Change-Id: I487750bee67a86e6dbbe7caf08ed46510b24aca7 Signed-off-by: Jim Zhang --- hfc/lib/impl/CryptoSuite_ECDSA_AES.js | 24 ++++++++++- hfc/lib/impl/ecdsa/key.js | 60 +++++++++++++++----------- test/unit/headless-tests.js | 62 ++++++++++++++++++++++++++- 3 files changed, 119 insertions(+), 27 deletions(-) diff --git a/hfc/lib/impl/CryptoSuite_ECDSA_AES.js b/hfc/lib/impl/CryptoSuite_ECDSA_AES.js index daf88eb477..78d72d0b8f 100644 --- a/hfc/lib/impl/CryptoSuite_ECDSA_AES.js +++ b/hfc/lib/impl/CryptoSuite_ECDSA_AES.js @@ -160,7 +160,29 @@ var CryptoSuite_ECDSA_AES = class extends api.CryptoSuite { * To be implemented */ importKey(raw, opts) { - throw new Error('Not implemented yet'); + if (!opts) + throw new Error('Missing required parameter "opts"'); + + if (!opts.algorithm) + throw new Error('Parameter "opts" missing required field "algorithm"'); + + if (opts.algorithm === 'X509Certificate') { + // importing public key from an x.509 certificate + var pemString = Buffer.from(raw).toString(); + try { + var publicKey = KEYUTIL.getKey(raw); + + if (publicKey.type === 'EC') { + return new ECDSAKey(publicKey, publicKey.ecparams.keylen); + } else { + // TODO PEM encoded private keys, DER encoded public certs and private keys, etc + throw new Error('Does not understand certificates other than ECDSA public keys'); + } + } catch(err) { + logger.error('Failed to parse public key from PEM: ' + err); + throw err; + } + } } /** diff --git a/hfc/lib/impl/ecdsa/key.js b/hfc/lib/impl/ecdsa/key.js index dc02821e73..4f1f84efc2 100644 --- a/hfc/lib/impl/ecdsa/key.js +++ b/hfc/lib/impl/ecdsa/key.js @@ -18,20 +18,23 @@ var Ber = require('asn1').Ber; var Hash = require('../../hash.js'); +var utils = require('../../utils.js'); var jsrsa = require('jsrsasign'); var asn1 = jsrsa.asn1; var KEYUTIL = jsrsa.KEYUTIL; var ECDSA = jsrsa.ECDSA; +var logger = utils.getLogger('ecdsa/key.js'); + /* * This module implements the {@link module:api.Key} interface, for ECDSA. * @module ECDSA_KEY */ module.exports = class ECDSA_KEY { /* - * this class represents the private key of an ECDSA key pair. + * this class represents the private or public key of an ECDSA key pair. * - * @param {Object} key This must be the "privKeyObj" part of the object generated by KEYUTIL.generateKeypair() + * @param {Object} key This must be the "privKeyObj" or "pubKeyObj" part of the object generated by jsrsasign.KEYUTIL.generateKeypair() */ constructor(key, keySize) { if (typeof key === 'undefined' || key === null) { @@ -54,27 +57,30 @@ module.exports = class ECDSA_KEY { * @returns {string} a string representation of the hash from a sequence based on the private key bytes */ getSKI() { - var sk = new Ber.Writer(); - sk.startSequence(); - sk.writeInt(1); - sk.writeBuffer(new Buffer(this._key.prvKeyHex, 'hex'), 4); - sk.writeByte(160); - sk.writeByte(7); - if (this._keySize === 384) { - // OID of P384 - sk.writeOID('1.3.132.0.34'); - } else { - // OID of P256 - sk.writeOID('1.2.840.10045.3.1.7'); - } - - sk.endSequence(); + var buff; + + var pointToOctet = function(key) { + var byteLen = (key.ecparams.keylen + 7) >> 3; + let buff = Buffer.allocUnsafe(1 + 2 * byteLen); + buff[0] = 4; // uncompressed point (https://www.security-audit.com/files/x9-62-09-20-98.pdf, section 4.3.6) + var xyhex = key.getPublicKeyXYHex(); + var xBuffer = Buffer.from(xyhex.x, 'hex'); + var yBuffer = Buffer.from(xyhex.y, 'hex'); + logger.debug('ECDSA curve param X: %s', xBuffer.toString('hex')); + logger.debug('ECDSA curve param Y: %s', yBuffer.toString('hex')); + xBuffer.copy(buff, 1 + byteLen - xBuffer.length); + yBuffer.copy(buff, 1 + 2 * byteLen - yBuffer.length); + return buff; + }; - if (this._keySize === 256) { - return Hash.sha3_256(sk.buffer); + if (this._key.isPublic) { + // referencing implementation of the Marshal() method of https://golang.org/src/crypto/elliptic/elliptic.go + buff = pointToOctet(this._key); } else { - return Hash.sha3_384(sk.buffer); + buff = pointToOctet(this.getPublicKey()._key); } + + return Hash.sha2_256(buff); } isSymmetric() { @@ -89,11 +95,15 @@ module.exports = class ECDSA_KEY { } getPublicKey() { - var f = new ECDSA({ curve: this._key.curveName }); - f.setPublicKeyHex(this._key.pubKeyHex); - f.isPrivate = false; - f.isPublic = true; - return new ECDSA_KEY(f, this._keySize); + if (this._key.isPublic) + return this; + else { + var f = new ECDSA({ curve: this._key.curveName }); + f.setPublicKeyHex(this._key.pubKeyHex); + f.isPrivate = false; + f.isPublic = true; + return new ECDSA_KEY(f, this._keySize); + } } /** diff --git a/test/unit/headless-tests.js b/test/unit/headless-tests.js index 77d7176893..90228b13d8 100644 --- a/test/unit/headless-tests.js +++ b/test/unit/headless-tests.js @@ -1032,6 +1032,34 @@ var TEST_KEY_PUBLIC = '04f46815aa00fe2ba2814b906aa4ef1755caf152658de8997a6a85808 var TEST_MSG_SIGNATURE_SHA2_256 = '3046022100a6460b29373fa16ee96172bfe04666140405fdef78182280545d451f08547736022100d9022fe620ceadabbef1714b894b8d6be4b74c0f9c573bd774871764f4f789c9'; var TEST_LONG_MSG_SIGNATURE_SHA2_256 = '3045022073266302d730b07499aabd0f88f12c8749a0f90144034dbc86a8cd742722ad29022100852346f93e50911008ab97afc452f83c5985a19fa3aa6d58f615c03bddaa90a1'; +var TEST_PUBLICKEY_PEM = '-----BEGIN ECDSA PUBLIC KEY-----\n' + +'MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEp53zR7+0ZkYyHw1jattcyl6S0Es9uaqc\n' + +'lABdfMBflwZWBB8jOj+CGW3l4v+qYnAaEJl/TFG73GGtCY5/FNx9E0FkmjCtXsUY\n' + +'tDKOY53CUoBQParnUL0mgpYkBRlguYIG\n' + +'-----END ECDSA PUBLIC KEY-----'; +var TEST_PRIVATEKEY_PEM = '-----BEGIN PRIVATE KEY-----MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgzXIgbq9dtAzwK1yknvFTyQmZKmoLkipQHZUjfE2ILb2hRANCAASaQgfH/7XGn9mQ261INTENal0rLGzZroTK7oKHp5IAPK1nDPu+WovQwiDuaL6CzinkufHxvoeZ3XEZOonRP3qP-----END PRIVATE KEY-----'; +// var TEST_CERT_PEM = '-----BEGIN CERTIFICATE-----MIIDVDCCAvqgAwIBAgIBATAKBggqhkjOPQQDAjBOMRMwEQYDVQQKDArOoyBBY21lIENvMRkwFwYDVQQDExB0ZXN0LmV4YW1wbGUuY29tMQ8wDQYDVQQqEwZHb3BoZXIxCzAJBgNVBAYTAk5MMB4XDTE2MTIxNjIzMTAxM1oXDTE2MTIxNzAxMTAxM1owTjETMBEGA1UECgwKzqMgQWNtZSBDbzEZMBcGA1UEAxMQdGVzdC5leGFtcGxlLmNvbTEPMA0GA1UEKhMGR29waGVyMQswCQYDVQQGEwJOTDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABFKnXh7hBdp6s9OJ/aadigT1z2WzBbSc7Hzb3rkaWFz4e+9alqqWg9lrur/mDYzG9dudC8jFjVa7KIh+2BxgBayjggHHMIIBwzAOBgNVHQ8BAf8EBAMCAgQwJgYDVR0lBB8wHQYIKwYBBQUHAwIGCCsGAQUFBwMBBgIqAwYDgQsBMA8GA1UdEwEB/wQFMAMBAf8wDQYDVR0OBAYEBAECAwQwDwYDVR0jBAgwBoAEAQIDBDBiBggrBgEFBQcBAQRWMFQwJgYIKwYBBQUHMAGGGmh0dHA6Ly9vY0JDQ1NQLmV4YW1wbGUuY29tMCoGCCsGAQUFBzAChh5odHRwOi8vY3J0LmV4YW1wbGUuY29tL2NhMS5jcnQwRgYDVR0RBD8wPYIQdGVzdC5leGFtcGxlLmNvbYERZ29waGVyQGdvbGFuZy5vcmeHBH8AAAGHECABSGAAACABAAAAAAAAAGgwDwYDVR0gBAgwBjAEBgIqAzAqBgNVHR4EIzAhoB8wDoIMLmV4YW1wbGUuY29tMA2CC2V4YW1wbGUuY29tMFcGA1UdHwRQME4wJaAjoCGGH2h0dHA6Ly9jcmwxLmV4YW1wbGUuY29tL2NhMS5jcmwwJaAjoCGGH2h0dHA6Ly9jcmwyLmV4YW1wbGUuY29tL2NhMS5jcmwwFgYDKgMEBA9leHRyYSBleHRlbnNpb24wCgYIKoZIzj0EAwIDSAAwRQIgcguBb6FUxO+X8DbY17gpqSGuNC4NT4BddPg1UWUxIC0CIQDNyHQAwzhw+512meXRwG92GfpzSBssDKLdwlrqiHOu5A==-----END CERTIFICATE-----'; +var TEST_CERT_PEM = '-----BEGIN CERTIFICATE-----' + +'MIIDVDCCAvqgAwIBAgIBATAKBggqhkjOPQQDAjBOMRMwEQYDVQQKDArOoyBBY21l' + +'IENvMRkwFwYDVQQDExB0ZXN0LmV4YW1wbGUuY29tMQ8wDQYDVQQqEwZHb3BoZXIx' + +'CzAJBgNVBAYTAk5MMB4XDTE2MTIxNjIzMTAxM1oXDTE2MTIxNzAxMTAxM1owTjET' + +'MBEGA1UECgwKzqMgQWNtZSBDbzEZMBcGA1UEAxMQdGVzdC5leGFtcGxlLmNvbTEP' + +'MA0GA1UEKhMGR29waGVyMQswCQYDVQQGEwJOTDBZMBMGByqGSM49AgEGCCqGSM49' + +'AwEHA0IABFKnXh7hBdp6s9OJ/aadigT1z2WzBbSc7Hzb3rkaWFz4e+9alqqWg9lr' + +'ur/mDYzG9dudC8jFjVa7KIh+2BxgBayjggHHMIIBwzAOBgNVHQ8BAf8EBAMCAgQw' + +'JgYDVR0lBB8wHQYIKwYBBQUHAwIGCCsGAQUFBwMBBgIqAwYDgQsBMA8GA1UdEwEB' + +'/wQFMAMBAf8wDQYDVR0OBAYEBAECAwQwDwYDVR0jBAgwBoAEAQIDBDBiBggrBgEF' + +'BQcBAQRWMFQwJgYIKwYBBQUHMAGGGmh0dHA6Ly9vY0JDQ1NQLmV4YW1wbGUuY29t' + +'MCoGCCsGAQUFBzAChh5odHRwOi8vY3J0LmV4YW1wbGUuY29tL2NhMS5jcnQwRgYD' + +'VR0RBD8wPYIQdGVzdC5leGFtcGxlLmNvbYERZ29waGVyQGdvbGFuZy5vcmeHBH8A' + +'AAGHECABSGAAACABAAAAAAAAAGgwDwYDVR0gBAgwBjAEBgIqAzAqBgNVHR4EIzAh' + +'oB8wDoIMLmV4YW1wbGUuY29tMA2CC2V4YW1wbGUuY29tMFcGA1UdHwRQME4wJaAj' + +'oCGGH2h0dHA6Ly9jcmwxLmV4YW1wbGUuY29tL2NhMS5jcmwwJaAjoCGGH2h0dHA6' + +'Ly9jcmwyLmV4YW1wbGUuY29tL2NhMS5jcmwwFgYDKgMEBA9leHRyYSBleHRlbnNp' + +'b24wCgYIKoZIzj0EAwIDSAAwRQIgcguBb6FUxO+X8DbY17gpqSGuNC4NT4BddPg1' + +'UWUxIC0CIQDNyHQAwzhw+512meXRwG92GfpzSBssDKLdwlrqiHOu5A==' + +'-----END CERTIFICATE-----'; + var jsrsa = require('jsrsasign'); var KEYUTIL = jsrsa.KEYUTIL; var ECDSA = jsrsa.ECDSA; @@ -1263,6 +1291,11 @@ test('\n\n ** CryptoSuite_ECDSA_AES - function tests **\n\n', function (t) { testVerify(TEST_MSG_SIGNATURE_SHA2_256, TEST_MSG); testVerify(TEST_LONG_MSG_SIGNATURE_SHA2_256, TEST_LONG_MSG); + // test importKey() + var pubKey = cryptoUtils.importKey(TEST_CERT_PEM, { algorithm: 'X509Certificate' }); + t.equal(pubKey.isPrivate(), false, 'Test imported public key isPrivate()'); + t.equal(pubKey.getSKI(), 'b5cb4942005c4ecaa9f73a49e1936a58baf549773db213cf1e22a1db39d9dbef', 'Test imported public key SKI'); + t.end(); }) .catch(function (err) { @@ -1319,7 +1352,7 @@ test('\n\n ** ECDSA Key Impl tests **\n\n', function (t) { var pair2 = KEYUTIL.generateKeypair('EC', 'secp384r1'); var key2 = new ecdsaKey(pair2.prvKeyObj, 384); - t.equal(key2.getSKI().length, 96, 'Checking generated SKI hash string for 384 curve keys'); + t.equal(key2.getSKI().length, 64, 'Checking generated SKI hash string for 384 curve keys'); t.equal(key1.isSymmetric() || key2.isSymmetric(), false, 'Checking if key is symmetric'); t.equal(key1.isPrivate() && key2.isPrivate(), true, 'Checking if key is private'); @@ -1327,6 +1360,33 @@ test('\n\n ** ECDSA Key Impl tests **\n\n', function (t) { t.equal(key1.getPublicKey().isPrivate(), false, 'Checking isPrivate() logic'); t.equal(key1.getPublicKey().toBytes().length, 182, 'Checking toBytes() output'); + // test public keys + var key3 = new ecdsaKey(pair1.pubKeyObj, 256); + t.equal(key3.getSKI().length, 64, 'Checking generated SKI hash string for 256 curve public key'); + + t.doesNotThrow( + function() { + key3.toBytes(); + }, + null, + 'Checking to dump a public ECDSAKey object to bytes' + ); + + var key4 = new ecdsaKey(pair2.pubKeyObj, 384); + t.equal(key4.getSKI().length, 64, 'Checking generated SKI hash string for 384 curve public key'); + + t.doesNotThrow( + function() { + key4.toBytes(); + }, + null, + 'Checking to dump a public ECDSAKey object to bytes' + ); + + t.equal(!key3.isPrivate() && !key4.isPrivate(), true, 'Checking if both keys are public'); + t.equal(key3.getPublicKey().isPrivate(), false, 'Checking getPublicKey() logic'); + t.equal(key4.getPublicKey().toBytes().length, 220, 'Checking toBytes() output'); + //test CSR generation var pair3 = KEYUTIL.generateKeypair('EC', 'secp256r1'); var key3 = new ecdsaKey(pair3.prvKeyObj, 256);