Browse Source

crypto: add PKCS12/PFX support

Fixes #2845.
v0.8.7-release
ssuda 13 years ago
committed by Ben Noordhuis
parent
commit
fb7348ae06
  1. 2
      doc/api/crypto.markdown
  2. 16
      doc/api/https.markdown
  3. 66
      doc/api/tls.markdown
  4. 8
      lib/crypto.js
  5. 2
      lib/tls.js
  6. 79
      src/node_crypto.cc
  7. 2
      src/node_crypto.h
  8. BIN
      test/fixtures/test_cert.pfx
  9. 19
      test/simple/test-crypto.js

2
doc/api/crypto.markdown

@ -14,8 +14,10 @@ It also offers a set of wrappers for OpenSSL's hash, hmac, cipher, decipher, sig
Creates a credentials object, with the optional details being a dictionary with keys: Creates a credentials object, with the optional details being a dictionary with keys:
* `pfx` : A string or buffer holding the PFX or PKCS12 encoded private key, certificate and CA certificates
* `key` : a string holding the PEM encoded private key * `key` : a string holding the PEM encoded private key
* `cert` : a string holding the PEM encoded certificate * `cert` : a string holding the PEM encoded certificate
* `passphrase` : A string of passphrase for the private key or pfx
* `ca` : either a string or list of strings of PEM encoded CA certificates to trust. * `ca` : either a string or list of strings of PEM encoded CA certificates to trust.
* `ciphers`: a string describing the ciphers to use or exclude. Consult * `ciphers`: a string describing the ciphers to use or exclude. Consult
<http://www.openssl.org/docs/apps/ciphers.html#CIPHER_LIST_FORMAT> for details <http://www.openssl.org/docs/apps/ciphers.html#CIPHER_LIST_FORMAT> for details

16
doc/api/https.markdown

