Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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');
Expand Down
67 changes: 41 additions & 26 deletions index.js
Original file line number Diff line number Diff line change
@@ -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');

Expand All @@ -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, '');
Expand All @@ -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)
})
});
}
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
},
"license": "MIT",
"dependencies": {
"nan": "2.2.0"
"nan": "2.2.0",
"promise-polyfill": "^6.0.2"
}
}
107 changes: 82 additions & 25 deletions src/x509.cc
Original file line number Diff line number Diff line change
Expand Up @@ -46,80 +46,137 @@ std::string parse_args(const Nan::FunctionCallbackInfo<v8::Value>& 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
X509_STORE_CTX_init(verify_ctx, store, cert, NULL);
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();
}


Expand Down
3 changes: 3 additions & 0 deletions test/certs/enduser-example-bad.com.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-----BEGIN CERTIFICATE-----
this is not the certificate you are looking for...
-----END CERTIFICATE-----
43 changes: 43 additions & 0 deletions test/certs/enduser-example-malformed.com.crt
Original file line number Diff line number Diff line change
@@ -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-----
Loading