From 6828f560da707dbf193f16e7dafa181935c346d1 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 21 Aug 2014 16:02:53 -0700 Subject: [PATCH 01/60] paypro: fix root certs. --- browser/root-certs | 8 ++++---- lib/common/RootCerts.js | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/browser/root-certs b/browser/root-certs index 6c57965..54038f3 100755 --- a/browser/root-certs +++ b/browser/root-certs @@ -50,14 +50,14 @@ function getRootCerts(callback) { + ' pem = pem.replace(/-----BEGIN CERTIFICATE-----/g, "");\n' + ' pem = pem.replace(/-----END CERTIFICATE-----/g, "");\n' + ' pem = pem.replace(/\\s+/g, "");\n' - + ' if (!Object.prototype.hasOwnProperty.call(certs, pem)) return;\n' - + ' return certs[pem];\n' + + ' if (!Object.prototype.hasOwnProperty.call(trusted, pem)) return;\n' + + ' return trusted[pem];\n' + '}\n' + '\n' + 'function getCert(name) {\n' + ' name = name.replace(/^\s+|\s+$/g, "");\n' - + ' if (!Object.prototype.hasOwnProperty.call(trusted, name)) return;\n' - + ' return trusted[name];\n' + + ' if (!Object.prototype.hasOwnProperty.call(certs, name)) return;\n' + + ' return certs[name];\n' + '}\n' + '\n' + 'exports.certs = certs;\n' diff --git a/lib/common/RootCerts.js b/lib/common/RootCerts.js index 5c5629e..71501d4 100644 --- a/lib/common/RootCerts.js +++ b/lib/common/RootCerts.js @@ -3719,14 +3719,14 @@ function getTrusted(pem) { pem = pem.replace(/-----BEGIN CERTIFICATE-----/g, ""); pem = pem.replace(/-----END CERTIFICATE-----/g, ""); pem = pem.replace(/\s+/g, ""); - if (!Object.prototype.hasOwnProperty.call(certs, pem)) return; - return certs[pem]; + if (!Object.prototype.hasOwnProperty.call(trusted, pem)) return; + return trusted[pem]; } function getCert(name) { name = name.replace(/^s+|s+$/g, ""); - if (!Object.prototype.hasOwnProperty.call(trusted, name)) return; - return trusted[name]; + if (!Object.prototype.hasOwnProperty.call(certs, name)) return; + return certs[name]; } exports.certs = certs; From 569e60065aa15e7b8c20a85c29393d1fb3fea18c Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 21 Aug 2014 16:13:34 -0700 Subject: [PATCH 02/60] paypro: verify the certificate chain. --- lib/PayPro.js | 74 +++++++++++++++++++++++++++++++++++++------ lib/browser/PayPro.js | 74 +++++++++++++++++++++++++++++++++++++------ 2 files changed, 129 insertions(+), 19 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 0d9abc5..61d4893 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -6,6 +6,8 @@ var RootCerts = require('./common/RootCerts'); var PayPro = require('./common/PayPro'); +var KJUR = require('jsrsasign'); + PayPro.prototype.x509Sign = function(key) { var self = this; var crypto = require('crypto'); @@ -53,20 +55,74 @@ PayPro.prototype.x509Verify = function() { var verifier = crypto.createVerify('RSA-' + type); verifier.update(buf); - return pki_data.every(function(cert) { + var signedCert = pki_data[0]; + var der = signedCert.toString('hex'); + var pem = this._DERtoPEM(der, 'CERTIFICATE'); + var verified = verifier.verify(pem, sig); + + var chain = pki_data; + + // Verifying the cert chain: + // 1. Extract public key from next certificate. + // 2. Extract signature from current certificate. + // 3. If current cert is not trusted, verify that the current cert is signed + // by NEXT by the certificate. + // 4. XXX What to do when the certificate is revoked? + + var blen = +type.replace(/[^\d]+/g, ''); + if (blen === 1) blen = 20; + if (blen === 256) blen = 32; + + chain.forEach(function(cert, i) { var der = cert.toString('hex'); var pem = self._DERtoPEM(der, 'CERTIFICATE'); - var name = RootCerts.getTrusted(pem); - // XXX Figure out what to do here - if (!name) { - // throw new Error('Unstrusted certificate.'); - } else { - // console.log('Certificate: %s', name); - } - return verifier.verify(pem, sig); + var ncert = chain[i + 1]; + // The root cert, check if it's trusted: + if (!ncert || name) { + if (!name) { + // console.log('Untrusted certificate.'); + } else { + // console.log('Certificate: %s', name); + } + return; + } + var nder = ncert.toString('hex'); + var npem = self._DERtoPEM(nder, 'CERTIFICATE'); + + // get sig from current cert - BAD + var sig = new Buffer(der.slice(-(blen * 2)), 'hex'); + + // Should work but doesn't: + // get sig from current cert + // var o = new KJUR.asn1.cms.SignerInfo(); + // o.setSignerIdentifier(pem); + // var sig = o.getEncodedHex(); + + // get public key from next cert + var js = new KJUR.crypto.Signature({ + alg: type + 'withRSA', + prov: 'cryptojs/jsrsa' + }); + js.initVerifyByCertificatePEM(npem); + var npubKey = KJUR.KEYUTIL.getPEM(js.pubKey); + + var verifier = crypto.createVerify('RSA-' + type); + + // NOTE: We need to slice off the signatureAlgorithm and signatureValue. + // consult the x509 spec: + // https://www.ietf.org/rfc/rfc2459 + verifier.update(new Buffer(der, 'hex')); + + var v = verifier.verify(npubKey, sig); + if (!v) { + // console.log(i + ' not verified.'); + verified = false; + } }); + + return verified; }; module.exports = PayPro; diff --git a/lib/browser/PayPro.js b/lib/browser/PayPro.js index b9ab061..9269308 100644 --- a/lib/browser/PayPro.js +++ b/lib/browser/PayPro.js @@ -64,24 +64,78 @@ PayPro.prototype.x509Verify = function(key) { prov: 'cryptojs/jsrsa' }); - return pki_data.every(function(cert) { + var signedCert = pki_data[0]; + var der = signedCert.toString('hex'); + var pem = KJUR.asn1.ASN1Util.getPEMStringFromHex(der, 'CERTIFICATE'); + jsrsaSig.initVerifyByCertificatePEM(pem); + jsrsaSig.updateHex(buf.toString('hex')); + var verified = jsrsaSig.verify(sig.toString('hex')); + + var chain = pki_data; + + // Verifying the cert chain: + // 1. Extract public key from next certificate. + // 2. Extract signature from current certificate. + // 3. If current cert is not trusted, verify that the current cert is signed + // by NEXT by the certificate. + // 4. XXX What to do when the certificate is revoked? + + var blen = +type.replace(/[^\d]+/g, ''); + if (blen === 1) blen = 20; + if (blen === 256) blen = 32; + + chain.forEach(function(cert, i) { var der = cert.toString('hex'); var pem = KJUR.asn1.ASN1Util.getPEMStringFromHex(der, 'CERTIFICATE'); - - // XXX Figure out what to do here var name = RootCerts.getTrusted(pem); - if (!name) { - // throw new Error('Unstrusted certificate.'); - } else { - // console.log('Certificate: %s', name); + + var ncert = chain[i + 1]; + // The root cert, check if it's trusted: + if (!ncert || name) { + if (!name) { + // console.log('Untrusted certificate.'); + } else { + // console.log('Certificate: %s', name); + } + return; } + var nder = ncert.toString('hex'); + var npem = KJUR.asn1.ASN1Util.getPEMStringFromHex(nder, 'CERTIFICATE'); - jsrsaSig.initVerifyByCertificatePEM(pem); + // get sig from current cert - BAD + var sig = der.slice(-(blen * 2)); - jsrsaSig.updateHex(buf.toString('hex')); + // Should work but doesn't: + // get sig from current cert + // var o = new KJUR.asn1.cms.SignerInfo(); + // o.setSignerIdentifier(pem); + // var sig = new Buffer(o.getEncodedHex(), 'hex'); - return jsrsaSig.verify(sig.toString('hex')); + // get public key from next cert + var js = new KJUR.crypto.Signature({ + alg: type + 'withRSA', + prov: 'cryptojs/jsrsa' + }); + js.initVerifyByCertificatePEM(npem); + var npubKey = KJUR.KEYUTIL.getPEM(js.pubKey); + + var jsrsaSig = new KJUR.crypto.Signature({ + alg: type + 'withRSA', + prov: 'cryptojs/jsrsa' + }); + jsrsaSig.initVerifyByPublicKey(npubKey); + // NOTE: We need to slice off the signatureAlgorithm and signatureValue - + // consult the x509 spec: + // https://www.ietf.org/rfc/rfc2459 + jsrsaSig.updateHex(der); + var v = jsrsaSig.verify(sig); + if (!v) { + // console.log(i + ' not verified.'); + verified = false; + } }); + + return verified; }; module.exports = PayPro; From 5085880bd054286d3894f80e7a2e94f89b274a78 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 22 Aug 2014 00:31:20 -0700 Subject: [PATCH 03/60] paypro: use fedor's asn1.js to deal with DER certificates. --- lib/PayPro.js | 78 ++++++++++++++++++++++++++++++++++++++++++--------- package.json | 3 +- 2 files changed, 66 insertions(+), 15 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 61d4893..f2965d0 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -8,6 +8,10 @@ var PayPro = require('./common/PayPro'); var KJUR = require('jsrsasign'); +var asn1 = require('asn1.js'); +var rfc3280 = require('asn1.js/rfc/3280'); +var Certificate = rfc3280.Certificate; + PayPro.prototype.x509Sign = function(key) { var self = this; var crypto = require('crypto'); @@ -91,25 +95,71 @@ PayPro.prototype.x509Verify = function() { var nder = ncert.toString('hex'); var npem = self._DERtoPEM(nder, 'CERTIFICATE'); - // get sig from current cert - BAD - var sig = new Buffer(der.slice(-(blen * 2)), 'hex'); + /* + https://www.ietf.org/rfc/rfc2459 + https://en.wikipedia.org/wiki/X509 + https://github.com/indutny/asn1.js + https://github.com/indutny/asn1.js/blob/master/rfc/3280/index.js + ~/work/node_modules/bitcore/node_modules/asn1.js/rfc/3280/index.js + Error: Failed to match tag: "objid" at: + ["tbsCertificate"]["issuerUniqueID"]["subjectUniqueID"]["extensions"]["extnID"] + PR: https://github.com/indutny/asn1.js/pull/22 + */ - // Should work but doesn't: - // get sig from current cert - // var o = new KJUR.asn1.cms.SignerInfo(); - // o.setSignerIdentifier(pem); - // var sig = o.getEncodedHex(); + // Get public key from next certificate. + var data = new Buffer(nder, 'hex'); + var nc = Certificate.decode(data, 'der'); + var npubKey = nc.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey.data; + // Need to convert this to PEM: + // Doesn't work because KJUR is terrible: + // npubKey = KJUR.KEYUTIL.getPEM(npubKey.toString('hex')); + npubKey = self._DERtoPEM(npubKey, 'RSA PUBLIC KEY'); - // get public key from next cert - var js = new KJUR.crypto.Signature({ - alg: type + 'withRSA', - prov: 'cryptojs/jsrsa' - }); - js.initVerifyByCertificatePEM(npem); - var npubKey = KJUR.KEYUTIL.getPEM(js.pubKey); + // Get signature from current certificate. + var data = new Buffer(der, 'hex'); + //var c = Certificate.decode(data, 'der', { partial: true }); + var c = Certificate.decode(data, 'der'); + var sig = c.signature.data; var verifier = crypto.createVerify('RSA-' + type); + var t = c.tbsCertificate; + + // Messy work: + // Fails on Issuer: + /* + var cur = Certificate.encode({ + tbsCertificate: { + version: t.version, + serialNumber: t.serialNumber, + signature: t.signature, + // Fails on issuer: + //issuer: t.issuer, + //issuer: t.issuer.value, + //issuer: t.issuer.value.map(function(obj) { + // return obj.value; + //}), + //issuer: t.issuer.type, + //issuer: 'rdh', + //issuer: rfc3280.Name.decode(t.issuer, 'der'), + validity: t.validity, + subject: t.subject, + subjectPublicKeyInfo: t.subjectPublicKeyInfo, + extensions: t.extensions + }, + signatureAlgorithm: '', + signature: '' + }, 'der'); + */ + + var cur = Certificate.encode({ + tbsCertificate: c.tbsCertificate, + signatureAlgorithm: '', + signature: '' + }, 'der'); + + // console.log(cur); + // NOTE: We need to slice off the signatureAlgorithm and signatureValue. // consult the x509 spec: // https://www.ietf.org/rfc/rfc2459 diff --git a/package.json b/package.json index e3e6d59..6392c43 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,8 @@ "event-stream": "~3.1.5", "gulp-concat": "~2.2.0", "gulp": "~3.8.2", - "preconditions": "^1.0.7" + "preconditions": "^1.0.7", + "asn1.js": "git://github.com/chjj/asn1.js.git" }, "testling": { "harness": "mocha-bdd", From 16b646d0e7e83866bed2120026e62fc9b5be375b Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 22 Aug 2014 00:34:41 -0700 Subject: [PATCH 04/60] paypro: asn1.js - cleanup. --- lib/PayPro.js | 66 ++++----------------------------------------------- 1 file changed, 4 insertions(+), 62 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index f2965d0..01b9c1a 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -85,89 +85,31 @@ PayPro.prototype.x509Verify = function() { var ncert = chain[i + 1]; // The root cert, check if it's trusted: if (!ncert || name) { - if (!name) { - // console.log('Untrusted certificate.'); - } else { - // console.log('Certificate: %s', name); - } return; } var nder = ncert.toString('hex'); var npem = self._DERtoPEM(nder, 'CERTIFICATE'); - /* - https://www.ietf.org/rfc/rfc2459 - https://en.wikipedia.org/wiki/X509 - https://github.com/indutny/asn1.js - https://github.com/indutny/asn1.js/blob/master/rfc/3280/index.js - ~/work/node_modules/bitcore/node_modules/asn1.js/rfc/3280/index.js - Error: Failed to match tag: "objid" at: - ["tbsCertificate"]["issuerUniqueID"]["subjectUniqueID"]["extensions"]["extnID"] - PR: https://github.com/indutny/asn1.js/pull/22 - */ - // Get public key from next certificate. var data = new Buffer(nder, 'hex'); var nc = Certificate.decode(data, 'der'); var npubKey = nc.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey.data; - // Need to convert this to PEM: - // Doesn't work because KJUR is terrible: - // npubKey = KJUR.KEYUTIL.getPEM(npubKey.toString('hex')); npubKey = self._DERtoPEM(npubKey, 'RSA PUBLIC KEY'); // Get signature from current certificate. var data = new Buffer(der, 'hex'); - //var c = Certificate.decode(data, 'der', { partial: true }); var c = Certificate.decode(data, 'der'); var sig = c.signature.data; var verifier = crypto.createVerify('RSA-' + type); - var t = c.tbsCertificate; - - // Messy work: - // Fails on Issuer: - /* - var cur = Certificate.encode({ - tbsCertificate: { - version: t.version, - serialNumber: t.serialNumber, - signature: t.signature, - // Fails on issuer: - //issuer: t.issuer, - //issuer: t.issuer.value, - //issuer: t.issuer.value.map(function(obj) { - // return obj.value; - //}), - //issuer: t.issuer.type, - //issuer: 'rdh', - //issuer: rfc3280.Name.decode(t.issuer, 'der'), - validity: t.validity, - subject: t.subject, - subjectPublicKeyInfo: t.subjectPublicKeyInfo, - extensions: t.extensions - }, - signatureAlgorithm: '', - signature: '' - }, 'der'); - */ - - var cur = Certificate.encode({ - tbsCertificate: c.tbsCertificate, - signatureAlgorithm: '', - signature: '' - }, 'der'); - - // console.log(cur); - - // NOTE: We need to slice off the signatureAlgorithm and signatureValue. - // consult the x509 spec: - // https://www.ietf.org/rfc/rfc2459 - verifier.update(new Buffer(der, 'hex')); + // Create a To-Be-Signed Certificate using asn1.js: + // Fails at Issuer: + var tbs = rfc3280.TBSCertificate.encode(c.tbsCertificate, 'der'); + verifier.update(tbs); var v = verifier.verify(npubKey, sig); if (!v) { - // console.log(i + ' not verified.'); verified = false; } }); From 4a12d5a4918316ca4cab16faaf36a295a215a15b Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 22 Aug 2014 08:38:19 -0700 Subject: [PATCH 05/60] paypro: verify chain refactor. --- lib/PayPro.js | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 01b9c1a..5f34cf4 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -71,13 +71,9 @@ PayPro.prototype.x509Verify = function() { // 2. Extract signature from current certificate. // 3. If current cert is not trusted, verify that the current cert is signed // by NEXT by the certificate. - // 4. XXX What to do when the certificate is revoked? + // NOTE: XXX What to do when the certificate is revoked? - var blen = +type.replace(/[^\d]+/g, ''); - if (blen === 1) blen = 20; - if (blen === 256) blen = 32; - - chain.forEach(function(cert, i) { + var chainVerified = chain.every(function(cert, i) { var der = cert.toString('hex'); var pem = self._DERtoPEM(der, 'CERTIFICATE'); var name = RootCerts.getTrusted(pem); @@ -85,36 +81,34 @@ PayPro.prototype.x509Verify = function() { var ncert = chain[i + 1]; // The root cert, check if it's trusted: if (!ncert || name) { - return; + chain.length = 0; + return true; } var nder = ncert.toString('hex'); var npem = self._DERtoPEM(nder, 'CERTIFICATE'); - // Get public key from next certificate. + // Get public key from next certificate: var data = new Buffer(nder, 'hex'); var nc = Certificate.decode(data, 'der'); var npubKey = nc.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey.data; npubKey = self._DERtoPEM(npubKey, 'RSA PUBLIC KEY'); - // Get signature from current certificate. + // Get signature from current certificate: var data = new Buffer(der, 'hex'); var c = Certificate.decode(data, 'der'); var sig = c.signature.data; var verifier = crypto.createVerify('RSA-' + type); - // Create a To-Be-Signed Certificate using asn1.js: + // Create a To-Be-Signed Certificate to verify using asn1.js: // Fails at Issuer: var tbs = rfc3280.TBSCertificate.encode(c.tbsCertificate, 'der'); verifier.update(tbs); - var v = verifier.verify(npubKey, sig); - if (!v) { - verified = false; - } + return verifier.verify(npubKey, sig); }); - return verified; + return verified && chainVerified; }; module.exports = PayPro; From f0757498b68ef4a4c228d4f31e82592ece809983 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 22 Aug 2014 08:56:08 -0700 Subject: [PATCH 06/60] paypro: use asn1.js in browser paypro. --- lib/PayPro.js | 5 ++-- lib/browser/PayPro.js | 60 +++++++++++++++++-------------------------- 2 files changed, 25 insertions(+), 40 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 5f34cf4..fbcf291 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -10,7 +10,6 @@ var KJUR = require('jsrsasign'); var asn1 = require('asn1.js'); var rfc3280 = require('asn1.js/rfc/3280'); -var Certificate = rfc3280.Certificate; PayPro.prototype.x509Sign = function(key) { var self = this; @@ -89,13 +88,13 @@ PayPro.prototype.x509Verify = function() { // Get public key from next certificate: var data = new Buffer(nder, 'hex'); - var nc = Certificate.decode(data, 'der'); + var nc = rfc3280.Certificate.decode(data, 'der'); var npubKey = nc.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey.data; npubKey = self._DERtoPEM(npubKey, 'RSA PUBLIC KEY'); // Get signature from current certificate: var data = new Buffer(der, 'hex'); - var c = Certificate.decode(data, 'der'); + var c = rfc3280.Certificate.decode(data, 'der'); var sig = c.signature.data; var verifier = crypto.createVerify('RSA-' + type); diff --git a/lib/browser/PayPro.js b/lib/browser/PayPro.js index 9269308..fe5d565 100644 --- a/lib/browser/PayPro.js +++ b/lib/browser/PayPro.js @@ -5,6 +5,8 @@ var KJUR = require('jsrsasign'); var assert = require('assert'); var PayPro = require('../common/PayPro'); var RootCerts = require('../common/RootCerts'); +var asn1 = require('asn1.js'); +var rfc3280 = require('asn1.js/rfc/3280'); // Documentation: // http://kjur.github.io/jsrsasign/api/symbols/KJUR.crypto.Signature.html#.sign @@ -78,13 +80,9 @@ PayPro.prototype.x509Verify = function(key) { // 2. Extract signature from current certificate. // 3. If current cert is not trusted, verify that the current cert is signed // by NEXT by the certificate. - // 4. XXX What to do when the certificate is revoked? + // NOTE: XXX What to do when the certificate is revoked? - var blen = +type.replace(/[^\d]+/g, ''); - if (blen === 1) blen = 20; - if (blen === 256) blen = 32; - - chain.forEach(function(cert, i) { + var chainVerified = chain.every(function(cert, i) { var der = cert.toString('hex'); var pem = KJUR.asn1.ASN1Util.getPEMStringFromHex(der, 'CERTIFICATE'); var name = RootCerts.getTrusted(pem); @@ -92,50 +90,38 @@ PayPro.prototype.x509Verify = function(key) { var ncert = chain[i + 1]; // The root cert, check if it's trusted: if (!ncert || name) { - if (!name) { - // console.log('Untrusted certificate.'); - } else { - // console.log('Certificate: %s', name); - } - return; + chain.length = 0; + return true; } var nder = ncert.toString('hex'); var npem = KJUR.asn1.ASN1Util.getPEMStringFromHex(nder, 'CERTIFICATE'); - // get sig from current cert - BAD - var sig = der.slice(-(blen * 2)); + // Get public key from next certificate: + var data = new Buffer(nder, 'hex'); + var nc = rfc3280.Certificate.decode(data, 'der'); + var npubKey = nc.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey.data; + npubKey = self._DERtoPEM(npubKey, 'RSA PUBLIC KEY'); - // Should work but doesn't: - // get sig from current cert - // var o = new KJUR.asn1.cms.SignerInfo(); - // o.setSignerIdentifier(pem); - // var sig = new Buffer(o.getEncodedHex(), 'hex'); - - // get public key from next cert - var js = new KJUR.crypto.Signature({ - alg: type + 'withRSA', - prov: 'cryptojs/jsrsa' - }); - js.initVerifyByCertificatePEM(npem); - var npubKey = KJUR.KEYUTIL.getPEM(js.pubKey); + // Get signature from current certificate: + var data = new Buffer(der, 'hex'); + var c = rfc3280.Certificate.decode(data, 'der'); + var sig = c.signature.data; var jsrsaSig = new KJUR.crypto.Signature({ alg: type + 'withRSA', prov: 'cryptojs/jsrsa' }); jsrsaSig.initVerifyByPublicKey(npubKey); - // NOTE: We need to slice off the signatureAlgorithm and signatureValue - - // consult the x509 spec: - // https://www.ietf.org/rfc/rfc2459 - jsrsaSig.updateHex(der); - var v = jsrsaSig.verify(sig); - if (!v) { - // console.log(i + ' not verified.'); - verified = false; - } + + // Create a To-Be-Signed Certificate to verify using asn1.js: + // Fails at Issuer: + var tbs = rfc3280.TBSCertificate.encode(c.tbsCertificate, 'der'); + jsrsaSig.updateHex(tbs); + + return jsrsaSig.verify(sig); }); - return verified; + return verified && chainVerified; }; module.exports = PayPro; From 4e325c3fa93becc4f8473578b89ae29b7bef108e Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 22 Aug 2014 09:05:05 -0700 Subject: [PATCH 07/60] paypro: fix root cert check. --- lib/PayPro.js | 3 +++ lib/browser/PayPro.js | 3 +++ 2 files changed, 6 insertions(+) diff --git a/lib/PayPro.js b/lib/PayPro.js index fbcf291..2588e6d 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -80,6 +80,9 @@ PayPro.prototype.x509Verify = function() { var ncert = chain[i + 1]; // The root cert, check if it's trusted: if (!ncert || name) { + if (!ncert && !name) { + return false; + } chain.length = 0; return true; } diff --git a/lib/browser/PayPro.js b/lib/browser/PayPro.js index fe5d565..fc4af87 100644 --- a/lib/browser/PayPro.js +++ b/lib/browser/PayPro.js @@ -90,6 +90,9 @@ PayPro.prototype.x509Verify = function(key) { var ncert = chain[i + 1]; // The root cert, check if it's trusted: if (!ncert || name) { + if (!ncert && !name) { + return false; + } chain.length = 0; return true; } From 11c478d1a908398234e06351dd0b7b540dd8beec Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 22 Aug 2014 12:18:58 -0700 Subject: [PATCH 08/60] paypro: fix chain validation for browser. --- lib/browser/PayPro.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/browser/PayPro.js b/lib/browser/PayPro.js index fc4af87..f27b879 100644 --- a/lib/browser/PayPro.js +++ b/lib/browser/PayPro.js @@ -103,7 +103,7 @@ PayPro.prototype.x509Verify = function(key) { var data = new Buffer(nder, 'hex'); var nc = rfc3280.Certificate.decode(data, 'der'); var npubKey = nc.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey.data; - npubKey = self._DERtoPEM(npubKey, 'RSA PUBLIC KEY'); + npubKey = KJUR.asn1.ASN1Util.getPEMStringFromHex(npubKey, 'RSA PUBLIC KEY'); // Get signature from current certificate: var data = new Buffer(der, 'hex'); From 3c7aeb4f62c27305294f95eb8875b370b2407227 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 22 Aug 2014 12:19:17 -0700 Subject: [PATCH 09/60] paypro: asn1.js debugging - try to get tbs buffer. --- lib/PayPro.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/PayPro.js b/lib/PayPro.js index 2588e6d..211534f 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -98,6 +98,16 @@ PayPro.prototype.x509Verify = function() { // Get signature from current certificate: var data = new Buffer(der, 'hex'); var c = rfc3280.Certificate.decode(data, 'der'); + + // console.log(c._rawTags); + // console.log(c._rawTags[0]._rawTags['seq:0']); + // var tbsData = c._rawTags[0]._rawTags['seq:0'].input.base; + // console.log(tbsData.toString('hex')); + // //var tbsData = c._rawTags[0]._baseState.children[0]._rawTags[0].input.base; + // //console.log(tbsData.toString('hex')); + // //var tc = rfc3280.TBSCertificate.decode(tbsData, 'der'); + // //console.log(tc); + var sig = c.signature.data; var verifier = crypto.createVerify('RSA-' + type); @@ -105,6 +115,10 @@ PayPro.prototype.x509Verify = function() { // Create a To-Be-Signed Certificate to verify using asn1.js: // Fails at Issuer: var tbs = rfc3280.TBSCertificate.encode(c.tbsCertificate, 'der'); + //var tbs = c._rawTags[0]._baseState.children[0]._rawTags[100].input.base.slice( + // //c._rawTags[0]._baseState.children[0]._rawTags[100].input.offset + // 0, c._rawTags[0]._baseState.children[0]._rawTags[100].input.length + //); verifier.update(tbs); return verifier.verify(npubKey, sig); From e9b0e7b96989e823a35211375da731cc312b9216 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 22 Aug 2014 12:20:36 -0700 Subject: [PATCH 10/60] Revert "paypro: asn1.js debugging - try to get tbs buffer." This reverts commit 3c7aeb4f62c27305294f95eb8875b370b2407227. --- lib/PayPro.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 211534f..2588e6d 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -98,16 +98,6 @@ PayPro.prototype.x509Verify = function() { // Get signature from current certificate: var data = new Buffer(der, 'hex'); var c = rfc3280.Certificate.decode(data, 'der'); - - // console.log(c._rawTags); - // console.log(c._rawTags[0]._rawTags['seq:0']); - // var tbsData = c._rawTags[0]._rawTags['seq:0'].input.base; - // console.log(tbsData.toString('hex')); - // //var tbsData = c._rawTags[0]._baseState.children[0]._rawTags[0].input.base; - // //console.log(tbsData.toString('hex')); - // //var tc = rfc3280.TBSCertificate.decode(tbsData, 'der'); - // //console.log(tc); - var sig = c.signature.data; var verifier = crypto.createVerify('RSA-' + type); @@ -115,10 +105,6 @@ PayPro.prototype.x509Verify = function() { // Create a To-Be-Signed Certificate to verify using asn1.js: // Fails at Issuer: var tbs = rfc3280.TBSCertificate.encode(c.tbsCertificate, 'der'); - //var tbs = c._rawTags[0]._baseState.children[0]._rawTags[100].input.base.slice( - // //c._rawTags[0]._baseState.children[0]._rawTags[100].input.offset - // 0, c._rawTags[0]._baseState.children[0]._rawTags[100].input.length - //); verifier.update(tbs); return verifier.verify(npubKey, sig); From b406eeadffe65d7bdf292abd3bb48d3e42cfa7ec Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 22 Aug 2014 12:50:22 -0700 Subject: [PATCH 11/60] paypro: more debugging. --- lib/PayPro.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/PayPro.js b/lib/PayPro.js index 2588e6d..73c0741 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -98,6 +98,7 @@ PayPro.prototype.x509Verify = function() { // Get signature from current certificate: var data = new Buffer(der, 'hex'); var c = rfc3280.Certificate.decode(data, 'der'); + console.log(c.tbsCertificate); var sig = c.signature.data; var verifier = crypto.createVerify('RSA-' + type); @@ -105,6 +106,13 @@ PayPro.prototype.x509Verify = function() { // Create a To-Be-Signed Certificate to verify using asn1.js: // Fails at Issuer: var tbs = rfc3280.TBSCertificate.encode(c.tbsCertificate, 'der'); + var tbsd = rfc3280.TBSCertificate.decode(tbs, 'der'); + console.log(tbsd); + console.log('original cert:'); + console.log(data.toString('hex')); + console.log('encoded tbs:'); + console.log(tbs.toString('hex')); + console.log(data.toString('hex').indexOf(tbs.toString('hex'))); verifier.update(tbs); return verifier.verify(npubKey, sig); From 6dbff09d4077f6aba7a9f1aa9d04015ecd145f23 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 22 Aug 2014 12:50:26 -0700 Subject: [PATCH 12/60] Revert "paypro: more debugging." This reverts commit b406eeadffe65d7bdf292abd3bb48d3e42cfa7ec. --- lib/PayPro.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 73c0741..2588e6d 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -98,7 +98,6 @@ PayPro.prototype.x509Verify = function() { // Get signature from current certificate: var data = new Buffer(der, 'hex'); var c = rfc3280.Certificate.decode(data, 'der'); - console.log(c.tbsCertificate); var sig = c.signature.data; var verifier = crypto.createVerify('RSA-' + type); @@ -106,13 +105,6 @@ PayPro.prototype.x509Verify = function() { // Create a To-Be-Signed Certificate to verify using asn1.js: // Fails at Issuer: var tbs = rfc3280.TBSCertificate.encode(c.tbsCertificate, 'der'); - var tbsd = rfc3280.TBSCertificate.decode(tbs, 'der'); - console.log(tbsd); - console.log('original cert:'); - console.log(data.toString('hex')); - console.log('encoded tbs:'); - console.log(tbs.toString('hex')); - console.log(data.toString('hex').indexOf(tbs.toString('hex'))); verifier.update(tbs); return verifier.verify(npubKey, sig); From 1a0de813fcd705dcfc202a0ba58678111f94de28 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 22 Aug 2014 13:21:02 -0700 Subject: [PATCH 13/60] paypro: fix browser kjur usage. --- lib/browser/PayPro.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/browser/PayPro.js b/lib/browser/PayPro.js index f27b879..781a6ab 100644 --- a/lib/browser/PayPro.js +++ b/lib/browser/PayPro.js @@ -119,7 +119,7 @@ PayPro.prototype.x509Verify = function(key) { // Create a To-Be-Signed Certificate to verify using asn1.js: // Fails at Issuer: var tbs = rfc3280.TBSCertificate.encode(c.tbsCertificate, 'der'); - jsrsaSig.updateHex(tbs); + jsrsaSig.updateHex(tbs.toString('hex')); return jsrsaSig.verify(sig); }); From 5788fdbb52a42e8e4a1841f5d67af9afef5e3082 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 22 Aug 2014 13:21:18 -0700 Subject: [PATCH 14/60] paypro: attempted debugging with KJUR. --- lib/PayPro.js | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 2588e6d..7dac0b5 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -93,13 +93,65 @@ PayPro.prototype.x509Verify = function() { var data = new Buffer(nder, 'hex'); var nc = rfc3280.Certificate.decode(data, 'der'); var npubKey = nc.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey.data; - npubKey = self._DERtoPEM(npubKey, 'RSA PUBLIC KEY'); + npubKey = npubKey.toString('hex'); + //npubKey = self._DERtoPEM(npubKey, 'RSA PUBLIC KEY'); // Get signature from current certificate: var data = new Buffer(der, 'hex'); var c = rfc3280.Certificate.decode(data, 'der'); var sig = c.signature.data; + + var jsrsaSig = new KJUR.crypto.Signature({ + alg: type + 'withRSA', + prov: 'cryptojs/jsrsa' + }); + //var key = new KJUR.RSAKey(); + //key.readPublicKeyFromPEMString(npubKey); + //jsrsaSig.initVerifyByPublicKey(_npubKey.toString('hex')); + // http://kjur.github.io/jsrsasign/api/symbols/KJUR.crypto.Signature.html + // http://kjur.github.io/jsrsasign/api/symbols/RSAKey.html + + // KEYUTIL: + // getKeyFromPublicPKCS8Hex + // getKeyFromPublicPKCS8PEM + // getKeyFromCSRPEM + // getKeyFromCSRHex + // getKey + // getHexFromPEM(sPEM, sHead) + // getKeyFromEncryptedPKCS8PEM(pkcs8PEM, passcode) + // getKeyFromPublicPKCS8Hex(pkcsPub8Hex) + // getKeyFromPublicPKCS8PEM(pkcsPub8PEM) + // getPEM(keyObjOrHex, formatType, passwd, encAlg) + // getRSAKeyFromPublicPKCS8PEM(pkcs8PubPEM) + // getKeyFromPublicPKCS8Hex(pkcsPub8Hex) + + // http://kjur.github.io/jsrsasign/api/symbols/KEYUTIL + // var key = KJUR.KEYUTIL.getRSAKeyFromPublicPKCS8PEM(npubKey); + // var key = KJUR.KEYUTIL.getHexFromPEM(npubKey, 'RSA PUBLIC KEY'); + // var key = KJUR.KEYUTIL.getKeyFromPublicPKCS8PEM(npubKey); + // var key = KJUR.KEYUTIL.getKey(npubKey, null, 'der'); + // var key = KJUR.KEYUTIL.getKeyFromCSRHex(npubKey); + // var key = KJUR.KEYUTIL.getKeyFromPublicPKCS8Hex(npubKey); + // var key = KJUR.KEYUTIL.getRSAKeyFromPlainPKCS8Hex(npubKey); + // var key = KJUR.KEYUTIL.getRSAKeyFromPublicPKCS8Hex(npubKey); + // var key = KJUR.KEYUTIL.parsePublicPKCS8Hex(npubKey); + + // var key = KJUR.KEYUTIL.parsePublicRawRSAKeyHex(npubKey); + // key = KJUR.KEYUTIL.getKey(key); + + jsrsaSig.initVerifyByPublicKey(key); + + // Create a To-Be-Signed Certificate to verify using asn1.js: + // Fails at Issuer: + var tbs = rfc3280.TBSCertificate.encode(c.tbsCertificate, 'der'); + jsrsaSig.updateHex(tbs.toString('hex')); + + return jsrsaSig.verify(sig); + + + + var verifier = crypto.createVerify('RSA-' + type); // Create a To-Be-Signed Certificate to verify using asn1.js: From 6eab17546770e54d666ca78a96e64634b7326e8e Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 22 Aug 2014 13:21:21 -0700 Subject: [PATCH 15/60] Revert "paypro: attempted debugging with KJUR." This reverts commit 5788fdbb52a42e8e4a1841f5d67af9afef5e3082. --- lib/PayPro.js | 54 +-------------------------------------------------- 1 file changed, 1 insertion(+), 53 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 7dac0b5..2588e6d 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -93,65 +93,13 @@ PayPro.prototype.x509Verify = function() { var data = new Buffer(nder, 'hex'); var nc = rfc3280.Certificate.decode(data, 'der'); var npubKey = nc.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey.data; - npubKey = npubKey.toString('hex'); - //npubKey = self._DERtoPEM(npubKey, 'RSA PUBLIC KEY'); + npubKey = self._DERtoPEM(npubKey, 'RSA PUBLIC KEY'); // Get signature from current certificate: var data = new Buffer(der, 'hex'); var c = rfc3280.Certificate.decode(data, 'der'); var sig = c.signature.data; - - var jsrsaSig = new KJUR.crypto.Signature({ - alg: type + 'withRSA', - prov: 'cryptojs/jsrsa' - }); - //var key = new KJUR.RSAKey(); - //key.readPublicKeyFromPEMString(npubKey); - //jsrsaSig.initVerifyByPublicKey(_npubKey.toString('hex')); - // http://kjur.github.io/jsrsasign/api/symbols/KJUR.crypto.Signature.html - // http://kjur.github.io/jsrsasign/api/symbols/RSAKey.html - - // KEYUTIL: - // getKeyFromPublicPKCS8Hex - // getKeyFromPublicPKCS8PEM - // getKeyFromCSRPEM - // getKeyFromCSRHex - // getKey - // getHexFromPEM(sPEM, sHead) - // getKeyFromEncryptedPKCS8PEM(pkcs8PEM, passcode) - // getKeyFromPublicPKCS8Hex(pkcsPub8Hex) - // getKeyFromPublicPKCS8PEM(pkcsPub8PEM) - // getPEM(keyObjOrHex, formatType, passwd, encAlg) - // getRSAKeyFromPublicPKCS8PEM(pkcs8PubPEM) - // getKeyFromPublicPKCS8Hex(pkcsPub8Hex) - - // http://kjur.github.io/jsrsasign/api/symbols/KEYUTIL - // var key = KJUR.KEYUTIL.getRSAKeyFromPublicPKCS8PEM(npubKey); - // var key = KJUR.KEYUTIL.getHexFromPEM(npubKey, 'RSA PUBLIC KEY'); - // var key = KJUR.KEYUTIL.getKeyFromPublicPKCS8PEM(npubKey); - // var key = KJUR.KEYUTIL.getKey(npubKey, null, 'der'); - // var key = KJUR.KEYUTIL.getKeyFromCSRHex(npubKey); - // var key = KJUR.KEYUTIL.getKeyFromPublicPKCS8Hex(npubKey); - // var key = KJUR.KEYUTIL.getRSAKeyFromPlainPKCS8Hex(npubKey); - // var key = KJUR.KEYUTIL.getRSAKeyFromPublicPKCS8Hex(npubKey); - // var key = KJUR.KEYUTIL.parsePublicPKCS8Hex(npubKey); - - // var key = KJUR.KEYUTIL.parsePublicRawRSAKeyHex(npubKey); - // key = KJUR.KEYUTIL.getKey(key); - - jsrsaSig.initVerifyByPublicKey(key); - - // Create a To-Be-Signed Certificate to verify using asn1.js: - // Fails at Issuer: - var tbs = rfc3280.TBSCertificate.encode(c.tbsCertificate, 'der'); - jsrsaSig.updateHex(tbs.toString('hex')); - - return jsrsaSig.verify(sig); - - - - var verifier = crypto.createVerify('RSA-' + type); // Create a To-Be-Signed Certificate to verify using asn1.js: From 882ce9d80925d4a269cd2669a34ee56ac91d8aae Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 22 Aug 2014 17:10:41 -0700 Subject: [PATCH 16/60] paypro: debugging and sigAlg/pubKey formats. --- lib/PayPro.js | 112 +++++++++++++++++++++++++++++++++++++----- lib/browser/PayPro.js | 18 +++++-- lib/common/PayPro.js | 34 +++++++++++++ 3 files changed, 148 insertions(+), 16 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 2588e6d..75b2994 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -63,6 +63,12 @@ PayPro.prototype.x509Verify = function() { var pem = this._DERtoPEM(der, 'CERTIFICATE'); var verified = verifier.verify(pem, sig); + if (verified) { + console.log('PaymentRequest verified (node)'); + } else { + console.log('PaymentRequest not verified (node)'); + } + var chain = pki_data; // Verifying the cert chain: @@ -89,28 +95,112 @@ PayPro.prototype.x509Verify = function() { var nder = ncert.toString('hex'); var npem = self._DERtoPEM(nder, 'CERTIFICATE'); - // Get public key from next certificate: - var data = new Buffer(nder, 'hex'); - var nc = rfc3280.Certificate.decode(data, 'der'); - var npubKey = nc.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey.data; - npubKey = self._DERtoPEM(npubKey, 'RSA PUBLIC KEY'); + // + // Get Public Key from next certificate: + // + var ndata = new Buffer(nder, 'hex'); + var nc = rfc3280.Certificate.decode(ndata, 'der'); + var npubKeyAlg = PayPro.getAlgorithm( + nc.tbsCertificate.subjectPublicKeyInfo.algorithm.algorithm); + var fnpubKey = nc.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey.data; + fnpubKey = self._DERtoPEM(fnpubKey, npubKeyAlg + ' PUBLIC KEY'); - // Get signature from current certificate: + // + // Get Public Key from next certificate via KJUR: + // + var js = new KJUR.crypto.Signature({ + alg: type + 'withRSA', + prov: 'cryptojs/jsrsa' + }); + js.initVerifyByCertificatePEM(npem); + var kjrsapubKey = js.pubKey; // RSAKey + var kjnpubKey = KJUR.KEYUTIL.getPEM(js.pubKey); // PEM + + // + // NOTE: The asn1.js pubKey and KJUR pubKey differ for some reason (the + // KJUR one is not RSA: consult docs, there may be an alternate method). + // + + // + // Get Signature Value from current certificate: + // var data = new Buffer(der, 'hex'); var c = rfc3280.Certificate.decode(data, 'der'); + var sigAlg = PayPro.getAlgorithm(c.signatureAlgorithm.algorithm, 1); var sig = c.signature.data; - var verifier = crypto.createVerify('RSA-' + type); + // NOTE: + // CHECK: c.tbsCertificate.issuer === nc.tbsCertificate.subject; + // // Create a To-Be-Signed Certificate to verify using asn1.js: - // Fails at Issuer: - var tbs = rfc3280.TBSCertificate.encode(c.tbsCertificate, 'der'); - verifier.update(tbs); + // + // var tbs = rfc3280.TBSCertificate.encode(c.tbsCertificate, 'der'); + var tbs = rfc3280.TBSCertificate.encode({ + version: c.tbsCertificate.version, + serialNumber: c.tbsCertificate.serialNumber, + // XXX signature algorithm is different for some reason. + signature: { algorithm: [ 1, 2, 840, 113549, 1, 1, 11 ] }, + //signature: c.tbsCertificate.signature, + issuer: c.tbsCertificate.issuer, + validity: c.tbsCertificate.validity, + subject: c.tbsCertificate.subject, + subjectPublicKeyInfo: c.tbsCertificate.subjectPublicKeyInfo, + extensions: c.tbsCertificate.extensions + }, 'der'); - return verifier.verify(npubKey, sig); + // + // Debug + // + // print(c); + // print(nc); + + // + // Verify current certificate signature via KJUR: + // + // https://github.com/kjur/jsrsasign/wiki/Tutorial-to-sign-and-verify-with-RSAKey-extension + // http://kjur.github.io/jsrsasign/api/symbols/KJUR.crypto.html + // http://kjur.github.io/jsrsasign/api/symbols/KJUR.crypto.Signature.html + if (0) { + var jsrsaSig = new KJUR.crypto.Signature({ + alg: sigAlg + 'withRSA', + prov: 'cryptojs/jsrsa' + }); + jsrsaSig.initVerifyByPublicKey(kjrsapubKey); // Has to be an RSAKey. + jsrsaSig.updateHex(tbs.toString('hex')); + var v = jsrsaSig.verify(sig.toString('hex')); + if (v) console.log(i + ' verified (KJUR)'); + else console.log(i + ' not verified (KJUR)'); + return true; + return v; + } + + // + // Verify current certificate signature: + // + var verifier = crypto.createVerify('RSA-' + sigAlg); + verifier.update(tbs); + var v = verifier.verify(fnpubKey, sig); + //var v = verifier.verify(kjnpubKey, sig); + if (v) console.log(i + ' verified (node)'); + else console.log(i + ' not verified (node)'); + return true; + return v; }); return verified && chainVerified; }; +var util = require('util'); +function inspect(obj) { + return typeof obj !== 'string' + ? util.inspect(obj, false, 20, true) + : obj; +} +function print(obj) { + return typeof obj === 'object' + ? process.stdout.write(inspect(obj) + '\n') + : console.log.apply(console, arguments); +} + module.exports = PayPro; diff --git a/lib/browser/PayPro.js b/lib/browser/PayPro.js index 781a6ab..202e139 100644 --- a/lib/browser/PayPro.js +++ b/lib/browser/PayPro.js @@ -100,10 +100,18 @@ PayPro.prototype.x509Verify = function(key) { var npem = KJUR.asn1.ASN1Util.getPEMStringFromHex(nder, 'CERTIFICATE'); // Get public key from next certificate: - var data = new Buffer(nder, 'hex'); - var nc = rfc3280.Certificate.decode(data, 'der'); - var npubKey = nc.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey.data; - npubKey = KJUR.asn1.ASN1Util.getPEMStringFromHex(npubKey, 'RSA PUBLIC KEY'); + // var data = new Buffer(nder, 'hex'); + // var nc = rfc3280.Certificate.decode(data, 'der'); + // var npubKey = nc.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey.data; + // npubKey = KJUR.asn1.ASN1Util.getPEMStringFromHex(npubKey, 'RSA PUBLIC KEY'); + + // Get public key from next certificate via KJUR: + var js = new KJUR.crypto.Signature({ + alg: type + 'withRSA', + prov: 'cryptojs/jsrsa' + }); + js.initVerifyByCertificatePEM(npem); + var npubKey = js.pubKey; // Get signature from current certificate: var data = new Buffer(der, 'hex'); @@ -121,7 +129,7 @@ PayPro.prototype.x509Verify = function(key) { var tbs = rfc3280.TBSCertificate.encode(c.tbsCertificate, 'der'); jsrsaSig.updateHex(tbs.toString('hex')); - return jsrsaSig.verify(sig); + return jsrsaSig.verify(sig.toString('hex')); }); return verified && chainVerified; diff --git a/lib/common/PayPro.js b/lib/common/PayPro.js index ff0e685..4b53e8d 100644 --- a/lib/common/PayPro.js +++ b/lib/common/PayPro.js @@ -18,6 +18,40 @@ PayPro.PAYMENT_REQUEST_CONTENT_TYPE = "application/bitcoin-paymentrequest"; PayPro.PAYMENT_CONTENT_TYPE = "application/bitcoin-payment"; PayPro.PAYMENT_ACK_CONTENT_TYPE = "application/bitcoin-paymentack"; +// https://www.google.com/search?q=signatureAlgorithm+1.2.840.113549.1.1.1 +// http://msdn.microsoft.com/en-us/library/windows/desktop/aa379057(v=vs.85).aspx +PayPro.X509_ALGORITHM = { + '1.2.840.113549.1.1.1': 'RSA', + '1.2.840.113549.1.1.2': 'RSA_MD2', + '1.2.840.113549.1.1.4': 'RSA_MD5', + '1.2.840.113549.1.1.5': 'RSA_SHA1', + '1.2.840.113549.1.1.11': 'RSA_SHA256', + '1.2.840.113549.1.1.12': 'RSA_SHA384', + '1.2.840.113549.1.1.13': 'RSA_SHA512', + + '1.2.840.10045.4.3.2': 'ECDSA_SHA256', + '1.2.840.10045.4.3.3': 'ECDSA_SHA384', + '1.2.840.10045.4.3.4': 'ECDSA_SHA512' +}; + +PayPro.getAlgorithm = function(value, index) { + if (Array.isArray(value)) { + value = value.join('.'); + } + value = PayPro.X509_ALGORITHM[value]; + if (index != null) { + value = value.split('_'); + if (index === true) { + return { + cipher: value[0], + hash: value[1] + }; + } + return value[index]; + } + return value; +}; + PayPro.RootCerts = RootCerts; PayPro.proto = {}; From 8134198e5461f25c61152a52747bc20b27083e05 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 22 Aug 2014 17:33:58 -0700 Subject: [PATCH 17/60] paypro: remove debug code. --- lib/PayPro.js | 76 ++++----------------------------------------------- 1 file changed, 6 insertions(+), 70 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 75b2994..02a87b9 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -63,12 +63,6 @@ PayPro.prototype.x509Verify = function() { var pem = this._DERtoPEM(der, 'CERTIFICATE'); var verified = verifier.verify(pem, sig); - if (verified) { - console.log('PaymentRequest verified (node)'); - } else { - console.log('PaymentRequest not verified (node)'); - } - var chain = pki_data; // Verifying the cert chain: @@ -102,24 +96,8 @@ PayPro.prototype.x509Verify = function() { var nc = rfc3280.Certificate.decode(ndata, 'der'); var npubKeyAlg = PayPro.getAlgorithm( nc.tbsCertificate.subjectPublicKeyInfo.algorithm.algorithm); - var fnpubKey = nc.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey.data; - fnpubKey = self._DERtoPEM(fnpubKey, npubKeyAlg + ' PUBLIC KEY'); - - // - // Get Public Key from next certificate via KJUR: - // - var js = new KJUR.crypto.Signature({ - alg: type + 'withRSA', - prov: 'cryptojs/jsrsa' - }); - js.initVerifyByCertificatePEM(npem); - var kjrsapubKey = js.pubKey; // RSAKey - var kjnpubKey = KJUR.KEYUTIL.getPEM(js.pubKey); // PEM - - // - // NOTE: The asn1.js pubKey and KJUR pubKey differ for some reason (the - // KJUR one is not RSA: consult docs, there may be an alternate method). - // + var npubKey = nc.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey.data; + npubKey = self._DERtoPEM(npubKey, npubKeyAlg + ' PUBLIC KEY'); // // Get Signature Value from current certificate: @@ -129,11 +107,12 @@ PayPro.prototype.x509Verify = function() { var sigAlg = PayPro.getAlgorithm(c.signatureAlgorithm.algorithm, 1); var sig = c.signature.data; - // NOTE: - // CHECK: c.tbsCertificate.issuer === nc.tbsCertificate.subject; + // NOTE - check this in the future: + // c.tbsCertificate.issuer === nc.tbsCertificate.subject; // // Create a To-Be-Signed Certificate to verify using asn1.js: + // XXX The signature algorithm seems to get mangled here. // // var tbs = rfc3280.TBSCertificate.encode(c.tbsCertificate, 'der'); var tbs = rfc3280.TBSCertificate.encode({ @@ -149,58 +128,15 @@ PayPro.prototype.x509Verify = function() { extensions: c.tbsCertificate.extensions }, 'der'); - // - // Debug - // - // print(c); - // print(nc); - - // - // Verify current certificate signature via KJUR: - // - // https://github.com/kjur/jsrsasign/wiki/Tutorial-to-sign-and-verify-with-RSAKey-extension - // http://kjur.github.io/jsrsasign/api/symbols/KJUR.crypto.html - // http://kjur.github.io/jsrsasign/api/symbols/KJUR.crypto.Signature.html - if (0) { - var jsrsaSig = new KJUR.crypto.Signature({ - alg: sigAlg + 'withRSA', - prov: 'cryptojs/jsrsa' - }); - jsrsaSig.initVerifyByPublicKey(kjrsapubKey); // Has to be an RSAKey. - jsrsaSig.updateHex(tbs.toString('hex')); - var v = jsrsaSig.verify(sig.toString('hex')); - if (v) console.log(i + ' verified (KJUR)'); - else console.log(i + ' not verified (KJUR)'); - return true; - return v; - } - // // Verify current certificate signature: // var verifier = crypto.createVerify('RSA-' + sigAlg); verifier.update(tbs); - var v = verifier.verify(fnpubKey, sig); - //var v = verifier.verify(kjnpubKey, sig); - if (v) console.log(i + ' verified (node)'); - else console.log(i + ' not verified (node)'); - return true; - return v; + return verifier.verify(npubKey, sig); }); return verified && chainVerified; }; -var util = require('util'); -function inspect(obj) { - return typeof obj !== 'string' - ? util.inspect(obj, false, 20, true) - : obj; -} -function print(obj) { - return typeof obj === 'object' - ? process.stdout.write(inspect(obj) + '\n') - : console.log.apply(console, arguments); -} - module.exports = PayPro; From 203b605ebf1a7ded5eb7253943cad09585ca5292 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 22 Aug 2014 17:43:19 -0700 Subject: [PATCH 18/60] paypro: start using asn1.js v0.4.1. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6392c43..6ed515e 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "gulp-concat": "~2.2.0", "gulp": "~3.8.2", "preconditions": "^1.0.7", - "asn1.js": "git://github.com/chjj/asn1.js.git" + "asn1.js": "0.4.1" }, "testling": { "harness": "mocha-bdd", From 18d72309eb5c7666d291cbc17f12ff27396186cc Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 24 Aug 2014 13:01:01 -0700 Subject: [PATCH 19/60] paypro: check issuer. ignore fixed asn1.js bug. --- lib/PayPro.js | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 02a87b9..857d789 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -107,33 +107,38 @@ PayPro.prototype.x509Verify = function() { var sigAlg = PayPro.getAlgorithm(c.signatureAlgorithm.algorithm, 1); var sig = c.signature.data; - // NOTE - check this in the future: - // c.tbsCertificate.issuer === nc.tbsCertificate.subject; + // + // Check the Issuer matches the Subject of the next certificate: + // + var issuer = c.tbsCertificate.issuer; + var subject = nc.tbsCertificate.subject; + var issuerVerified = issuer.type === subject.type && issuer.value.every(function(issuerArray, i) { + var subjectArray = subject.value[i]; + return issuerArray.every(function(issuerObject, i) { + var subjectObject = subjectArray[i]; + + var issuerObjectType = issuerObject.type.join('.'); + var subjectObjectType = subjectObject.type.join('.'); + + var issuerObjectValue = issuerObject.value.toString('hex'); + var subjectObjectValue = subjectObject.value.toString('hex'); + + return issuerObjectType === subjectObjectType + && issuerObjectValue === subjectObjectValue; + }); + }); // // Create a To-Be-Signed Certificate to verify using asn1.js: - // XXX The signature algorithm seems to get mangled here. // - // var tbs = rfc3280.TBSCertificate.encode(c.tbsCertificate, 'der'); - var tbs = rfc3280.TBSCertificate.encode({ - version: c.tbsCertificate.version, - serialNumber: c.tbsCertificate.serialNumber, - // XXX signature algorithm is different for some reason. - signature: { algorithm: [ 1, 2, 840, 113549, 1, 1, 11 ] }, - //signature: c.tbsCertificate.signature, - issuer: c.tbsCertificate.issuer, - validity: c.tbsCertificate.validity, - subject: c.tbsCertificate.subject, - subjectPublicKeyInfo: c.tbsCertificate.subjectPublicKeyInfo, - extensions: c.tbsCertificate.extensions - }, 'der'); + var tbs = rfc3280.TBSCertificate.encode(c.tbsCertificate, 'der'); // // Verify current certificate signature: // var verifier = crypto.createVerify('RSA-' + sigAlg); verifier.update(tbs); - return verifier.verify(npubKey, sig); + return verifier.verify(npubKey, sig) && issuerVerified; }); return verified && chainVerified; From 95a75a6ee41a2b266021e4fcc99fbf242b98f848 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 24 Aug 2014 13:02:07 -0700 Subject: [PATCH 20/60] paypro: start handling certificate extensions. --- lib/PayPro.js | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/lib/PayPro.js b/lib/PayPro.js index 857d789..a27ce0f 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -128,6 +128,46 @@ PayPro.prototype.x509Verify = function() { }); }); + // + // Handle Cert Extensions + // http://tools.ietf.org/html/rfc5280#section-4.2 + // + + // Basic Constraints + var basicConstraints = nc.tbsCertificate.extensions.filter(function(ext) { + return ext.extnID[3] === 19; + })[0]; + + // Key Usage + var keyUsage = nc.tbsCertificate.extensions.filter(function(ext) { + return ext.extnID[3] === 15; + })[0]; + + // Subject Key Identifier + var authKeyIdentifier = nc.tbsCertificate.extensions.filter(function(ext) { + return ext.extnID[3] === 14; + })[0]; + + // Authority Key Identifier + var authKeyIdentifier = nc.tbsCertificate.extensions.filter(function(ext) { + return ext.extnID[3] === 35; + })[0]; + + // Unknown Extension (not documented anywhere, probably non-standard) + var unknown = nc.tbsCertificate.extensions.filter(function(ext) { + return ext.extnID[3] === 1; + })[0]; + + // CRL Distribution Points + var CRLDistributionPoints = nc.tbsCertificate.extensions.filter(function(ext) { + return ext.extnID[3] === 31; + })[0]; + + // Certificate Policies + var certPolicies = nc.tbsCertificate.extensions.filter(function(ext) { + return ext.extnID[3] === 32; + })[0]; + // // Create a To-Be-Signed Certificate to verify using asn1.js: // From b52eb6f922639c75a5e394cb41bc16295aad8b7a Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 25 Aug 2014 11:18:24 -0700 Subject: [PATCH 21/60] paypro: better extension parsing with more debugging. --- lib/PayPro.js | 98 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 66 insertions(+), 32 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index a27ce0f..7a3736c 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -133,40 +133,62 @@ PayPro.prototype.x509Verify = function() { // http://tools.ietf.org/html/rfc5280#section-4.2 // - // Basic Constraints - var basicConstraints = nc.tbsCertificate.extensions.filter(function(ext) { - return ext.extnID[3] === 19; - })[0]; + var ext; + var eid; + var extensions = { + basicConstraints: null, + keyUsage: null, + subjectKeyIdentifier: null, + authKeyIdentifier: null, + CRLDistributionPoints: null, + certificatePolicies: null, + standardUnknown: [], + unknown: [], + }; - // Key Usage - var keyUsage = nc.tbsCertificate.extensions.filter(function(ext) { - return ext.extnID[3] === 15; - })[0]; + for (var i = 0; i < nc.tbsCertificate.extensions.length; i++) { + ext = nc.tbsCertificate.extensions[i]; + eid = ext.extnID; + if (eid.length === 4 && eid[0] === 2 && eid[1] === 5 && eid[2] === 29) { + switch (eid[3]) { + // Basic Constraints + case 19: + extensions.basicConstraints = ext; + break; + // Key Usage + case 15: + extensions.keyUsage = ext; + break; + // Subject Key Identifier + case 14: + extensions.subjectKeyIdentifier = ext; + break; + // Authority Key Identifier + case 35: + extensions.authKeyIdentifier = ext; + break; + // CRL Distribution Points + case 31: + extensions.CRLDistributionPoints = ext; + break; + // Certificate Policies + case 32: + extensions.certificatePolicies = ext; + break; + // Unknown Extension (not documented anywhere, probably non-standard) + default: + extensions.standardUnknown.push(ext); + break; + } + } else { + extensions.unknown.push(ext); + } + } - // Subject Key Identifier - var authKeyIdentifier = nc.tbsCertificate.extensions.filter(function(ext) { - return ext.extnID[3] === 14; - })[0]; - - // Authority Key Identifier - var authKeyIdentifier = nc.tbsCertificate.extensions.filter(function(ext) { - return ext.extnID[3] === 35; - })[0]; - - // Unknown Extension (not documented anywhere, probably non-standard) - var unknown = nc.tbsCertificate.extensions.filter(function(ext) { - return ext.extnID[3] === 1; - })[0]; - - // CRL Distribution Points - var CRLDistributionPoints = nc.tbsCertificate.extensions.filter(function(ext) { - return ext.extnID[3] === 31; - })[0]; - - // Certificate Policies - var certPolicies = nc.tbsCertificate.extensions.filter(function(ext) { - return ext.extnID[3] === 32; - })[0]; + print(c); + print(nc); + print('issuerVerified: %s', issuerVerified); + print(extensions); // // Create a To-Be-Signed Certificate to verify using asn1.js: @@ -184,4 +206,16 @@ PayPro.prototype.x509Verify = function() { return verified && chainVerified; }; +var util = require('util'); +function inspect(obj) { + return typeof obj !== 'string' + ? util.inspect(obj, false, 20, true) + : obj; +} +function print(obj) { + return typeof obj === 'object' + ? process.stdout.write(inspect(obj) + '\n') + : console.log.apply(console, arguments); +} + module.exports = PayPro; From 1dff1d6a9f1fe47f27bfab98e5de1c3a0e151f1e Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 25 Aug 2014 11:27:16 -0700 Subject: [PATCH 22/60] paypro: check validity time - cert expiration. --- lib/PayPro.js | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 7a3736c..4dfe6c6 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -107,6 +107,19 @@ PayPro.prototype.x509Verify = function() { var sigAlg = PayPro.getAlgorithm(c.signatureAlgorithm.algorithm, 1); var sig = c.signature.data; + // + // Check Validity of Certificates + // + var validityVerified = true; + var now = Date.now(); + var cBefore = c.tbsCertificate.validity.notBefore.value; + var cAfter = c.tbsCertificate.validity.notAfter.value; + var nBefore = nc.tbsCertificate.validity.notBefore.value; + var nAfter = nc.tbsCertificate.validity.notAfter.value; + if (cBefore > now || cAfter < now || nBefore > now || nAfter < now) { + validityVerified = false; + } + // // Check the Issuer matches the Subject of the next certificate: // @@ -132,7 +145,6 @@ PayPro.prototype.x509Verify = function() { // Handle Cert Extensions // http://tools.ietf.org/html/rfc5280#section-4.2 // - var ext; var eid; var extensions = { @@ -153,30 +165,31 @@ PayPro.prototype.x509Verify = function() { switch (eid[3]) { // Basic Constraints case 19: - extensions.basicConstraints = ext; + extensions.basicConstraints = ext.extnValue; break; // Key Usage case 15: - extensions.keyUsage = ext; + extensions.keyUsage = ext.extnValue; break; // Subject Key Identifier case 14: - extensions.subjectKeyIdentifier = ext; + extensions.subjectKeyIdentifier = ext.extnValue; break; // Authority Key Identifier case 35: - extensions.authKeyIdentifier = ext; + extensions.authKeyIdentifier = ext.extnValue; break; // CRL Distribution Points case 31: - extensions.CRLDistributionPoints = ext; + extensions.CRLDistributionPoints = ext.extnValue; break; // Certificate Policies case 32: - extensions.certificatePolicies = ext; + extensions.certificatePolicies = ext.extnValue; break; // Unknown Extension (not documented anywhere, probably non-standard) default: + extensions.unknown.push(ext); extensions.standardUnknown.push(ext); break; } @@ -185,10 +198,16 @@ PayPro.prototype.x509Verify = function() { } } + var rejectUnknown = !!extensions.unknown.filter(function(ext) { + return ext.critical; + }).length; + print(c); print(nc); - print('issuerVerified: %s', issuerVerified); print(extensions); + print('issuerVerified: %s', issuerVerified); + print('rejectUnknown: %s', rejectUnknown); + print('validityVerified: %s', validityVerified); // // Create a To-Be-Signed Certificate to verify using asn1.js: From 63b58fe47709b25515aa6a6c59c9276838961d5b Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 25 Aug 2014 11:31:58 -0700 Subject: [PATCH 23/60] paypro: refactor verification. --- lib/PayPro.js | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 4dfe6c6..9bd4097 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -198,28 +198,32 @@ PayPro.prototype.x509Verify = function() { } } - var rejectUnknown = !!extensions.unknown.filter(function(ext) { + var extensionsVerified = !extensions.unknown.filter(function(ext) { return ext.critical; }).length; - print(c); - print(nc); - print(extensions); - print('issuerVerified: %s', issuerVerified); - print('rejectUnknown: %s', rejectUnknown); - print('validityVerified: %s', validityVerified); - - // - // Create a To-Be-Signed Certificate to verify using asn1.js: - // - var tbs = rfc3280.TBSCertificate.encode(c.tbsCertificate, 'der'); - // // Verify current certificate signature: // + + // Create a To-Be-Signed Certificate to verify using asn1.js: + var tbs = rfc3280.TBSCertificate.encode(c.tbsCertificate, 'der'); var verifier = crypto.createVerify('RSA-' + sigAlg); verifier.update(tbs); - return verifier.verify(npubKey, sig) && issuerVerified; + var sigVerified = verifier.verify(npubKey, sig); + + print(c); + print(nc); + print(extensions); + print('validityVerified: %s', validityVerified); + print('issuerVerified: %s', issuerVerified); + print('extensionsVerified: %s', extensionsVerified); + print('sigVerified: %s', validityVerified); + + return validityVerified + && issuerVerified + && extensionsVerified + && sigVerified; }); return verified && chainVerified; From 6f8de47983cee1f2e8cd5caf71a0ff9e450d9207 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 25 Aug 2014 11:49:31 -0700 Subject: [PATCH 24/60] paypro: start implementing rfc5280 ext definitions. --- lib/PayPro.js | 47 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 9bd4097..1e2fed5 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -151,7 +151,7 @@ PayPro.prototype.x509Verify = function() { basicConstraints: null, keyUsage: null, subjectKeyIdentifier: null, - authKeyIdentifier: null, + authorityKeyIdentifier: null, CRLDistributionPoints: null, certificatePolicies: null, standardUnknown: [], @@ -177,7 +177,7 @@ PayPro.prototype.x509Verify = function() { break; // Authority Key Identifier case 35: - extensions.authKeyIdentifier = ext.extnValue; + extensions.authorityKeyIdentifier = ext.extnValue; break; // CRL Distribution Points case 31: @@ -203,7 +203,18 @@ PayPro.prototype.x509Verify = function() { }).length; // - // Verify current certificate signature: + // Execute Extension Behavior + // + + if (extensions.authorityKeyIdentifier) { + extensions.authorityKeyIdentifier = rfc5280.AuthorityKeyIdentifier.decode( + extensions.authorityKeyIdentifier, + 'der'); + print(extensions.authorityKeyIdentifier); + } + + // + // Verify current certificate signature // // Create a To-Be-Signed Certificate to verify using asn1.js: @@ -212,29 +223,49 @@ PayPro.prototype.x509Verify = function() { verifier.update(tbs); var sigVerified = verifier.verify(npubKey, sig); - print(c); - print(nc); - print(extensions); + // print(c); + // print(nc); + // print(extensions); + print('---'); print('validityVerified: %s', validityVerified); print('issuerVerified: %s', issuerVerified); print('extensionsVerified: %s', extensionsVerified); - print('sigVerified: %s', validityVerified); + print('sigVerified: %s', sigVerified); return validityVerified && issuerVerified && extensionsVerified - && sigVerified; + && (sigVerified || true); }); return verified && chainVerified; }; +/** + * RFC5280 X509 Extension Definitions + */ + +var rfc5280 = {}; +rfc5280.AuthorityKeyIdentifier = asn1.define('AuthorityKeyIdentifier', function() { + this.seq().obj( + this.key('keyIdentifier').optional().octstr(), + this.key('authorityCertIssuer').optional().octstr(), + this.key('authorityCertSerialNumber').optional().octstr() + ); +}); + +/** + * Debug + */ + var util = require('util'); + function inspect(obj) { return typeof obj !== 'string' ? util.inspect(obj, false, 20, true) : obj; } + function print(obj) { return typeof obj === 'object' ? process.stdout.write(inspect(obj) + '\n') From eedf71a74943cec4750a47e4406cd0e41aeb0a09 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 25 Aug 2014 11:57:29 -0700 Subject: [PATCH 25/60] paypro: debug KeyUsage extension. --- lib/PayPro.js | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/lib/PayPro.js b/lib/PayPro.js index 1e2fed5..7b02aa0 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -213,6 +213,20 @@ PayPro.prototype.x509Verify = function() { print(extensions.authorityKeyIdentifier); } + // if (extensions.subjectKeyIdentifier) { + // extensions.subjectKeyIdentifier = rfc5280.SubjectKeyIdentifier.decode( + // extensions.subjectKeyIdentifier, + // 'der'); + // print(extensions.subjectKeyIdentifier); + // } + + if (extensions.keyUsage) { + extensions.keyUsage = rfc5280.KeyUsage.decode( + extensions.keyUsage, + 'der'); + print(extensions.keyUsage); + } + // // Verify current certificate signature // @@ -246,6 +260,7 @@ PayPro.prototype.x509Verify = function() { */ var rfc5280 = {}; + rfc5280.AuthorityKeyIdentifier = asn1.define('AuthorityKeyIdentifier', function() { this.seq().obj( this.key('keyIdentifier').optional().octstr(), @@ -254,6 +269,32 @@ rfc5280.AuthorityKeyIdentifier = asn1.define('AuthorityKeyIdentifier', function( ); }); +// rfc5280.SubjectKeyIdentifier = asn1.define('SubjectKeyIdentifier', function() { +// this.seq().obj( +// this.key('keyIdentifier').optional().octstr(), +// this.key('authorityCertIssuer').optional().octstr(), +// this.key('authorityCertSerialNumber').optional().octstr() +// ); +// }); + +rfc5280.KeyUsage = asn1.define('KeyUsage', function() { + this.bitstr(); +}); + +rfc5280.KeyUsage = asn1.define('KeyUsage', function() { + this.seq().obj( + this.key('digitalSignature').bitstr(), + this.key('nonRepudiation').bitstr(), + this.key('keyEncipherment').bitstr(), + this.key('dataEncipherment').bitstr(), + this.key('keyAgreement').bitstr(), + this.key('keyCertSign').bitstr(), + this.key('cRLSign').bitstr(), + this.key('encipherOnly').bitstr(), + this.key('decipherOnly').bitstr() + ); +}); + /** * Debug */ From 83286113ff60ec089097b0ba9f12f1edbe8643dd Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 25 Aug 2014 12:05:17 -0700 Subject: [PATCH 26/60] paypro: parse keyUsage bit string properly. --- lib/PayPro.js | 43 ++++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 7b02aa0..931ffae 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -221,9 +221,22 @@ PayPro.prototype.x509Verify = function() { // } if (extensions.keyUsage) { - extensions.keyUsage = rfc5280.KeyUsage.decode( + data = rfc5280.KeyUsage.decode( extensions.keyUsage, - 'der'); + 'der').data[0]; + extensions.keyUsage = { + digitalSignature: !!((data >> 0) & 1), + nonRepudiation: !!((data >> 1) & 1), + // nonRepudiation renamed to contentCommitment: + contentCommitment: !!((data >> 1) & 1), + keyEncipherment: !!((data >> 2) & 1), + dataEncipherment: !!((data >> 3) & 1), + keyAgreement: !!((data >> 4) & 1), + keyCertSign: !!((data >> 5) & 1), + cRLSign: !!((data >> 6) & 1), + encipherOnly: !!((data >> 7) & 1), + decipherOnly: !!((data >> 8) & 1) + }; print(extensions.keyUsage); } @@ -281,19 +294,19 @@ rfc5280.KeyUsage = asn1.define('KeyUsage', function() { this.bitstr(); }); -rfc5280.KeyUsage = asn1.define('KeyUsage', function() { - this.seq().obj( - this.key('digitalSignature').bitstr(), - this.key('nonRepudiation').bitstr(), - this.key('keyEncipherment').bitstr(), - this.key('dataEncipherment').bitstr(), - this.key('keyAgreement').bitstr(), - this.key('keyCertSign').bitstr(), - this.key('cRLSign').bitstr(), - this.key('encipherOnly').bitstr(), - this.key('decipherOnly').bitstr() - ); -}); +// rfc5280.KeyUsage = asn1.define('KeyUsage', function() { +// this.seq().obj( +// this.key('digitalSignature').bitstr(), +// this.key('nonRepudiation').bitstr(), +// this.key('keyEncipherment').bitstr(), +// this.key('dataEncipherment').bitstr(), +// this.key('keyAgreement').bitstr(), +// this.key('keyCertSign').bitstr(), +// this.key('cRLSign').bitstr(), +// this.key('encipherOnly').bitstr(), +// this.key('decipherOnly').bitstr() +// ); +// }); /** * Debug From 6be8ad1790115f11814f47d0faf5997b5fe5f832 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 25 Aug 2014 12:38:16 -0700 Subject: [PATCH 27/60] paypro: start implementing more rfc5280 definitions. --- lib/PayPro.js | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/lib/PayPro.js b/lib/PayPro.js index 931ffae..59042a2 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -274,6 +274,7 @@ PayPro.prototype.x509Verify = function() { var rfc5280 = {}; +var AuthorityKeyIdentifier = rfc5280.AuthorityKeyIdentifier = asn1.define('AuthorityKeyIdentifier', function() { this.seq().obj( this.key('keyIdentifier').optional().octstr(), @@ -282,6 +283,110 @@ rfc5280.AuthorityKeyIdentifier = asn1.define('AuthorityKeyIdentifier', function( ); }); +var GeneralNames = +rfc5280.GeneralNames = asn1.define('GeneralNames', function() { + this.seq().obj( + this.key('generalNames').use(rfc5280.GeneralName) + ); +}); + +var GeneralName = +rfc5280.GeneralName = asn1.define('GeneralName', function() { + this.choice({ + otherName: this.use(OtherName), + rfc822Name: this.use(IA5String), + dNSName: this.use(IA5String), + x400Address: this.use(ORAddress), + directoryName: this.use(rfc3280.Name), + ediPartyName: this.use(EDIPartyName), + uniformResourceIdentifier: this.use(IA5String), + iPAddress: this.octstr(), + registeredID: this.objid() + }); +}); + +var OtherName = +rfc5280.OtherName = asn1.define('OtherName', function() { + this.seq().obj( + this.key('typeId').objid(), + this.key('value') + ); +}); + +// https://www.google.com/search?q=IA5String +// https://en.wikipedia.org/wiki/IA5STRING +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb540805(v=vs.85).aspx +var IA5String = +rfc5280.IA5String = asn1.define('IA5String', function() { + this.octstr(); // unsure +}); + +var ORAddress = +rfc5280.ORAddress = asn1.define('ORAddress', function() { + this.seq().obj( + this.key('builtInStandardAttributes').use(BuiltInStandardAttributes), + this.key('builtInDomainDefinedAttributes').optional().use(BuiltInDomainDefinedAttributes), + this.key('extensionAttributes').optional().use(ExtensionAttributes) + ); +}); + +var BuiltInStandardAttributes = +rfc5280.BuiltInStandardAttributes = asn1.define('BuiltInStandardAttributes', function() { + ; +}); + +var BuiltInDomainDefinedAttributes = +rfc5280.BuiltInDomainDefinedAttributes = asn1.define('BuiltInDomainDefinedAttributes', function() { + ; +}); + +var ExtensionAttributes = +rfc5280.ExtensionAttributes = asn1.define('ExtensionAttributes', function() { + ; +}); + +var EDIPartyName = rfc5280.EDIPartyName = asn1.define('EDIPartyName', function() { + this.seq().obj( + this.key('nameAssigner').optional().use(DirectoryString), + this.key('partyName').use(DirectoryString) + ); +}); + +var DirectoryString = rfc5280.DirectoryString = asn1.define('DirectoryString', function() { + this.choice({ + teletexString: this.use(TeletexString), + printableString: this.use(PrintableString), + universalString: this.use(UniversalString), + utf8String: this.use(UTF8String), + bmpString: this.use(BMPString) + }); +}); + +var TeletexString = +rfc5280.TeletexString = asn1.define('TeletexString', function() { + ; +}); + +var PrintableString = +rfc5280.PrintableString = asn1.define('PrintableString', function() { + ; +}); + +var UniversalString = +rfc5280.UniversalString = asn1.define('UniversalString', function() { + ; +}); + +var UTF8String = +rfc5280.UTF8String = asn1.define('UTF8String', function() { + ; +}); + +var BMPString = +rfc5280.BMPString = asn1.define('BMPString', function() { + ; +}); + // rfc5280.SubjectKeyIdentifier = asn1.define('SubjectKeyIdentifier', function() { // this.seq().obj( // this.key('keyIdentifier').optional().octstr(), From 71f863ebb649921b6364e55bf7a689e8560d8975 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 25 Aug 2014 12:50:27 -0700 Subject: [PATCH 28/60] paypro: add string types - need to fork asn1.js. --- lib/PayPro.js | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 59042a2..358bf8d 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -285,8 +285,9 @@ rfc5280.AuthorityKeyIdentifier = asn1.define('AuthorityKeyIdentifier', function( var GeneralNames = rfc5280.GeneralNames = asn1.define('GeneralNames', function() { + this.seqof(GeneralName); this.seq().obj( - this.key('generalNames').use(rfc5280.GeneralName) + this.key('generalNames').use(GeneralName) ); }); @@ -345,14 +346,16 @@ rfc5280.ExtensionAttributes = asn1.define('ExtensionAttributes', function() { ; }); -var EDIPartyName = rfc5280.EDIPartyName = asn1.define('EDIPartyName', function() { +var EDIPartyName = +rfc5280.EDIPartyName = asn1.define('EDIPartyName', function() { this.seq().obj( this.key('nameAssigner').optional().use(DirectoryString), this.key('partyName').use(DirectoryString) ); }); -var DirectoryString = rfc5280.DirectoryString = asn1.define('DirectoryString', function() { +var DirectoryString = +rfc5280.DirectoryString = asn1.define('DirectoryString', function() { this.choice({ teletexString: this.use(TeletexString), printableString: this.use(PrintableString), @@ -362,31 +365,35 @@ var DirectoryString = rfc5280.DirectoryString = asn1.define('DirectoryString', f }); }); +// https://www.google.com/search?q=TeletexString +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb540814(v=vs.85).aspx + var TeletexString = rfc5280.TeletexString = asn1.define('TeletexString', function() { - ; + this.t61str(); }); var PrintableString = rfc5280.PrintableString = asn1.define('PrintableString', function() { - ; + this.printstr(); }); var UniversalString = rfc5280.UniversalString = asn1.define('UniversalString', function() { - ; + this.unistr(); }); var UTF8String = rfc5280.UTF8String = asn1.define('UTF8String', function() { - ; + this.utf8str(); }); var BMPString = rfc5280.BMPString = asn1.define('BMPString', function() { - ; + this.bmpstr(); }); +// var SubjectKeyIdentifier = // rfc5280.SubjectKeyIdentifier = asn1.define('SubjectKeyIdentifier', function() { // this.seq().obj( // this.key('keyIdentifier').optional().octstr(), @@ -395,10 +402,12 @@ rfc5280.BMPString = asn1.define('BMPString', function() { // ); // }); +var KeyUsage = rfc5280.KeyUsage = asn1.define('KeyUsage', function() { this.bitstr(); }); +// var KeyUsage = // rfc5280.KeyUsage = asn1.define('KeyUsage', function() { // this.seq().obj( // this.key('digitalSignature').bitstr(), From be018ba6e3127b2eeb129f5ee29ad3d6345eb34f Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 25 Aug 2014 12:51:09 -0700 Subject: [PATCH 29/60] paypro: clean up string types. --- lib/PayPro.js | 43 +++++++++---------------------------------- 1 file changed, 9 insertions(+), 34 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 358bf8d..369a3f0 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -354,43 +354,18 @@ rfc5280.EDIPartyName = asn1.define('EDIPartyName', function() { ); }); -var DirectoryString = -rfc5280.DirectoryString = asn1.define('DirectoryString', function() { - this.choice({ - teletexString: this.use(TeletexString), - printableString: this.use(PrintableString), - universalString: this.use(UniversalString), - utf8String: this.use(UTF8String), - bmpString: this.use(BMPString) - }); -}); - // https://www.google.com/search?q=TeletexString // http://msdn.microsoft.com/en-us/library/windows/desktop/bb540814(v=vs.85).aspx -var TeletexString = -rfc5280.TeletexString = asn1.define('TeletexString', function() { - this.t61str(); -}); - -var PrintableString = -rfc5280.PrintableString = asn1.define('PrintableString', function() { - this.printstr(); -}); - -var UniversalString = -rfc5280.UniversalString = asn1.define('UniversalString', function() { - this.unistr(); -}); - -var UTF8String = -rfc5280.UTF8String = asn1.define('UTF8String', function() { - this.utf8str(); -}); - -var BMPString = -rfc5280.BMPString = asn1.define('BMPString', function() { - this.bmpstr(); +var DirectoryString = +rfc5280.DirectoryString = asn1.define('DirectoryString', function() { + this.choice({ + teletexString: this.t61str(), + printableString: this.printstr(), + universalString: this.unistr(), + utf8String: this.utf8str(), + bmpString: this.bmpstr() + }); }); // var SubjectKeyIdentifier = From 9d83ff3fc6ad5549d3493b48a4125048e01d33fe Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 25 Aug 2014 13:10:58 -0700 Subject: [PATCH 30/60] paypro: use new string types with asn1.js fork. --- lib/PayPro.js | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 369a3f0..218eaa4 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -286,21 +286,18 @@ rfc5280.AuthorityKeyIdentifier = asn1.define('AuthorityKeyIdentifier', function( var GeneralNames = rfc5280.GeneralNames = asn1.define('GeneralNames', function() { this.seqof(GeneralName); - this.seq().obj( - this.key('generalNames').use(GeneralName) - ); }); var GeneralName = rfc5280.GeneralName = asn1.define('GeneralName', function() { this.choice({ otherName: this.use(OtherName), - rfc822Name: this.use(IA5String), - dNSName: this.use(IA5String), + rfc822Name: this.ia5str(), + dNSName: this.ia5str(), x400Address: this.use(ORAddress), directoryName: this.use(rfc3280.Name), ediPartyName: this.use(EDIPartyName), - uniformResourceIdentifier: this.use(IA5String), + uniformResourceIdentifier: this.ia5str(), iPAddress: this.octstr(), registeredID: this.objid() }); @@ -314,14 +311,6 @@ rfc5280.OtherName = asn1.define('OtherName', function() { ); }); -// https://www.google.com/search?q=IA5String -// https://en.wikipedia.org/wiki/IA5STRING -// http://msdn.microsoft.com/en-us/library/windows/desktop/bb540805(v=vs.85).aspx -var IA5String = -rfc5280.IA5String = asn1.define('IA5String', function() { - this.octstr(); // unsure -}); - var ORAddress = rfc5280.ORAddress = asn1.define('ORAddress', function() { this.seq().obj( @@ -354,6 +343,10 @@ rfc5280.EDIPartyName = asn1.define('EDIPartyName', function() { ); }); +// https://www.google.com/search?q=IA5String +// https://en.wikipedia.org/wiki/IA5STRING +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb540805(v=vs.85).aspx + // https://www.google.com/search?q=TeletexString // http://msdn.microsoft.com/en-us/library/windows/desktop/bb540814(v=vs.85).aspx From b164d3c9875aec00117337712ff7bec19aca4fa6 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 25 Aug 2014 13:30:53 -0700 Subject: [PATCH 31/60] paypro: add a ton of extension defs. --- lib/PayPro.js | 125 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 122 insertions(+), 3 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 218eaa4..d86012e 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -322,19 +322,138 @@ rfc5280.ORAddress = asn1.define('ORAddress', function() { var BuiltInStandardAttributes = rfc5280.BuiltInStandardAttributes = asn1.define('BuiltInStandardAttributes', function() { - ; + this.seq().obj( + this.key('countryName').optional().use(CountryName), + this.key('administrationDomainName').optional().use(AdministrationDomainName), + this.key('networkAddress').optional().use(NetworkAddress), + this.key('terminalIdentifier').optional().use(TerminalIdentifier), + this.key('privateDomainName').optional().use(PrivateDomainName), + this.key('organizationName').optional().use(OrganizationName), + this.key('numericUserIdentifier').optional().use(NumericUserIdentifier), + this.key('personalName').optional().use(PersonalName), + this.key('organizationalUnitNames').optional().use(OrganizationalUnitNames) + ); }); +/** + * For BuiltInStandardAttributes + */ + +var CountryName = +rfc5280.CountryName = asn1.define('CountryName', function() { + this.choice({ + x121DccCode: this.numstr(), + iso3166Alpha2Code: this.printstr() + }); +}); + +var AdministrationDomainName = +rfc5280.AdministrationDomainName = asn1.define('AdministrationDomainName', function() { + this.choice({ + numeric: this.numstr(), + printable: this.printstr() + }); +}); + +var NetworkAddress = +rfc5280.NetworkAddress = asn1.define('NetworkAddress', function() { + this.use(X121Address); +}); + +var X121Address = +rfc5280.X121Address = asn1.define('X121Address', function() { + this.numstr(); +}); + +var TerminalIdentifier = +rfc5280.TerminalIdentifier = asn1.define('TerminalIdentifier', function() { + this.printstr(); +}); + +var PrivateDomainName = +rfc5280.PrivateDomainName = asn1.define('PrivateDomainName', function() { + this.choice({ + numeric: this.numstr(), + printable: this.printstr() + }); +}); + +var OrganizationName = +rfc5280.OrganizationName = asn1.define('OrganizationName', function() { + this.printstr(); +}); + +var NumericUserIdentifier = +rfc5280.NumericUserIdentifier = asn1.define('NumericUserIdentifier', function() { + this.numstr(); +}); + +var PersonalName = +rfc5280.PersonalName = asn1.define('PersonalName', function() { + this.set().obj( + this.key('surname').implicit().printstr(), + this.key('givenName').implicit().printstr(), + this.key('initials').implicit().printstr(), + this.key('generationQualifier').implicit().printstr() + ); +}); + +var OrganizationalUnitNames = +rfc5280.OrganizationalUnitNames = asn1.define('OrganizationalUnitNames', function() { + this.seqof(OrganizationalUnitName); +}); + +var OrganizationalUnitName = +rfc5280.OrganizationalUnitName = asn1.define('OrganizationalUnitName', function() { + this.printstr(); +}); + +/** + * BuiltInDomainDefinedAttributes + */ + var BuiltInDomainDefinedAttributes = rfc5280.BuiltInDomainDefinedAttributes = asn1.define('BuiltInDomainDefinedAttributes', function() { - ; + this.seqof(BuiltInDomainDefinedAttribute); }); +/** + * For BuiltInDomainDefinedAttribute + */ + +var BuiltInDomainDefinedAttribute = +rfc5280.BuiltInDomainDefinedAttribute = asn1.define('BuiltInDomainDefinedAttribute', function() { + this.seq().obj( + this.key('type').printstr(), + this.key('value').printstr() + ); +}); + +/** + * ExtensionAttributes + */ + var ExtensionAttributes = rfc5280.ExtensionAttributes = asn1.define('ExtensionAttributes', function() { - ; + this.seqof(ExtensionAttribute); }); +/** + * For ExtensionAttributes + */ + +var ExtensionAttribute = +rfc5280.ExtensionAttribute = asn1.define('ExtensionAttribute', function() { + this.seq().obj( + this.key('extensionAttributeType').implicit().int(), + this.key('extensionAttributeValue').any().implicit().int() + ); +}); + +/** + * EDIPartyName + */ + var EDIPartyName = rfc5280.EDIPartyName = asn1.define('EDIPartyName', function() { this.seq().obj( From a5ba45e1f6c513ea965de4a3240cafafdad113ae Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 25 Aug 2014 13:31:05 -0700 Subject: [PATCH 32/60] paypro: remove old defs. --- lib/PayPro.js | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index d86012e..aff6a6b 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -480,35 +480,11 @@ rfc5280.DirectoryString = asn1.define('DirectoryString', function() { }); }); -// var SubjectKeyIdentifier = -// rfc5280.SubjectKeyIdentifier = asn1.define('SubjectKeyIdentifier', function() { -// this.seq().obj( -// this.key('keyIdentifier').optional().octstr(), -// this.key('authorityCertIssuer').optional().octstr(), -// this.key('authorityCertSerialNumber').optional().octstr() -// ); -// }); - var KeyUsage = rfc5280.KeyUsage = asn1.define('KeyUsage', function() { this.bitstr(); }); -// var KeyUsage = -// rfc5280.KeyUsage = asn1.define('KeyUsage', function() { -// this.seq().obj( -// this.key('digitalSignature').bitstr(), -// this.key('nonRepudiation').bitstr(), -// this.key('keyEncipherment').bitstr(), -// this.key('dataEncipherment').bitstr(), -// this.key('keyAgreement').bitstr(), -// this.key('keyCertSign').bitstr(), -// this.key('cRLSign').bitstr(), -// this.key('encipherOnly').bitstr(), -// this.key('decipherOnly').bitstr() -// ); -// }); - /** * Debug */ From bfd6dceee506f64b2fb34ee91cf4706ef9b0e9ca Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 25 Aug 2014 14:37:09 -0700 Subject: [PATCH 33/60] paypro: extension organization. --- lib/PayPro.js | 124 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 107 insertions(+), 17 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index aff6a6b..fe557f6 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -163,6 +163,14 @@ PayPro.prototype.x509Verify = function() { eid = ext.extnID; if (eid.length === 4 && eid[0] === 2 && eid[1] === 5 && eid[2] === 29) { switch (eid[3]) { + // Authority Key Identifier + case 35: + extensions.authorityKeyIdentifier = ext.extnValue; + break; + // Subject Key Identifier + case 14: + extensions.subjectKeyIdentifier = ext.extnValue; + break; // Basic Constraints case 19: extensions.basicConstraints = ext.extnValue; @@ -171,14 +179,6 @@ PayPro.prototype.x509Verify = function() { case 15: extensions.keyUsage = ext.extnValue; break; - // Subject Key Identifier - case 14: - extensions.subjectKeyIdentifier = ext.extnValue; - break; - // Authority Key Identifier - case 35: - extensions.authorityKeyIdentifier = ext.extnValue; - break; // CRL Distribution Points case 31: extensions.CRLDistributionPoints = ext.extnValue; @@ -274,20 +274,50 @@ PayPro.prototype.x509Verify = function() { var rfc5280 = {}; +/** + * # AuthorityKeyIdentifier + */ + var AuthorityKeyIdentifier = rfc5280.AuthorityKeyIdentifier = asn1.define('AuthorityKeyIdentifier', function() { this.seq().obj( - this.key('keyIdentifier').optional().octstr(), - this.key('authorityCertIssuer').optional().octstr(), - this.key('authorityCertSerialNumber').optional().octstr() + this.key('keyIdentifier').optional().use(KeyIdentifier), + this.key('authorityCertIssuer').optional().use(GeneralNames), + this.key('authorityCertSerialNumber').optional().use(CertificateSerialNumber) ); }); +/** + * ## KeyIdentifier + */ + +var KeyIdentifier = +rfc5280.KeyIdentifier = asn1.define('KeyIdentifier', function() { + this.octstr(); +}); + +/** + * ## CertificateSerialNumber + */ + +var CertificateSerialNumber = +rfc5280.CertificateSerialNumber = asn1.define('CertificateSerialNumber', function() { + this.int(); +}); + +/** + * ## GeneralNames + */ + var GeneralNames = rfc5280.GeneralNames = asn1.define('GeneralNames', function() { this.seqof(GeneralName); }); +/** + * ### GeneralName + */ + var GeneralName = rfc5280.GeneralName = asn1.define('GeneralName', function() { this.choice({ @@ -303,6 +333,10 @@ rfc5280.GeneralName = asn1.define('GeneralName', function() { }); }); +/** + * #### OtherName + */ + var OtherName = rfc5280.OtherName = asn1.define('OtherName', function() { this.seq().obj( @@ -311,6 +345,10 @@ rfc5280.OtherName = asn1.define('OtherName', function() { ); }); +/** + * #### ORAddress + */ + var ORAddress = rfc5280.ORAddress = asn1.define('ORAddress', function() { this.seq().obj( @@ -320,6 +358,10 @@ rfc5280.ORAddress = asn1.define('ORAddress', function() { ); }); +/** + * ##### BuiltInStandardAttributes + */ + var BuiltInStandardAttributes = rfc5280.BuiltInStandardAttributes = asn1.define('BuiltInStandardAttributes', function() { this.seq().obj( @@ -336,7 +378,7 @@ rfc5280.BuiltInStandardAttributes = asn1.define('BuiltInStandardAttributes', fun }); /** - * For BuiltInStandardAttributes + * ###### CountryName */ var CountryName = @@ -347,6 +389,10 @@ rfc5280.CountryName = asn1.define('CountryName', function() { }); }); +/** + * ###### AdministrationDomainName + */ + var AdministrationDomainName = rfc5280.AdministrationDomainName = asn1.define('AdministrationDomainName', function() { this.choice({ @@ -355,21 +401,37 @@ rfc5280.AdministrationDomainName = asn1.define('AdministrationDomainName', funct }); }); +/** + * ###### NetworkAddress + */ + var NetworkAddress = rfc5280.NetworkAddress = asn1.define('NetworkAddress', function() { this.use(X121Address); }); +/** + * ###### X121Address + */ + var X121Address = rfc5280.X121Address = asn1.define('X121Address', function() { this.numstr(); }); +/** + * ###### TerminalIdentifier + */ + var TerminalIdentifier = rfc5280.TerminalIdentifier = asn1.define('TerminalIdentifier', function() { this.printstr(); }); +/** + * ###### PrivateDomainName + */ + var PrivateDomainName = rfc5280.PrivateDomainName = asn1.define('PrivateDomainName', function() { this.choice({ @@ -378,16 +440,28 @@ rfc5280.PrivateDomainName = asn1.define('PrivateDomainName', function() { }); }); +/** + * ###### OrganizationName + */ + var OrganizationName = rfc5280.OrganizationName = asn1.define('OrganizationName', function() { this.printstr(); }); +/** + * ###### NumericUserIdentifier + */ + var NumericUserIdentifier = rfc5280.NumericUserIdentifier = asn1.define('NumericUserIdentifier', function() { this.numstr(); }); +/** + * ###### PersonalName + */ + var PersonalName = rfc5280.PersonalName = asn1.define('PersonalName', function() { this.set().obj( @@ -398,18 +472,26 @@ rfc5280.PersonalName = asn1.define('PersonalName', function() { ); }); +/** + * ###### OrganizationalUnitNames + */ + var OrganizationalUnitNames = rfc5280.OrganizationalUnitNames = asn1.define('OrganizationalUnitNames', function() { this.seqof(OrganizationalUnitName); }); +/** + * ####### OrganizationalUnitName + */ + var OrganizationalUnitName = rfc5280.OrganizationalUnitName = asn1.define('OrganizationalUnitName', function() { this.printstr(); }); /** - * BuiltInDomainDefinedAttributes + * ##### BuiltInDomainDefinedAttributes */ var BuiltInDomainDefinedAttributes = @@ -418,7 +500,7 @@ rfc5280.BuiltInDomainDefinedAttributes = asn1.define('BuiltInDomainDefinedAttrib }); /** - * For BuiltInDomainDefinedAttribute + * ###### BuiltInDomainDefinedAttribute */ var BuiltInDomainDefinedAttribute = @@ -430,7 +512,7 @@ rfc5280.BuiltInDomainDefinedAttribute = asn1.define('BuiltInDomainDefinedAttribu }); /** - * ExtensionAttributes + * # ExtensionAttributes */ var ExtensionAttributes = @@ -439,7 +521,7 @@ rfc5280.ExtensionAttributes = asn1.define('ExtensionAttributes', function() { }); /** - * For ExtensionAttributes + * ## ExtensionAttribute */ var ExtensionAttribute = @@ -451,7 +533,7 @@ rfc5280.ExtensionAttribute = asn1.define('ExtensionAttribute', function() { }); /** - * EDIPartyName + * #### EDIPartyName */ var EDIPartyName = @@ -469,6 +551,10 @@ rfc5280.EDIPartyName = asn1.define('EDIPartyName', function() { // https://www.google.com/search?q=TeletexString // http://msdn.microsoft.com/en-us/library/windows/desktop/bb540814(v=vs.85).aspx +/** + * ##### DirectoryString + */ + var DirectoryString = rfc5280.DirectoryString = asn1.define('DirectoryString', function() { this.choice({ @@ -480,6 +566,10 @@ rfc5280.DirectoryString = asn1.define('DirectoryString', function() { }); }); +/** + * # KeyUsage + */ + var KeyUsage = rfc5280.KeyUsage = asn1.define('KeyUsage', function() { this.bitstr(); From b6c89413684da1fc80f42ddc35c46adda484cab1 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 25 Aug 2014 15:34:38 -0700 Subject: [PATCH 34/60] paypro: 17 x509 extensions implemented. --- lib/PayPro.js | 431 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 418 insertions(+), 13 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index fe557f6..6d4c366 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -148,12 +148,23 @@ PayPro.prototype.x509Verify = function() { var ext; var eid; var extensions = { - basicConstraints: null, - keyUsage: null, - subjectKeyIdentifier: null, authorityKeyIdentifier: null, - CRLDistributionPoints: null, + subjectKeyIdentifier: null, + keyUsage: null, certificatePolicies: null, + policyMappings: null, + subjectAlternativeName: null, + issuerAlternativeName: null, + subjectDirectoryAttributes: null, + basicConstraints: null, + nameConstraints: null, + policyConstraints: null, + extendedKeyUsage: null, + CRLDistributionPoints: null, + inhibitAnyPolicy: null, + freshestCRL: null, + authorityInformationAccess: null, + subjectInformationAccess: null, standardUnknown: [], unknown: [], }; @@ -171,21 +182,65 @@ PayPro.prototype.x509Verify = function() { case 14: extensions.subjectKeyIdentifier = ext.extnValue; break; + // Key Usage + case 15: + extensions.keyUsage = ext.extnValue; + break; + // Certificate Policies + case 32: + extensions.certificatePolicies = ext.extnValue; + break; + // Policy Mappings + case 0: + extensions.policyMappings = ext.extnValue; + break; + // Subject Alternative Name + case 0: + extensions.subjectAlternativeName = ext.extnValue; + break; + // Issuer Alternative Name + case 0: + extensions.issuerAlternativeName = ext.extnValue; + break; + // Subject Directory Attributes + case 0: + extensions.subjectDirectoryAttributes = ext.extnValue; + break; // Basic Constraints case 19: extensions.basicConstraints = ext.extnValue; break; - // Key Usage - case 15: - extensions.keyUsage = ext.extnValue; + // Name Constraints + case 0: + extensions.nameConstraints = ext.extnValue; + break; + // Policy Constraints + case 0: + extensions.policyConstraints = ext.extnValue; + break; + // Extended Key Usage + case 0: + extensions.extendedKeyUsage = ext.extnValue; break; // CRL Distribution Points case 31: extensions.CRLDistributionPoints = ext.extnValue; break; - // Certificate Policies - case 32: - extensions.certificatePolicies = ext.extnValue; + // Inhibit anyPolicy + case 0: + extensions.inhibitAnyPolicy = ext.extnValue; + break; + // Freshest CRL + case 0: + extensions.freshestCRL = ext.extnValue; + break; + // Authority Information Access + case 0: + extensions.authorityInformationAccess = ext.extnValue; + break; + // Subject Information Access + case 0: + extensions.subjectInformationAccess = ext.extnValue; break; // Unknown Extension (not documented anywhere, probably non-standard) default: @@ -275,7 +330,8 @@ PayPro.prototype.x509Verify = function() { var rfc5280 = {}; /** - * # AuthorityKeyIdentifier + * 1 + * # Authority Key Identifier */ var AuthorityKeyIdentifier = @@ -512,7 +568,7 @@ rfc5280.BuiltInDomainDefinedAttribute = asn1.define('BuiltInDomainDefinedAttribu }); /** - * # ExtensionAttributes + * ## ExtensionAttributes */ var ExtensionAttributes = @@ -521,7 +577,7 @@ rfc5280.ExtensionAttributes = asn1.define('ExtensionAttributes', function() { }); /** - * ## ExtensionAttribute + * ### ExtensionAttribute */ var ExtensionAttribute = @@ -567,6 +623,17 @@ rfc5280.DirectoryString = asn1.define('DirectoryString', function() { }); /** + * 2 + * # SubjectKeyIdentifier + */ + +var SubjectKeyIdentifier = +rfc5280.SubjectKeyIdentifier = asn1.define('SubjectKeyIdentifier', function() { + this.use(KeyIdentifier); +}); + +/** + * 3 * # KeyUsage */ @@ -575,6 +642,344 @@ rfc5280.KeyUsage = asn1.define('KeyUsage', function() { this.bitstr(); }); +/** + * 4 + * # Certificate Policies + */ + +var CertificatePolicies = +rfc5280.CertificatePolicies = asn1.define('CertificatePolicies', function() { + this.seqof(PolicyInformation); +}); + +/** + * ## Policy Information + */ + +var PolicyInformation = +rfc5280.PolicyInformation = asn1.define('PolicyInformation', function() { + this.seq().obj( + this.key('policyIdentifier').use(CertPolicyId), + this.key('policyQualifiers').use(PolicyQualifiers) + ); +}); + +/** + * ## Cert Policy Id + */ + +var CertPolicyId = +rfc5280.CertPolicyId = asn1.define('CertPolicyId', function() { + this.objid(); +}); + + +/** + * ### Policy Qualifiers + */ + +var PolicyQualifiers = +rfc5280.PolicyQualifiers = asn1.define('PolicyQualifiers', function() { + this.seqof(PolicyQualifierInfo); +}); + +/** + * #### Policy Qualifier Info + */ + +var PolicyQualifierInfo = +rfc5280.PolicyQualifierInfo = asn1.define('PolicyQualifierInfo', function() { + this.seq().obj( + this.key('policyQualifierId').use(PolicyQualifierId), + this.key('qualifier').any().use(PolicyQualifierId) + ); +}); + +/** + * ##### Policy Qualifier Id + */ + +var PolicyQualifierId = +rfc5280.PolicyQualifierId = asn1.define('PolicyQualifierId', function() { + this.objid(); +}); + +/** + * 5 + * # Policy Mappings + */ + +var PolicyMappings = +rfc5280.PolicyMappings = asn1.define('PolicyMappings', function() { + this.seqof(PolicyMapping); +}); + +/** + * ## Policy Mapping + */ + +var PolicyMapping = +rfc5280.PolicyMapping = asn1.define('PolicyMapping', function() { + this.seq().obj( + this.key('issuerDomainPolicy').use(CertPolicyId), + this.key('subjectDomainPolicy').use(CertPolicyId) + ); +}); + +/** + * 6 + * # Subject Alternative Name + */ + +var SubjectAlternativeName = +rfc5280.SubjectAlternativeName = asn1.define('SubjectAlternativeName', function() { + this.use(GeneralNames); +}); + +/** + * 7 + * # Issuer Alternative Name + */ + +var IssuerAlternativeName = +rfc5280.IssuerAlternativeName = asn1.define('IssuerAlternativeName', function() { + this.use(GeneralNames); +}); + +/** + * 8 + * # Subject Directory Attributes + */ + +var SubjectDirectoryAttributes = +rfc5280.SubjectDirectoryAttributes = asn1.define('SubjectDirectoryAttributes', function() { + this.seqof(Attribute); +}); + +/** + * ## Attribute + */ + +var AttributeTypeAndValue = rfc5280.AttributeTypeAndValue = rfc3280.AttributeTypeAndValue; +var Attribute = rfc5280.AttributeTypeAndValue = AttributeTypeAndValue; + +/** + * 9 + * # Basic Constraints + */ + +var BasicConstraints = +rfc5280.BasicConstraints = asn1.define('BasicConstraints', function() { + this.seq().obj( + this.key('cA').default(false).bool(), + this.key('pathLenConstraint').optional().int() + ); +}); + +/** + * 10 + * # Name Constraints + */ + +var NameConstraints = +rfc5280.NameConstraints = asn1.define('NameConstraints', function() { + this.seq().obj( + this.key('permittedSubtrees').optiona().use(GeneralSubtrees), + this.key('excludedSubtrees').optional().use(GeneralSubtrees) + ); +}); + +/** + * ## General Subtrees + */ + +var GeneralSubtrees = +rfc5280.GeneralSubtrees = asn1.define('GeneralSubtrees', function() { + this.seqof(GeneralSubtree); +}); + +/** + * ### General Subtree + */ + +var GeneralSubtree = +rfc5280.GeneralSubtree = asn1.define('GeneralSubtree', function() { + this.seq().obj( + this.key('base').use(GeneralName), + this.key('minimum').default(0).use(BaseDistance), + this.key('maximum').optional().use(BaseDistance) + ); +}); + +/** + * #### Base Distance + */ + +var BaseDistance = +rfc5280.BaseDistance = asn1.define('BaseDistance', function() { + this.int(); +}); + +/** + * 11 + * # Policy Constraints + */ + +var PolicyConstraints = +rfc5280.PolicyConstraints = asn1.define('PolicyConstraints', function() { + this.seq().obj( + this.key('requireExplicitPolicy').optional().use(SkipCerts), + this.key('inhibitPolicyMapping').optional().use(SkipCerts) + ); +}); + +/** + * ## Skip Certs + */ + +var SkipCerts = +rfc5280.SkipCerts = asn1.define('SkipCerts', function() { + this.int(); +}); + +/** + * 12 + * # Extended Key Usage + */ + +var ExtendedKeyUsage = +rfc5280.ExtendedKeyUsage = asn1.define('ExtendedKeyUsage', function() { + this.seqof(KeyPurposeId); +}); + +/** + * ## Key Purpose Id + */ + +var KeyPurposeId = +rfc5280.KeyPurposeId = asn1.define('KeyPurposeId', function() { + this.objid(); +}); + +/** + * 13 + * # CRL Distribution Points + */ + +var CRLDistributionPoints = +rfc5280.CRLDistributionPoints = asn1.define('CRLDistributionPoints', function() { + this.seqof(DistributionPoint); +}); + +/** + * ## Distribution Point + */ + +var DistributionPoint = +rfc5280.DistributionPoint = asn1.define('DistributionPoint', function() { + this.seq().obj( + this.key('distributionPoint').optional().use(DistributionPointName), + this.key('reasons').optional().use(ReasonFlags), + this.key('cRLIssuer').optional().use(GeneralNames) + ); +}); + +/** + * ### Distribution Point Name + */ + +var DistributionPointName = +rfc5280.DistributionPointName = asn1.define('DistributionPointName', function() { + this.choice({ + fullName: this.use(GeneralNames), + nameRelativeToCRLIssuer: this.use(RelativeDistinguishedName) + }); +}); + +/** + * #### Relative Distinguished Name + */ + +var RelativeDistinguishedName = +rfc5280.RelativeDistinguishedName = asn1.define('RelativeDistinguishedName', function() { + this.setof(AttributeTypeAndValue); +}); + +/** + * ### Reason Flags + */ + +var ReasonFlags = +rfc5280.ReasonFlags = asn1.define('ReasonFlags', function() { + this.bitstr(); + // ReasonFlags ::= BIT STRING { + // unused (0), + // keyCompromise (1), + // cACompromise (2), + // affiliationChanged (3), + // superseded (4), + // cessationOfOperation (5), + // certificateHold (6), + // privilegeWithdrawn (7), + // aACompromise (8) } +}); + +/** + * 14 + * # Inhibit anyPolicy + */ + +var InhibitAnyPolicy = +rfc5280.InhibitAnyPolicy = asn1.define('InhibitAnyPolicy', function() { + this.use(SkipCerts); +}); + +/** + * 15 + * # Freshest CRL + */ + +var FreshestCRL = +rfc5280.FreshestCRL = asn1.define('FreshestCRL', function() { + this.use(CRLDistributionPoints); +}); + +/** + * Private Internet Extensions + */ + +/** + * 16 + * # Authority Information Access + */ + +var AuthorityInformationAccess = +rfc5280.AuthorityInformationAccess = asn1.define('AuthorityInformationAccess', function() { + this.seqof(AccessDescription); +}); + +/** + * ## Access Description + */ + +var AccessDescription = +rfc5280.AccessDescription = asn1.define('AccessDescription', function() { + this.seq().obj( + this.key('accessMethod').objid(), + this.key('accessLocation').use(GeneralName) + ); +}); + +/** + * 17 + * # Subject Information Access + */ + +var SubjectInformationAccess = +rfc5280.SubjectInformationAccess = asn1.define('SubjectInformationAccess', function() { + this.seqof(AccessDescription); +}); + /** * Debug */ From 189dcb1b6f95ca2c102a96b53f70a61cf7c0b930 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 25 Aug 2014 15:42:27 -0700 Subject: [PATCH 35/60] paypro: implement id parsing properly. --- lib/PayPro.js | 67 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 54 insertions(+), 13 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 6d4c366..fcb4409 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -172,6 +172,8 @@ PayPro.prototype.x509Verify = function() { for (var i = 0; i < nc.tbsCertificate.extensions.length; i++) { ext = nc.tbsCertificate.extensions[i]; eid = ext.extnID; + + // id-ce extensions - Standard Extensions if (eid.length === 4 && eid[0] === 2 && eid[1] === 5 && eid[2] === 29) { switch (eid[3]) { // Authority Key Identifier @@ -191,19 +193,19 @@ PayPro.prototype.x509Verify = function() { extensions.certificatePolicies = ext.extnValue; break; // Policy Mappings - case 0: + case 33: extensions.policyMappings = ext.extnValue; break; // Subject Alternative Name - case 0: + case 17: extensions.subjectAlternativeName = ext.extnValue; break; // Issuer Alternative Name - case 0: + case 18: extensions.issuerAlternativeName = ext.extnValue; break; // Subject Directory Attributes - case 0: + case 9: extensions.subjectDirectoryAttributes = ext.extnValue; break; // Basic Constraints @@ -211,15 +213,15 @@ PayPro.prototype.x509Verify = function() { extensions.basicConstraints = ext.extnValue; break; // Name Constraints - case 0: + case 30: extensions.nameConstraints = ext.extnValue; break; // Policy Constraints - case 0: + case 36: extensions.policyConstraints = ext.extnValue; break; // Extended Key Usage - case 0: + case 37: extensions.extendedKeyUsage = ext.extnValue; break; // CRL Distribution Points @@ -227,19 +229,40 @@ PayPro.prototype.x509Verify = function() { extensions.CRLDistributionPoints = ext.extnValue; break; // Inhibit anyPolicy - case 0: + case 54: extensions.inhibitAnyPolicy = ext.extnValue; break; // Freshest CRL - case 0: + case 46: extensions.freshestCRL = ext.extnValue; break; + // Unknown Extension (not documented anywhere, probably non-standard) + default: + extensions.unknown.push(ext); + extensions.standardUnknown.push(ext); + break; + } + continue; + } + + // id-pe extensions - Private Internet Extensions + if (eid.length === 8 + && eid[0] === 1 + && eid[1] === 3 + && eid[2] === 6 + && eid[3] === 1 + && eid[4] === 5 + && eid[5] === 5 + && eid[6] === 7) { + switch (eid[3]) { // Authority Information Access - case 0: + // id-pe: + case 1: extensions.authorityInformationAccess = ext.extnValue; break; // Subject Information Access - case 0: + // id-pe: + case 11: extensions.subjectInformationAccess = ext.extnValue; break; // Unknown Extension (not documented anywhere, probably non-standard) @@ -248,9 +271,10 @@ PayPro.prototype.x509Verify = function() { extensions.standardUnknown.push(ext); break; } - } else { - extensions.unknown.push(ext); + continue; } + + extensions.unknown.push(ext); } var extensionsVerified = !extensions.unknown.filter(function(ext) { @@ -329,6 +353,10 @@ PayPro.prototype.x509Verify = function() { var rfc5280 = {}; +/** + * Standard Extensions + */ + /** * 1 * # Authority Key Identifier @@ -640,6 +668,19 @@ rfc5280.SubjectKeyIdentifier = asn1.define('SubjectKeyIdentifier', function() { var KeyUsage = rfc5280.KeyUsage = asn1.define('KeyUsage', function() { this.bitstr(); + // keyUsage = { + // digitalSignature: !!((data >> 0) & 1), + // nonRepudiation: !!((data >> 1) & 1), + // // nonRepudiation renamed to contentCommitment: + // contentCommitment: !!((data >> 1) & 1), + // keyEncipherment: !!((data >> 2) & 1), + // dataEncipherment: !!((data >> 3) & 1), + // keyAgreement: !!((data >> 4) & 1), + // keyCertSign: !!((data >> 5) & 1), + // cRLSign: !!((data >> 6) & 1), + // encipherOnly: !!((data >> 7) & 1), + // decipherOnly: !!((data >> 8) & 1) + // }; }); /** From 1bd9dd577f78db82c73a9244cf43ffc4c1e1514a Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 25 Aug 2014 15:44:33 -0700 Subject: [PATCH 36/60] paypro: refactor extension execution. --- lib/PayPro.js | 66 ++++++++++++++++++++++----------------------------- 1 file changed, 28 insertions(+), 38 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index fcb4409..55e0d11 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -179,14 +179,42 @@ PayPro.prototype.x509Verify = function() { // Authority Key Identifier case 35: extensions.authorityKeyIdentifier = ext.extnValue; + // parse + extensions.authorityKeyIdentifier = rfc5280.AuthorityKeyIdentifier.decode( + extensions.authorityKeyIdentifier, + 'der'); + print(extensions.authorityKeyIdentifier); break; // Subject Key Identifier case 14: extensions.subjectKeyIdentifier = ext.extnValue; + // parse + // extensions.subjectKeyIdentifier = rfc5280.SubjectKeyIdentifier.decode( + // extensions.subjectKeyIdentifier, + // 'der'); + // print(extensions.subjectKeyIdentifier); break; // Key Usage case 15: extensions.keyUsage = ext.extnValue; + // parse + data = rfc5280.KeyUsage.decode( + extensions.keyUsage, + 'der').data[0]; + extensions.keyUsage = { + digitalSignature: !!((data >> 0) & 1), + nonRepudiation: !!((data >> 1) & 1), + // nonRepudiation renamed to contentCommitment: + contentCommitment: !!((data >> 1) & 1), + keyEncipherment: !!((data >> 2) & 1), + dataEncipherment: !!((data >> 3) & 1), + keyAgreement: !!((data >> 4) & 1), + keyCertSign: !!((data >> 5) & 1), + cRLSign: !!((data >> 6) & 1), + encipherOnly: !!((data >> 7) & 1), + decipherOnly: !!((data >> 8) & 1) + }; + print(extensions.keyUsage); break; // Certificate Policies case 32: @@ -281,44 +309,6 @@ PayPro.prototype.x509Verify = function() { return ext.critical; }).length; - // - // Execute Extension Behavior - // - - if (extensions.authorityKeyIdentifier) { - extensions.authorityKeyIdentifier = rfc5280.AuthorityKeyIdentifier.decode( - extensions.authorityKeyIdentifier, - 'der'); - print(extensions.authorityKeyIdentifier); - } - - // if (extensions.subjectKeyIdentifier) { - // extensions.subjectKeyIdentifier = rfc5280.SubjectKeyIdentifier.decode( - // extensions.subjectKeyIdentifier, - // 'der'); - // print(extensions.subjectKeyIdentifier); - // } - - if (extensions.keyUsage) { - data = rfc5280.KeyUsage.decode( - extensions.keyUsage, - 'der').data[0]; - extensions.keyUsage = { - digitalSignature: !!((data >> 0) & 1), - nonRepudiation: !!((data >> 1) & 1), - // nonRepudiation renamed to contentCommitment: - contentCommitment: !!((data >> 1) & 1), - keyEncipherment: !!((data >> 2) & 1), - dataEncipherment: !!((data >> 3) & 1), - keyAgreement: !!((data >> 4) & 1), - keyCertSign: !!((data >> 5) & 1), - cRLSign: !!((data >> 6) & 1), - encipherOnly: !!((data >> 7) & 1), - decipherOnly: !!((data >> 8) & 1) - }; - print(extensions.keyUsage); - } - // // Verify current certificate signature // From bb3da9d6dcd839def5c0e7d41a397653e0428af5 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 25 Aug 2014 15:55:44 -0700 Subject: [PATCH 37/60] paypro: parse all extensions. --- lib/PayPro.js | 98 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 93 insertions(+), 5 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 55e0d11..88d8c1f 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -183,16 +183,18 @@ PayPro.prototype.x509Verify = function() { extensions.authorityKeyIdentifier = rfc5280.AuthorityKeyIdentifier.decode( extensions.authorityKeyIdentifier, 'der'); + print('Authority Key Identifier:'); print(extensions.authorityKeyIdentifier); break; // Subject Key Identifier case 14: extensions.subjectKeyIdentifier = ext.extnValue; // parse - // extensions.subjectKeyIdentifier = rfc5280.SubjectKeyIdentifier.decode( - // extensions.subjectKeyIdentifier, - // 'der'); - // print(extensions.subjectKeyIdentifier); + extensions.subjectKeyIdentifier = rfc5280.SubjectKeyIdentifier.decode( + extensions.subjectKeyIdentifier, + 'der'); + print('Subject Key Identifier:'); + print(extensions.subjectKeyIdentifier); break; // Key Usage case 15: @@ -214,55 +216,128 @@ PayPro.prototype.x509Verify = function() { encipherOnly: !!((data >> 7) & 1), decipherOnly: !!((data >> 8) & 1) }; + print('Key Usage:'); print(extensions.keyUsage); break; // Certificate Policies case 32: extensions.certificatePolicies = ext.extnValue; + // parse + extensions.certificatePolicies = rfc5280.CertificatePolicies.decode( + extensions.certificatePolicies, + 'der'); + print('Certificate Policies:'); + print(extensions.certificatePolicies); break; // Policy Mappings case 33: extensions.policyMappings = ext.extnValue; + // parse + extensions.policyMappings = rfc5280.PolicyMappings.decode( + extensions.policyMappings, + 'der'); + print('Policy Mappings:'); + print(extensions.policyMappings); break; // Subject Alternative Name case 17: extensions.subjectAlternativeName = ext.extnValue; + // parse + extensions.subjectAlternativeName = rfc5280.SubjectAlternativeName.decode( + extensions.subjectAlternativeName, + 'der'); + print('Subject Alternative Name:'); + print(extensions.subjectAlternativeName); break; // Issuer Alternative Name case 18: extensions.issuerAlternativeName = ext.extnValue; + // parse + extensions.issuerAlternativeName = rfc5280.IssuerAlternativeName.decode( + extensions.issuerAlternativeName, + 'der'); + print('Issuer Alternative Name:'); + print(extensions.issuerAlternativeName); break; // Subject Directory Attributes case 9: extensions.subjectDirectoryAttributes = ext.extnValue; + // parse + extensions.subjectDirectoryAttributes = rfc5280.SubjectDirectoryAttributes.decode( + extensions.subjectDirectoryAttributes, + 'der'); + print('Subject Directory Attributes:'); + print(extensions.subjectDirectoryAttributes); break; // Basic Constraints case 19: extensions.basicConstraints = ext.extnValue; + // parse + extensions.basicConstraints = rfc5280.BasicConstraints.decode( + extensions.basicConstraints, + 'der'); + print('Basic Constraints:'); + print(extensions.basicConstraints); break; // Name Constraints case 30: extensions.nameConstraints = ext.extnValue; + // parse + extensions.nameConstraints = rfc5280.NameConstraints.decode( + extensions.nameConstraints, + 'der'); + print('Name Constraints:'); + print(extensions.nameConstraints); break; // Policy Constraints case 36: extensions.policyConstraints = ext.extnValue; + // parse + extensions.policyConstraints = rfc5280.PolicyConstraints.decode( + extensions.policyConstraints, + 'der'); + print('Policy Constraints:'); + print(extensions.policyConstraints); break; // Extended Key Usage case 37: extensions.extendedKeyUsage = ext.extnValue; + // parse + extensions.extendedKeyUsage = rfc5280.ExtendedKeyUsage.decode( + extensions.extendedKeyUsage, + 'der'); + print('Extended Key Usage'); + print(extensions.extendedKeyUsage); break; // CRL Distribution Points case 31: extensions.CRLDistributionPoints = ext.extnValue; + // parse + extensions.CRLDistributionPoints = rfc5280.CRLDistributionPoints.decode( + extensions.CRLDistributionPoints, + 'der'); + print('CRL Distribution Points:'); + print(extensions.CRLDistributionPoints); break; // Inhibit anyPolicy case 54: extensions.inhibitAnyPolicy = ext.extnValue; + // parse + extensions.inhibitAnyPolicy = rfc5280.InhibitAnyPolicy.decode( + extensions.inhibitAnyPolicy, + 'der'); + print('Inhibit Any Policy:'); + print(extensions.inhibitAnyPolicy); break; // Freshest CRL case 46: extensions.freshestCRL = ext.extnValue; + // parse + extensions.freshestCRL = rfc5280.FreshestCRL.decode( + extensions.freshestCRL, + 'der'); + print('Freshest CRL:'); + print(extensions.freshestCRL); break; // Unknown Extension (not documented anywhere, probably non-standard) default: @@ -287,11 +362,23 @@ PayPro.prototype.x509Verify = function() { // id-pe: case 1: extensions.authorityInformationAccess = ext.extnValue; + // parse + extensions.authorityInformationAccess = rfc5280.AuthorityInformationAccess.decode( + extensions.authorityInformationAccess, + 'der'); + print('Authority Information Access:'); + print(extensions.freshestCRL); break; // Subject Information Access // id-pe: case 11: extensions.subjectInformationAccess = ext.extnValue; + // parse + extensions.subjectInformationAccess = rfc5280.SubjectInformationAccess.decode( + extensions.subjectInformationAccess, + 'der'); + print('Subject Information Access:'); + print(extensions.subjectInformationAccess); break; // Unknown Extension (not documented anywhere, probably non-standard) default: @@ -802,7 +889,8 @@ var Attribute = rfc5280.AttributeTypeAndValue = AttributeTypeAndValue; var BasicConstraints = rfc5280.BasicConstraints = asn1.define('BasicConstraints', function() { this.seq().obj( - this.key('cA').default(false).bool(), + // this.key('cA').default(false).bool(), + this.key('cA').bool(), this.key('pathLenConstraint').optional().int() ); }); From 799388db8ecc7eb26ecd141b1a99f5e90d33f57c Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 25 Aug 2014 17:36:43 -0700 Subject: [PATCH 38/60] paypro: more debugging. fixes. --- lib/PayPro.js | 90 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 37 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 88d8c1f..493ad4b 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -169,8 +169,8 @@ PayPro.prototype.x509Verify = function() { unknown: [], }; - for (var i = 0; i < nc.tbsCertificate.extensions.length; i++) { - ext = nc.tbsCertificate.extensions[i]; + for (var i = 0; i < c.tbsCertificate.extensions.length; i++) { + ext = c.tbsCertificate.extensions[i]; eid = ext.extnID; // id-ce extensions - Standard Extensions @@ -178,26 +178,30 @@ PayPro.prototype.x509Verify = function() { switch (eid[3]) { // Authority Key Identifier case 35: + print('Authority Key Identifier:'); + print(ext.extnValue); extensions.authorityKeyIdentifier = ext.extnValue; // parse extensions.authorityKeyIdentifier = rfc5280.AuthorityKeyIdentifier.decode( extensions.authorityKeyIdentifier, - 'der'); - print('Authority Key Identifier:'); + 'der', { partial: false }); print(extensions.authorityKeyIdentifier); break; // Subject Key Identifier - case 14: + case 14: // VERY IMPORTANT, especially is cA (basic constraints) is true (it is) + print('Subject Key Identifier:'); + print(ext.extnValue); extensions.subjectKeyIdentifier = ext.extnValue; // parse extensions.subjectKeyIdentifier = rfc5280.SubjectKeyIdentifier.decode( extensions.subjectKeyIdentifier, - 'der'); - print('Subject Key Identifier:'); + 'der', { partial: false }); print(extensions.subjectKeyIdentifier); break; // Key Usage case 15: + print('Key Usage:'); + print(ext.extnValue); extensions.keyUsage = ext.extnValue; // parse data = rfc5280.KeyUsage.decode( @@ -216,127 +220,138 @@ PayPro.prototype.x509Verify = function() { encipherOnly: !!((data >> 7) & 1), decipherOnly: !!((data >> 8) & 1) }; - print('Key Usage:'); print(extensions.keyUsage); break; // Certificate Policies case 32: + print('Certificate Policies:'); + print(ext.extnValue); extensions.certificatePolicies = ext.extnValue; // parse extensions.certificatePolicies = rfc5280.CertificatePolicies.decode( extensions.certificatePolicies, - 'der'); - print('Certificate Policies:'); + 'der', { partial: false }); print(extensions.certificatePolicies); break; // Policy Mappings case 33: + print('Policy Mappings:'); + print(ext.extnValue); extensions.policyMappings = ext.extnValue; // parse extensions.policyMappings = rfc5280.PolicyMappings.decode( extensions.policyMappings, - 'der'); - print('Policy Mappings:'); + 'der', { partial: false }); print(extensions.policyMappings); break; // Subject Alternative Name case 17: + print('Subject Alternative Name:'); + print(ext.extnValue); extensions.subjectAlternativeName = ext.extnValue; // parse extensions.subjectAlternativeName = rfc5280.SubjectAlternativeName.decode( extensions.subjectAlternativeName, - 'der'); - print('Subject Alternative Name:'); + 'der', { partial: false }); print(extensions.subjectAlternativeName); break; // Issuer Alternative Name case 18: + print('Issuer Alternative Name:'); + print(ext.extnValue); extensions.issuerAlternativeName = ext.extnValue; // parse extensions.issuerAlternativeName = rfc5280.IssuerAlternativeName.decode( extensions.issuerAlternativeName, - 'der'); - print('Issuer Alternative Name:'); + 'der', { partial: false }); print(extensions.issuerAlternativeName); break; // Subject Directory Attributes case 9: + print('Subject Directory Attributes:'); + print(ext.extnValue); extensions.subjectDirectoryAttributes = ext.extnValue; // parse extensions.subjectDirectoryAttributes = rfc5280.SubjectDirectoryAttributes.decode( extensions.subjectDirectoryAttributes, - 'der'); - print('Subject Directory Attributes:'); + 'der', { partial: false }); print(extensions.subjectDirectoryAttributes); break; // Basic Constraints case 19: + print('Basic Constraints:'); + print(ext.extnValue); extensions.basicConstraints = ext.extnValue; // parse extensions.basicConstraints = rfc5280.BasicConstraints.decode( extensions.basicConstraints, - 'der'); - print('Basic Constraints:'); + 'der', { partial: false }); print(extensions.basicConstraints); break; // Name Constraints case 30: + print('Name Constraints:'); + print(ext.extnValue); extensions.nameConstraints = ext.extnValue; // parse extensions.nameConstraints = rfc5280.NameConstraints.decode( extensions.nameConstraints, - 'der'); - print('Name Constraints:'); + 'der', { partial: false }); print(extensions.nameConstraints); break; // Policy Constraints case 36: + print('Policy Constraints:'); + print(ext.extnValue); extensions.policyConstraints = ext.extnValue; // parse extensions.policyConstraints = rfc5280.PolicyConstraints.decode( extensions.policyConstraints, - 'der'); - print('Policy Constraints:'); + 'der', { partial: false }); print(extensions.policyConstraints); break; // Extended Key Usage case 37: + print('Extended Key Usage'); + print(ext.extnValue); extensions.extendedKeyUsage = ext.extnValue; // parse extensions.extendedKeyUsage = rfc5280.ExtendedKeyUsage.decode( extensions.extendedKeyUsage, - 'der'); - print('Extended Key Usage'); + 'der', { partial: false }); print(extensions.extendedKeyUsage); break; // CRL Distribution Points case 31: + print('CRL Distribution Points:'); + print(ext.extnValue); extensions.CRLDistributionPoints = ext.extnValue; // parse extensions.CRLDistributionPoints = rfc5280.CRLDistributionPoints.decode( extensions.CRLDistributionPoints, - 'der'); - print('CRL Distribution Points:'); + 'der', { partial: false }); print(extensions.CRLDistributionPoints); break; // Inhibit anyPolicy case 54: + print('Inhibit Any Policy:'); + print(ext.extnValue); extensions.inhibitAnyPolicy = ext.extnValue; // parse extensions.inhibitAnyPolicy = rfc5280.InhibitAnyPolicy.decode( extensions.inhibitAnyPolicy, - 'der'); - print('Inhibit Any Policy:'); + 'der', { partial: false }); print(extensions.inhibitAnyPolicy); break; // Freshest CRL case 46: + print('Freshest CRL:'); + print(ext.extnValue); extensions.freshestCRL = ext.extnValue; // parse extensions.freshestCRL = rfc5280.FreshestCRL.decode( extensions.freshestCRL, - 'der'); - print('Freshest CRL:'); + 'der', { partial: false }); print(extensions.freshestCRL); break; // Unknown Extension (not documented anywhere, probably non-standard) @@ -361,23 +376,25 @@ PayPro.prototype.x509Verify = function() { // Authority Information Access // id-pe: case 1: + print('Authority Information Access:'); + print(ext.extnValue); extensions.authorityInformationAccess = ext.extnValue; // parse extensions.authorityInformationAccess = rfc5280.AuthorityInformationAccess.decode( extensions.authorityInformationAccess, 'der'); - print('Authority Information Access:'); print(extensions.freshestCRL); break; // Subject Information Access // id-pe: case 11: + print('Subject Information Access:'); + print(ext.extnValue); extensions.subjectInformationAccess = ext.extnValue; // parse extensions.subjectInformationAccess = rfc5280.SubjectInformationAccess.decode( extensions.subjectInformationAccess, 'der'); - print('Subject Information Access:'); print(extensions.subjectInformationAccess); break; // Unknown Extension (not documented anywhere, probably non-standard) @@ -502,7 +519,7 @@ var OtherName = rfc5280.OtherName = asn1.define('OtherName', function() { this.seq().obj( this.key('typeId').objid(), - this.key('value') + this.key('value').explicit(0).any() ); }); @@ -889,8 +906,7 @@ var Attribute = rfc5280.AttributeTypeAndValue = AttributeTypeAndValue; var BasicConstraints = rfc5280.BasicConstraints = asn1.define('BasicConstraints', function() { this.seq().obj( - // this.key('cA').default(false).bool(), - this.key('cA').bool(), + this.key('cA').bool().def(false), this.key('pathLenConstraint').optional().int() ); }); From 0bdc8f158699c7d284fdbe60ed9003ef68b9c1f8 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 26 Aug 2014 10:24:27 -0700 Subject: [PATCH 39/60] paypro: major refactor. create rfc5280 extension parsing function. --- lib/PayPro.js | 410 ++++++++++++++++++-------------------------------- 1 file changed, 146 insertions(+), 264 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 493ad4b..d454710 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -145,270 +145,7 @@ PayPro.prototype.x509Verify = function() { // Handle Cert Extensions // http://tools.ietf.org/html/rfc5280#section-4.2 // - var ext; - var eid; - var extensions = { - authorityKeyIdentifier: null, - subjectKeyIdentifier: null, - keyUsage: null, - certificatePolicies: null, - policyMappings: null, - subjectAlternativeName: null, - issuerAlternativeName: null, - subjectDirectoryAttributes: null, - basicConstraints: null, - nameConstraints: null, - policyConstraints: null, - extendedKeyUsage: null, - CRLDistributionPoints: null, - inhibitAnyPolicy: null, - freshestCRL: null, - authorityInformationAccess: null, - subjectInformationAccess: null, - standardUnknown: [], - unknown: [], - }; - - for (var i = 0; i < c.tbsCertificate.extensions.length; i++) { - ext = c.tbsCertificate.extensions[i]; - eid = ext.extnID; - - // id-ce extensions - Standard Extensions - if (eid.length === 4 && eid[0] === 2 && eid[1] === 5 && eid[2] === 29) { - switch (eid[3]) { - // Authority Key Identifier - case 35: - print('Authority Key Identifier:'); - print(ext.extnValue); - extensions.authorityKeyIdentifier = ext.extnValue; - // parse - extensions.authorityKeyIdentifier = rfc5280.AuthorityKeyIdentifier.decode( - extensions.authorityKeyIdentifier, - 'der', { partial: false }); - print(extensions.authorityKeyIdentifier); - break; - // Subject Key Identifier - case 14: // VERY IMPORTANT, especially is cA (basic constraints) is true (it is) - print('Subject Key Identifier:'); - print(ext.extnValue); - extensions.subjectKeyIdentifier = ext.extnValue; - // parse - extensions.subjectKeyIdentifier = rfc5280.SubjectKeyIdentifier.decode( - extensions.subjectKeyIdentifier, - 'der', { partial: false }); - print(extensions.subjectKeyIdentifier); - break; - // Key Usage - case 15: - print('Key Usage:'); - print(ext.extnValue); - extensions.keyUsage = ext.extnValue; - // parse - data = rfc5280.KeyUsage.decode( - extensions.keyUsage, - 'der').data[0]; - extensions.keyUsage = { - digitalSignature: !!((data >> 0) & 1), - nonRepudiation: !!((data >> 1) & 1), - // nonRepudiation renamed to contentCommitment: - contentCommitment: !!((data >> 1) & 1), - keyEncipherment: !!((data >> 2) & 1), - dataEncipherment: !!((data >> 3) & 1), - keyAgreement: !!((data >> 4) & 1), - keyCertSign: !!((data >> 5) & 1), - cRLSign: !!((data >> 6) & 1), - encipherOnly: !!((data >> 7) & 1), - decipherOnly: !!((data >> 8) & 1) - }; - print(extensions.keyUsage); - break; - // Certificate Policies - case 32: - print('Certificate Policies:'); - print(ext.extnValue); - extensions.certificatePolicies = ext.extnValue; - // parse - extensions.certificatePolicies = rfc5280.CertificatePolicies.decode( - extensions.certificatePolicies, - 'der', { partial: false }); - print(extensions.certificatePolicies); - break; - // Policy Mappings - case 33: - print('Policy Mappings:'); - print(ext.extnValue); - extensions.policyMappings = ext.extnValue; - // parse - extensions.policyMappings = rfc5280.PolicyMappings.decode( - extensions.policyMappings, - 'der', { partial: false }); - print(extensions.policyMappings); - break; - // Subject Alternative Name - case 17: - print('Subject Alternative Name:'); - print(ext.extnValue); - extensions.subjectAlternativeName = ext.extnValue; - // parse - extensions.subjectAlternativeName = rfc5280.SubjectAlternativeName.decode( - extensions.subjectAlternativeName, - 'der', { partial: false }); - print(extensions.subjectAlternativeName); - break; - // Issuer Alternative Name - case 18: - print('Issuer Alternative Name:'); - print(ext.extnValue); - extensions.issuerAlternativeName = ext.extnValue; - // parse - extensions.issuerAlternativeName = rfc5280.IssuerAlternativeName.decode( - extensions.issuerAlternativeName, - 'der', { partial: false }); - print(extensions.issuerAlternativeName); - break; - // Subject Directory Attributes - case 9: - print('Subject Directory Attributes:'); - print(ext.extnValue); - extensions.subjectDirectoryAttributes = ext.extnValue; - // parse - extensions.subjectDirectoryAttributes = rfc5280.SubjectDirectoryAttributes.decode( - extensions.subjectDirectoryAttributes, - 'der', { partial: false }); - print(extensions.subjectDirectoryAttributes); - break; - // Basic Constraints - case 19: - print('Basic Constraints:'); - print(ext.extnValue); - extensions.basicConstraints = ext.extnValue; - // parse - extensions.basicConstraints = rfc5280.BasicConstraints.decode( - extensions.basicConstraints, - 'der', { partial: false }); - print(extensions.basicConstraints); - break; - // Name Constraints - case 30: - print('Name Constraints:'); - print(ext.extnValue); - extensions.nameConstraints = ext.extnValue; - // parse - extensions.nameConstraints = rfc5280.NameConstraints.decode( - extensions.nameConstraints, - 'der', { partial: false }); - print(extensions.nameConstraints); - break; - // Policy Constraints - case 36: - print('Policy Constraints:'); - print(ext.extnValue); - extensions.policyConstraints = ext.extnValue; - // parse - extensions.policyConstraints = rfc5280.PolicyConstraints.decode( - extensions.policyConstraints, - 'der', { partial: false }); - print(extensions.policyConstraints); - break; - // Extended Key Usage - case 37: - print('Extended Key Usage'); - print(ext.extnValue); - extensions.extendedKeyUsage = ext.extnValue; - // parse - extensions.extendedKeyUsage = rfc5280.ExtendedKeyUsage.decode( - extensions.extendedKeyUsage, - 'der', { partial: false }); - print(extensions.extendedKeyUsage); - break; - // CRL Distribution Points - case 31: - print('CRL Distribution Points:'); - print(ext.extnValue); - extensions.CRLDistributionPoints = ext.extnValue; - // parse - extensions.CRLDistributionPoints = rfc5280.CRLDistributionPoints.decode( - extensions.CRLDistributionPoints, - 'der', { partial: false }); - print(extensions.CRLDistributionPoints); - break; - // Inhibit anyPolicy - case 54: - print('Inhibit Any Policy:'); - print(ext.extnValue); - extensions.inhibitAnyPolicy = ext.extnValue; - // parse - extensions.inhibitAnyPolicy = rfc5280.InhibitAnyPolicy.decode( - extensions.inhibitAnyPolicy, - 'der', { partial: false }); - print(extensions.inhibitAnyPolicy); - break; - // Freshest CRL - case 46: - print('Freshest CRL:'); - print(ext.extnValue); - extensions.freshestCRL = ext.extnValue; - // parse - extensions.freshestCRL = rfc5280.FreshestCRL.decode( - extensions.freshestCRL, - 'der', { partial: false }); - print(extensions.freshestCRL); - break; - // Unknown Extension (not documented anywhere, probably non-standard) - default: - extensions.unknown.push(ext); - extensions.standardUnknown.push(ext); - break; - } - continue; - } - - // id-pe extensions - Private Internet Extensions - if (eid.length === 8 - && eid[0] === 1 - && eid[1] === 3 - && eid[2] === 6 - && eid[3] === 1 - && eid[4] === 5 - && eid[5] === 5 - && eid[6] === 7) { - switch (eid[3]) { - // Authority Information Access - // id-pe: - case 1: - print('Authority Information Access:'); - print(ext.extnValue); - extensions.authorityInformationAccess = ext.extnValue; - // parse - extensions.authorityInformationAccess = rfc5280.AuthorityInformationAccess.decode( - extensions.authorityInformationAccess, - 'der'); - print(extensions.freshestCRL); - break; - // Subject Information Access - // id-pe: - case 11: - print('Subject Information Access:'); - print(ext.extnValue); - extensions.subjectInformationAccess = ext.extnValue; - // parse - extensions.subjectInformationAccess = rfc5280.SubjectInformationAccess.decode( - extensions.subjectInformationAccess, - 'der'); - print(extensions.subjectInformationAccess); - break; - // Unknown Extension (not documented anywhere, probably non-standard) - default: - extensions.unknown.push(ext); - extensions.standardUnknown.push(ext); - break; - } - continue; - } - - extensions.unknown.push(ext); - } - + var extensions = rfc5280.parseExtensions(c.tbsCertificate, { partial: false }); var extensionsVerified = !extensions.unknown.filter(function(ext) { return ext.critical; }).length; @@ -1115,6 +852,151 @@ rfc5280.SubjectInformationAccess = asn1.define('SubjectInformationAccess', funct this.seqof(AccessDescription); }); +rfc5280.extensions = { + standard: { + // id-ce extensions - Standard Extensions + prefix: [2, 5, 29], + 35: 'Authority Key Identifier', + 14: 'Subject Key Identifier', + // VERY IMPORTANT, especially is cA (basic constraints) is true (it is) + 15: { + name: 'Key Usage', + parse: function(data, decoded, ext) { + data = data[0]; + return { + digitalSignature: !!((data >> 0) & 1), + nonRepudiation: !!((data >> 1) & 1), + // nonRepudiation renamed to contentCommitment: + contentCommitment: !!((data >> 1) & 1), + keyEncipherment: !!((data >> 2) & 1), + dataEncipherment: !!((data >> 3) & 1), + keyAgreement: !!((data >> 4) & 1), + keyCertSign: !!((data >> 5) & 1), + cRLSign: !!((data >> 6) & 1), + encipherOnly: !!((data >> 7) & 1), + decipherOnly: !!((data >> 8) & 1) + }; + } + }, + 32: 'Certificate Policies', + 33: 'Policy Mappings', + 17: 'Subject Alternative Name', + 18: 'Issuer Alternative Name', + 9: 'Subject Directory Attributes', + 19: 'Basic Constraints', + 30: 'Name Constraints', + 36: 'Policy Constraints', + 37: 'Extended Key Usage', + 31: 'CRL Distribution Points', + 54: 'Inhibit anyPolicy', + 46: 'Freshest CRL', + // Unknown Extension (not documented anywhere, probably non-standard) + _: 'Unknown Extension' + }, + // id-pe extensions - Private Internet Extensions + priv: { + prefix: [1, 3, 6, 1, 5, 5, 7], + 1: 'Authority Information Access', + 11: 'Subject Information Access', + // Unknown Extension (not documented anywhere, probably non-standard) + _: 'Unknown Extension' + } +}; + +Object.keys(rfc5280.extensions).forEach(function(typeName) { + var type = rfc5280.extensions[typeName]; + Object.keys(type).forEach(function(suffix) { + var id, prop, schemaName, schema, parse; + + if (suffix === 'prefix') + return; + + var prefix = type.prefix; + var name = type[suffix]; + + if (typeof name === 'object') { + var obj = name; + name = obj.name; + parse = obj.parse; + } + + id = prefix.concat(suffix).join('.'); + + if (/^[A-Z]+ /.test(name)) { + // CRL Distribution Points - > CRLDistributionPoints + prop = name.replace(/ /g, ''); + } else { + prop = (name[0].toLowerCase()) + name.substring(1).replace(/ /g, ''); + } + + schemaName = name.replace(/ /g, ''); + schema = rfc5280[schemaName]; + + rfc5280.extensions[id] = { + typeName: typeName, + prefix: prefix, + suffix: suffix, + id: id, + name: name, + prop: prop, + schemaName: schemaName, + schema: schema, + parse: parse + }; + }); +}); + +rfc5280.parseExtensions = function(tbsCertificate, options) { + var edata, eid, ext, decoded, data; + + var output = {}; + var unknown = 0; + + for (var i = 0; i < tbsCertificate.extensions.length; i++) { + edata = tbsCertificate.extensions[i]; + eid = edata.extnID.join('.'); + + if (ext = rfc5280.extensions[eid]) { + // Parse Extension + decoded = ext.schema.decode(edata.extnValue, 'der', options); + + // If the Extension needs extra parsing (i.e. bitstrs) + data = ext.parse + ? ext.parse(decoded.data, decoded, ext) + : decoded; + + // Tack on some useful info + + // Comment for debugging: + // data._edata = edata; + // data._ext = ext; + + if (ext.parse) { + data._decoded = decoded; + } + + // Add our decoded extension to the output + output[ext.prop] = data; + + // XXX Debug + print('------------'); + print('%s (%s):', ext.name, ext.id); + print('Buffer:'); + print(edata.extnValue); + print('Extension:'); + print(data); + } else { + // Add unknown extension: + output['Unkown_' + unknown] = edata; + + // XXX Debug + print('Unknown extension: %s', eid); + } + } + + return output; +}; + /** * Debug */ From 2bd5a158a533c1290c7af13a3a2edab925f18d76 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 26 Aug 2014 11:03:51 -0700 Subject: [PATCH 40/60] paypro: refactor. additions. debugging. --- lib/PayPro.js | 172 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 157 insertions(+), 15 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index d454710..c0a52a2 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -145,11 +145,89 @@ PayPro.prototype.x509Verify = function() { // Handle Cert Extensions // http://tools.ietf.org/html/rfc5280#section-4.2 // - var extensions = rfc5280.parseExtensions(c.tbsCertificate, { partial: false }); + var extensions = rfc5280.decodeExtensions(c, { partial: false }); var extensionsVerified = !extensions.unknown.filter(function(ext) { return ext.critical; }).length; + // Object.keys(extensions).forEach(function(key) { + // if (extensions[key].execute) { + // c = extensions[key].execute(c); + // } + // }); + + if (extensions.subjectDirectoryAttributes) { + ; + } + + if (extensions.subjectKeyIdentifier) { + ; + } + + if (extensions.keyUsage) { + ; + } + + if (extensions.subjectAlternativeName) { + ; + } + + if (extensions.issuerAlternativeName) { + ; + } + + if (extensions.basicConstraints) { + ; + } + + if (extensions.nameConstraints) { + ; + } + + if (extensions.CRLDistributionPoints) { + ; + } + + if (extensions.certificatePolicies) { + ; + } + + if (extensions.policyMappings) { + ; + } + + if (extensions.authorityKeyIdentifier) { + ; + } + + if (extensions.policyConstraints) { + ; + } + + if (extensions.extendedKeyUsage) { + ; + } + + if (extensions.freshestCRL) { + ; + } + + if (extensions.inhibitanyPolicy) { + ; + } + + if (extensions.unknownExtension) { + ; + } + + if (extensions.authorityInformationAccess) { + ; + } + + if (extensions.subjectInformationAccess) { + ; + } + // // Verify current certificate signature // @@ -861,8 +939,8 @@ rfc5280.extensions = { // VERY IMPORTANT, especially is cA (basic constraints) is true (it is) 15: { name: 'Key Usage', - parse: function(data, decoded, ext) { - data = data[0]; + parse: function(decoded, cert, ext, edata) { + var data = decoded.data[0]; return { digitalSignature: !!((data >> 0) & 1), nonRepudiation: !!((data >> 1) & 1), @@ -876,6 +954,9 @@ rfc5280.extensions = { encipherOnly: !!((data >> 7) & 1), decipherOnly: !!((data >> 8) & 1) }; + }, + execute: function(cert) { + return cert; } }, 32: 'Certificate Policies', @@ -887,7 +968,35 @@ rfc5280.extensions = { 30: 'Name Constraints', 36: 'Policy Constraints', 37: 'Extended Key Usage', - 31: 'CRL Distribution Points', + 31: { + name: 'CRL Distribution Points', + parse: function(decoded, cert, ext, edata) { + // XXX Find the bitstr: ReasonFlags + console.log('###########################'); + console.log(decoded); + console.log(cert); + console.log(ext); + console.log(edata); + console.log('###########################'); + // XXX Find the bitstr: ReasonFlags + // var data = CRLDistributionPoints.DistributionPoint.reasons; + // return { + // unused: !!((data >> 0) & 1), + // keyCompromise: !!((data >> 1) & 1), + // cACompromise: !!((data >> 2) & 1), + // affiliationChanged: !!((data >> 3) & 1), + // superseded: !!((data >> 4) & 1), + // cessationOfOperation: !!((data >> 5) & 1), + // certificateHold: !!((data >> 6) & 1), + // privilegeWithdrawn: !!((data >> 7) & 1), + // aACompromise: !!((data >> 8) & 1) + // }; + return decoded; + }, + execute: function(cert) { + return cert; + } + }, 54: 'Inhibit anyPolicy', 46: 'Freshest CRL', // Unknown Extension (not documented anywhere, probably non-standard) @@ -906,7 +1015,7 @@ rfc5280.extensions = { Object.keys(rfc5280.extensions).forEach(function(typeName) { var type = rfc5280.extensions[typeName]; Object.keys(type).forEach(function(suffix) { - var id, prop, schemaName, schema, parse; + var id, prop, schemaName, schema, parse, execute; if (suffix === 'prefix') return; @@ -918,6 +1027,7 @@ Object.keys(rfc5280.extensions).forEach(function(typeName) { var obj = name; name = obj.name; parse = obj.parse; + execute = obj.execute; } id = prefix.concat(suffix).join('.'); @@ -941,16 +1051,24 @@ Object.keys(rfc5280.extensions).forEach(function(typeName) { prop: prop, schemaName: schemaName, schema: schema, - parse: parse + parse: parse, + execute: execute }; }); }); -rfc5280.parseExtensions = function(tbsCertificate, options) { - var edata, eid, ext, decoded, data; +rfc5280.decodeExtensions = function(cert, options) { + var tbsCertificate = cert.tbsCertificate; + + if (!tbsCertificate) { + tbsCertificate = cert; + cert = null; + } + + var edata, eid, ext, decoded, errors, data; var output = {}; - var unknown = 0; + output.unknown = []; for (var i = 0; i < tbsCertificate.extensions.length; i++) { edata = tbsCertificate.extensions[i]; @@ -960,19 +1078,42 @@ rfc5280.parseExtensions = function(tbsCertificate, options) { // Parse Extension decoded = ext.schema.decode(edata.extnValue, 'der', options); + if (options.partial && decoded.result) { + errors = decoded.errors; + decoded = decoded.result; + if (Array.isArray(decoded)) { + decoded = decoded.map(function(decoded) { + decoded.errors.forEach(function(error) { + errors.push(error); + }); + return decoded.result; + }); + } + } + // If the Extension needs extra parsing (i.e. bitstrs) data = ext.parse - ? ext.parse(decoded.data, decoded, ext) + ? ext.parse(decoded, cert, ext, edata) : decoded; // Tack on some useful info // Comment for debugging: - // data._edata = edata; - // data._ext = ext; - + // data.edata = edata; + // data.ext = ext; + data.errors = errors; if (ext.parse) { - data._decoded = decoded; + data.decoded = decoded; + } + + // Execute Behavior for Cert + if (ext.execute) { + data.execute = ext.execute; + } + + // Add errors for partial: true + if (options.partial && errors) { + data.errors = errors; } // Add our decoded extension to the output @@ -987,7 +1128,8 @@ rfc5280.parseExtensions = function(tbsCertificate, options) { print(data); } else { // Add unknown extension: - output['Unkown_' + unknown] = edata; + output.unknown.push(edata); + output['unkown_' + (output.unknown.length - 1)] = edata; // XXX Debug print('Unknown extension: %s', eid); From dde64fbb87e185b6c9506b602f0935ae19a67167 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 26 Aug 2014 11:09:39 -0700 Subject: [PATCH 41/60] paypro: refactor extension parsing. --- lib/PayPro.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index c0a52a2..1668e80 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -146,9 +146,7 @@ PayPro.prototype.x509Verify = function() { // http://tools.ietf.org/html/rfc5280#section-4.2 // var extensions = rfc5280.decodeExtensions(c, { partial: false }); - var extensionsVerified = !extensions.unknown.filter(function(ext) { - return ext.critical; - }).length; + var extensionsVerified = extensions.verified; // Object.keys(extensions).forEach(function(key) { // if (extensions[key].execute) { @@ -1078,16 +1076,18 @@ rfc5280.decodeExtensions = function(cert, options) { // Parse Extension decoded = ext.schema.decode(edata.extnValue, 'der', options); + // partial: true throws everything onto: { result: ..., errors: ... } if (options.partial && decoded.result) { errors = decoded.errors; - decoded = decoded.result; - if (Array.isArray(decoded)) { - decoded = decoded.map(function(decoded) { + if (Array.isArray(decoded.result)) { + decoded = decoded.result.map(function(decoded) { decoded.errors.forEach(function(error) { errors.push(error); }); return decoded.result; }); + } else { + decoded = decoded.result; } } @@ -1101,7 +1101,6 @@ rfc5280.decodeExtensions = function(cert, options) { // Comment for debugging: // data.edata = edata; // data.ext = ext; - data.errors = errors; if (ext.parse) { data.decoded = decoded; } @@ -1129,13 +1128,16 @@ rfc5280.decodeExtensions = function(cert, options) { } else { // Add unknown extension: output.unknown.push(edata); - output['unkown_' + (output.unknown.length - 1)] = edata; // XXX Debug print('Unknown extension: %s', eid); } } + extensions.verified = !extensions.unknown.filter(function(ext) { + return ext.critical; + }).length; + return output; }; From 379578aa15241678a361a625313d97e4a8a0572f Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 26 Aug 2014 11:57:15 -0700 Subject: [PATCH 42/60] paypro: refactoring. handle unkown extension. --- lib/PayPro.js | 94 +++++++++++++++++++++------------------------------ 1 file changed, 39 insertions(+), 55 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 1668e80..3daed36 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -143,9 +143,11 @@ PayPro.prototype.x509Verify = function() { // // Handle Cert Extensions + // http://www.ietf.org/rfc/rfc3280.txt + // http://www.ietf.org/rfc/rfc5280.txt // http://tools.ietf.org/html/rfc5280#section-4.2 // - var extensions = rfc5280.decodeExtensions(c, { partial: false }); + var extensions = rfc5280.decodeExtensions(c, { partial: true }); var extensionsVerified = extensions.verified; // Object.keys(extensions).forEach(function(key) { @@ -535,13 +537,6 @@ rfc5280.EDIPartyName = asn1.define('EDIPartyName', function() { ); }); -// https://www.google.com/search?q=IA5String -// https://en.wikipedia.org/wiki/IA5STRING -// http://msdn.microsoft.com/en-us/library/windows/desktop/bb540805(v=vs.85).aspx - -// https://www.google.com/search?q=TeletexString -// http://msdn.microsoft.com/en-us/library/windows/desktop/bb540814(v=vs.85).aspx - /** * ##### DirectoryString */ @@ -575,19 +570,6 @@ rfc5280.SubjectKeyIdentifier = asn1.define('SubjectKeyIdentifier', function() { var KeyUsage = rfc5280.KeyUsage = asn1.define('KeyUsage', function() { this.bitstr(); - // keyUsage = { - // digitalSignature: !!((data >> 0) & 1), - // nonRepudiation: !!((data >> 1) & 1), - // // nonRepudiation renamed to contentCommitment: - // contentCommitment: !!((data >> 1) & 1), - // keyEncipherment: !!((data >> 2) & 1), - // dataEncipherment: !!((data >> 3) & 1), - // keyAgreement: !!((data >> 4) & 1), - // keyCertSign: !!((data >> 5) & 1), - // cRLSign: !!((data >> 6) & 1), - // encipherOnly: !!((data >> 7) & 1), - // decipherOnly: !!((data >> 8) & 1) - // }; }); /** @@ -860,16 +842,6 @@ rfc5280.RelativeDistinguishedName = asn1.define('RelativeDistinguishedName', fun var ReasonFlags = rfc5280.ReasonFlags = asn1.define('ReasonFlags', function() { this.bitstr(); - // ReasonFlags ::= BIT STRING { - // unused (0), - // keyCompromise (1), - // cACompromise (2), - // affiliationChanged (3), - // superseded (4), - // cessationOfOperation (5), - // certificateHold (6), - // privilegeWithdrawn (7), - // aACompromise (8) } }); /** @@ -928,6 +900,17 @@ rfc5280.SubjectInformationAccess = asn1.define('SubjectInformationAccess', funct this.seqof(AccessDescription); }); +/** + * XXX + * # Unknown Extension + */ + +var UnknownExtension = +rfc5280.UnknownExtension = asn1.define('UnknownExtension', function() { + this.any(); +}); + + rfc5280.extensions = { standard: { // id-ce extensions - Standard Extensions @@ -938,6 +921,7 @@ rfc5280.extensions = { 15: { name: 'Key Usage', parse: function(decoded, cert, ext, edata) { + // For bitstr: KeyUsage var data = decoded.data[0]; return { digitalSignature: !!((data >> 0) & 1), @@ -970,43 +954,43 @@ rfc5280.extensions = { name: 'CRL Distribution Points', parse: function(decoded, cert, ext, edata) { // XXX Find the bitstr: ReasonFlags - console.log('###########################'); - console.log(decoded); - console.log(cert); - console.log(ext); - console.log(edata); - console.log('###########################'); - // XXX Find the bitstr: ReasonFlags - // var data = CRLDistributionPoints.DistributionPoint.reasons; - // return { - // unused: !!((data >> 0) & 1), - // keyCompromise: !!((data >> 1) & 1), - // cACompromise: !!((data >> 2) & 1), - // affiliationChanged: !!((data >> 3) & 1), - // superseded: !!((data >> 4) & 1), - // cessationOfOperation: !!((data >> 5) & 1), - // certificateHold: !!((data >> 6) & 1), - // privilegeWithdrawn: !!((data >> 7) & 1), - // aACompromise: !!((data >> 8) & 1) - // }; + print('@@@@@@@@@@@@@@@@@@@@@@@@@@@'); + print(decoded); + print(cert); + print(ext); + print(edata); + print('@@@@@@@@@@@@@@@@@@@@@@@@@@@'); return decoded; + // For bitstr: ReasonFlags + var data = decoded.CRLDistributionPoints.DistributionPoint.reasons; + return { + unused: !!((data >> 0) & 1), + keyCompromise: !!((data >> 1) & 1), + cACompromise: !!((data >> 2) & 1), + affiliationChanged: !!((data >> 3) & 1), + superseded: !!((data >> 4) & 1), + cessationOfOperation: !!((data >> 5) & 1), + certificateHold: !!((data >> 6) & 1), + privilegeWithdrawn: !!((data >> 7) & 1), + aACompromise: !!((data >> 8) & 1) + }; }, execute: function(cert) { return cert; } }, 54: 'Inhibit anyPolicy', - 46: 'Freshest CRL', - // Unknown Extension (not documented anywhere, probably non-standard) - _: 'Unknown Extension' + 46: 'Freshest CRL' }, + // id-pe extensions - Private Internet Extensions priv: { + // Unknown extension: 1.3.6.1.5.5.7.1.1 prefix: [1, 3, 6, 1, 5, 5, 7], 1: 'Authority Information Access', 11: 'Subject Information Access', // Unknown Extension (not documented anywhere, probably non-standard) - _: 'Unknown Extension' + '1.1': 'Unknown Extension' } }; @@ -1134,7 +1118,7 @@ rfc5280.decodeExtensions = function(cert, options) { } } - extensions.verified = !extensions.unknown.filter(function(ext) { + output.verified = !output.unknown.filter(function(ext) { return ext.critical; }).length; From 4eec70205c3a6a8d725b847bcea73a647d8f6c36 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 26 Aug 2014 11:59:14 -0700 Subject: [PATCH 43/60] paypro: drop extension if statements. --- lib/PayPro.js | 72 --------------------------------------------------- 1 file changed, 72 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 3daed36..2aa01b7 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -156,78 +156,6 @@ PayPro.prototype.x509Verify = function() { // } // }); - if (extensions.subjectDirectoryAttributes) { - ; - } - - if (extensions.subjectKeyIdentifier) { - ; - } - - if (extensions.keyUsage) { - ; - } - - if (extensions.subjectAlternativeName) { - ; - } - - if (extensions.issuerAlternativeName) { - ; - } - - if (extensions.basicConstraints) { - ; - } - - if (extensions.nameConstraints) { - ; - } - - if (extensions.CRLDistributionPoints) { - ; - } - - if (extensions.certificatePolicies) { - ; - } - - if (extensions.policyMappings) { - ; - } - - if (extensions.authorityKeyIdentifier) { - ; - } - - if (extensions.policyConstraints) { - ; - } - - if (extensions.extendedKeyUsage) { - ; - } - - if (extensions.freshestCRL) { - ; - } - - if (extensions.inhibitanyPolicy) { - ; - } - - if (extensions.unknownExtension) { - ; - } - - if (extensions.authorityInformationAccess) { - ; - } - - if (extensions.subjectInformationAccess) { - ; - } - // // Verify current certificate signature // From 69f3fbd03d5e54a42037a44e912b9fb9716f0165 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 26 Aug 2014 15:17:06 -0700 Subject: [PATCH 44/60] paypro: a lot of parser debugging. --- lib/PayPro.js | 113 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 107 insertions(+), 6 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 2aa01b7..9d3afdb 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -147,7 +147,7 @@ PayPro.prototype.x509Verify = function() { // http://www.ietf.org/rfc/rfc5280.txt // http://tools.ietf.org/html/rfc5280#section-4.2 // - var extensions = rfc5280.decodeExtensions(c, { partial: true }); + var extensions = rfc5280.decodeExtensions(c, { partial: false }); var extensionsVerified = extensions.verified; // Object.keys(extensions).forEach(function(key) { @@ -201,7 +201,15 @@ var rfc5280 = {}; var AuthorityKeyIdentifier = rfc5280.AuthorityKeyIdentifier = asn1.define('AuthorityKeyIdentifier', function() { + // XXX Parse Error + this.any(); + return; + + // decodedTag: {"cls":"context","primitive":true,"tag":0,"tagStr":"end"} + // expectedTag: "octstr" + this.seq().obj( + // this.key('_unknown').end(), this.key('keyIdentifier').optional().use(KeyIdentifier), this.key('authorityCertIssuer').optional().use(GeneralNames), this.key('authorityCertSerialNumber').optional().use(CertificateSerialNumber) @@ -242,7 +250,9 @@ rfc5280.GeneralNames = asn1.define('GeneralNames', function() { var GeneralName = rfc5280.GeneralName = asn1.define('GeneralName', function() { this.choice({ - otherName: this.use(OtherName), + _unknown: this.int(), + otherName: this.use(AnotherName), + // otherName: this.use(rfc3280.Name), rfc822Name: this.ia5str(), dNSName: this.ia5str(), x400Address: this.use(ORAddress), @@ -255,11 +265,79 @@ rfc5280.GeneralName = asn1.define('GeneralName', function() { }); /** - * #### OtherName + * #### AnotherName + * Also referenced as "OtherName" */ -var OtherName = -rfc5280.OtherName = asn1.define('OtherName', function() { +var AnotherName = +rfc5280.AnotherName = asn1.define('AnotherName', function() { + // XXX Parse Error + // this.any(); + // return; + + // XXX THE ROOT OF THE PROBLEM LIES HERE: + // Used by Subject Alternative Name + // Fails on the objid() + + // input._reporterState.path is empty array, which is why we get '(shallow)' + // { _reporterState: { obj: {}, path: [], options: { partial: false }, errors: [] }, + // base: , + // offset: 2, + // length: 28 } + + // ~/work/node_modules/asn1.js/lib/asn1/decoders/der.js + // ~/work/node_modules/asn1.js/lib/asn1/base/node.js L459 + + // node._decode(input) call error. + // Failed to match tag: "seq" at: (shallow) + // node._decode(input) call error. + // Failed to match tag: "ia5str" at: (shallow) + // node._decode(input) call error. + // Failed to match tag: "ia5str" at: (shallow) + // node._decode(input) call error. + // Failed to match tag: "seq" at: (shallow) + // node._decode(input) call error. + // Failed to match tag: "seqof" at: (shallow) + // node._decode(input) call error. + // Choice not matched at: (shallow) + + // node._decode(input) call error. + // Failed to match tag: "seq" at: (shallow) + // node._decode(input) call error. + // Failed to match tag: "ia5str" at: (shallow) + // node._decode(input) call error. + // Failed to match tag: "octstr" at: (shallow) + // node._decode(input) call error. + // Failed to match tag: "objid" at: (shallow) + + // ~/work/node_modules/asn1.js/lib/asn1/decoders/der.js L66 + // It's decoding it as an int (decodedTag): + // It's describing GeneralNames: + // decodedTag: {"cls":"context","primitive":true,"tag":2,"tagStr":"int"} + // expectedTag: "seq" + // decodedTag: {"cls":"context","primitive":true,"tag":2,"tagStr":"int"} + // expectedTag: "ia5str" + // decodedTag: {"cls":"context","primitive":true,"tag":2,"tagStr":"int"} + // expectedTag: "ia5str" + // decodedTag: {"cls":"context","primitive":true,"tag":2,"tagStr":"int"} + // expectedTag: "seq" + // decodedTag: {"cls":"context","primitive":true,"tag":2,"tagStr":"int"} + // expectedTag: "seqof" + // decodedTag: {"cls":"context","primitive":true,"tag":2,"tagStr":"int"} + // expectedTag: "seq" + // decodedTag: {"cls":"context","primitive":true,"tag":2,"tagStr":"int"} + // expectedTag: "ia5str" + // decodedTag: {"cls":"context","primitive":true,"tag":2,"tagStr":"int"} + // expectedTag: "octstr" + // decodedTag: {"cls":"context","primitive":true,"tag":2,"tagStr":"int"} + // expectedTag: "objid" + + // Specification: + // this.seq().obj( + // this.key('typeId').objid(), + // this.key('value').explicit(0).any() + // ); + this.seq().obj( this.key('typeId').objid(), this.key('value').explicit(0).any() @@ -748,16 +826,38 @@ rfc5280.DistributionPoint = asn1.define('DistributionPoint', function() { var DistributionPointName = rfc5280.DistributionPointName = asn1.define('DistributionPointName', function() { + // XXX Parse Error + // this.any(); + // return; + + // decodedTag: {"cls":"context","primitive":false,"tag":0,"tagStr":"end"} + // expectedTag: "seqof" + // decodedTag: {"cls":"context","primitive":false,"tag":0,"tagStr":"end"} + // expectedTag: "setof" + + // Has tag "end": + // ~/work/node_modules/asn1.js/lib/asn1/constants/der.js + // ~/work/node_modules/asn1.js/lib/asn1/decoders/der.js + // function derDecodeTag(buf, fail) { + this.choice({ + // _unknown: this.end(), fullName: this.use(GeneralNames), nameRelativeToCRLIssuer: this.use(RelativeDistinguishedName) }); }); +// rfc3280.Name; +// rfc3280.RDNSequence; +// rfc3280.RelativeDistinguishedName + /** * #### Relative Distinguished Name */ +var RelativeDistinguishedName = +rfc5280.RelativeDistinguishedName = rfc3280.RelativeDistinguishedName; + var RelativeDistinguishedName = rfc5280.RelativeDistinguishedName = asn1.define('RelativeDistinguishedName', function() { this.setof(AttributeTypeAndValue); @@ -850,11 +950,11 @@ rfc5280.extensions = { name: 'Key Usage', parse: function(decoded, cert, ext, edata) { // For bitstr: KeyUsage + // NOTE: nonRepudiation was renamed to contentCommitment: var data = decoded.data[0]; return { digitalSignature: !!((data >> 0) & 1), nonRepudiation: !!((data >> 1) & 1), - // nonRepudiation renamed to contentCommitment: contentCommitment: !!((data >> 1) & 1), keyEncipherment: !!((data >> 2) & 1), dataEncipherment: !!((data >> 3) & 1), @@ -881,6 +981,7 @@ rfc5280.extensions = { 31: { name: 'CRL Distribution Points', parse: function(decoded, cert, ext, edata) { + return decoded; // XXX Find the bitstr: ReasonFlags print('@@@@@@@@@@@@@@@@@@@@@@@@@@@'); print(decoded); From c366a1114523a1880c807fcd82cf2b265130eae6 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 26 Aug 2014 15:18:26 -0700 Subject: [PATCH 45/60] paypro: ignore 2-3 failing parsers for now. --- lib/PayPro.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 9d3afdb..bca7c5b 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -827,8 +827,8 @@ rfc5280.DistributionPoint = asn1.define('DistributionPoint', function() { var DistributionPointName = rfc5280.DistributionPointName = asn1.define('DistributionPointName', function() { // XXX Parse Error - // this.any(); - // return; + this.any(); + return; // decodedTag: {"cls":"context","primitive":false,"tag":0,"tagStr":"end"} // expectedTag: "seqof" From 7115dc97a46ae4c8af0a842b74d5ee066f7f0d76 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 26 Aug 2014 15:20:25 -0700 Subject: [PATCH 46/60] paypro: parse error workaround. --- lib/PayPro.js | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index bca7c5b..b671e77 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -201,15 +201,12 @@ var rfc5280 = {}; var AuthorityKeyIdentifier = rfc5280.AuthorityKeyIdentifier = asn1.define('AuthorityKeyIdentifier', function() { - // XXX Parse Error - this.any(); - return; - // decodedTag: {"cls":"context","primitive":true,"tag":0,"tagStr":"end"} // expectedTag: "octstr" this.seq().obj( - // this.key('_unknown').end(), + // XXX Workaround parser error: + this.key('_unknown').any(), this.key('keyIdentifier').optional().use(KeyIdentifier), this.key('authorityCertIssuer').optional().use(GeneralNames), this.key('authorityCertSerialNumber').optional().use(CertificateSerialNumber) @@ -250,6 +247,7 @@ rfc5280.GeneralNames = asn1.define('GeneralNames', function() { var GeneralName = rfc5280.GeneralName = asn1.define('GeneralName', function() { this.choice({ + // XXX Workaround parser error: _unknown: this.int(), otherName: this.use(AnotherName), // otherName: this.use(rfc3280.Name), @@ -826,10 +824,6 @@ rfc5280.DistributionPoint = asn1.define('DistributionPoint', function() { var DistributionPointName = rfc5280.DistributionPointName = asn1.define('DistributionPointName', function() { - // XXX Parse Error - this.any(); - return; - // decodedTag: {"cls":"context","primitive":false,"tag":0,"tagStr":"end"} // expectedTag: "seqof" // decodedTag: {"cls":"context","primitive":false,"tag":0,"tagStr":"end"} @@ -841,7 +835,8 @@ rfc5280.DistributionPointName = asn1.define('DistributionPointName', function() // function derDecodeTag(buf, fail) { this.choice({ - // _unknown: this.end(), + // XXX Workaround parser error: + _unknown: this.any(), fullName: this.use(GeneralNames), nameRelativeToCRLIssuer: this.use(RelativeDistinguishedName) }); From 60b266a0db62b3b8864d2569a1fcae77b7353206 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 26 Aug 2014 16:26:34 -0700 Subject: [PATCH 47/60] paypro: more extension debugging. --- lib/PayPro.js | 130 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 108 insertions(+), 22 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index b671e77..173a93b 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -150,6 +150,87 @@ PayPro.prototype.x509Verify = function() { var extensions = rfc5280.decodeExtensions(c, { partial: false }); var extensionsVerified = extensions.verified; + // The two most important extensions: + // "The keyIdentifier field of the authorityKeyIdentifier extension MUST be + // included in all certificates generated by conforming CAs to facilitate + // certification path construction." + var aki = extensions.authorityKeyIdentifier; + aki.sha1Key = aki.raw.slice(4, 24); + var ski = extensions.subjectKeyIdentifier; + ski.sha1Key = ski.decoded; + var ku = extensions.keyUsage; + + // Next Extensions: + var nextensions = rfc5280.decodeExtensions(nc, { partial: false }); + var nextensionsVerified = nextensions.verified; + var naki = nextensions.authorityKeyIdentifier; + naki.sha1Key = naki.raw.slice(4, 24); + var nski = nextensions.subjectKeyIdentifier; + nski.sha1Key = nski.decoded; + var nku = nextensions.keyUsage; + + // Subject Key was derived from Next Public Key + + // Authority Key Identifier: + // { decoded: { _unknown: }, + // raw: } + + // ~/work/node_modules/asn1.js/lib/asn1/decoders/der.js + // ~/work/node_modules/asn1.js/lib/asn1/constants/der.js + + // 0x30 - SEQ + // 0x16 - Octet Len = 22 - the sha is 20 bytes + // 0x80 - ?? + // 0x14 - ?? + // 0xd2 - + // 0xc4 - + // 0xb0 - + // 0xd2 - + // 0x91 - + // 0xd4 - + // 0x4c - + // 0x11 - + // 0x71 - + // 0xb3 - + // 0x61 - + // 0xcb - + // 0x3d - + // 0xa1 - + // 0xfe - + // 0xdd - + // 0xa8 - + // 0x6a - + // 0xd4 - + // 0xe3 - + + // Subject Key Identifier + // { decoded: , + // raw: } + + // 0x04 - octet string + // 0x14 = 20 bytes + // rest: sha1 (20 bytes) + + // if (extensions.subjectDirectoryAttributes.decoded.cA) { + + // followed by 0100 = 64 = 0x40 = exactly 7 bits + + print('Authority Key Identifier:'); + print(aki); + print(''); + print('Subject Key Identifier'); + print(ski); + print('Key Usage:'); + print(ku); + print(''); + print('Next Authority Key Identifier:'); + print(naki); + print(''); + print('Next Subject Key Identifier'); + print(nski); + print('Next Key Usage:'); + print(nku); + // Object.keys(extensions).forEach(function(key) { // if (extensions[key].execute) { // c = extensions[key].execute(c); @@ -558,7 +639,7 @@ rfc5280.DirectoryString = asn1.define('DirectoryString', function() { /** * 2 - * # SubjectKeyIdentifier + * # Subject Key Identifier */ var SubjectKeyIdentifier = @@ -568,7 +649,7 @@ rfc5280.SubjectKeyIdentifier = asn1.define('SubjectKeyIdentifier', function() { /** * 3 - * # KeyUsage + * # Key Usage */ var KeyUsage = @@ -976,14 +1057,15 @@ rfc5280.extensions = { 31: { name: 'CRL Distribution Points', parse: function(decoded, cert, ext, edata) { - return decoded; // XXX Find the bitstr: ReasonFlags - print('@@@@@@@@@@@@@@@@@@@@@@@@@@@'); - print(decoded); - print(cert); - print(ext); - print(edata); - print('@@@@@@@@@@@@@@@@@@@@@@@@@@@'); + if (process.env.NODE_DEBUG) { + print('@@@@@@@@@@@@@@@@@@@@@@@@@@@'); + print(decoded); + print(cert); + print(ext); + print(edata); + print('@@@@@@@@@@@@@@@@@@@@@@@@@@@'); + } return decoded; // For bitstr: ReasonFlags var data = decoded.CRLDistributionPoints.DistributionPoint.reasons; @@ -1100,18 +1182,18 @@ rfc5280.decodeExtensions = function(cert, options) { } // If the Extension needs extra parsing (i.e. bitstrs) - data = ext.parse - ? ext.parse(decoded, cert, ext, edata) - : decoded; + data = { + decoded: ext.parse + ? ext.parse(decoded, cert, ext, edata) + : decoded, + raw: edata.extnValue + }; // Tack on some useful info // Comment for debugging: // data.edata = edata; // data.ext = ext; - if (ext.parse) { - data.decoded = decoded; - } // Execute Behavior for Cert if (ext.execute) { @@ -1127,18 +1209,22 @@ rfc5280.decodeExtensions = function(cert, options) { output[ext.prop] = data; // XXX Debug - print('------------'); - print('%s (%s):', ext.name, ext.id); - print('Buffer:'); - print(edata.extnValue); - print('Extension:'); - print(data); + if (process.env.NODE_DEBUG) { + print('------------'); + print('%s (%s):', ext.name, ext.id); + print('Buffer:'); + print(edata.extnValue); + print('Extension:'); + print(data); + } } else { // Add unknown extension: output.unknown.push(edata); // XXX Debug - print('Unknown extension: %s', eid); + if (process.env.NODE_DEBUG) { + print('Unknown extension: %s', eid); + } } } From 6cbf1319193a5c0195a9f6b3044f3303bb063f34 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 27 Aug 2014 10:30:55 -0700 Subject: [PATCH 48/60] paypro: move extensions to asn1.js --- lib/PayPro.js | 975 +------------------------------------------------- 1 file changed, 3 insertions(+), 972 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 173a93b..ec1cefa 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -10,6 +10,7 @@ var KJUR = require('jsrsasign'); var asn1 = require('asn1.js'); var rfc3280 = require('asn1.js/rfc/3280'); +var rfc5280 = require('asn1.js/rfc/5280'); PayPro.prototype.x509Sign = function(key) { var self = this; @@ -147,7 +148,7 @@ PayPro.prototype.x509Verify = function() { // http://www.ietf.org/rfc/rfc5280.txt // http://tools.ietf.org/html/rfc5280#section-4.2 // - var extensions = rfc5280.decodeExtensions(c, { partial: false }); + var extensions = rfc5280.decodeExtensions(c, 'der', { partial: false }); var extensionsVerified = extensions.verified; // The two most important extensions: @@ -161,7 +162,7 @@ PayPro.prototype.x509Verify = function() { var ku = extensions.keyUsage; // Next Extensions: - var nextensions = rfc5280.decodeExtensions(nc, { partial: false }); + var nextensions = rfc5280.decodeExtensions(nc, 'der', { partial: false }); var nextensionsVerified = nextensions.verified; var naki = nextensions.authorityKeyIdentifier; naki.sha1Key = naki.raw.slice(4, 24); @@ -265,976 +266,6 @@ PayPro.prototype.x509Verify = function() { return verified && chainVerified; }; -/** - * RFC5280 X509 Extension Definitions - */ - -var rfc5280 = {}; - -/** - * Standard Extensions - */ - -/** - * 1 - * # Authority Key Identifier - */ - -var AuthorityKeyIdentifier = -rfc5280.AuthorityKeyIdentifier = asn1.define('AuthorityKeyIdentifier', function() { - // decodedTag: {"cls":"context","primitive":true,"tag":0,"tagStr":"end"} - // expectedTag: "octstr" - - this.seq().obj( - // XXX Workaround parser error: - this.key('_unknown').any(), - this.key('keyIdentifier').optional().use(KeyIdentifier), - this.key('authorityCertIssuer').optional().use(GeneralNames), - this.key('authorityCertSerialNumber').optional().use(CertificateSerialNumber) - ); -}); - -/** - * ## KeyIdentifier - */ - -var KeyIdentifier = -rfc5280.KeyIdentifier = asn1.define('KeyIdentifier', function() { - this.octstr(); -}); - -/** - * ## CertificateSerialNumber - */ - -var CertificateSerialNumber = -rfc5280.CertificateSerialNumber = asn1.define('CertificateSerialNumber', function() { - this.int(); -}); - -/** - * ## GeneralNames - */ - -var GeneralNames = -rfc5280.GeneralNames = asn1.define('GeneralNames', function() { - this.seqof(GeneralName); -}); - -/** - * ### GeneralName - */ - -var GeneralName = -rfc5280.GeneralName = asn1.define('GeneralName', function() { - this.choice({ - // XXX Workaround parser error: - _unknown: this.int(), - otherName: this.use(AnotherName), - // otherName: this.use(rfc3280.Name), - rfc822Name: this.ia5str(), - dNSName: this.ia5str(), - x400Address: this.use(ORAddress), - directoryName: this.use(rfc3280.Name), - ediPartyName: this.use(EDIPartyName), - uniformResourceIdentifier: this.ia5str(), - iPAddress: this.octstr(), - registeredID: this.objid() - }); -}); - -/** - * #### AnotherName - * Also referenced as "OtherName" - */ - -var AnotherName = -rfc5280.AnotherName = asn1.define('AnotherName', function() { - // XXX Parse Error - // this.any(); - // return; - - // XXX THE ROOT OF THE PROBLEM LIES HERE: - // Used by Subject Alternative Name - // Fails on the objid() - - // input._reporterState.path is empty array, which is why we get '(shallow)' - // { _reporterState: { obj: {}, path: [], options: { partial: false }, errors: [] }, - // base: , - // offset: 2, - // length: 28 } - - // ~/work/node_modules/asn1.js/lib/asn1/decoders/der.js - // ~/work/node_modules/asn1.js/lib/asn1/base/node.js L459 - - // node._decode(input) call error. - // Failed to match tag: "seq" at: (shallow) - // node._decode(input) call error. - // Failed to match tag: "ia5str" at: (shallow) - // node._decode(input) call error. - // Failed to match tag: "ia5str" at: (shallow) - // node._decode(input) call error. - // Failed to match tag: "seq" at: (shallow) - // node._decode(input) call error. - // Failed to match tag: "seqof" at: (shallow) - // node._decode(input) call error. - // Choice not matched at: (shallow) - - // node._decode(input) call error. - // Failed to match tag: "seq" at: (shallow) - // node._decode(input) call error. - // Failed to match tag: "ia5str" at: (shallow) - // node._decode(input) call error. - // Failed to match tag: "octstr" at: (shallow) - // node._decode(input) call error. - // Failed to match tag: "objid" at: (shallow) - - // ~/work/node_modules/asn1.js/lib/asn1/decoders/der.js L66 - // It's decoding it as an int (decodedTag): - // It's describing GeneralNames: - // decodedTag: {"cls":"context","primitive":true,"tag":2,"tagStr":"int"} - // expectedTag: "seq" - // decodedTag: {"cls":"context","primitive":true,"tag":2,"tagStr":"int"} - // expectedTag: "ia5str" - // decodedTag: {"cls":"context","primitive":true,"tag":2,"tagStr":"int"} - // expectedTag: "ia5str" - // decodedTag: {"cls":"context","primitive":true,"tag":2,"tagStr":"int"} - // expectedTag: "seq" - // decodedTag: {"cls":"context","primitive":true,"tag":2,"tagStr":"int"} - // expectedTag: "seqof" - // decodedTag: {"cls":"context","primitive":true,"tag":2,"tagStr":"int"} - // expectedTag: "seq" - // decodedTag: {"cls":"context","primitive":true,"tag":2,"tagStr":"int"} - // expectedTag: "ia5str" - // decodedTag: {"cls":"context","primitive":true,"tag":2,"tagStr":"int"} - // expectedTag: "octstr" - // decodedTag: {"cls":"context","primitive":true,"tag":2,"tagStr":"int"} - // expectedTag: "objid" - - // Specification: - // this.seq().obj( - // this.key('typeId').objid(), - // this.key('value').explicit(0).any() - // ); - - this.seq().obj( - this.key('typeId').objid(), - this.key('value').explicit(0).any() - ); -}); - -/** - * #### ORAddress - */ - -var ORAddress = -rfc5280.ORAddress = asn1.define('ORAddress', function() { - this.seq().obj( - this.key('builtInStandardAttributes').use(BuiltInStandardAttributes), - this.key('builtInDomainDefinedAttributes').optional().use(BuiltInDomainDefinedAttributes), - this.key('extensionAttributes').optional().use(ExtensionAttributes) - ); -}); - -/** - * ##### BuiltInStandardAttributes - */ - -var BuiltInStandardAttributes = -rfc5280.BuiltInStandardAttributes = asn1.define('BuiltInStandardAttributes', function() { - this.seq().obj( - this.key('countryName').optional().use(CountryName), - this.key('administrationDomainName').optional().use(AdministrationDomainName), - this.key('networkAddress').optional().use(NetworkAddress), - this.key('terminalIdentifier').optional().use(TerminalIdentifier), - this.key('privateDomainName').optional().use(PrivateDomainName), - this.key('organizationName').optional().use(OrganizationName), - this.key('numericUserIdentifier').optional().use(NumericUserIdentifier), - this.key('personalName').optional().use(PersonalName), - this.key('organizationalUnitNames').optional().use(OrganizationalUnitNames) - ); -}); - -/** - * ###### CountryName - */ - -var CountryName = -rfc5280.CountryName = asn1.define('CountryName', function() { - this.choice({ - x121DccCode: this.numstr(), - iso3166Alpha2Code: this.printstr() - }); -}); - -/** - * ###### AdministrationDomainName - */ - -var AdministrationDomainName = -rfc5280.AdministrationDomainName = asn1.define('AdministrationDomainName', function() { - this.choice({ - numeric: this.numstr(), - printable: this.printstr() - }); -}); - -/** - * ###### NetworkAddress - */ - -var NetworkAddress = -rfc5280.NetworkAddress = asn1.define('NetworkAddress', function() { - this.use(X121Address); -}); - -/** - * ###### X121Address - */ - -var X121Address = -rfc5280.X121Address = asn1.define('X121Address', function() { - this.numstr(); -}); - -/** - * ###### TerminalIdentifier - */ - -var TerminalIdentifier = -rfc5280.TerminalIdentifier = asn1.define('TerminalIdentifier', function() { - this.printstr(); -}); - -/** - * ###### PrivateDomainName - */ - -var PrivateDomainName = -rfc5280.PrivateDomainName = asn1.define('PrivateDomainName', function() { - this.choice({ - numeric: this.numstr(), - printable: this.printstr() - }); -}); - -/** - * ###### OrganizationName - */ - -var OrganizationName = -rfc5280.OrganizationName = asn1.define('OrganizationName', function() { - this.printstr(); -}); - -/** - * ###### NumericUserIdentifier - */ - -var NumericUserIdentifier = -rfc5280.NumericUserIdentifier = asn1.define('NumericUserIdentifier', function() { - this.numstr(); -}); - -/** - * ###### PersonalName - */ - -var PersonalName = -rfc5280.PersonalName = asn1.define('PersonalName', function() { - this.set().obj( - this.key('surname').implicit().printstr(), - this.key('givenName').implicit().printstr(), - this.key('initials').implicit().printstr(), - this.key('generationQualifier').implicit().printstr() - ); -}); - -/** - * ###### OrganizationalUnitNames - */ - -var OrganizationalUnitNames = -rfc5280.OrganizationalUnitNames = asn1.define('OrganizationalUnitNames', function() { - this.seqof(OrganizationalUnitName); -}); - -/** - * ####### OrganizationalUnitName - */ - -var OrganizationalUnitName = -rfc5280.OrganizationalUnitName = asn1.define('OrganizationalUnitName', function() { - this.printstr(); -}); - -/** - * ##### BuiltInDomainDefinedAttributes - */ - -var BuiltInDomainDefinedAttributes = -rfc5280.BuiltInDomainDefinedAttributes = asn1.define('BuiltInDomainDefinedAttributes', function() { - this.seqof(BuiltInDomainDefinedAttribute); -}); - -/** - * ###### BuiltInDomainDefinedAttribute - */ - -var BuiltInDomainDefinedAttribute = -rfc5280.BuiltInDomainDefinedAttribute = asn1.define('BuiltInDomainDefinedAttribute', function() { - this.seq().obj( - this.key('type').printstr(), - this.key('value').printstr() - ); -}); - -/** - * ## ExtensionAttributes - */ - -var ExtensionAttributes = -rfc5280.ExtensionAttributes = asn1.define('ExtensionAttributes', function() { - this.seqof(ExtensionAttribute); -}); - -/** - * ### ExtensionAttribute - */ - -var ExtensionAttribute = -rfc5280.ExtensionAttribute = asn1.define('ExtensionAttribute', function() { - this.seq().obj( - this.key('extensionAttributeType').implicit().int(), - this.key('extensionAttributeValue').any().implicit().int() - ); -}); - -/** - * #### EDIPartyName - */ - -var EDIPartyName = -rfc5280.EDIPartyName = asn1.define('EDIPartyName', function() { - this.seq().obj( - this.key('nameAssigner').optional().use(DirectoryString), - this.key('partyName').use(DirectoryString) - ); -}); - -/** - * ##### DirectoryString - */ - -var DirectoryString = -rfc5280.DirectoryString = asn1.define('DirectoryString', function() { - this.choice({ - teletexString: this.t61str(), - printableString: this.printstr(), - universalString: this.unistr(), - utf8String: this.utf8str(), - bmpString: this.bmpstr() - }); -}); - -/** - * 2 - * # Subject Key Identifier - */ - -var SubjectKeyIdentifier = -rfc5280.SubjectKeyIdentifier = asn1.define('SubjectKeyIdentifier', function() { - this.use(KeyIdentifier); -}); - -/** - * 3 - * # Key Usage - */ - -var KeyUsage = -rfc5280.KeyUsage = asn1.define('KeyUsage', function() { - this.bitstr(); -}); - -/** - * 4 - * # Certificate Policies - */ - -var CertificatePolicies = -rfc5280.CertificatePolicies = asn1.define('CertificatePolicies', function() { - this.seqof(PolicyInformation); -}); - -/** - * ## Policy Information - */ - -var PolicyInformation = -rfc5280.PolicyInformation = asn1.define('PolicyInformation', function() { - this.seq().obj( - this.key('policyIdentifier').use(CertPolicyId), - this.key('policyQualifiers').use(PolicyQualifiers) - ); -}); - -/** - * ## Cert Policy Id - */ - -var CertPolicyId = -rfc5280.CertPolicyId = asn1.define('CertPolicyId', function() { - this.objid(); -}); - - -/** - * ### Policy Qualifiers - */ - -var PolicyQualifiers = -rfc5280.PolicyQualifiers = asn1.define('PolicyQualifiers', function() { - this.seqof(PolicyQualifierInfo); -}); - -/** - * #### Policy Qualifier Info - */ - -var PolicyQualifierInfo = -rfc5280.PolicyQualifierInfo = asn1.define('PolicyQualifierInfo', function() { - this.seq().obj( - this.key('policyQualifierId').use(PolicyQualifierId), - this.key('qualifier').any().use(PolicyQualifierId) - ); -}); - -/** - * ##### Policy Qualifier Id - */ - -var PolicyQualifierId = -rfc5280.PolicyQualifierId = asn1.define('PolicyQualifierId', function() { - this.objid(); -}); - -/** - * 5 - * # Policy Mappings - */ - -var PolicyMappings = -rfc5280.PolicyMappings = asn1.define('PolicyMappings', function() { - this.seqof(PolicyMapping); -}); - -/** - * ## Policy Mapping - */ - -var PolicyMapping = -rfc5280.PolicyMapping = asn1.define('PolicyMapping', function() { - this.seq().obj( - this.key('issuerDomainPolicy').use(CertPolicyId), - this.key('subjectDomainPolicy').use(CertPolicyId) - ); -}); - -/** - * 6 - * # Subject Alternative Name - */ - -var SubjectAlternativeName = -rfc5280.SubjectAlternativeName = asn1.define('SubjectAlternativeName', function() { - this.use(GeneralNames); -}); - -/** - * 7 - * # Issuer Alternative Name - */ - -var IssuerAlternativeName = -rfc5280.IssuerAlternativeName = asn1.define('IssuerAlternativeName', function() { - this.use(GeneralNames); -}); - -/** - * 8 - * # Subject Directory Attributes - */ - -var SubjectDirectoryAttributes = -rfc5280.SubjectDirectoryAttributes = asn1.define('SubjectDirectoryAttributes', function() { - this.seqof(Attribute); -}); - -/** - * ## Attribute - */ - -var AttributeTypeAndValue = rfc5280.AttributeTypeAndValue = rfc3280.AttributeTypeAndValue; -var Attribute = rfc5280.AttributeTypeAndValue = AttributeTypeAndValue; - -/** - * 9 - * # Basic Constraints - */ - -var BasicConstraints = -rfc5280.BasicConstraints = asn1.define('BasicConstraints', function() { - this.seq().obj( - this.key('cA').bool().def(false), - this.key('pathLenConstraint').optional().int() - ); -}); - -/** - * 10 - * # Name Constraints - */ - -var NameConstraints = -rfc5280.NameConstraints = asn1.define('NameConstraints', function() { - this.seq().obj( - this.key('permittedSubtrees').optiona().use(GeneralSubtrees), - this.key('excludedSubtrees').optional().use(GeneralSubtrees) - ); -}); - -/** - * ## General Subtrees - */ - -var GeneralSubtrees = -rfc5280.GeneralSubtrees = asn1.define('GeneralSubtrees', function() { - this.seqof(GeneralSubtree); -}); - -/** - * ### General Subtree - */ - -var GeneralSubtree = -rfc5280.GeneralSubtree = asn1.define('GeneralSubtree', function() { - this.seq().obj( - this.key('base').use(GeneralName), - this.key('minimum').default(0).use(BaseDistance), - this.key('maximum').optional().use(BaseDistance) - ); -}); - -/** - * #### Base Distance - */ - -var BaseDistance = -rfc5280.BaseDistance = asn1.define('BaseDistance', function() { - this.int(); -}); - -/** - * 11 - * # Policy Constraints - */ - -var PolicyConstraints = -rfc5280.PolicyConstraints = asn1.define('PolicyConstraints', function() { - this.seq().obj( - this.key('requireExplicitPolicy').optional().use(SkipCerts), - this.key('inhibitPolicyMapping').optional().use(SkipCerts) - ); -}); - -/** - * ## Skip Certs - */ - -var SkipCerts = -rfc5280.SkipCerts = asn1.define('SkipCerts', function() { - this.int(); -}); - -/** - * 12 - * # Extended Key Usage - */ - -var ExtendedKeyUsage = -rfc5280.ExtendedKeyUsage = asn1.define('ExtendedKeyUsage', function() { - this.seqof(KeyPurposeId); -}); - -/** - * ## Key Purpose Id - */ - -var KeyPurposeId = -rfc5280.KeyPurposeId = asn1.define('KeyPurposeId', function() { - this.objid(); -}); - -/** - * 13 - * # CRL Distribution Points - */ - -var CRLDistributionPoints = -rfc5280.CRLDistributionPoints = asn1.define('CRLDistributionPoints', function() { - this.seqof(DistributionPoint); -}); - -/** - * ## Distribution Point - */ - -var DistributionPoint = -rfc5280.DistributionPoint = asn1.define('DistributionPoint', function() { - this.seq().obj( - this.key('distributionPoint').optional().use(DistributionPointName), - this.key('reasons').optional().use(ReasonFlags), - this.key('cRLIssuer').optional().use(GeneralNames) - ); -}); - -/** - * ### Distribution Point Name - */ - -var DistributionPointName = -rfc5280.DistributionPointName = asn1.define('DistributionPointName', function() { - // decodedTag: {"cls":"context","primitive":false,"tag":0,"tagStr":"end"} - // expectedTag: "seqof" - // decodedTag: {"cls":"context","primitive":false,"tag":0,"tagStr":"end"} - // expectedTag: "setof" - - // Has tag "end": - // ~/work/node_modules/asn1.js/lib/asn1/constants/der.js - // ~/work/node_modules/asn1.js/lib/asn1/decoders/der.js - // function derDecodeTag(buf, fail) { - - this.choice({ - // XXX Workaround parser error: - _unknown: this.any(), - fullName: this.use(GeneralNames), - nameRelativeToCRLIssuer: this.use(RelativeDistinguishedName) - }); -}); - -// rfc3280.Name; -// rfc3280.RDNSequence; -// rfc3280.RelativeDistinguishedName - -/** - * #### Relative Distinguished Name - */ - -var RelativeDistinguishedName = -rfc5280.RelativeDistinguishedName = rfc3280.RelativeDistinguishedName; - -var RelativeDistinguishedName = -rfc5280.RelativeDistinguishedName = asn1.define('RelativeDistinguishedName', function() { - this.setof(AttributeTypeAndValue); -}); - -/** - * ### Reason Flags - */ - -var ReasonFlags = -rfc5280.ReasonFlags = asn1.define('ReasonFlags', function() { - this.bitstr(); -}); - -/** - * 14 - * # Inhibit anyPolicy - */ - -var InhibitAnyPolicy = -rfc5280.InhibitAnyPolicy = asn1.define('InhibitAnyPolicy', function() { - this.use(SkipCerts); -}); - -/** - * 15 - * # Freshest CRL - */ - -var FreshestCRL = -rfc5280.FreshestCRL = asn1.define('FreshestCRL', function() { - this.use(CRLDistributionPoints); -}); - -/** - * Private Internet Extensions - */ - -/** - * 16 - * # Authority Information Access - */ - -var AuthorityInformationAccess = -rfc5280.AuthorityInformationAccess = asn1.define('AuthorityInformationAccess', function() { - this.seqof(AccessDescription); -}); - -/** - * ## Access Description - */ - -var AccessDescription = -rfc5280.AccessDescription = asn1.define('AccessDescription', function() { - this.seq().obj( - this.key('accessMethod').objid(), - this.key('accessLocation').use(GeneralName) - ); -}); - -/** - * 17 - * # Subject Information Access - */ - -var SubjectInformationAccess = -rfc5280.SubjectInformationAccess = asn1.define('SubjectInformationAccess', function() { - this.seqof(AccessDescription); -}); - -/** - * XXX - * # Unknown Extension - */ - -var UnknownExtension = -rfc5280.UnknownExtension = asn1.define('UnknownExtension', function() { - this.any(); -}); - - -rfc5280.extensions = { - standard: { - // id-ce extensions - Standard Extensions - prefix: [2, 5, 29], - 35: 'Authority Key Identifier', - 14: 'Subject Key Identifier', - // VERY IMPORTANT, especially is cA (basic constraints) is true (it is) - 15: { - name: 'Key Usage', - parse: function(decoded, cert, ext, edata) { - // For bitstr: KeyUsage - // NOTE: nonRepudiation was renamed to contentCommitment: - var data = decoded.data[0]; - return { - digitalSignature: !!((data >> 0) & 1), - nonRepudiation: !!((data >> 1) & 1), - contentCommitment: !!((data >> 1) & 1), - keyEncipherment: !!((data >> 2) & 1), - dataEncipherment: !!((data >> 3) & 1), - keyAgreement: !!((data >> 4) & 1), - keyCertSign: !!((data >> 5) & 1), - cRLSign: !!((data >> 6) & 1), - encipherOnly: !!((data >> 7) & 1), - decipherOnly: !!((data >> 8) & 1) - }; - }, - execute: function(cert) { - return cert; - } - }, - 32: 'Certificate Policies', - 33: 'Policy Mappings', - 17: 'Subject Alternative Name', - 18: 'Issuer Alternative Name', - 9: 'Subject Directory Attributes', - 19: 'Basic Constraints', - 30: 'Name Constraints', - 36: 'Policy Constraints', - 37: 'Extended Key Usage', - 31: { - name: 'CRL Distribution Points', - parse: function(decoded, cert, ext, edata) { - // XXX Find the bitstr: ReasonFlags - if (process.env.NODE_DEBUG) { - print('@@@@@@@@@@@@@@@@@@@@@@@@@@@'); - print(decoded); - print(cert); - print(ext); - print(edata); - print('@@@@@@@@@@@@@@@@@@@@@@@@@@@'); - } - return decoded; - // For bitstr: ReasonFlags - var data = decoded.CRLDistributionPoints.DistributionPoint.reasons; - return { - unused: !!((data >> 0) & 1), - keyCompromise: !!((data >> 1) & 1), - cACompromise: !!((data >> 2) & 1), - affiliationChanged: !!((data >> 3) & 1), - superseded: !!((data >> 4) & 1), - cessationOfOperation: !!((data >> 5) & 1), - certificateHold: !!((data >> 6) & 1), - privilegeWithdrawn: !!((data >> 7) & 1), - aACompromise: !!((data >> 8) & 1) - }; - }, - execute: function(cert) { - return cert; - } - }, - 54: 'Inhibit anyPolicy', - 46: 'Freshest CRL' - }, - - // id-pe extensions - Private Internet Extensions - priv: { - // Unknown extension: 1.3.6.1.5.5.7.1.1 - prefix: [1, 3, 6, 1, 5, 5, 7], - 1: 'Authority Information Access', - 11: 'Subject Information Access', - // Unknown Extension (not documented anywhere, probably non-standard) - '1.1': 'Unknown Extension' - } -}; - -Object.keys(rfc5280.extensions).forEach(function(typeName) { - var type = rfc5280.extensions[typeName]; - Object.keys(type).forEach(function(suffix) { - var id, prop, schemaName, schema, parse, execute; - - if (suffix === 'prefix') - return; - - var prefix = type.prefix; - var name = type[suffix]; - - if (typeof name === 'object') { - var obj = name; - name = obj.name; - parse = obj.parse; - execute = obj.execute; - } - - id = prefix.concat(suffix).join('.'); - - if (/^[A-Z]+ /.test(name)) { - // CRL Distribution Points - > CRLDistributionPoints - prop = name.replace(/ /g, ''); - } else { - prop = (name[0].toLowerCase()) + name.substring(1).replace(/ /g, ''); - } - - schemaName = name.replace(/ /g, ''); - schema = rfc5280[schemaName]; - - rfc5280.extensions[id] = { - typeName: typeName, - prefix: prefix, - suffix: suffix, - id: id, - name: name, - prop: prop, - schemaName: schemaName, - schema: schema, - parse: parse, - execute: execute - }; - }); -}); - -rfc5280.decodeExtensions = function(cert, options) { - var tbsCertificate = cert.tbsCertificate; - - if (!tbsCertificate) { - tbsCertificate = cert; - cert = null; - } - - var edata, eid, ext, decoded, errors, data; - - var output = {}; - output.unknown = []; - - for (var i = 0; i < tbsCertificate.extensions.length; i++) { - edata = tbsCertificate.extensions[i]; - eid = edata.extnID.join('.'); - - if (ext = rfc5280.extensions[eid]) { - // Parse Extension - decoded = ext.schema.decode(edata.extnValue, 'der', options); - - // partial: true throws everything onto: { result: ..., errors: ... } - if (options.partial && decoded.result) { - errors = decoded.errors; - if (Array.isArray(decoded.result)) { - decoded = decoded.result.map(function(decoded) { - decoded.errors.forEach(function(error) { - errors.push(error); - }); - return decoded.result; - }); - } else { - decoded = decoded.result; - } - } - - // If the Extension needs extra parsing (i.e. bitstrs) - data = { - decoded: ext.parse - ? ext.parse(decoded, cert, ext, edata) - : decoded, - raw: edata.extnValue - }; - - // Tack on some useful info - - // Comment for debugging: - // data.edata = edata; - // data.ext = ext; - - // Execute Behavior for Cert - if (ext.execute) { - data.execute = ext.execute; - } - - // Add errors for partial: true - if (options.partial && errors) { - data.errors = errors; - } - - // Add our decoded extension to the output - output[ext.prop] = data; - - // XXX Debug - if (process.env.NODE_DEBUG) { - print('------------'); - print('%s (%s):', ext.name, ext.id); - print('Buffer:'); - print(edata.extnValue); - print('Extension:'); - print(data); - } - } else { - // Add unknown extension: - output.unknown.push(edata); - - // XXX Debug - if (process.env.NODE_DEBUG) { - print('Unknown extension: %s', eid); - } - } - } - - output.verified = !output.unknown.filter(function(ext) { - return ext.critical; - }).length; - - return output; -}; - /** * Debug */ From e86b70fd4aa15b86f977a2962d990ae081e833aa Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 27 Aug 2014 10:32:27 -0700 Subject: [PATCH 49/60] paypro: cleanup debugging code and comments. --- lib/PayPro.js | 99 ++------------------------------------------------- 1 file changed, 3 insertions(+), 96 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index ec1cefa..7dbc308 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -6,8 +6,6 @@ var RootCerts = require('./common/RootCerts'); var PayPro = require('./common/PayPro'); -var KJUR = require('jsrsasign'); - var asn1 = require('asn1.js'); var rfc3280 = require('asn1.js/rfc/3280'); var rfc5280 = require('asn1.js/rfc/5280'); @@ -71,7 +69,8 @@ PayPro.prototype.x509Verify = function() { // 2. Extract signature from current certificate. // 3. If current cert is not trusted, verify that the current cert is signed // by NEXT by the certificate. - // NOTE: XXX What to do when the certificate is revoked? + // NOTE: What to do when the certificate is + // revoked -> Hit CRL Distribution Points URL var chainVerified = chain.every(function(cert, i) { var der = cert.toString('hex'); @@ -144,9 +143,6 @@ PayPro.prototype.x509Verify = function() { // // Handle Cert Extensions - // http://www.ietf.org/rfc/rfc3280.txt - // http://www.ietf.org/rfc/rfc5280.txt - // http://tools.ietf.org/html/rfc5280#section-4.2 // var extensions = rfc5280.decodeExtensions(c, 'der', { partial: false }); var extensionsVerified = extensions.verified; @@ -170,68 +166,6 @@ PayPro.prototype.x509Verify = function() { nski.sha1Key = nski.decoded; var nku = nextensions.keyUsage; - // Subject Key was derived from Next Public Key - - // Authority Key Identifier: - // { decoded: { _unknown: }, - // raw: } - - // ~/work/node_modules/asn1.js/lib/asn1/decoders/der.js - // ~/work/node_modules/asn1.js/lib/asn1/constants/der.js - - // 0x30 - SEQ - // 0x16 - Octet Len = 22 - the sha is 20 bytes - // 0x80 - ?? - // 0x14 - ?? - // 0xd2 - - // 0xc4 - - // 0xb0 - - // 0xd2 - - // 0x91 - - // 0xd4 - - // 0x4c - - // 0x11 - - // 0x71 - - // 0xb3 - - // 0x61 - - // 0xcb - - // 0x3d - - // 0xa1 - - // 0xfe - - // 0xdd - - // 0xa8 - - // 0x6a - - // 0xd4 - - // 0xe3 - - - // Subject Key Identifier - // { decoded: , - // raw: } - - // 0x04 - octet string - // 0x14 = 20 bytes - // rest: sha1 (20 bytes) - - // if (extensions.subjectDirectoryAttributes.decoded.cA) { - - // followed by 0100 = 64 = 0x40 = exactly 7 bits - - print('Authority Key Identifier:'); - print(aki); - print(''); - print('Subject Key Identifier'); - print(ski); - print('Key Usage:'); - print(ku); - print(''); - print('Next Authority Key Identifier:'); - print(naki); - print(''); - print('Next Subject Key Identifier'); - print(nski); - print('Next Key Usage:'); - print(nku); - // Object.keys(extensions).forEach(function(key) { // if (extensions[key].execute) { // c = extensions[key].execute(c); @@ -248,40 +182,13 @@ PayPro.prototype.x509Verify = function() { verifier.update(tbs); var sigVerified = verifier.verify(npubKey, sig); - // print(c); - // print(nc); - // print(extensions); - print('---'); - print('validityVerified: %s', validityVerified); - print('issuerVerified: %s', issuerVerified); - print('extensionsVerified: %s', extensionsVerified); - print('sigVerified: %s', sigVerified); - return validityVerified && issuerVerified && extensionsVerified - && (sigVerified || true); + && sigVerified; }); return verified && chainVerified; }; -/** - * Debug - */ - -var util = require('util'); - -function inspect(obj) { - return typeof obj !== 'string' - ? util.inspect(obj, false, 20, true) - : obj; -} - -function print(obj) { - return typeof obj === 'object' - ? process.stdout.write(inspect(obj) + '\n') - : console.log.apply(console, arguments); -} - module.exports = PayPro; From 852ee54e3651f3f2f86e36a9d7fafb1d468dc90f Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 28 Aug 2014 16:35:27 -0700 Subject: [PATCH 50/60] paypro: lots of debugging. parse raw DER to get raw tbsCertificate. --- lib/PayPro.js | 89 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 76 insertions(+), 13 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 7dbc308..5add732 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -64,6 +64,20 @@ PayPro.prototype.x509Verify = function() { var chain = pki_data; +/* + var anchor = rfc3280.Certificate.decode( + new Buffer(chain[0].toString('hex'), 'hex'), 'der'); + var anSigAlg = PayPro.getAlgorithm(anchor.signatureAlgorithm.algorithm, 1); + var anSig = anchor.signature.data; + + var ca = rfc3280.Certificate.decode( + new Buffer(chain[chain.length - 1].toString('hex'), 'hex'), 'der'); + var caPubKeyAlg = PayPro.getAlgorithm( + ca.tbsCertificate.subjectPublicKeyInfo.algorithm.algorithm); + var caPubKey = ca.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey.data; + caPubKey = self._DERtoPEM(caPubKey, caPubKeyAlg + ' PUBLIC KEY'); +*/ + // Verifying the cert chain: // 1. Extract public key from next certificate. // 2. Extract signature from current certificate. @@ -151,20 +165,20 @@ PayPro.prototype.x509Verify = function() { // "The keyIdentifier field of the authorityKeyIdentifier extension MUST be // included in all certificates generated by conforming CAs to facilitate // certification path construction." - var aki = extensions.authorityKeyIdentifier; - aki.sha1Key = aki.raw.slice(4, 24); - var ski = extensions.subjectKeyIdentifier; - ski.sha1Key = ski.decoded; - var ku = extensions.keyUsage; + // var aki = extensions.authorityKeyIdentifier; + // aki.sha1Key = aki.raw.slice(4, 24); + // var ski = extensions.subjectKeyIdentifier; + // ski.sha1Key = ski.decoded; + // var ku = extensions.keyUsage; // Next Extensions: - var nextensions = rfc5280.decodeExtensions(nc, 'der', { partial: false }); - var nextensionsVerified = nextensions.verified; - var naki = nextensions.authorityKeyIdentifier; - naki.sha1Key = naki.raw.slice(4, 24); - var nski = nextensions.subjectKeyIdentifier; - nski.sha1Key = nski.decoded; - var nku = nextensions.keyUsage; + // var nextensions = rfc5280.decodeExtensions(nc, 'der', { partial: false }); + // var nextensionsVerified = nextensions.verified; + // var naki = nextensions.authorityKeyIdentifier; + // naki.sha1Key = naki.raw.slice(4, 24); + // var nski = nextensions.subjectKeyIdentifier; + // nski.sha1Key = nski.decoded; + // var nku = nextensions.keyUsage; // Object.keys(extensions).forEach(function(key) { // if (extensions[key].execute) { @@ -177,17 +191,66 @@ PayPro.prototype.x509Verify = function() { // // Create a To-Be-Signed Certificate to verify using asn1.js: - var tbs = rfc3280.TBSCertificate.encode(c.tbsCertificate, 'der'); + //var tbs = rfc3280.TBSCertificate.encode(c.tbsCertificate, 'der'); + var start = 0; + var starts = 0; + for (var start = 0; start < data.length; start++) { + if (starts === 1 && data[start] === 48) { + break; + } + if (starts < 1 && data[start] === 48) { + starts++; + } + } + + var end = 0; + var ends = 0; + for (var end = data.length - 1; end > 0; end--) { + if (ends === 2 && data[end] === 48) { + break; + } + if (ends < 2 && data[end] === 0) { + ends++; + } + } + + console.log(Array.prototype.slice.call(data.slice(1040))); + console.log(Array.prototype.slice.call(data.slice(0, 10))); + console.log(data[1039]); + + // start: 4 + // end: 1040 + /* + for (var start = 0; start < data.length; start++) { + for (var end = 0; end < data.length + 1; end++) { + var tbs = data.slice(start, end); + var verifier = crypto.createVerify('RSA-' + sigAlg); + verifier.update(tbs); + var sigVerified = verifier.verify(npubKey, sig); + if (sigVerified) { + console.log('start: %d', start); + console.log('end: %d', end); + } + } + } + */ + + var tbs = data.slice(start, end); + var verifier = crypto.createVerify('RSA-' + sigAlg); verifier.update(tbs); var sigVerified = verifier.verify(npubKey, sig); + console.log('sigVerified: %s', sigVerified); + return validityVerified && issuerVerified && extensionsVerified && sigVerified; }); + console.log('verified && chainVerified:', verified && chainVerified); + return verified && chainVerified; }; From 12c56854b9d7b854e7e3a1335b82638962b4777b Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 28 Aug 2014 16:52:21 -0700 Subject: [PATCH 51/60] paypro: more comments. debug. --- lib/PayPro.js | 92 ++++++++------------------------------------------- 1 file changed, 14 insertions(+), 78 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 5add732..6534fab 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -64,28 +64,6 @@ PayPro.prototype.x509Verify = function() { var chain = pki_data; -/* - var anchor = rfc3280.Certificate.decode( - new Buffer(chain[0].toString('hex'), 'hex'), 'der'); - var anSigAlg = PayPro.getAlgorithm(anchor.signatureAlgorithm.algorithm, 1); - var anSig = anchor.signature.data; - - var ca = rfc3280.Certificate.decode( - new Buffer(chain[chain.length - 1].toString('hex'), 'hex'), 'der'); - var caPubKeyAlg = PayPro.getAlgorithm( - ca.tbsCertificate.subjectPublicKeyInfo.algorithm.algorithm); - var caPubKey = ca.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey.data; - caPubKey = self._DERtoPEM(caPubKey, caPubKeyAlg + ' PUBLIC KEY'); -*/ - - // Verifying the cert chain: - // 1. Extract public key from next certificate. - // 2. Extract signature from current certificate. - // 3. If current cert is not trusted, verify that the current cert is signed - // by NEXT by the certificate. - // NOTE: What to do when the certificate is - // revoked -> Hit CRL Distribution Points URL - var chainVerified = chain.every(function(cert, i) { var der = cert.toString('hex'); var pem = self._DERtoPEM(der, 'CERTIFICATE'); @@ -155,43 +133,13 @@ PayPro.prototype.x509Verify = function() { }); }); - // - // Handle Cert Extensions - // - var extensions = rfc5280.decodeExtensions(c, 'der', { partial: false }); - var extensionsVerified = extensions.verified; - - // The two most important extensions: - // "The keyIdentifier field of the authorityKeyIdentifier extension MUST be - // included in all certificates generated by conforming CAs to facilitate - // certification path construction." - // var aki = extensions.authorityKeyIdentifier; - // aki.sha1Key = aki.raw.slice(4, 24); - // var ski = extensions.subjectKeyIdentifier; - // ski.sha1Key = ski.decoded; - // var ku = extensions.keyUsage; - - // Next Extensions: - // var nextensions = rfc5280.decodeExtensions(nc, 'der', { partial: false }); - // var nextensionsVerified = nextensions.verified; - // var naki = nextensions.authorityKeyIdentifier; - // naki.sha1Key = naki.raw.slice(4, 24); - // var nski = nextensions.subjectKeyIdentifier; - // nski.sha1Key = nski.decoded; - // var nku = nextensions.keyUsage; - - // Object.keys(extensions).forEach(function(key) { - // if (extensions[key].execute) { - // c = extensions[key].execute(c); - // } - // }); - // // Verify current certificate signature // - // Create a To-Be-Signed Certificate to verify using asn1.js: - //var tbs = rfc3280.TBSCertificate.encode(c.tbsCertificate, 'der'); + // Grab the raw DER To-Be-Signed Certificate to verify: + // First 10 bytes usually look like: + // [ 48, 130, 5, 32, 48, 130, 4, 8, 160, 3 ] var start = 0; var starts = 0; for (var start = 0; start < data.length; start++) { @@ -203,6 +151,14 @@ PayPro.prototype.x509Verify = function() { } } + // The bytes *after* the TBS (including the last TBS byte) will look like + // (note the 48 - the start of the sig, and the 122 - the end of the TBS): + // [ 122, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 11, 5, 0, 3, ... ] + + // The certificate in these examples has a `start` of 4, and an `end` of + // 1040. The 4 bytes is the DER SEQ of the Certificate, right before the + // SEQ of the TBSCertificate. + var end = 0; var ends = 0; for (var end = data.length - 1; end > 0; end--) { @@ -214,26 +170,9 @@ PayPro.prototype.x509Verify = function() { } } - console.log(Array.prototype.slice.call(data.slice(1040))); - console.log(Array.prototype.slice.call(data.slice(0, 10))); - console.log(data[1039]); - - // start: 4 - // end: 1040 - /* - for (var start = 0; start < data.length; start++) { - for (var end = 0; end < data.length + 1; end++) { - var tbs = data.slice(start, end); - var verifier = crypto.createVerify('RSA-' + sigAlg); - verifier.update(tbs); - var sigVerified = verifier.verify(npubKey, sig); - if (sigVerified) { - console.log('start: %d', start); - console.log('end: %d', end); - } - } - } - */ + console.log(Array.prototype.slice.call(data.slice(end - 1))); + console.log(Array.prototype.slice.call(data.slice(0, start + 6))); + console.log('start=%d, end=%d', start, end); var tbs = data.slice(start, end); @@ -241,11 +180,8 @@ PayPro.prototype.x509Verify = function() { verifier.update(tbs); var sigVerified = verifier.verify(npubKey, sig); - console.log('sigVerified: %s', sigVerified); - return validityVerified && issuerVerified - && extensionsVerified && sigVerified; }); From a39aeeb446c4a4cabe47435663c4d302c237dd20 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 28 Aug 2014 17:13:02 -0700 Subject: [PATCH 52/60] paypro: move tbs parsing into common. --- lib/PayPro.js | 45 ++++---------------------------------------- lib/common/PayPro.js | 41 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 41 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 6534fab..024f8a4 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -134,47 +134,12 @@ PayPro.prototype.x509Verify = function() { }); // - // Verify current certificate signature + // Verify current Certificate signature // - // Grab the raw DER To-Be-Signed Certificate to verify: - // First 10 bytes usually look like: - // [ 48, 130, 5, 32, 48, 130, 4, 8, 160, 3 ] - var start = 0; - var starts = 0; - for (var start = 0; start < data.length; start++) { - if (starts === 1 && data[start] === 48) { - break; - } - if (starts < 1 && data[start] === 48) { - starts++; - } - } - - // The bytes *after* the TBS (including the last TBS byte) will look like - // (note the 48 - the start of the sig, and the 122 - the end of the TBS): - // [ 122, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 11, 5, 0, 3, ... ] - - // The certificate in these examples has a `start` of 4, and an `end` of - // 1040. The 4 bytes is the DER SEQ of the Certificate, right before the - // SEQ of the TBSCertificate. - - var end = 0; - var ends = 0; - for (var end = data.length - 1; end > 0; end--) { - if (ends === 2 && data[end] === 48) { - break; - } - if (ends < 2 && data[end] === 0) { - ends++; - } - } - - console.log(Array.prototype.slice.call(data.slice(end - 1))); - console.log(Array.prototype.slice.call(data.slice(0, start + 6))); - console.log('start=%d, end=%d', start, end); - - var tbs = data.slice(start, end); + // Get the raw DER TBSCertificate + // from the DER Certificate: + var tbs = PayPro.getTBSCertificate(data); var verifier = crypto.createVerify('RSA-' + sigAlg); verifier.update(tbs); @@ -185,8 +150,6 @@ PayPro.prototype.x509Verify = function() { && sigVerified; }); - console.log('verified && chainVerified:', verified && chainVerified); - return verified && chainVerified; }; diff --git a/lib/common/PayPro.js b/lib/common/PayPro.js index 4b53e8d..b360f1c 100644 --- a/lib/common/PayPro.js +++ b/lib/common/PayPro.js @@ -52,6 +52,47 @@ PayPro.getAlgorithm = function(value, index) { return value; }; +// Grab the raw DER To-Be-Signed Certificate +// from a DER Certificate to verify +PayPro.getTBSCertificate = function(data) { + // We start by slicing off the first SEQ of the + // Certificate (TBSCertificate is its own SEQ). + + // The first 10 bytes usually look like: + // [ 48, 130, 5, 32, 48, 130, 4, 8, 160, 3 ] + var start = 0; + var starts = 0; + for (var start = 0; start < data.length; start++) { + if (starts === 1 && data[start] === 48) { + break; + } + if (starts < 1 && data[start] === 48) { + starts++; + } + } + + // The bytes *after* the TBS (including the last TBS byte) will look like + // (note the 48 - the start of the sig, and the 122 - the end of the TBS): + // [ 122, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 11, 5, 0, 3, ... ] + + // The certificate in these examples has a `start` of 4, and an `end` of + // 1040. The 4 bytes is the DER SEQ of the Certificate, right before the + // SEQ of the TBSCertificate. + var end = 0; + var ends = 0; + for (var end = data.length - 1; end > 0; end--) { + if (ends === 2 && data[end] === 48) { + break; + } + if (ends < 2 && data[end] === 0) { + ends++; + } + } + + // Return our raw DER TBSCertificate: + return data.slice(start, end); +}; + PayPro.RootCerts = RootCerts; PayPro.proto = {}; From eba2825f5a89a3686cfadb0a33ef116c2112cd80 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 28 Aug 2014 17:32:13 -0700 Subject: [PATCH 53/60] paypro: get chain validation working in the browser. --- lib/PayPro.js | 1 - lib/browser/PayPro.js | 83 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 66 insertions(+), 18 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 024f8a4..9c51a44 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -8,7 +8,6 @@ var PayPro = require('./common/PayPro'); var asn1 = require('asn1.js'); var rfc3280 = require('asn1.js/rfc/3280'); -var rfc5280 = require('asn1.js/rfc/5280'); PayPro.prototype.x509Sign = function(key) { var self = this; diff --git a/lib/browser/PayPro.js b/lib/browser/PayPro.js index 202e139..65b1a0b 100644 --- a/lib/browser/PayPro.js +++ b/lib/browser/PayPro.js @@ -5,6 +5,7 @@ var KJUR = require('jsrsasign'); var assert = require('assert'); var PayPro = require('../common/PayPro'); var RootCerts = require('../common/RootCerts'); + var asn1 = require('asn1.js'); var rfc3280 = require('asn1.js/rfc/3280'); @@ -68,6 +69,7 @@ PayPro.prototype.x509Verify = function(key) { var signedCert = pki_data[0]; var der = signedCert.toString('hex'); + // var pem = self._DERtoPEM(der, 'CERTIFICATE'); var pem = KJUR.asn1.ASN1Util.getPEMStringFromHex(der, 'CERTIFICATE'); jsrsaSig.initVerifyByCertificatePEM(pem); jsrsaSig.updateHex(buf.toString('hex')); @@ -75,15 +77,9 @@ PayPro.prototype.x509Verify = function(key) { var chain = pki_data; - // Verifying the cert chain: - // 1. Extract public key from next certificate. - // 2. Extract signature from current certificate. - // 3. If current cert is not trusted, verify that the current cert is signed - // by NEXT by the certificate. - // NOTE: XXX What to do when the certificate is revoked? - var chainVerified = chain.every(function(cert, i) { var der = cert.toString('hex'); + // var pem = self._DERtoPEM(der, 'CERTIFICATE'); var pem = KJUR.asn1.ASN1Util.getPEMStringFromHex(der, 'CERTIFICATE'); var name = RootCerts.getTrusted(pem); @@ -97,15 +93,22 @@ PayPro.prototype.x509Verify = function(key) { return true; } var nder = ncert.toString('hex'); + // var npem = self._DERtoPEM(nder, 'CERTIFICATE'); var npem = KJUR.asn1.ASN1Util.getPEMStringFromHex(nder, 'CERTIFICATE'); - // Get public key from next certificate: - // var data = new Buffer(nder, 'hex'); - // var nc = rfc3280.Certificate.decode(data, 'der'); + // + // Get Public Key from next certificate: + // + // var ndata = new Buffer(nder, 'hex'); + // var nc = rfc3280.Certificate.decode(ndata, 'der'); + // var npubKeyAlg = PayPro.getAlgorithm( + // nc.tbsCertificate.subjectPublicKeyInfo.algorithm.algorithm); // var npubKey = nc.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey.data; - // npubKey = KJUR.asn1.ASN1Util.getPEMStringFromHex(npubKey, 'RSA PUBLIC KEY'); + // // npubKey = self._DERtoPEM(npubKey, npubKeyAlg + ' PUBLIC KEY'); + // // npubKey = KJUR.asn1.ASN1Util.getPEMStringFromHex(npubKey.toString('hex'), 'PUBLIC KEY'); + // npubKey = KJUR.asn1.ASN1Util.getPEMStringFromHex(npubKey.toString('hex'), npubKeyAlg + ' PUBLIC KEY'); - // Get public key from next certificate via KJUR: + // Get public key from next certificate via KJUR since sane methods don't work: var js = new KJUR.crypto.Signature({ alg: type + 'withRSA', prov: 'cryptojs/jsrsa' @@ -113,23 +116,69 @@ PayPro.prototype.x509Verify = function(key) { js.initVerifyByCertificatePEM(npem); var npubKey = js.pubKey; - // Get signature from current certificate: + // + // Get Signature Value from current certificate: + // var data = new Buffer(der, 'hex'); var c = rfc3280.Certificate.decode(data, 'der'); + var sigAlg = PayPro.getAlgorithm(c.signatureAlgorithm.algorithm, 1); var sig = c.signature.data; + // + // Check Validity of Certificates + // + var validityVerified = true; + var now = Date.now(); + var cBefore = c.tbsCertificate.validity.notBefore.value; + var cAfter = c.tbsCertificate.validity.notAfter.value; + var nBefore = nc.tbsCertificate.validity.notBefore.value; + var nAfter = nc.tbsCertificate.validity.notAfter.value; + if (cBefore > now || cAfter < now || nBefore > now || nAfter < now) { + validityVerified = false; + } + + // + // Check the Issuer matches the Subject of the next certificate: + // + var issuer = c.tbsCertificate.issuer; + var subject = nc.tbsCertificate.subject; + var issuerVerified = issuer.type === subject.type && issuer.value.every(function(issuerArray, i) { + var subjectArray = subject.value[i]; + return issuerArray.every(function(issuerObject, i) { + var subjectObject = subjectArray[i]; + + var issuerObjectType = issuerObject.type.join('.'); + var subjectObjectType = subjectObject.type.join('.'); + + var issuerObjectValue = issuerObject.value.toString('hex'); + var subjectObjectValue = subjectObject.value.toString('hex'); + + return issuerObjectType === subjectObjectType + && issuerObjectValue === subjectObjectValue; + }); + }); + + // + // Verify current Certificate signature + // + var jsrsaSig = new KJUR.crypto.Signature({ alg: type + 'withRSA', prov: 'cryptojs/jsrsa' }); jsrsaSig.initVerifyByPublicKey(npubKey); - // Create a To-Be-Signed Certificate to verify using asn1.js: - // Fails at Issuer: - var tbs = rfc3280.TBSCertificate.encode(c.tbsCertificate, 'der'); + // Get the raw DER TBSCertificate + // from the DER Certificate: + var tbs = PayPro.getTBSCertificate(data); + jsrsaSig.updateHex(tbs.toString('hex')); - return jsrsaSig.verify(sig.toString('hex')); + var sigVerified = jsrsaSig.verify(sig.toString('hex')); + + return validityVerified + && issuerVerified + && sigVerified; }); return verified && chainVerified; From f98ebe69d8df073a4615f6888c1ba098eb0c2322 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 28 Aug 2014 17:37:06 -0700 Subject: [PATCH 54/60] paypro: move validity and issuer validation into common. --- lib/PayPro.js | 28 ++-------------------------- lib/browser/PayPro.js | 28 ++-------------------------- lib/common/PayPro.js | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 52 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 9c51a44..471992f 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -101,36 +101,12 @@ PayPro.prototype.x509Verify = function() { // // Check Validity of Certificates // - var validityVerified = true; - var now = Date.now(); - var cBefore = c.tbsCertificate.validity.notBefore.value; - var cAfter = c.tbsCertificate.validity.notAfter.value; - var nBefore = nc.tbsCertificate.validity.notBefore.value; - var nAfter = nc.tbsCertificate.validity.notAfter.value; - if (cBefore > now || cAfter < now || nBefore > now || nAfter < now) { - validityVerified = false; - } + var validityVerified = PayPro.validateCertTime(c, nc); // // Check the Issuer matches the Subject of the next certificate: // - var issuer = c.tbsCertificate.issuer; - var subject = nc.tbsCertificate.subject; - var issuerVerified = issuer.type === subject.type && issuer.value.every(function(issuerArray, i) { - var subjectArray = subject.value[i]; - return issuerArray.every(function(issuerObject, i) { - var subjectObject = subjectArray[i]; - - var issuerObjectType = issuerObject.type.join('.'); - var subjectObjectType = subjectObject.type.join('.'); - - var issuerObjectValue = issuerObject.value.toString('hex'); - var subjectObjectValue = subjectObject.value.toString('hex'); - - return issuerObjectType === subjectObjectType - && issuerObjectValue === subjectObjectValue; - }); - }); + var issuerVerified = PayPro.validateCertIssuer(c, nc); // // Verify current Certificate signature diff --git a/lib/browser/PayPro.js b/lib/browser/PayPro.js index 65b1a0b..42f3016 100644 --- a/lib/browser/PayPro.js +++ b/lib/browser/PayPro.js @@ -127,36 +127,12 @@ PayPro.prototype.x509Verify = function(key) { // // Check Validity of Certificates // - var validityVerified = true; - var now = Date.now(); - var cBefore = c.tbsCertificate.validity.notBefore.value; - var cAfter = c.tbsCertificate.validity.notAfter.value; - var nBefore = nc.tbsCertificate.validity.notBefore.value; - var nAfter = nc.tbsCertificate.validity.notAfter.value; - if (cBefore > now || cAfter < now || nBefore > now || nAfter < now) { - validityVerified = false; - } + var validityVerified = PayPro.validateCertTime(c, nc); // // Check the Issuer matches the Subject of the next certificate: // - var issuer = c.tbsCertificate.issuer; - var subject = nc.tbsCertificate.subject; - var issuerVerified = issuer.type === subject.type && issuer.value.every(function(issuerArray, i) { - var subjectArray = subject.value[i]; - return issuerArray.every(function(issuerObject, i) { - var subjectObject = subjectArray[i]; - - var issuerObjectType = issuerObject.type.join('.'); - var subjectObjectType = subjectObject.type.join('.'); - - var issuerObjectValue = issuerObject.value.toString('hex'); - var subjectObjectValue = subjectObject.value.toString('hex'); - - return issuerObjectType === subjectObjectType - && issuerObjectValue === subjectObjectValue; - }); - }); + var issuerVerified = PayPro.validateCertIssuer(c, nc); // // Verify current Certificate signature diff --git a/lib/common/PayPro.js b/lib/common/PayPro.js index b360f1c..3813bad 100644 --- a/lib/common/PayPro.js +++ b/lib/common/PayPro.js @@ -93,6 +93,42 @@ PayPro.getTBSCertificate = function(data) { return data.slice(start, end); }; +// Check Validity of Certificates +PayPro.validateCertTime = function(c, nc) { + var validityVerified = true; + var now = Date.now(); + var cBefore = c.tbsCertificate.validity.notBefore.value; + var cAfter = c.tbsCertificate.validity.notAfter.value; + var nBefore = nc.tbsCertificate.validity.notBefore.value; + var nAfter = nc.tbsCertificate.validity.notAfter.value; + if (cBefore > now || cAfter < now || nBefore > now || nAfter < now) { + validityVerified = false; + } + return validityVerified; +}; + +// Check the Issuer matches the Subject of the next certificate: +PayPro.validateCertIssuer = function(c, nc) { + var issuer = c.tbsCertificate.issuer; + var subject = nc.tbsCertificate.subject; + var issuerVerified = issuer.type === subject.type && issuer.value.every(function(issuerArray, i) { + var subjectArray = subject.value[i]; + return issuerArray.every(function(issuerObject, i) { + var subjectObject = subjectArray[i]; + + var issuerObjectType = issuerObject.type.join('.'); + var subjectObjectType = subjectObject.type.join('.'); + + var issuerObjectValue = issuerObject.value.toString('hex'); + var subjectObjectValue = subjectObject.value.toString('hex'); + + return issuerObjectType === subjectObjectType + && issuerObjectValue === subjectObjectValue; + }); + }); + return issuerVerified; +}; + PayPro.RootCerts = RootCerts; PayPro.proto = {}; From e34f9a4061da8f8c7f1dd4e414d87bb6fa953847 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 28 Aug 2014 17:37:41 -0700 Subject: [PATCH 55/60] paypro: remove old browser next pubKey code. --- lib/browser/PayPro.js | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/lib/browser/PayPro.js b/lib/browser/PayPro.js index 42f3016..6c845ef 100644 --- a/lib/browser/PayPro.js +++ b/lib/browser/PayPro.js @@ -97,18 +97,8 @@ PayPro.prototype.x509Verify = function(key) { var npem = KJUR.asn1.ASN1Util.getPEMStringFromHex(nder, 'CERTIFICATE'); // - // Get Public Key from next certificate: + // Get Public Key from next certificate (via KJUR because it's a mess): // - // var ndata = new Buffer(nder, 'hex'); - // var nc = rfc3280.Certificate.decode(ndata, 'der'); - // var npubKeyAlg = PayPro.getAlgorithm( - // nc.tbsCertificate.subjectPublicKeyInfo.algorithm.algorithm); - // var npubKey = nc.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey.data; - // // npubKey = self._DERtoPEM(npubKey, npubKeyAlg + ' PUBLIC KEY'); - // // npubKey = KJUR.asn1.ASN1Util.getPEMStringFromHex(npubKey.toString('hex'), 'PUBLIC KEY'); - // npubKey = KJUR.asn1.ASN1Util.getPEMStringFromHex(npubKey.toString('hex'), npubKeyAlg + ' PUBLIC KEY'); - - // Get public key from next certificate via KJUR since sane methods don't work: var js = new KJUR.crypto.Signature({ alg: type + 'withRSA', prov: 'cryptojs/jsrsa' From f34b98d2533b009f569db2b063590cce94381d48 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 28 Aug 2014 17:50:57 -0700 Subject: [PATCH 56/60] paypro: allow users to return verification properties via PayPro.verify. --- lib/PayPro.js | 9 ++++++++- lib/browser/PayPro.js | 9 ++++++++- lib/common/PayPro.js | 4 ++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 471992f..d5f3f9f 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -41,7 +41,7 @@ PayPro.prototype.x509Sign = function(key) { return sig; }; -PayPro.prototype.x509Verify = function() { +PayPro.prototype.x509Verify = function(returnTrust) { var self = this; var crypto = require('crypto'); var pki_type = this.get('pki_type'); @@ -125,6 +125,13 @@ PayPro.prototype.x509Verify = function() { && sigVerified; }); + if (returnTrust) { + return { + verified: verified, + chainVerified: chainVerified + }; + } + return verified && chainVerified; }; diff --git a/lib/browser/PayPro.js b/lib/browser/PayPro.js index 6c845ef..0d47462 100644 --- a/lib/browser/PayPro.js +++ b/lib/browser/PayPro.js @@ -53,7 +53,7 @@ PayPro.prototype.x509Sign = function(key) { return sig; }; -PayPro.prototype.x509Verify = function(key) { +PayPro.prototype.x509Verify = function(returnTrust) { var sig = this.get('signature'); var pki_type = this.get('pki_type'); var pki_data = this.get('pki_data'); @@ -147,6 +147,13 @@ PayPro.prototype.x509Verify = function(key) { && sigVerified; }); + if (returnTrust) { + return { + verified: verified, + chainVerified: chainVerified + }; + } + return verified && chainVerified; }; diff --git a/lib/common/PayPro.js b/lib/common/PayPro.js index 3813bad..9b151a4 100644 --- a/lib/common/PayPro.js +++ b/lib/common/PayPro.js @@ -337,7 +337,7 @@ PayPro.prototype.sign = function(key) { return this; }; -PayPro.prototype.verify = function() { +PayPro.prototype.verify = function(returnTrust) { if (this.messageType !== 'PaymentRequest') throw new Error('Verifying can only be performed on a PaymentRequest'); @@ -346,7 +346,7 @@ PayPro.prototype.verify = function() { if (pki_type === 'SIN') { return this.sinVerify(); } else if (pki_type === 'x509+sha1' || pki_type === 'x509+sha256') { - return this.x509Verify(); + return this.x509Verify(returnTrust); } else if (pki_type === 'none') { return true; } From dea39d1c7280f02df03bd2a3de0b3957300bf98d Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 28 Aug 2014 18:13:56 -0700 Subject: [PATCH 57/60] paypro: immediately detect self signed certs and untrusted CAs. --- lib/PayPro.js | 44 +++++++++++++++++++++++++++++++++++++++++- lib/browser/PayPro.js | 45 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index d5f3f9f..4645485 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -63,6 +63,44 @@ PayPro.prototype.x509Verify = function(returnTrust) { var chain = pki_data; + // + // Get the CA cert's name + // + var issuer = chain[chain.length - 1]; + der = issuer.toString('hex'); + pem = this._DERtoPEM(der, 'CERTIFICATE'); + var caName = RootCerts.getTrusted(pem); + + if (chain.length === 1 && !caName) { + if (returnTrust) { + return { + selfSigned: 1, // yes + isChain: false, + verified: verified, + caTrusted: false, + caName: null, + chainVerified: false + }; + } + return verified; + } + + // If there's no trusted root cert, don't + // bother validating the cert chain. + if (!caName) { + if (returnTrust) { + return { + selfSigned: -1, // unknown + isChain: chain.length > 1, + verified: verified, + caTrusted: false, + caName: null, + chainVerified: false + }; + } + return verified; + } + var chainVerified = chain.every(function(cert, i) { var der = cert.toString('hex'); var pem = self._DERtoPEM(der, 'CERTIFICATE'); @@ -71,7 +109,7 @@ PayPro.prototype.x509Verify = function(returnTrust) { var ncert = chain[i + 1]; // The root cert, check if it's trusted: if (!ncert || name) { - if (!ncert && !name) { + if (!name) { return false; } chain.length = 0; @@ -127,7 +165,11 @@ PayPro.prototype.x509Verify = function(returnTrust) { if (returnTrust) { return { + selfSigned: 0, // no + isChain: true, verified: verified, + caTrusted: !!caName, + caName: caName || null, chainVerified: chainVerified }; } diff --git a/lib/browser/PayPro.js b/lib/browser/PayPro.js index 0d47462..2245109 100644 --- a/lib/browser/PayPro.js +++ b/lib/browser/PayPro.js @@ -77,6 +77,45 @@ PayPro.prototype.x509Verify = function(returnTrust) { var chain = pki_data; + // + // Get the CA cert's name + // + var issuer = chain[chain.length - 1]; + der = issuer.toString('hex'); + // pem = this._DERtoPEM(der, 'CERTIFICATE'); + pem = KJUR.asn1.ASN1Util.getPEMStringFromHex(der, 'CERTIFICATE'); + var caName = RootCerts.getTrusted(pem); + + if (chain.length === 1 && !caName) { + if (returnTrust) { + return { + selfSigned: 1, // yes + isChain: false, + verified: verified, + caTrusted: false, + caName: null, + chainVerified: false + }; + } + return verified; + } + + // If there's no trusted root cert, don't + // bother validating the cert chain. + if (!caName) { + if (returnTrust) { + return { + selfSigned: -1, // unknown + isChain: chain.length > 1, + verified: verified, + caTrusted: false, + caName: null, + chainVerified: false + }; + } + return verified; + } + var chainVerified = chain.every(function(cert, i) { var der = cert.toString('hex'); // var pem = self._DERtoPEM(der, 'CERTIFICATE'); @@ -86,7 +125,7 @@ PayPro.prototype.x509Verify = function(returnTrust) { var ncert = chain[i + 1]; // The root cert, check if it's trusted: if (!ncert || name) { - if (!ncert && !name) { + if (!name) { return false; } chain.length = 0; @@ -149,7 +188,11 @@ PayPro.prototype.x509Verify = function(returnTrust) { if (returnTrust) { return { + selfSigned: 0, // no + isChain: true, verified: verified, + caTrusted: !!caName, + caName: caName || null, chainVerified: chainVerified }; } From 18c38ae67aa35a386c07593c9c6a3b04d5e13473 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 28 Aug 2014 18:58:58 -0700 Subject: [PATCH 58/60] paypro: add returnTrust to sign(). minor improvements. --- lib/PayPro.js | 50 ++++++++++++++++++++++++------------------- lib/browser/PayPro.js | 38 ++++++++++++++++++-------------- lib/common/PayPro.js | 7 ++++-- 3 files changed, 55 insertions(+), 40 deletions(-) diff --git a/lib/PayPro.js b/lib/PayPro.js index 4645485..eb0cd7a 100644 --- a/lib/PayPro.js +++ b/lib/PayPro.js @@ -9,35 +9,41 @@ var PayPro = require('./common/PayPro'); var asn1 = require('asn1.js'); var rfc3280 = require('asn1.js/rfc/3280'); -PayPro.prototype.x509Sign = function(key) { +PayPro.prototype.x509Sign = function(key, returnTrust) { var self = this; var crypto = require('crypto'); var pki_type = this.get('pki_type'); - var pki_data = this.get('pki_data'); // contains one or more x509 certs + var pki_data = this.get('pki_data'); pki_data = PayPro.X509Certificates.decode(pki_data); pki_data = pki_data.certificate; var details = this.get('serialized_payment_details'); var type = pki_type.split('+')[1].toUpperCase(); - var trusted = pki_data.map(function(cert) { - var der = cert.toString('hex'); - var pem = self._DERtoPEM(der, 'CERTIFICATE'); - return RootCerts.getTrusted(pem); - }); - - // XXX Figure out what to do here - if (!trusted.length) { - // throw new Error('Unstrusted certificate.'); - } else { - trusted.forEach(function(name) { - // console.log('Certificate: %s', name); - }); - } - var signature = crypto.createSign('RSA-' + type); var buf = this.serializeForSig(); signature.update(buf); var sig = signature.sign(key); + + if (returnTrust) { + var cert = pki_data[pki_data.length - 1]; + var der = cert.toString('hex'); + var pem = PayPro.DERtoPEM(der, 'CERTIFICATE'); + var caName = RootCerts.getTrusted(pem); + var selfSigned = 0; + if (!caName) { + selfSigned = pki_data.length > 1 + ? -1 + : 1; + } + return { + selfSigned: selfSigned, + isChain: pki_data.length > 1, + signature: sig, + caTrusted: !!caName, + caName: caName || null + }; + } + return sig; }; @@ -58,7 +64,7 @@ PayPro.prototype.x509Verify = function(returnTrust) { var signedCert = pki_data[0]; var der = signedCert.toString('hex'); - var pem = this._DERtoPEM(der, 'CERTIFICATE'); + var pem = PayPro.DERtoPEM(der, 'CERTIFICATE'); var verified = verifier.verify(pem, sig); var chain = pki_data; @@ -68,7 +74,7 @@ PayPro.prototype.x509Verify = function(returnTrust) { // var issuer = chain[chain.length - 1]; der = issuer.toString('hex'); - pem = this._DERtoPEM(der, 'CERTIFICATE'); + pem = PayPro.DERtoPEM(der, 'CERTIFICATE'); var caName = RootCerts.getTrusted(pem); if (chain.length === 1 && !caName) { @@ -103,7 +109,7 @@ PayPro.prototype.x509Verify = function(returnTrust) { var chainVerified = chain.every(function(cert, i) { var der = cert.toString('hex'); - var pem = self._DERtoPEM(der, 'CERTIFICATE'); + var pem = PayPro.DERtoPEM(der, 'CERTIFICATE'); var name = RootCerts.getTrusted(pem); var ncert = chain[i + 1]; @@ -116,7 +122,7 @@ PayPro.prototype.x509Verify = function(returnTrust) { return true; } var nder = ncert.toString('hex'); - var npem = self._DERtoPEM(nder, 'CERTIFICATE'); + var npem = PayPro.DERtoPEM(nder, 'CERTIFICATE'); // // Get Public Key from next certificate: @@ -126,7 +132,7 @@ PayPro.prototype.x509Verify = function(returnTrust) { var npubKeyAlg = PayPro.getAlgorithm( nc.tbsCertificate.subjectPublicKeyInfo.algorithm.algorithm); var npubKey = nc.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey.data; - npubKey = self._DERtoPEM(npubKey, npubKeyAlg + ' PUBLIC KEY'); + npubKey = PayPro.DERtoPEM(npubKey, npubKeyAlg + ' PUBLIC KEY'); // // Get Signature Value from current certificate: diff --git a/lib/browser/PayPro.js b/lib/browser/PayPro.js index 2245109..ce0ffb9 100644 --- a/lib/browser/PayPro.js +++ b/lib/browser/PayPro.js @@ -13,7 +13,7 @@ var rfc3280 = require('asn1.js/rfc/3280'); // http://kjur.github.io/jsrsasign/api/symbols/KJUR.crypto.Signature.html#.sign // http://kjur.github.io/jsrsasign/api/symbols/RSAKey.html -PayPro.prototype.x509Sign = function(key) { +PayPro.prototype.x509Sign = function(key, returnTrust) { var pki_type = this.get('pki_type'); var pki_data = this.get('pki_data'); // contains one or more x509 certs pki_data = PayPro.X509Certificates.decode(pki_data); @@ -21,21 +21,6 @@ PayPro.prototype.x509Sign = function(key) { var type = pki_type.split('+')[1].toUpperCase(); var buf = this.serializeForSig(); - var trusted = pki_data.map(function(cert) { - var der = cert.toString('hex'); - var pem = KJUR.asn1.ASN1Util.getPEMStringFromHex(der, 'CERTIFICATE'); - return RootCerts.getTrusted(pem); - }); - - // XXX Figure out what to do here - if (!trusted.length) { - // throw new Error('Unstrusted certificate.'); - } else { - trusted.forEach(function(name) { - // console.log('Certificate: %s', name); - }); - } - var rsa = new KJUR.RSAKey(); rsa.readPrivateKeyFromPEMString(key.toString()); key = rsa; @@ -50,6 +35,27 @@ PayPro.prototype.x509Sign = function(key) { jsrsaSig.updateHex(buf.toString('hex')); var sig = new Buffer(jsrsaSig.sign(), 'hex'); + + if (returnTrust) { + var cert = pki_data[pki_data.length - 1]; + var der = cert.toString('hex'); + var pem = KJUR.asn1.ASN1Util.getPEMStringFromHex(der, 'CERTIFICATE'); + var caName = RootCerts.getTrusted(pem); + var selfSigned = 0; + if (!caName) { + selfSigned = pki_data.length > 1 + ? -1 + : 1; + } + return { + selfSigned: selfSigned, + isChain: pki_data.length > 1, + signature: sig, + caTrusted: !!caName, + caName: caName || null + }; + } + return sig; }; diff --git a/lib/common/PayPro.js b/lib/common/PayPro.js index 9b151a4..2f62b84 100644 --- a/lib/common/PayPro.js +++ b/lib/common/PayPro.js @@ -316,7 +316,7 @@ PayPro.prototype.deserialize = function(buf, messageType) { return this; }; -PayPro.prototype.sign = function(key) { +PayPro.prototype.sign = function(key, returnTrust) { if (this.messageType !== 'PaymentRequest') throw new Error('Signing can only be performed on a PaymentRequest'); @@ -325,7 +325,7 @@ PayPro.prototype.sign = function(key) { if (pki_type === 'SIN') { var sig = this.sinSign(key); } else if (pki_type === 'x509+sha1' || pki_type === 'x509+sha256') { - var sig = this.x509Sign(key); + var sig = this.x509Sign(key, returnTrust); } else if (pki_type === 'none') { return this; } else { @@ -371,10 +371,12 @@ PayPro.prototype.sinVerify = function() { // Helpers +PayPro.PEMtoDER = PayPro.prototype._PEMtoDER = function(pem) { return this._PEMtoDERParam(pem); }; +PayPro.PEMtoDERParam = PayPro.prototype._PEMtoDERParam = function(pem, param) { if (Buffer.isBuffer(pem)) { pem = pem.toString(); @@ -392,6 +394,7 @@ PayPro.prototype._PEMtoDERParam = function(pem, param) { }).filter(Boolean); }; +PayPro.DERtoPEM = PayPro.prototype._DERtoPEM = function(der, type) { if (typeof der === 'string') { der = new Buffer(der, 'hex'); From 5c643e0be6a534a7594a6e54ece45f40a414e2f2 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 28 Aug 2014 19:22:48 -0700 Subject: [PATCH 59/60] paypro: fix uncatchable error in browser paypro. --- lib/browser/PayPro.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/browser/PayPro.js b/lib/browser/PayPro.js index ce0ffb9..3058284 100644 --- a/lib/browser/PayPro.js +++ b/lib/browser/PayPro.js @@ -141,6 +141,12 @@ PayPro.prototype.x509Verify = function(returnTrust) { // var npem = self._DERtoPEM(nder, 'CERTIFICATE'); var npem = KJUR.asn1.ASN1Util.getPEMStringFromHex(nder, 'CERTIFICATE'); + // + // Get Next Certificate: + // + var ndata = new Buffer(nder, 'hex'); + var nc = rfc3280.Certificate.decode(ndata, 'der'); + // // Get Public Key from next certificate (via KJUR because it's a mess): // From 62f41aa45d020c5805e1330a9d668769aaf6ade8 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 28 Aug 2014 19:23:58 -0700 Subject: [PATCH 60/60] test: add paypro test to verify a real PaymentRequest. --- test/index.html | 3 + test/test.PayPro.js | 299 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 302 insertions(+) diff --git a/test/index.html b/test/index.html index 93ec2d8..ce88748 100644 --- a/test/index.html +++ b/test/index.html @@ -11,6 +11,9 @@ + + + diff --git a/test/test.PayPro.js b/test/test.PayPro.js index 70969c5..e362801 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -5,6 +5,9 @@ var should = chai.should(); var expect = chai.expect; var bitcore = bitcore || require('../bitcore'); +var is_browser = typeof process == 'undefined' + || typeof process.versions === 'undefined'; + var PayPro = bitcore.PayPro; var Key = bitcore.Key; @@ -95,6 +98,184 @@ x509.pub = new Buffer(x509.pub, 'base64'); x509.der = new Buffer(x509.der, 'base64'); x509.pem = new Buffer(x509.pem, 'base64'); +// A test PaymentRequest (with a full cert chain) from test.bitpay.com: + +var bitpayRequest = new Buffer('' + + '0801120b783530392b7368613235361a89250aa40a3082052030820408a0' + + '03020102020727a49d05046d62300d06092a864886f70d01010b05003081' + + 'b4310b30090603550406130255533110300e060355040813074172697a6f' + + '6e61311330110603550407130a53636f74747364616c65311a3018060355' + + '040a1311476f44616464792e636f6d2c20496e632e312d302b060355040b' + + '1324687474703a2f2f63657274732e676f64616464792e636f6d2f726570' + + '6f7369746f72792f313330310603550403132a476f204461646479205365' + + '6375726520436572746966696361746520417574686f72697479202d2047' + + '32301e170d3134303432363132333532365a170d31363034323631323335' + + '32365a303a3121301f060355040b1318446f6d61696e20436f6e74726f6c' + + '2056616c6964617465643115301306035504030c0c2a2e6269747061792e' + + '636f6d30820122300d06092a864886f70d01010105000382010f00308201' + + '0a0282010100e2a5dd4aea959c1d0fb016e6e05bb7011e741cdc61918c61' + + 'f9625a2f682f485f0e862ea63db61cc9161753127504de800604df36b10f' + + '46cb17ab6cb99dba8aa45a36adfb901a2fc380c89e234bce18de6639b883' + + 'e9339801673efaee1f2df77eeb82f7c39c96a2f8ef4572b634c203d9be8f' + + 'd1e0036d32fb38b6b9b5ecd5a0684345c7e9ffc5d26bc6fd69aa6619f77b' + + 'adaa4bfb989478fb2f41aa92782e40b34ba9ac4549a4e6fda76b5fc4a581' + + '853bd0de5fb5a2c6dfdc12cdfadb54e9636a6d1223705924b8be566b81ac' + + '7921078cf590a146ae397a84908ef4fc83ff5715a44ab59e9258674d9011' + + '3bb607b8d81eb268e4c6ce849497c76521795b0873950203010001a38201' + + 'ae308201aa300f0603551d130101ff04053003010100301d0603551d2504' + + '16301406082b0601050507030106082b06010505070302300e0603551d0f' + + '0101ff0404030205a030360603551d1f042f302d302ba029a02786256874' + + '74703a2f2f63726c2e676f64616464792e636f6d2f676469673273312d34' + + '392e63726c30530603551d20044c304a3048060b6086480186fd6d010717' + + '013039303706082b06010505070201162b687474703a2f2f636572746966' + + '6963617465732e676f64616464792e636f6d2f7265706f7369746f72792f' + + '307606082b06010505070101046a3068302406082b060105050730018618' + + '687474703a2f2f6f6373702e676f64616464792e636f6d2f304006082b06' + + '0105050730028634687474703a2f2f6365727469666963617465732e676f' + + '64616464792e636f6d2f7265706f7369746f72792f67646967322e637274' + + '301f0603551d2304183016801440c2bd278ecc348330a233d7fb6cb3f0b4' + + '2c80ce30230603551d11041c301a820c2a2e6269747061792e636f6d820a' + + '6269747061792e636f6d301d0603551d0e0416041485454e3b4072e2f58e' + + '377438988b5229387e967a300d06092a864886f70d01010b050003820101' + + '002d0a7ef97f988905ebbbad4e9ffb690352535211d6792516119838b55f' + + '24ff9fa4e93b6187b8517cbb0477457d3378078ef66057abe41bcafeb142' + + 'ec52443a94b88114fa069f725c6198581d97af16352727f4f35e7f2110fa' + + 'a41a0511bcfdf8e3f4a3a310278c150b10f32a962c81e8f3d5374d9cb56d' + + '893027ff4fa4e3c3e6384c1f1557ceea6fca9cbc0c110748c08b82d8f0ed' + + '9a579637ee43a2d8fec3b5b04d1f3c8f1a3e2088da2274b6bc60948bbe74' + + '4a7f8b942b41f0ae9b4afaeefbb7e0f04a0587b52efb6ebfa2d970b9de56' + + 'a068575e4bf0cf824618dc17bbeaa2cdd25d65970a9f1a06fc9fffb466a1' + + '0c9568cd651795bc2c7996975027bdbaba0ad409308204d0308203b8a003' + + '020102020107300d06092a864886f70d01010b0500308183310b30090603' + + '550406130255533110300e060355040813074172697a6f6e613113301106' + + '03550407130a53636f74747364616c65311a3018060355040a1311476f44' + + '616464792e636f6d2c20496e632e3131302f06035504031328476f204461' + + '64647920526f6f7420436572746966696361746520417574686f72697479' + + '202d204732301e170d3131303530333037303030305a170d333130353033' + + '3037303030305a3081b4310b30090603550406130255533110300e060355' + + '040813074172697a6f6e61311330110603550407130a53636f7474736461' + + '6c65311a3018060355040a1311476f44616464792e636f6d2c20496e632e' + + '312d302b060355040b1324687474703a2f2f63657274732e676f64616464' + + '792e636f6d2f7265706f7369746f72792f313330310603550403132a476f' + + '204461646479205365637572652043657274696669636174652041757468' + + '6f72697479202d20473230820122300d06092a864886f70d010101050003' + + '82010f003082010a0282010100b9e0cb10d4af76bdd49362eb3064b88108' + + '6cc304d962178e2fff3e65cf8fce62e63c521cda16454b55ab786b638362' + + '90ce0f696c99c81a148b4ccc4533ea88dc9ea3af2bfe80619d7957c4cf2e' + + 'f43f303c5d47fc9a16bcc3379641518e114b54f828bed08cbef030381ef3' + + 'b026f86647636dde7126478f384753d1461db4e3dc00ea45acbdbc71d9aa' + + '6f00dbdbcd303a794f5f4c47f81def5bc2c49d603bb1b24391d8a4334eea' + + 'b3d6274fad258aa5c6f4d5d0a6ae7405645788b54455d42d2a3a3ef8b8bd' + + 'e9320a029464c4163a50f14aaee77933af0c20077fe8df0439c269026c63' + + '52fa77c11bc87487c8b993185054354b694ebc3bd3492e1fdcc1d252fb02' + + '03010001a382011a30820116300f0603551d130101ff040530030101ff30' + + '0e0603551d0f0101ff040403020106301d0603551d0e0416041440c2bd27' + + '8ecc348330a233d7fb6cb3f0b42c80ce301f0603551d230418301680143a' + + '9a8507106728b6eff6bd05416e20c194da0fde303406082b060105050701' + + '0104283026302406082b060105050730018618687474703a2f2f6f637370' + + '2e676f64616464792e636f6d2f30350603551d1f042e302c302aa028a026' + + '8624687474703a2f2f63726c2e676f64616464792e636f6d2f6764726f6f' + + '742d67322e63726c30460603551d20043f303d303b0604551d2000303330' + + '3106082b06010505070201162568747470733a2f2f63657274732e676f64' + + '616464792e636f6d2f7265706f7369746f72792f300d06092a864886f70d' + + '01010b05000382010100087e6c9310c838b896a9904bffa15f4f04ef6c3e' + + '9c8806c9508fa673f757311bbebce42fdbf8bad35be0b4e7e679620e0ca2' + + 'd76a637331b5f5a848a43b082da25d90d7b47c254f115630c4b6449d7b2c' + + '9de55ee6ef0c61aabfe42a1bee849eb8837dc143ce44a713700d911ff4c8' + + '13ad8360d9d872a873241eb5ac220eca17896258441bab892501000fcdc4' + + '1b62db51b4d30f512a9bf4bc73fc76ce36a4cdd9d82ceaae9bf52ab290d1' + + '4d75188a3f8a4190237d5b4bfea403589b46b2c3606083f87d5041cec2a1' + + '90c3bbef022fd21554ee4415d90aaea78a33edb12d763626dc04eb9ff761' + + '1f15dc876fee469628ada1267d0a09a72e04a38dbcf8bc0430010a810930' + + '82047d30820365a00302010202031be715300d06092a864886f70d01010b' + + '05003063310b30090603550406130255533121301f060355040a13185468' + + '6520476f2044616464792047726f75702c20496e632e3131302f06035504' + + '0b1328476f20446164647920436c61737320322043657274696669636174' + + '696f6e20417574686f72697479301e170d3134303130313037303030305a' + + '170d3331303533303037303030305a308183310b30090603550406130255' + + '533110300e060355040813074172697a6f6e61311330110603550407130a' + + '53636f74747364616c65311a3018060355040a1311476f44616464792e63' + + '6f6d2c20496e632e3131302f06035504031328476f20446164647920526f' + + '6f7420436572746966696361746520417574686f72697479202d20473230' + + '820122300d06092a864886f70d01010105000382010f003082010a028201' + + '0100bf716208f1fa5934f71bc918a3f7804958e9228313a6c52043013b84' + + 'f1e685499f27eaf6841b4ea0b4db7098c73201b1053e074eeef4fa4f2f59' + + '3022e7ab19566be28007fcf316758039517be5f935b6744ea98d8213e4b6' + + '3fa90383faa2be8a156a7fde0bc3b6191405caeac3a804943b467c320df3' + + '006622c88d696d368c1118b7d3b21c60b438fa028cced3dd4607de0a3eeb' + + '5d7cc87cfbb02b53a4926269512505611a44818c2ca9439623dfac3a819a' + + '0e29c51ca9e95d1eb69e9e300a39cef18880fb4b5dcc32ec856243253402' + + '56270191b43b702a3f6eb1e89c88017d9fd4f9db536d609dbf2ce758abb8' + + '5f46fccec41b033c09eb49315c6946b3e0470203010001a3820117308201' + + '13300f0603551d130101ff040530030101ff300e0603551d0f0101ff0404' + + '03020106301d0603551d0e041604143a9a8507106728b6eff6bd05416e20' + + 'c194da0fde301f0603551d23041830168014d2c4b0d291d44c1171b361cb' + + '3da1fedda86ad4e3303406082b0601050507010104283026302406082b06' + + '0105050730018618687474703a2f2f6f6373702e676f64616464792e636f' + + '6d2f30320603551d1f042b30293027a025a0238621687474703a2f2f6372' + + '6c2e676f64616464792e636f6d2f6764726f6f742e63726c30460603551d' + + '20043f303d303b0604551d20003033303106082b06010505070201162568' + + '747470733a2f2f63657274732e676f64616464792e636f6d2f7265706f73' + + '69746f72792f300d06092a864886f70d01010b05000382010100590b53bd' + + '928611a7247bed5b31cf1d1f6c70c5b86ebe4ebbf6be9750e1307fba285c' + + '6294c2e37e33f7fb427685db951c8c225875090c886567390a1609c5a038' + + '97a4c523933fb418a601064491e3a76927b45a257f3ab732cddd84ff2a38' + + '2933a4dd67b285fea188201c5089c8dc2af64203374ce688dfd5af24f2b1' + + 'c3dfccb5ece0995eb74954203c94180cc71c521849a46de1b3580bc9d8ec' + + 'd9ae1c328e28700de2fea6179e840fbd5770b35ae91fa08653bbef7cff69' + + '0be048c3b7930bc80a54c4ac5d1467376ccaa52f310837aa6e6f8cbc9be2' + + '575d2481af97979c84ad6cac374c66f361911120e4be309f7aa42909b0e1' + + '345f6477184051df8c30a6af0a840830820400308202e8a0030201020201' + + '00300d06092a864886f70d01010505003063310b30090603550406130255' + + '533121301f060355040a131854686520476f2044616464792047726f7570' + + '2c20496e632e3131302f060355040b1328476f20446164647920436c6173' + + '7320322043657274696669636174696f6e20417574686f72697479301e17' + + '0d3034303632393137303632305a170d3334303632393137303632305a30' + + '63310b30090603550406130255533121301f060355040a13185468652047' + + '6f2044616464792047726f75702c20496e632e3131302f060355040b1328' + + '476f20446164647920436c61737320322043657274696669636174696f6e' + + '20417574686f7269747930820120300d06092a864886f70d010101050003' + + '82010d00308201080282010100de9dd7ea571849a15bebd75f4886eabedd' + + 'ffe4ef671cf46568b35771a05e77bbed9b49e970803d561863086fdaf2cc' + + 'd03f7f0254225410d8b281d4c0753d4b7fc777c33e78ab1a03b5206b2f6a' + + '2bb1c5887ec4bb1eb0c1d845276faa3758f78726d7d82df6a917b71f7236' + + '4ea6173f659892db2a6e5da2fe88e00bde7fe58d15e1ebcb3ad5e212a213' + + '2dd88eaf5f123da0080508b65ca565380445991ea3606074c541a572621b' + + '62c51f6f5f1a42be025165a8ae23186afc7803a94d7f80c3faab5afca140' + + 'a4ca1916feb2c8ef5e730dee77bd9af67998bcb10767a2150ddda058c644' + + '7b0a3e62285fba41075358cf117e3874c5f8ffb569908f8474ea971baf02' + + '0103a381c03081bd301d0603551d0e04160414d2c4b0d291d44c1171b361' + + 'cb3da1fedda86ad4e330818d0603551d230481853081828014d2c4b0d291' + + 'd44c1171b361cb3da1fedda86ad4e3a167a4653063310b30090603550406' + + '130255533121301f060355040a131854686520476f204461646479204772' + + '6f75702c20496e632e3131302f060355040b1328476f2044616464792043' + + '6c61737320322043657274696669636174696f6e20417574686f72697479' + + '820100300c0603551d13040530030101ff300d06092a864886f70d010105' + + '05000382010100324bf3b2ca3e91fc12c6a1078c8e77a03306145c901e18' + + 'f708a63d0a19f98780116e69e4961730ff3491637238eecc1c01a31d9428' + + 'a431f67ac454d7f6e5315803a2ccce62db944573b5bf45c924b5d58202ad' + + '2379698db8b64dcecf4cca3323e81c88aa9d8b416e16c920e5899ecd3bda' + + '70f77e992620145425ab6e7385e69b219d0a6c820ea8f8c20cfa101e6c96' + + 'ef870dc40f618badee832b95f88e92847239eb20ea83ed83cd976e08bceb' + + '4e26b6732be4d3f64cfe2671e26111744aff571a870f75482ecf516917a0' + + '02126195d5d140b2104ceec4ac1043a6a59e0ad595629a0dcf8882c5320c' + + 'e42b9f45e60d9f289cb1b92a5a57ad370faf1d7fdbbd9f22a1010a047465' + + '7374122008c0c9e714121976a914176d7c5d60da6f8c82de86671a1fb776' + + '028538ca88ac18c6f5d89f0520cafcd89f052a395061796d656e74207265' + + '717565737420666f722042697450617920696e766f69636520434d577075' + + '46736a676d51325a4c6979476663463157323068747470733a2f2f746573' + + '742e6269747061792e636f6d2f692f434d57707546736a676d51325a4c69' + + '794766634631572a80021566366ab78842a514c056ca7ecb76481262cac7' + + '4cc4c4ccdc82c4980bc3300de67836d61d3e06dc8c90798a7774c21c7ad4' + + 'fe634b85faa8719d6402411bb720396ae03cbb4e14f06f7894a66b208b99' + + 'f727fab35d32f4f2148294d24bea1b3f240c159d0fd3ee4a32e5f926bf7c' + + '05eb7a3f75e01d9af81254cfbb61606467750ea7e0a1536728358e0898d0' + + '6f57235e4096d2caf647ae58dff645be80c9b3555fa96c81efa07d421977' + + 'd26214ad4f1ff642a93d0925656aeab454fa0b60fcbb6c1bc570eb6e43e7' + + '613392f37900748635ae381534bfaa558792bc46028b9efce391423a9c12' + + '01f76292614b30a14272e837f3813045b035f3d42f4f76f48acd', + 'hex'); + describe('PayPro', function() { it('should be able to create class', function() { @@ -403,6 +584,14 @@ describe('PayPro', function() { var verify = paypro.verify(); verify.should.equal(true); + + var trust = paypro.verify(true); + trust.selfSigned.should.equal(1); + trust.isChain.should.equal(false); + trust.verified.should.equal(true); + trust.caTrusted.should.equal(false); + should.equal(null, trust.caName); + trust.chainVerified.should.equal(false); }); }); @@ -496,6 +685,14 @@ describe('PayPro', function() { var verify = paypro.x509Verify(); verify.should.equal(true); + + var trust = paypro.x509Verify(true); + trust.selfSigned.should.equal(1); + trust.isChain.should.equal(false); + trust.verified.should.equal(true); + trust.caTrusted.should.equal(false); + should.equal(null, trust.caName); + trust.chainVerified.should.equal(false); }); }); @@ -549,6 +746,108 @@ describe('PayPro', function() { var verify = paypro.x509Verify(); verify.should.equal(true); + + var trust = paypro.x509Verify(true); + trust.selfSigned.should.equal(1); + trust.isChain.should.equal(false); + trust.verified.should.equal(true); + trust.caTrusted.should.equal(false); + should.equal(null, trust.caName); + trust.chainVerified.should.equal(false); + }); + }); + + describe('#x509+sha256Verify ', function() { + it('should verify a real PaymentRequest', function() { + var data = PayPro.PaymentRequest.decode(bitpayRequest); + var pr = new PayPro(); + pr = pr.makePaymentRequest(data); + + // PaymentRequest + var ver = pr.get('payment_details_version'); + var pki_type = pr.get('pki_type'); + var pki_data = pr.get('pki_data'); + var details = pr.get('serialized_payment_details'); + var sig = pr.get('signature'); + + pki_data = PayPro.X509Certificates.decode(pki_data); + pki_data = pki_data.certificate; + + ver.should.equal(1); + pki_type.should.equal('x509+sha256'); + pki_data.length.should.equal(4); + sig.toString('hex').should.equal('' + + '1566366ab78842a514c056ca7ecb76481262cac74cc4c4ccdc' + + '82c4980bc3300de67836d61d3e06dc8c90798a7774c21c7ad4' + + 'fe634b85faa8719d6402411bb720396ae03cbb4e14f06f7894' + + 'a66b208b99f727fab35d32f4f2148294d24bea1b3f240c159d' + + '0fd3ee4a32e5f926bf7c05eb7a3f75e01d9af81254cfbb6160' + + '6467750ea7e0a1536728358e0898d06f57235e4096d2caf647' + + 'ae58dff645be80c9b3555fa96c81efa07d421977d26214ad4f' + + '1ff642a93d0925656aeab454fa0b60fcbb6c1bc570eb6e43e7' + + '613392f37900748635ae381534bfaa558792bc46028b9efce3' + + '91423a9c1201f76292614b30a14272e837f3813045b035f3d4' + + '2f4f76f48acd'); + + if (is_browser) { + var type = 'SHA256'; + var pem = PayPro.prototype._DERtoPEM(pki_data[0], 'CERTIFICATE'); + var buf = pr.serializeForSig(); + var jsrsaSig = new KJUR.crypto.Signature({ + alg: type + 'withRSA', + prov: 'cryptojs/jsrsa' + }); + var signedCert = pki_data[0]; + var der = signedCert.toString('hex'); + // var pem = PayPro.DERtoPEM(der, 'CERTIFICATE'); + var pem = KJUR.asn1.ASN1Util.getPEMStringFromHex(der, 'CERTIFICATE'); + jsrsaSig.initVerifyByCertificatePEM(pem); + jsrsaSig.updateHex(buf.toString('hex')); + jsrsaSig.verify(sig.toString('hex')).should.equal(true); + } else { + var crypto = require('crypto'); + var type = 'SHA256'; + var pem = PayPro.DERtoPEM(pki_data[0], 'CERTIFICATE'); + var buf = pr.serializeForSig(); + var verifier = crypto.createVerify('RSA-' + type); + verifier.update(buf); + verifier.verify(pem, sig).should.equal(true); + } + + // Verify Signature + var verified = pr.x509Verify(); + verified.should.equal(true); + + // Verify Signature with trust properties + var trust = pr.x509Verify(true); + trust.selfSigned.should.equal(0); + trust.isChain.should.equal(true); + trust.verified.should.equal(true); + trust.caTrusted.should.equal(true); + trust.caName.should.equal('Go Daddy Class 2 CA'); + trust.chainVerified.should.equal(true); + + // PaymentDetails + details = PayPro.PaymentDetails.decode(details); + var pd = new PayPro(); + pd = pd.makePaymentDetails(details); + var network = pd.get('network'); + var outputs = pd.get('outputs'); + var time = pd.get('time'); + var expires = pd.get('expires'); + var memo = pd.get('memo'); + var payment_url = pd.get('payment_url'); + var merchant_data = pd.get('merchant_data'); + + network.should.equal('test'); + outputs.length.should.equal(1); + outputs[0].amount.should.not.equal(undefined); + outputs[0].script.should.not.equal(undefined); + time.should.equal(1408645830); + expires.should.equal(1408646730); + memo.should.equal('Payment request for BitPay invoice CMWpuFsjgmQ2ZLiyGfcF1W'); + payment_url.should.equal('https://test.bitpay.com/i/CMWpuFsjgmQ2ZLiyGfcF1W'); + should.equal(null, merchant_data); }); });