From a34fda8fe2379c93af3a929c6d6872e10d499040 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Mon, 24 Aug 2015 12:24:26 +0100 Subject: [PATCH] Adds method to verify a certificate's signing chain --- README.md | 12 +++++ lib/pem.js | 32 ++++++++++++ test/pem.js | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 182 insertions(+) diff --git a/README.md b/README.md index c5a59b49..eb3dbea4 100644 --- a/README.md +++ b/README.md @@ -180,6 +180,18 @@ Where * **options** is an optional options object with `cipher` and `clientKeyPassword` (ciphers:["aes128", "aes192", "aes256", "camellia128", "camellia192", "camellia256", "des", "des3", "idea"]) * **callback** is a callback function with an error object and `{pkcs12}` (binary) +## Verify a certificate signing chain + +Use `verifySigningChain` to assert that a given certificate has a valid signing chain. + + pem.verifySigningChain(certificate, ca, callback) + +Where + +* **certificate** is a PEM encoded certificate string +* **ca** is a PEM encoded CA certificate string or an array of certificate strings +* **callback** is a callback function with an error object and a boolean as arguments + ### Setting openssl location In some systems the `openssl` executable might not be available by the default name or it is not included in $PATH. In this case you can define the location of the executable yourself as a one time action after you have loaded the pem module: diff --git a/lib/pem.js b/lib/pem.js index d0ce8963..e4403dc9 100644 --- a/lib/pem.js +++ b/lib/pem.js @@ -20,6 +20,7 @@ module.exports.getFingerprint = getFingerprint; module.exports.getModulus = getModulus; module.exports.getModulusFromProtected = getModulusFromProtected; module.exports.createPkcs12 = createPkcs12; +module.exports.verifySigningChain = verifySigningChain; module.exports.config = config; // PUBLIC API @@ -564,6 +565,37 @@ function createPkcs12(key, certificate, password, options, callback) { }); } +/** + * Verifies the signing chain of the passed certificate + * + * @param {String} PEM encoded certificate + * @param {Array} List of CA certificates + * @param {Function} callback Callback function with an error object and a boolean valid + */ +function verifySigningChain(certificate, ca, callback) { + if (!Array.isArray(ca)) { + ca = [ca]; + } + + var files = [ + ca.join('\n'), + certificate + ]; + + var params = ['verify', + '-CAfile', + '--TMPFILE--', + '--TMPFILE--' + ]; + + spawnWrapper(params, files, function(err, code, stdout) { + if (err) { + return callback(err); + } + + callback(null, stdout.trim().slice(-4) === ': OK'); + }); +} // HELPER FUNCTIONS diff --git a/test/pem.js b/test/pem.js index ee6c638a..0e3d1378 100644 --- a/test/pem.js +++ b/test/pem.js @@ -497,5 +497,143 @@ exports['General Tests'] = { }); }); }); + }, + 'Verify sigining chain': function(test) { + pem.createCertificate({ + commonName: 'CA Certificate' + }, function (error, ca) { + test.ifError(error); + + pem.createCertificate({ + serviceKey: ca.serviceKey, + serviceCertificate: ca.certificate, + serial: Date.now(), + }, function (error, cert) { + test.ifError(error); + + pem.verifySigningChain(cert.certificate, ca.certificate, function (error, valid) { + test.ifError(error); + test.ok(valid === true); + + test.done(); + }); + }); + }); + }, + 'Verify deep sigining chain': function(test) { + pem.createCertificate({ + commonName: 'CA Certificate' + }, function (error, ca) { + test.ifError(error); + + pem.createCertificate({ + commonName: 'Intermediate CA Certificate', + serviceKey: ca.serviceKey, + serviceCertificate: ca.certificate, + serial: Date.now(), + }, function (error, intermediate) { + test.ifError(error); + + pem.createCertificate({ + serviceKey: intermediate.clientKey, + serviceCertificate: intermediate.certificate, + serial: Date.now(), + }, function (error, cert) { + test.ifError(error); + + pem.verifySigningChain(cert.certificate, [ca.certificate, intermediate.certificate], function (error, valid) { + test.ifError(error); + test.ok(valid === true); + + test.done(); + }); + }); + }); + }); + }, + 'Fail to verify invalid sigining chain': function(test) { + pem.createCertificate({ + commonName: 'CA Certificate' + }, function (error, ca) { + test.ifError(error); + + pem.createCertificate({ + serviceKey: ca.serviceKey, + serviceCertificate: ca.certificate, + serial: Date.now(), + }, function (error, cert) { + test.ifError(error); + + pem.verifySigningChain(cert.certificate, cert.certificate, function (error, valid) { + test.ifError(error); + test.ok(valid === false); + + test.done(); + }); + }); + }); + }, + 'Fail to verify deep sigining chain with missing CA certificate': function(test) { + pem.createCertificate({ + commonName: 'CA Certificate' + }, function (error, ca) { + test.ifError(error); + + pem.createCertificate({ + commonName: 'Intermediate CA Certificate', + serviceKey: ca.serviceKey, + serviceCertificate: ca.certificate, + serial: Date.now(), + }, function (error, intermediate) { + test.ifError(error); + + pem.createCertificate({ + serviceKey: intermediate.clientKey, + serviceCertificate: intermediate.certificate, + serial: Date.now(), + }, function (error, cert) { + test.ifError(error); + + pem.verifySigningChain(cert.certificate, [intermediate.certificate], function (error, valid) { + test.ifError(error); + test.ok(valid === false); + + test.done(); + }); + }); + }); + }); + }, + 'Fail to verify deep sigining chain with missing intermediate certificate': function(test) { + pem.createCertificate({ + commonName: 'CA Certificate' + }, function (error, ca) { + test.ifError(error); + + pem.createCertificate({ + commonName: 'Intermediate CA Certificate', + serviceKey: ca.serviceKey, + serviceCertificate: ca.certificate, + serial: Date.now(), + }, function (error, intermediate) { + test.ifError(error); + + pem.createCertificate({ + serviceKey: intermediate.clientKey, + serviceCertificate: intermediate.certificate, + serial: Date.now(), + days: 1024 + }, function (error, cert) { + test.ifError(error); + + pem.verifySigningChain(cert.certificate, [ca.certificate], function (error, valid) { + test.ifError(error); + test.ok(valid === false); + + test.done(); + }); + }); + }); + }); } }; \ No newline at end of file