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: 3 additions & 3 deletions include/x509.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ using namespace v8;
void get_issuer(const FunctionCallbackInfo<Value> &args);
char* parse_args(const FunctionCallbackInfo<Value> &args);
void parse_cert(const FunctionCallbackInfo<Value> &args);
void extract_p12(const FunctionCallbackInfo<Value> &args);
void verify(const FunctionCallbackInfo<Value> &args);
#else
Handle<Value> get_altnames(const Arguments &args);
Handle<Value> get_subject(const Arguments &args);
Handle<Value> get_issuer(const Arguments &args);
Handle<Value> parse_cert(const Arguments &args);
Handle<Value> extract_p12(const Arguments &args);
Handle<Value> verify(const Arguments &args);
#endif

Handle<Value> try_parse(char *data);
Expand All @@ -44,6 +44,6 @@ Handle<Value> parse_serial(ASN1_INTEGER *serial);
Handle<Object> parse_name(X509_NAME *subject);
char* real_name(char *data);
Handle<Value> extract_from_p12(char *data, char* password);

Handle<Value> verify_cert(char *inputcert, const Handle<Object>& calist);

#endif
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ exports.getAltNames = x509.getAltNames;
exports.getSubject = x509.getSubject;
exports.getIssuer = x509.getIssuer;
exports.extractP12 = x509.extractP12;
exports.verify = x509.verify;

