From 5dbebe14d9d0b861d6a783dc875ad168604c363c Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 22 Jul 2016 23:44:15 -0700 Subject: [PATCH] bip70: x509 improvements. --- lib/bcoin/bip70/asn1.js | 5 +- lib/bcoin/bip70/index.js | 22 +++++- lib/bcoin/bip70/x509.js | 153 +++++++++++++++++++++++++++------------ 3 files changed, 127 insertions(+), 53 deletions(-) diff --git a/lib/bcoin/bip70/asn1.js b/lib/bcoin/bip70/asn1.js index f7041a8a..39f2ee07 100644 --- a/lib/bcoin/bip70/asn1.js +++ b/lib/bcoin/bip70/asn1.js @@ -418,10 +418,13 @@ asn1.fromPEM = function fromPEM(pem) { }; }; -asn1.toPEM = function toPEM(der, type) { +asn1.toPEM = function toPEM(der, type, suffix) { var pem = ''; var i; + if (suffix) + type += ' ' + suffix; + type = type.toUpperCase(); der = der.toString('base64'); diff --git a/lib/bcoin/bip70/index.js b/lib/bcoin/bip70/index.js index 81b1311d..f83a9232 100644 --- a/lib/bcoin/bip70/index.js +++ b/lib/bcoin/bip70/index.js @@ -218,15 +218,15 @@ PaymentRequest.prototype.verify = function verify() { return x509.verifySubject(alg.hash, msg, sig, chain); }; -PaymentRequest.prototype.verifyChain = function verifyChain(ignoreTime) { +PaymentRequest.prototype.verifyChain = function verifyChain() { if (!this.pkiType || this.pkiType === 'none') return true; - return x509.verifyChain(this.getChain(), ignoreTime); + return x509.verifyChain(this.getChain()); }; PaymentRequest.prototype.getCA = function getCA() { - var chain; + var chain, root, ca; if (!this.pkiType || this.pkiType === 'none') return; @@ -236,7 +236,21 @@ PaymentRequest.prototype.getCA = function getCA() { if (chain.length === 0) return; - return x509.getTrusted(chain[chain.length - 1]); + root = x509.parse(chain[chain.length - 1]); + + if (!root) + return; + + ca = x509.getTrusted(root); + + if (!ca) + return; + + return { + name: ca.name, + fingerprint: ca.fingerprint, + cert: root + }; }; function PaymentDetails(options) { diff --git a/lib/bcoin/bip70/x509.js b/lib/bcoin/bip70/x509.js index 38081e68..2c528eac 100644 --- a/lib/bcoin/bip70/x509.js +++ b/lib/bcoin/bip70/x509.js @@ -16,19 +16,41 @@ x509.certs = []; x509.trusted = {}; x509.getTrusted = function getTrusted(cert) { - var hash; - - if (!Buffer.isBuffer(cert)) - cert = cert.raw; - - hash = utils.hash256(cert).toString('hex'); - + var fingerprint = utils.sha256(cert.raw); + var hash = fingerprint.toString('hex'); return x509.trusted[hash]; }; +x509.getSubjectOID = function getSubjectOID(cert, oid) { + var subject = cert.tbs.subject; + var i, entry; + + for (i = 0; i < subject.length; i++) { + entry = subject[i]; + if (entry.type === oid) + return entry.value; + } +}; + +x509.getCAName = function getCAName(cert) { + // This seems to work the best in practice + // for getting a human-readable and + // descriptive name for the CA. + // See: + // http://oid-info.com/get/2.5.4 + // Precedence: + // (3) commonName + // (11) organizationUnitName + // (10) organizationName + return x509.getSubjectOID(cert, '2.5.4.3') + || x509.getSubjectOID(cert, '2.5.4.11') + || x509.getSubjectOID(cert, '2.5.4.10') + || 'Unknown'; +}; + x509.setTrust = function setTrust(certs) { var keys = Object.keys(certs); - var i, key, cert, hash, pem; + var i, key, cert, pem, fingerprint, hash, trust; for (i = 0; i < keys.length; i++) { key = keys[i]; @@ -42,16 +64,19 @@ x509.setTrust = function setTrust(certs) { assert(Buffer.isBuffer(cert), 'Certificates must be PEM or DER.'); - hash = utils.hash256(cert).toString('hex'); + cert = x509.parse(cert); + assert(cert, 'Could not parse certificate.'); - cert = { - name: key, - fingerprint: hash, - cert: asn1.parseCert(cert) + fingerprint = utils.sha256(cert.raw); + hash = fingerprint.toString('hex'); + + trust = { + name: x509.getCAName(cert), + fingerprint: fingerprint }; - x509.certs.push(cert); - x509.trusted[hash] = cert; + x509.certs.push(trust); + x509.trusted[hash] = trust; } }; @@ -84,6 +109,12 @@ x509.oid = { '1.2.840.10045.4.3.4' : { key: 'ecdsa', hash: 'sha512' } }; +x509.pemTag = { + dsa: 'DSA', + rsa: 'RSA', + ecdsa: 'EC' +}; + x509.getKeyAlgorithm = function getKeyAlgorithm(cert) { var alg = cert.tbs.pubkey.alg.alg; return x509.oid[alg]; @@ -104,18 +135,21 @@ x509.parse = function parse(der) { x509.getPublicKey = function getPublicKey(cert) { var alg = x509.getKeyAlgorithm(cert); - var key, params, pem; + var key, params, pem, tag; if (!alg) return; key = cert.tbs.pubkey.pubkey; params = cert.tbs.pubkey.alg.params; + tag = x509.pemTag[alg.key]; - pem = asn1.toPEM(key, alg.key + ' PUBLIC KEY'); + pem = asn1.toPEM(key, tag, 'public key'); + // Key parameters, usually present + // if selecting an EC curve. if (params) - pem += asn1.toPEM(params, alg.key + ' PARAMETERS'); + pem += asn1.toPEM(params, tag, 'parameters'); return pem; }; @@ -127,7 +161,7 @@ x509.verifyTime = function verifyTime(cert) { }; x509.signSubject = function signSubject(hash, msg, key, chain) { - var cert, alg; + var cert, alg, tag; assert(chain.length !== 0, 'No chain available.'); @@ -137,8 +171,10 @@ x509.signSubject = function signSubject(hash, msg, key, chain) { alg = x509.getKeyAlgorithm(cert); assert(alg, 'Certificate uses an unknown algorithm.'); - if (Buffer.isBuffer(key)) - key = asn1.toPEM(key, alg.key + ' PRIVATE KEY'); + if (Buffer.isBuffer(key)) { + tag = x509.pemTag[alg.key]; + key = asn1.toPEM(key, tag, 'private key'); + } return x509.sign(alg.key, hash, msg, key); }; @@ -167,62 +203,83 @@ x509.verifySubject = function verifySubject(hash, msg, sig, chain) { return x509.verify(alg.key, hash, msg, sig, key); }; -x509.verifyChain = function verifyChain(chain, ignoreTime) { +x509.verifyChain = function verifyChain(chain) { var i, child, parent, alg, key, sig, msg; - if (chain.length < 2) - return false; + chain = chain.slice(); - for (i = 1; i < chain.length; i++) { - child = chain[i - 1]; - parent = chain[i]; - - child = x509.parse(child); + // Parse certificates and + // check validity time. + for (i = 0; i < chain.length; i++) { + child = x509.parse(chain[i]); if (!child) return false; - parent = x509.parse(parent); + chain[i] = child; - if (!parent) + if (!x509.verifyTime(child)) return false; + } - if (!ignoreTime) { - if (!x509.verifyTime(child)) - return false; - - if (!x509.verifyTime(parent)) - return false; - } + // Verify signatures. + for (i = 1; i < chain.length; i++) { + child = chain[i - 1]; + parent = chain[i]; alg = x509.getSigAlgorithm(child); + msg = child.tbs.raw; + sig = child.sig; + key = x509.getPublicKey(parent); if (!alg || !alg.hash) return false; - key = x509.getPublicKey(parent); - if (!key) return false; - sig = child.sig; - msg = child.tbs.raw; - if (!x509.verify(alg.key, alg.hash, msg, sig, key)) return false; - - if (x509.getTrusted(parent)) - return true; } + // If trust hasn't been + // setup, just return. if (x509.certs.length === 0) return true; + // Make sure we trust one + // of the certs in the chain. + for (i = 0; i < chain.length; i++) { + child = chain[i]; + + // If any certificate in the chain + // is trusted, assume we also trust + // the parent. + if (x509.getTrusted(child)) + return true; + } + + // No trusted certs present. return false; }; +x509.normalizeAlg = function normalizeAlg(alg, hash) { + var name = alg.toUpperCase() + '-' + hash.toUpperCase(); + + switch (name) { + case 'ECDSA-SHA1': + name = 'ecdsa-with-SHA1'; + break; + case 'ECDSA-SHA256': + name = 'ecdsa-with-SHA256'; + break; + } + + return name; +}; + x509.verify = function verify(alg, hash, msg, sig, key) { - var algo = alg.toUpperCase() + '-' + hash.toUpperCase(); + var algo = x509.normalizeAlg(alg, hash); var verify; try { @@ -235,7 +292,7 @@ x509.verify = function verify(alg, hash, msg, sig, key) { }; x509.sign = function sign(alg, hash, msg, key) { - var algo = alg.toUpperCase() + '-' + hash.toUpperCase(); + var algo = x509.normalizeAlg(alg, hash); var sig = crypto.createSign(algo); sig.update(msg); return sig.sign(key);