@ -32,6 +32,19 @@ Example:
res.end("hello world\n"); res.end("hello world\n");
}).listen(8000); }).listen(8000);
Or
var https = require('https');
var fs = require('fs');
var options = {
pfx: fs.readFileSync('server.pfx')
};
https.createServer(options, function (req, res) {
res.writeHead(200);
res.end("hello world\n");
}).listen(8000);
## https.request(options, callback) ## https.request(options, callback)
@ -91,8 +104,9 @@ The options argument has the following options
The following options from [tls.connect()](tls.html#tls.connect) can also be The following options from [tls.connect()](tls.html#tls.connect) can also be
specified. However, a [globalAgent](#https.globalAgent) silently ignores these. specified. However, a [globalAgent](#https.globalAgent) silently ignores these.
- `pfx`: Certificate, Private key and CA certificates to use for SSL. Default `null`.
- `key`: Private key to use for SSL. Default `null`. - `key`: Private key to use for SSL. Default `null`.
- `passphrase`: A string of passphrase for the private key. Default `null`. - `passphrase`: A string of passphrase for the private key or pfx. Default `null`.
- `cert`: Public x509 certificate to use. Default `null`. - `cert`: Public x509 certificate to use. Default `null`.
- `ca`: An authority certificate or array of authority certificates to check - `ca`: An authority certificate or array of authority certificates to check
the remote host against. the remote host against.

66
doc/api/tls.markdown

@ -28,6 +28,17 @@ Alternatively you can send the CSR to a Certificate Authority for signing.
(TODO: docs on creating a CA, for now interested users should just look at (TODO: docs on creating a CA, for now interested users should just look at
`test/fixtures/keys/Makefile` in the Node source code) `test/fixtures/keys/Makefile` in the Node source code)
To create .pfx or .p12, do this:
openssl pkcs12 -export -in agent5-cert.pem -inkey agent5-key.pem \
-certfile ca-cert.pem -out agent5.pfx
- `in`: certificate
- `inkey`: private key
- `certfile`: all CA certs concatenated in one file like
`cat ca1-cert.pem ca2-cert.pem > ca-cert.pem`
## Client-initiated renegotiation attack mitigation ## Client-initiated renegotiation attack mitigation
<!-- type=misc --> <!-- type=misc -->
@ -72,10 +83,14 @@ The `connectionListener` argument is automatically set as a listener for the
[secureConnection](#event_secureConnection_) event. [secureConnection](#event_secureConnection_) event.
The `options` object has these possibilities: The `options` object has these possibilities:
- `pfx`: A string or `Buffer` containing the private key, certificate and
CA certs of the server in PFX or PKCS12 format. (Mutually exclusive with
the `key`, `cert` and `ca` options.)
- `key`: A string or `Buffer` containing the private key of the server in - `key`: A string or `Buffer` containing the private key of the server in
PEM format. (Required) PEM format. (Required)
- `passphrase`: A string of passphrase for the private key. - `passphrase`: A string of passphrase for the private key or pfx.
- `cert`: A string or `Buffer` containing the certificate key of the server in - `cert`: A string or `Buffer` containing the certificate key of the server in
PEM format. (Required) PEM format. (Required)
@ -137,7 +152,29 @@ Here is a simple example echo server:
console.log('server bound'); console.log('server bound');
}); });
Or
var tls = require('tls');
var fs = require('fs');
var options = {
pfx: fs.readFileSync('server.pfx'),
// This is necessary only if using the client certificate authentication.
requestCert: true,
};
var server = tls.createServer(options, function(cleartextStream) {
console.log('server connected',
cleartextStream.authorized ? 'authorized' : 'unauthorized');
cleartextStream.write("welcome!\n");
cleartextStream.setEncoding('utf8');
cleartextStream.pipe(cleartextStream);
});
server.listen(8000, function() {
console.log('server bound');
});
You can test this server by connecting to it with `openssl s_client`: You can test this server by connecting to it with `openssl s_client`:
@ -149,10 +186,13 @@ You can test this server by connecting to it with `openssl s_client`:
Creates a new client connection to the given `port` and `host`. (If `host` Creates a new client connection to the given `port` and `host`. (If `host`
defaults to `localhost`.) `options` should be an object which specifies defaults to `localhost`.) `options` should be an object which specifies
- `pfx`: A string or `Buffer` containing the private key, certificate and
CA certs of the server in PFX or PKCS12 format.
- `key`: A string or `Buffer` containing the private key of the client in - `key`: A string or `Buffer` containing the private key of the client in
PEM format. PEM format.
- `passphrase`: A string of passphrase for the private key. - `passphrase`: A string of passphrase for the private key or pfx.
- `cert`: A string or `Buffer` containing the certificate key of the client in - `cert`: A string or `Buffer` containing the certificate key of the client in
PEM format. PEM format.
@ -206,6 +246,28 @@ Here is an example of a client of echo server as described previously:
server.close(); server.close();
}); });
Or
var tls = require('tls');
var fs = require('fs');
var options = {
pfx: fs.readFileSync('client.pfx')
};
var cleartextStream = tls.connect(8000, options, function() {
console.log('client connected',
cleartextStream.authorized ? 'authorized' : 'unauthorized');
process.stdin.pipe(cleartextStream);
process.stdin.resume();
});
cleartextStream.setEncoding('utf8');
cleartextStream.on('data', function(data) {
console.log(data);
});
cleartextStream.on('end', function() {
server.close();
});
## tls.createSecurePair([credentials], [isServer], [requestCert], [rejectUnauthorized]) ## tls.createSecurePair([credentials], [isServer], [requestCert], [rejectUnauthorized])

8
lib/crypto.js

@ -114,6 +114,14 @@ exports.createCredentials = function(options, context) {
c.context.setSessionIdContext(options.sessionIdContext); c.context.setSessionIdContext(options.sessionIdContext);
} }
if (options.pfx) {
if (options.passphrase) {
c.context.loadPKCS12(options.pfx, options.passphrase);
} else {
c.context.loadPKCS12(options.pfx);
}
}
return c; return c;
}; };

2
lib/tls.js