exports.parseCert = function(path) {
var ret = x509.parseCert(path);
Expand Down
1 change: 1 addition & 0 deletions src/addon.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ void init(Handle<Object> exports) {
exports->Set(String::NewSymbol("getIssuer"), FunctionTemplate::New(get_issuer)->GetFunction());
exports->Set(String::NewSymbol("parseCert"), FunctionTemplate::New(parse_cert)->GetFunction());
exports->Set(String::NewSymbol("extractP12"), FunctionTemplate::New(extract_p12)->GetFunction());
exports->Set(String::NewSymbol("verify"), FunctionTemplate::New(verify)->GetFunction());
}

NODE_MODULE(wopenssl, init)
1 change: 1 addition & 0 deletions src/pkcs12.cc
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ Handle<Value> extract_from_p12(char *data, char* password) {
p12 = d2i_PKCS12_fp(fp, NULL);
fclose (fp);
if (!p12) {
ERR_print_errors_fp(stderr);
ThrowException(Exception::TypeError(String::New("Error reading PKCS#12 file\n")));
return scope.Close(Undefined());
}
Expand Down
159 changes: 158 additions & 1 deletion src/x509.cc
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,23 @@ void parse_cert(const FunctionCallbackInfo<Value> &args) {
args.GetReturnValue().Set(exports);
}

void verify(const FunctionCallbackInfo<Value> &args) {
if (args.Length() < 1) {
ThrowException(Exception::Error(String::New("Must provide a certificate file")));
return NULL;
}

if (!args[0]->IsString()) {
ThrowException(Exception::TypeError(String::New("Certificate must be strings.")));
return NULL;
}


Handle<Object> array(args[1]->ToObject());
Local<Object> exports(verify_cert(args[0]->ToString()));
args.GetReturnValue().Set(exports);
}


#else
/*
Expand Down Expand Up @@ -115,6 +132,29 @@ Handle<Value> parse_cert(const Arguments &args) {
String::Utf8Value value(args[0]);
return scope.Close(try_parse(*value));
}


Handle<Value> verify(const Arguments &args) {
HandleScope scope;

if (args.Length() < 1) {
ThrowException(Exception::Error(String::New("Must provide a certificate")));
return scope.Close(Undefined());
}

if (!args[0]->IsString()) {
ThrowException(Exception::TypeError(String::New("Certificate must be a strings.")));
return scope.Close(Undefined());
}


String::Utf8Value cert(args[0]);
Handle<Object> ca(args[1]->ToObject());
return scope.Close(verify_cert(*cert, ca));
}



#endif // NODE_VERSION_AT_LEAST


Expand All @@ -123,7 +163,6 @@ Handle<Value> parse_cert(const Arguments &args) {
* This is where everything is handled for both -0.11.2 and 0.11.3+.
*/


Handle<Value> try_parse(char *data) {
HandleScope scope;
Handle<Object> exports(Object::New());
Expand Down Expand Up @@ -379,3 +418,121 @@ char* real_name(char *data) {

return data;
}


Handle<Value> verify_cert(char *inputcert, const Handle<Object>& calist) {
HandleScope scope;
Handle<Object> exports(Object::New());
X509_STORE_CTX *ctx;
X509_STORE *store;
X509 *cert;
STACK_OF(X509) *chain = NULL;
char error[128];

store = X509_STORE_new();
ctx = X509_STORE_CTX_new();
if (!ctx || !store) {
ThrowException(Exception::Error(String::New("Cannot allocate x509 store container")));
return scope.Close(Undefined());
}

BIO *bio = BIO_new(BIO_s_mem());
int result = BIO_puts(bio, inputcert);

if (result == -2) {
ThrowException(Exception::Error(String::New("BIO doesn't support BIO_puts.")));
return scope.Close(exports);
}
else if (result <= 0) {
ThrowException(Exception::Error(String::New("No data was written to BIO.")));
return scope.Close(exports);
}

// Try raw read
cert = PEM_read_bio_X509(bio, NULL, 0, NULL);

if (cert == NULL) {
// Switch to file BIO
bio = BIO_new(BIO_s_file());

// If raw read fails, try reading the input as a filename.
if (!BIO_read_filename(bio, inputcert)) {
ThrowException(Exception::Error(String::New("File doesn't exist.")));
return scope.Close(exports);
}

// Try reading the bio again with the file in it.
cert = PEM_read_bio_X509(bio, NULL, 0, NULL);

if (cert == NULL) {
ThrowException(Exception::Error(String::New("Unable to parse certificate.")));
return scope.Close(exports);
}
}
BIO_free(bio);




if (calist->IsArray())
{
chain = sk_X509_new_null();
Array *carray = Array::Cast(*calist);
for (int i = 0; i < carray->Length(); i++)
{
String::Utf8Value certstring(carray->Get(i));
bio = BIO_new(BIO_s_mem());
result = BIO_puts(bio, *certstring);

if (result == -2) {
ThrowException(Exception::Error(String::New("BIO doesn't support BIO_puts.")));
return scope.Close(exports);
}
else if (result <= 0) {
ThrowException(Exception::Error(String::New("No data was written to BIO.")));
return scope.Close(exports);
}

// Try raw read
cert = PEM_read_bio_X509(bio, NULL, 0, NULL);

if (cert == NULL) {
// Switch to file BIO
bio = BIO_new(BIO_s_file());

// If raw read fails, try reading the input as a filename.
if (!BIO_read_filename(bio, *certstring)) {
ThrowException(Exception::Error(String::New("File doesn't exist.")));
return scope.Close(exports);
}

// Try reading the bio again with the file in it.
cert = PEM_read_bio_X509(bio, NULL, 0, NULL);

if (cert == NULL) {
ThrowException(Exception::Error(String::New("Unable to parse CA certificate.")));
return scope.Close(exports);
}
}
sk_X509_push(chain, cert);
BIO_free(bio);

}
}


X509_STORE_add_cert(store, cert);
X509_STORE_CTX_init(ctx, store, cert, chain);

if (X509_verify_cert(ctx) <= 0) {
ThrowException(Exception::Error(String::New(X509_verify_cert_error_string(X509_STORE_CTX_get_error(ctx)))));
return scope.Close(exports);
}


X509_free(cert);
X509_STORE_CTX_free(ctx);


return scope.Close(exports);
}
10 changes: 9 additions & 1 deletion test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,20 @@ var x509 = require('../index'),
// All cert files should read without throwing an error.
// Simple enough test, no?


console.log("=========== [ Certificates parsing ] ==========");

fs.readdirSync(path.join(__dirname, 'certs')).forEach(function (file) {
console.log("File: %s", file);
console.log(x509.parseCert(path.join(__dirname, 'certs', file)));
// x509.parseCert(path.join(__dirname, 'certs', file));
console.log();
});

console.log("=========== [ p12 extracting ] ==========");

console.log(x509.parseCert(x509.extractP12("test/p12/cert.p12", "password").certificate));

console.log("=========== [ crl verifying ] ==========");

console.log(x509.parseCert(x509.extractP12("test/p12/cert.p12", "password").certificate));
console.log(x509.verifycrl("test/verify/keys/mandela.crt", "test/verify/keys/crl.pem"));
8 changes: 8 additions & 0 deletions test/verify/build-ca
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/sh

#
# Build a root certificate
#

export EASY_RSA="${EASY_RSA:-.}"
"$EASY_RSA/pkitool" --interact --initca $*
11 changes: 11 additions & 0 deletions test/verify/build-dh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/sh

# Build Diffie-Hellman parameters for the server side
# of an SSL/TLS connection.

if [ -d $KEY_DIR ] && [ $KEY_SIZE ]; then
$OPENSSL dhparam -out ${KEY_DIR}/dh${KEY_SIZE}.pem ${KEY_SIZE}
else
echo 'Please source the vars script first (i.e. "source ./vars")'
echo 'Make sure you have edited it to reflect your configuration.'
fi
7 changes: 7 additions & 0 deletions test/verify/build-inter
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/sh

# Make an intermediate CA certificate/private key pair using a locally generated
# root certificate.

export EASY_RSA="${EASY_RSA:-.}"
"$EASY_RSA/pkitool" --interact --inter $*
7 changes: 7 additions & 0 deletions test/verify/build-key
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/sh

# Make a certificate/private key pair using a locally generated
# root certificate.

export EASY_RSA="${EASY_RSA:-.}"
"$EASY_RSA/pkitool" --interact $*
7 changes: 7 additions & 0 deletions test/verify/build-key-pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/sh

# Similar to build-key, but protect the private key
# with a password.

export EASY_RSA="${EASY_RSA:-.}"
"$EASY_RSA/pkitool" --interact --pass $*
8 changes: 8 additions & 0 deletions test/verify/build-key-pkcs12
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/sh

# Make a certificate/private key pair using a locally generated
# root certificate and convert it to a PKCS #12 file including the
# the CA certificate as well.

export EASY_RSA="${EASY_RSA:-.}"
"$EASY_RSA/pkitool" --interact --pkcs12 $*
10 changes: 10 additions & 0 deletions test/verify/build-key-server
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/sh

# Make a certificate/private key pair using a locally generated
# root certificate.
#
# Explicitly set nsCertType to server using the "server"
# extension in the openssl.cnf file.

export EASY_RSA="${EASY_RSA:-.}"
"$EASY_RSA/pkitool" --interact --server $*
7 changes: 7 additions & 0 deletions test/verify/build-req
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/sh

# Build a certificate signing request and private key. Use this
# when your root certificate and key is not available locally.

export EASY_RSA="${EASY_RSA:-.}"
"$EASY_RSA/pkitool" --interact --csr $*
7 changes: 7 additions & 0 deletions test/verify/build-req-pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/sh

# Like build-req, but protect your private key
# with a password.

export EASY_RSA="${EASY_RSA:-.}"
"$EASY_RSA/pkitool" --interact --csr --pass $*
16 changes: 16 additions & 0 deletions test/verify/clean-all
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/sh

# Initialize the $KEY_DIR directory.
# Note that this script does a
# rm -rf on $KEY_DIR so be careful!

if [ "$KEY_DIR" ]; then
rm -rf "$KEY_DIR"
mkdir "$KEY_DIR" && \
chmod go-rwx "$KEY_DIR" && \
touch "$KEY_DIR/index.txt" && \
echo 01 >"$KEY_DIR/serial"
else
echo 'Please source the vars script first (i.e. "source ./vars")'
echo 'Make sure you have edited it to reflect your configuration.'
fi
39 changes: 39 additions & 0 deletions test/verify/inherit-inter
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/bin/sh

# Build a new PKI which is rooted on an intermediate certificate generated
# by ./build-inter or ./pkitool --inter from a parent PKI. The new PKI should
# have independent vars settings, and must use a different KEY_DIR directory
# from the parent. This tool can be used to generate arbitrary depth
# certificate chains.
#
# To build an intermediate CA, follow the same steps for a regular PKI but
# replace ./build-key or ./pkitool --initca with this script.

# The EXPORT_CA file will contain the CA certificate chain and should be
# referenced by the OpenVPN "ca" directive in config files. The ca.crt file
# will only contain the local intermediate CA -- it's needed by the easy-rsa
# scripts but not by OpenVPN directly.
EXPORT_CA="export-ca.crt"

if [ $# -ne 2 ]; then
echo "usage: $0 <parent-key-dir> <common-name>"
echo "parent-key-dir: the KEY_DIR directory of the parent PKI"
echo "common-name: the common name of the intermediate certificate in the parent PKI"
exit 1;
fi

if [ "$KEY_DIR" ]; then
cp "$1/$2.crt" "$KEY_DIR/ca.crt"
cp "$1/$2.key" "$KEY_DIR/ca.key"

if [ -e "$1/$EXPORT_CA" ]; then
PARENT_CA="$1/$EXPORT_CA"
else
PARENT_CA="$1/ca.crt"
fi
cp "$PARENT_CA" "$KEY_DIR/$EXPORT_CA"
cat "$KEY_DIR/ca.crt" >> "$KEY_DIR/$EXPORT_CA"
else
echo 'Please source the vars script first (i.e. "source ./vars")'
echo 'Make sure you have edited it to reflect your configuration.'
fi
Loading