diff --git a/README.md b/README.md index 6a49152..162a0a1 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ cert = { subject: ``` -#### x509.verify(`cert`, `CABundlePath`, function(err, result){ /*...*/}) +#### x509.verify(`cert`, `CABundle`, function(err, result){ /*...*/}) Performs basic certificate validation against a bundle of ca certificates. @@ -122,9 +122,7 @@ the certificate is valid. The error messages are the same returned by openssl: [x509_verify_cert_error_string](https://www.openssl.org/docs/man1.0.2/crypto/X509_STORE_CTX_get_error.html) - -**Note:** -As now, this function only accepts absolute paths to existing files as arguments +The cert and CABundle arguments may be any combination of paths or buffers. ```js const x509 = require('x509'); diff --git a/index.js b/index.js index 539a76c..2b22184 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,5 @@ +var Promise = require('promise-polyfill'); // Needed since we're supporting Node 0.10 environments + var x509 = require('./build/Release/x509'); var fs = require('fs'); @@ -6,40 +8,38 @@ exports.getAltNames = x509.getAltNames; exports.getSubject = x509.getSubject; exports.getIssuer = x509.getIssuer; -exports.verify = function(certPath, CABundlePath, cb) { - if (!certPath) { - throw new TypeError('Certificate path is required'); +exports.verify = function(certPathOrString, CABundlePathOrString, cb) { + if (!certPathOrString) { + throw new TypeError('The certificate path or the certificate string itself is required'); } - if (!CABundlePath) { - throw new TypeError('CA Bundle path is required'); + if (!CABundlePathOrString) { + throw new TypeError('The certificate bundle path or the bundle string itself is required'); } - fs.stat(certPath, function(certPathErr) { + Promise.all([ + getPathOrStringBuffer(certPathOrString), + getPathOrStringBuffer(CABundlePathOrString) + ]).then(function(results){ + var certBuffer = results[0]; + var caBuffer = results[1]; - if (certPathErr) { - return cb(certPathErr); + try { + var parsedCert = x509.parseCert(String(certBuffer)); + } catch(Exception) { + return cb(new TypeError('Unable to parse certificate.')); } - fs.stat(CABundlePath, function(bundlePathErr) { - - if (bundlePathErr) { - return cb(bundlePathErr); - } - - try { - x509.verify(certPath, CABundlePath); - cb(null); - } - catch (verificationError) { - cb(verificationError); - } - }); - }); + try { + x509.verify(certBuffer, caBuffer); + cb(null, parsedCert); //Might as well pass back the parsed certificate on verify + } catch (verificationError) { + cb(verificationError); + } + }, cb); }; - -exports.parseCert = function(path) { - var ret = x509.parseCert(path); +exports.parseCert = function(pathOrBuffer) { + var ret = x509.parseCert(pathOrBuffer); var exts = {}; for (var key in ret.extensions) { var newkey = key.replace('X509v3', '').replace(/ /g, ''); @@ -50,3 +50,18 @@ exports.parseCert = function(path) { ret.extensions = exts; return ret; }; + +function getPathOrStringBuffer(pathOrString){ + if(String(pathOrString).indexOf('-----BEGIN') === 0){ + return Promise.resolve(Buffer(pathOrString, 'utf8')); + } else{ + return new Promise(function(res, rej){ + fs.readFile(pathOrString, function(err, fileBuffer){ + if(err){ + return rej(err); + } + res(fileBuffer) + }) + }); + } +} \ No newline at end of file diff --git a/package.json b/package.json index 9009c5e..dae8c75 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ }, "license": "MIT", "dependencies": { - "nan": "2.2.0" + "nan": "2.2.0", + "promise-polyfill": "^6.0.2" } } diff --git a/src/x509.cc b/src/x509.cc index 0e04958..df427f4 100644 --- a/src/x509.cc +++ b/src/x509.cc @@ -46,64 +46,109 @@ std::string parse_args(const Nan::FunctionCallbackInfo& info) { return *String::Utf8Value(info[0]->ToString()); } - - NAN_METHOD(verify) { Nan::HandleScope scope; OpenSSL_add_all_algorithms(); - std::string cert_path = *String::Utf8Value(info[0]->ToString()); - std::string ca_bundlestr = *String::Utf8Value(info[1]->ToString()); + std::string cert_buffer = *String::Utf8Value(info[0]->ToString()); + std::string ca_buffer = *String::Utf8Value(info[1]->ToString()); X509_STORE *store = NULL; X509_STORE_CTX *verify_ctx = NULL; X509 *cert = NULL; - BIO *cert_bio = BIO_new(BIO_s_file()); + BIO *cert_bio = NULL; // create store store = X509_STORE_new(); if (store == NULL) { - X509_STORE_free(store); - BIO_free_all(cert_bio); Nan::ThrowError("Failed to create X509 certificate store."); + ERR_clear_error(); + return; } + // create store context verify_ctx = X509_STORE_CTX_new(); - if (verify_ctx == NULL) { X509_STORE_free(store); - BIO_free_all(cert_bio); + ERR_clear_error(); Nan::ThrowError("Failed to create X509 verification context."); + return; } - // load file in BIO - int ret = BIO_read_filename(cert_bio, cert_path.c_str()); - if (ret != 1) { + // load cert buffer into BIO + cert_bio = BIO_new(BIO_s_mem()); + if (cert_bio == NULL) { + X509_STORE_CTX_free(verify_ctx); X509_STORE_free(store); - X509_free(cert); + ERR_clear_error(); + Nan::ThrowError("Failed to create certificate buffer"); + return; + } + + int ret = BIO_puts(cert_bio, cert_buffer.c_str()); + if (ret <= 0) { BIO_free_all(cert_bio); X509_STORE_CTX_free(verify_ctx); - Nan::ThrowError("Error reading file"); + X509_STORE_free(store); + ERR_clear_error(); + Nan::ThrowError("Error loading certificate"); + return; } // read from BIO cert = PEM_read_bio_X509(cert_bio, NULL, 0, NULL); if (cert == NULL) { - X509_STORE_free(store); - X509_free(cert); - X509_STORE_CTX_free(verify_ctx); BIO_free_all(cert_bio); + X509_STORE_CTX_free(verify_ctx); + X509_STORE_free(store); + ERR_clear_error(); Nan::ThrowError("Failed to load cert"); + return; } - // load CA bundle - ret = X509_STORE_load_locations(store, ca_bundlestr.c_str(), NULL); - if (ret != 1) { + // load CA bundle from memory or path + STACK_OF(X509_INFO) *ca_inf_stack; + BIO *bio_ca = BIO_new(BIO_s_mem()); + if (bio_ca == NULL) { + X509_free(cert); + BIO_free_all(cert_bio); + X509_STORE_CTX_free(verify_ctx); + X509_STORE_free(store); + ERR_clear_error(); + Nan::ThrowError("Failed to create ca buffer"); + return; + } + ret = BIO_puts(bio_ca, ca_buffer.c_str()); + if (ret == -1) { + BIO_free_all(bio_ca); + X509_free(cert); + BIO_free_all(cert_bio); + X509_STORE_CTX_free(verify_ctx); X509_STORE_free(store); + ERR_clear_error(); + Nan::ThrowError("Failed to load ca buffer"); + return; + } + ca_inf_stack = PEM_X509_INFO_read_bio(bio_ca, NULL, NULL, NULL); + if (ca_inf_stack == NULL) { + BIO_free_all(bio_ca); X509_free(cert); BIO_free_all(cert_bio); X509_STORE_CTX_free(verify_ctx); - Nan::ThrowError("Error loading CA chain file"); + X509_STORE_free(store); + ERR_clear_error(); + Nan::ThrowError("Failed to load ca"); + return; + } + // Loop through all certs in the CA + for (int i = 0; i < sk_X509_INFO_num(ca_inf_stack); i++) { + X509_INFO *ca_inf = sk_X509_INFO_value(ca_inf_stack, i); + if (ca_inf->x509) { + X509_STORE_add_cert(store, ca_inf->x509); + } + if(ca_inf->crl) { + X509_STORE_add_crl(store, ca_inf->crl); + } } // verify @@ -111,15 +156,27 @@ NAN_METHOD(verify) { ret = X509_verify_cert(verify_ctx); if (ret <= 0) { - Nan::ThrowError(X509_verify_cert_error_string(verify_ctx->error)); + int verify_error = verify_ctx->error; + sk_X509_INFO_pop_free(ca_inf_stack, X509_INFO_free); + BIO_free_all(bio_ca); + X509_free(cert); + BIO_free_all(cert_bio); + X509_STORE_CTX_free(verify_ctx); + X509_STORE_free(store); + Nan::ThrowError(X509_verify_cert_error_string(verify_error)); + ERR_clear_error(); + return; } - X509_STORE_free(store); + sk_X509_INFO_pop_free(ca_inf_stack, X509_INFO_free); + BIO_free_all(bio_ca); X509_free(cert); - X509_STORE_CTX_free(verify_ctx); BIO_free_all(cert_bio); + X509_STORE_CTX_free(verify_ctx); + X509_STORE_free(store); - info.GetReturnValue().Set(Nan::New(true)); + info.GetReturnValue().Set(Nan::New(true)); + ERR_clear_error(); } diff --git a/test/certs/enduser-example-bad.com.crt b/test/certs/enduser-example-bad.com.crt new file mode 100644 index 0000000..272e99a --- /dev/null +++ b/test/certs/enduser-example-bad.com.crt @@ -0,0 +1,3 @@ +-----BEGIN CERTIFICATE----- +this is not the certificate you are looking for... +-----END CERTIFICATE----- diff --git a/test/certs/enduser-example-malformed.com.crt b/test/certs/enduser-example-malformed.com.crt new file mode 100644 index 0000000..4f4d318 --- /dev/null +++ b/test/certs/enduser-example-malformed.com.crt @@ -0,0 +1,43 @@ +-----BEGIN CERTIFICATE----- +MIIHqjCCBZKgAwIBAgICEAEwDQYJKoZIhvcNAQEFBQAweTESMBAGA1UEAwwJZ2lv +dmFubmkyMQ4wDAYDVQQIDAVNb256YTELMAkGA1UEBhMCSVQxJDAiBgkqhkiG9w0B +CQEWFWdpb3Zhbm5pMkBleGFtcGxlLmNvbTEMMAoGA1UECgwDQUFBMRIwEAYDVQQL +DAlub24gbG8gc28wHhcNMTYwODA0MTUxMDE3WhcNMTcwODA0MTUxMDE3WjCBrjFF +MEMGA1UEAww8VmFsaWRTaWduIFs2NTIzOGJiODBmZjVjMTQ1LDY1MjM4YmI4MGZm +NWMxNDZdIFVzZXJzKDUwKSBERU1PMREwDwYDVQQIDAhMb21iYXJkeTELMAkGA1UE +BhMCSVQxIzAhBgkqhkiG9w0BCQEWFHZhbGlkc2lnbkBsaW5rLW1lLml0MQ8wDQYD +VQQKDAZMaW5rbWUxDzANBgNVBAsMBkxpbmttZTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBANh2LEbkUX4ojZacXr6I9YL1BdChcoWfFqGNXk2qX8LVv0AA +dHjt57L9bfGAeQ49U16WsR9ud4aoOw9D+OMf4BXn1egKIFkNQw4EAL8FUqVHC9zB +Q2EybjV15gjkgsvPf/UMBA/Ag2Gj8JT2piULirpI6O7LN/dq68ElGCxpWwr2+zv7 +BlSQ3j8ctDqZbnXdQjtlm/xDfr1ND7cGo7TtwgPbxFpQwuPEJiuTwKA6Qu+C4Do/ +GJgK81rV8Q04XwY0fX4hniP2M5Zhq3idgekJZR9qZ+lPnu3dAJ/2yrEw+bbL0uwf +1bvJG2I+wqthIlfZXc4kgDTBi1Wi4VKxyAl9RE1vhwKvzDYPCUdwW5X+4jVmKJsd +GjzY59kMOO3LejxaY43E8gYwRfjbClhz1pWxlkxcEbqrXU8hzdKjk7qHsgSZ2UWG +9ybmYA2VvP+lA+hzqjaQS1Cypu2xS37QWdK0E/z0WLMoZrzaCV/mTSA/1+rqdCDG +GA37C4W1otbnwCbaEnGdjVrE1oHhseNRNVegKTUvwA91dHN+3DtMVtcCAzRCc3Y+ +UmVzWCJbJrKJezYrUr8JbeR9csB2niyd//80vdeZ/1gDVDSCeargCIIEXvksRWVY +P81ZUxI1Gc4olmUw4KLhfQjdtdtOs3chPy9WPtemL6r9G51Vu5G6/rKqBV2LAgMB +AAGjggIEMIICADAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTS8qwcImqEYUd4NJ6Z +qxUvq0UvfjAfBgNVHSMEGDAWgBQRUBXt1vbYYNuqEKhsLEp+LkMXPjALBgNVHQ8E +BAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwfgYDVR0fBHcwdTA7oDmgN4Y1aHR0 +cDovL3BraS5zcGFya2xpbmdjYS5jb20vU3BhcmtsaW5nSW50ZXJtaWRpYXRlMS5j +cmwwNqA0oDKGMGh0dHA6Ly9wa2kuYmFja3VwLmNvbS9TcGFya2xpbmdJbnRlcm1p +ZGlhdGUxLmNybDAjBgNVHREEHDAaggtleGFtcGxlLmNvbYILZXhhbXBsZS5vcmcw +gegGCCsGAQUFBwEBBIHbMIHYMEEGCCsGAQUFBzAChjVodHRwOi8vcGtpLnNwYXJr +bGluZ2NhLmNvbS9TcGFya2xpbmdJbnRlcm1lZGlhdGUxLmNydDA8BggrBgEFBQcw +AoYwaHR0cDovL3BraS5iYWNrdXAuY29tL1NwYXJrbGluZ0ludGVybWVkaWF0ZTEu +Y3J0MCwGCCsGAQUFBzABhiBodHRwOi8vcGtpLnNwYXJrbGluZ2NhLmNvbS9vY3Nw +LzAnBggrBgEFBQcwAYYbaHR0cDovL3BraS5iYWNrdXAuY29tL29jc3AvMA0GCSqG +SIb3DQEBBQUAA4ICAQBZV0tenQyAWW5O2DLCXxbGUIEgOhikulAOA0jWdwhvMpVu +GLHH1Uf9p37FNm08tmjVoub0oyQ/QexvParxiE4HyvJepMdtBMtVJhV9ej2YX/fu +YpAQHzLiRqFOvC3DHnOmkapkgV4he/qFnysfwc9KUNN6/vq4kp9ChjXv8Wy25Jfr +50unv+b5FTSARkhc3vX64eXbe4w4xcxyqJexZa0rgH2+ezRwRuhSBofp8Oa7vS11 +nBBoGPeTYBQi/FfE1QDKR+Ji2aRcdFNj0CYXIMVEqTt9dXSobFwAxXa9eG2TL5nY +KKBht2aOr1XVc7eXK+EgErAMIuopFz78cnHs4QnTQgTMf/HBPSdVmjUPlSSNwyW2 +qrgAodwZksfoHTKj2Ka8FOYuUSb8kJFD+3FRCbwLiFZUjsXp+0hAdrHHhLGAzEsX +UkRV36aswQS4CQ7uDm8uZzbPi/H5lUmBZOtHy2abrW3C18QiCMNqMjErUh2ZJEpk +I8n4vFBctOyN7tO0v4sliv8h1Q1Kokts3GdrhTxCoUZfuu/zPDnwp25thX9eOT1E +GW8HUE06ULRd2CFOAqGqtkLeb2sxtTjXZ1H7IMTMIBwav1CCvFUtaYzy5VO8Mdq8 +S8PVNIfcn2GwopOkpYUAqKkREyq3dXPTciB0cj7JiEjpAZWfsSUoTC6rtRcosq== +-----END CERTIFICATE----- diff --git a/test/test.js b/test/test.js index c364fdc..04f6741 100644 --- a/test/test.js +++ b/test/test.js @@ -6,35 +6,71 @@ var x509 = require('../index'), // All cert files should read without throwing an error. // Simple enough test, no? fs.readdirSync(path.join(__dirname, 'certs')).forEach(function (file) { + if (file === 'enduser-example-bad.com.crt') return; console.log("File: %s", file); console.log(x509.parseCert(path.join(__dirname, 'certs', file))); // x509.parseCert(path.join(__dirname, 'certs', file)); console.log(); }); +function fillPath(filename) { + return path.join(__dirname, filename) +} -x509.verify( - path.join(__dirname, 'certs/enduser-example.com.crt'), - path.join(__dirname, 'CA_chains/enduser-example.com.chain'), +function loadFile(filename) { + return fs.readFileSync(fillPath(filename)) +} + + +function x509_verify_test(cert_path, ca_path, cb) { + x509.verify(fillPath(cert_path), fillPath(ca_path), cb); + x509.verify(loadFile(cert_path), fillPath(ca_path), cb); + x509.verify(fillPath(cert_path), loadFile(ca_path), cb); + x509.verify(loadFile(cert_path), loadFile(ca_path), cb); +} + +x509_verify_test( + 'certs/enduser-example.com.crt', + 'CA_chains/enduser-example.com.chain', function (err) { + console.log('x509 verify'); assert.strictEqual(err, null); } ); +x509_verify_test( + 'certs/enduser-example-bad.com.crt', + 'CA_chains/enduser-example.com.chain', + function (err) { + console.log('x509 verify invalid cert'); + assert.throws(assert.ifError.bind(null, err), /Unable to parse certificate./) + } +); + +x509_verify_test( + 'certs/enduser-example-malformed.com.crt', + 'CA_chains/enduser-example.com.chain', + function (err) { + console.log('x509 verify altered cert'); + assert.throws(assert.ifError.bind(null, err), /certificate signature failure/) + } +); -x509.verify( - path.join(__dirname, 'certs/acaline.com.crt'), - path.join(__dirname, 'CA_chains/enduser-example.com.chain'), +x509_verify_test( + 'certs/acaline.com.crt', + 'CA_chains/enduser-example.com.chain', function (err, result) { + console.log('x509 verify no local issuer'); assert.throws(assert.ifError.bind(null, err), /unable to get local issuer/) } ); x509.verify( - path.join(__dirname, 'certs/notexisting.com.crt'), - path.join(__dirname, 'CA_chains/enduser-example.com.chain'), + 'certs/notexisting.com.crt', + 'CA_chains/enduser-example.com.chain', function (err, result) { + console.log('x509 verify no local file'); assert.throws(assert.ifError.bind(null, err), /ENOENT/) } );