@ -912,6 +912,7 @@ function Server(/* [options], listener */) {
this.setOptions(options); this.setOptions(options);
var sharedCreds = crypto.createCredentials({ var sharedCreds = crypto.createCredentials({
pfx : self.pfx,
key: self.key, key: self.key,
passphrase: self.passphrase, passphrase: self.passphrase,
cert: self.cert, cert: self.cert,
@ -996,6 +997,7 @@ Server.prototype.setOptions = function(options) {
this.rejectUnauthorized = false; this.rejectUnauthorized = false;
} }
if (options.pfx) this.pfx = options.pfx;
if (options.key) this.key = options.key; if (options.key) this.key = options.key;
if (options.passphrase) this.passphrase = options.passphrase; if (options.passphrase) this.passphrase = options.passphrase;
if (options.cert) this.cert = options.cert; if (options.cert) this.cert = options.cert;

79
src/node_crypto.cc

@ -43,6 +43,7 @@
# include <pthread.h> # include <pthread.h>
#endif #endif
#if OPENSSL_VERSION_NUMBER >= 0x10000000L #if OPENSSL_VERSION_NUMBER >= 0x10000000L
# define OPENSSL_CONST const # define OPENSSL_CONST const
#else #else
@ -169,6 +170,7 @@ void SecureContext::Initialize(Handle<Object> target) {
NODE_SET_PROTOTYPE_METHOD(t, "setSessionIdContext", NODE_SET_PROTOTYPE_METHOD(t, "setSessionIdContext",
SecureContext::SetSessionIdContext); SecureContext::SetSessionIdContext);
NODE_SET_PROTOTYPE_METHOD(t, "close", SecureContext::Close); NODE_SET_PROTOTYPE_METHOD(t, "close", SecureContext::Close);
NODE_SET_PROTOTYPE_METHOD(t, "loadPKCS12", SecureContext::LoadPKCS12);
target->Set(String::NewSymbol("SecureContext"), t->GetFunction()); target->Set(String::NewSymbol("SecureContext"), t->GetFunction());
} }
@ -595,6 +597,83 @@ Handle<Value> SecureContext::Close(const Arguments& args) {
return False(); return False();
} }
//Takes .pfx or .p12 and password in string or buffer format
Handle<Value> SecureContext::LoadPKCS12(const Arguments& args) {
HandleScope scope;
BIO* in = NULL;
PKCS12* p12 = NULL;
EVP_PKEY* pkey = NULL;
X509* cert = NULL;
STACK_OF(X509)* extraCerts = NULL;
char* pass = NULL;
bool ret = false;
SecureContext *sc = ObjectWrap::Unwrap<SecureContext>(args.Holder());
if (args.Length() < 1) {
return ThrowException(Exception::TypeError(
String::New("Bad parameter")));
}
in = LoadBIO(args[0]);
if (in == NULL) {
return ThrowException(Exception::Error(
String::New("Unable to load BIO")));
}
if (args.Length() >= 2) {
ASSERT_IS_STRING_OR_BUFFER(args[1]);
int passlen = DecodeBytes(args[1], BINARY);
if (passlen < 0) {
BIO_free(in);
return ThrowException(Exception::TypeError(
String::New("Bad password")));
}
pass = new char[passlen + 1];
int pass_written = DecodeWrite(pass, passlen, args[1], BINARY);
assert(pass_written == passlen);
pass[passlen] = '\0';
}
if (d2i_PKCS12_bio(in, &p12) &&
PKCS12_parse(p12, pass, &pkey, &cert, &extraCerts) &&
SSL_CTX_use_certificate(sc->ctx_, cert) &&
SSL_CTX_use_PrivateKey(sc->ctx_, pkey))
{
// set extra certs
while (X509* x509 = sk_X509_pop(extraCerts)) {
if (!sc->ca_store_) {
sc->ca_store_ = X509_STORE_new();
SSL_CTX_set_cert_store(sc->ctx_, sc->ca_store_);
}
X509_STORE_add_cert(sc->ca_store_, x509);
SSL_CTX_add_client_CA(sc->ctx_, x509);
}
EVP_PKEY_free(pkey);
X509_free(cert);
sk_X509_free(extraCerts);
ret = true;
}
PKCS12_free(p12);
BIO_free(in);
delete[] pass;
if (!ret) {
unsigned long err = ERR_get_error();
const char *str = ERR_reason_error_string(err);
return ThrowException(Exception::Error(String::New(str)));
}
return True();
}
#ifdef SSL_PRINT_DEBUG #ifdef SSL_PRINT_DEBUG
# define DEBUG_PRINT(...) fprintf (stderr, __VA_ARGS__) # define DEBUG_PRINT(...) fprintf (stderr, __VA_ARGS__)

2
src/node_crypto.h

@ -35,6 +35,7 @@
#include <openssl/x509v3.h> #include <openssl/x509v3.h>
#include <openssl/hmac.h> #include <openssl/hmac.h>
#include <openssl/rand.h> #include <openssl/rand.h>
#include <openssl/pkcs12.h>
#ifdef OPENSSL_NPN_NEGOTIATED #ifdef OPENSSL_NPN_NEGOTIATED
#include <node_buffer.h> #include <node_buffer.h>
@ -68,6 +69,7 @@ class SecureContext : ObjectWrap {
static v8::Handle<v8::Value> SetOptions(const v8::Arguments& args); static v8::Handle<v8::Value> SetOptions(const v8::Arguments& args);
static v8::Handle<v8::Value> SetSessionIdContext(const v8::Arguments& args); static v8::Handle<v8::Value> SetSessionIdContext(const v8::Arguments& args);
static v8::Handle<v8::Value> Close(const v8::Arguments& args); static v8::Handle<v8::Value> Close(const v8::Arguments& args);
static v8::Handle<v8::Value> LoadPKCS12(const v8::Arguments& args);
SecureContext() : ObjectWrap() { SecureContext() : ObjectWrap() {
ctx_ = NULL; ctx_ = NULL;

BIN
test/fixtures/test_cert.pfx

Binary file not shown.

19
test/simple/test-crypto.js

@ -38,6 +38,7 @@ var path = require('path');
// Test Certificates // Test Certificates
var caPem = fs.readFileSync(common.fixturesDir + '/test_ca.pem', 'ascii'); var caPem = fs.readFileSync(common.fixturesDir + '/test_ca.pem', 'ascii');
var certPem = fs.readFileSync(common.fixturesDir + '/test_cert.pem', 'ascii'); var certPem = fs.readFileSync(common.fixturesDir + '/test_cert.pem', 'ascii');
var certPfx = fs.readFileSync(common.fixturesDir + '/test_cert.pfx');
var keyPem = fs.readFileSync(common.fixturesDir + '/test_key.pem', 'ascii'); var keyPem = fs.readFileSync(common.fixturesDir + '/test_key.pem', 'ascii');
var rsaPubPem = fs.readFileSync(common.fixturesDir + '/test_rsa_pubkey.pem', var rsaPubPem = fs.readFileSync(common.fixturesDir + '/test_rsa_pubkey.pem',
'ascii'); 'ascii');
@ -54,8 +55,24 @@ try {
process.exit(); process.exit();
} }
// Test HMAC // PFX tests
assert.doesNotThrow(function() {
crypto.createCredentials({pfx:certPfx, passphrase:'sample'});
});
assert.throws(function() {
crypto.createCredentials({pfx:certPfx});
}, 'mac verify failure');
assert.throws(function() {
crypto.createCredentials({pfx:certPfx, passphrase:'test'});
}, 'mac verify failure');
assert.throws(function() {
crypto.createCredentials({pfx:'sample', passphrase:'test'});
}, 'not enough data');
// Test HMAC
var h1 = crypto.createHmac('sha1', 'Node') var h1 = crypto.createHmac('sha1', 'Node')
.update('some data') .update('some data')
.update('to hmac') .update('to hmac')

Loading…
Cancel
Save