Skip to content

Commit

Permalink
tls: support OCSP on client and server
Browse files Browse the repository at this point in the history
  • Loading branch information
indutny committed Apr 17, 2014
1 parent 77d1f4a commit b3ef289
Show file tree
Hide file tree
Showing 19 changed files with 622 additions and 145 deletions.
54 changes: 54 additions & 0 deletions doc/api/tls.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,10 @@ Construct a new TLSSocket object from existing TCP socket.

- `session`: Optional, a `Buffer` instance, containing TLS session

- `requestOCSP`: Optional, if `true` - OCSP status request extension would
be added to client hello, and `OCSPResponse` event will be emitted on socket
before establishing secure communication

## tls.createSecurePair([context], [isServer], [requestCert], [rejectUnauthorized])

Stability: 0 - Deprecated. Use tls.TLSSocket instead.
Expand Down Expand Up @@ -508,6 +512,44 @@ NOTE: adding this event listener will have an effect only on connections
established after addition of event listener.


### Event: 'OCSPRequest'

`function (certificate, issuer, callback) { }`

Emitted when the client sends a certificate status request. You could parse
server's current certificate to obtain OCSP url and certificate id, and after
obtaining OCSP response invoke `callback(null, resp)`, where `resp` is a
`Buffer` instance. Both `certificate` and `issuer` are a `Buffer`
DER-representations of the primary and issuer's certificates. They could be used
to obtain OCSP certificate id and OCSP endpoint url.

Alternatively, `callback(null, null)` could be called, meaning that there is no
OCSP response.

Calling `callback(err)` will result in a `socket.destroy(err)` call.

Typical flow:

1. Client connects to server and sends `OCSPRequest` to it (via status info
extension in ClientHello.)
2. Server receives request and invokes `OCSPRequest` event listener if present
3. Server grabs OCSP url from either `certificate` or `issuer` and performs an
[OCSP request] to the CA
4. Server receives `OCSPResponse` from CA and sends it back to client via
`callback` argument
5. Client validates the response and either destroys socket or performs a
handshake.

NOTE: `issuer` could be null, if certficiate is self-signed or if issuer is not
in the root certificates list. (You could provide an issuer via `ca` option.)

NOTE: adding this event listener will have an effect only on connections
established after addition of event listener.

NOTE: you may want to use some npm module like [asn1.js] to parse the
certificates.


### server.listen(port, [host], [callback])

Begin accepting connections on the specified `port` and `host`. If the
Expand Down Expand Up @@ -577,6 +619,16 @@ If `tlsSocket.authorized === false` then the error can be found in
`tlsSocket.authorizationError`. Also if NPN was used - you can check
`tlsSocket.npnProtocol` for negotiated protocol.

### Event: 'OCSPResponse'

`function (response) { }`

This event will be emitted if `requestOCSP` option was set. `response` is a
buffer object, containing server's OCSP response.

Traditionally, the `response` is a signed object from the server's CA that
contains information about server's certificate revocation status.

### tlsSocket.encrypted

Static boolean value, always `true`. May be used to distinguish TLS sockets
Expand Down Expand Up @@ -711,3 +763,5 @@ The numeric representation of the local port.
[Forward secrecy]: http://en.wikipedia.org/wiki/Perfect_forward_secrecy
[DHE]: https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange
[ECDHE]: https://en.wikipedia.org/wiki/Elliptic_curve_Diffie%E2%80%93Hellman
[asn1.js]: http://npmjs.org/package/asn1.js
[OCSP request]: http://en.wikipedia.org/wiki/OCSP_stapling
50 changes: 38 additions & 12 deletions lib/_tls_common.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,18 +68,8 @@ exports.createSecureContext = function createSecureContext(options, context) {
}
}

if (options.cert) c.context.setCert(options.cert);

if (options.ciphers)
c.context.setCiphers(options.ciphers);
else
c.context.setCiphers(tls.DEFAULT_CIPHERS);

if (util.isUndefined(options.ecdhCurve))
c.context.setECDHCurve(tls.DEFAULT_ECDH_CURVE);
else if (options.ecdhCurve)
c.context.setECDHCurve(options.ecdhCurve);

// NOTE: It's important to add CA before the cert to be able to load
// cert's issuer in C++ code.
if (options.ca) {
if (util.isArray(options.ca)) {
for (var i = 0, len = options.ca.length; i < len; i++) {
Expand All @@ -92,6 +82,18 @@ exports.createSecureContext = function createSecureContext(options, context) {
c.context.addRootCerts();
}

if (options.cert) c.context.setCert(options.cert);

if (options.ciphers)
c.context.setCiphers(options.ciphers);
else
c.context.setCiphers(tls.DEFAULT_CIPHERS);

if (util.isUndefined(options.ecdhCurve))
c.context.setECDHCurve(tls.DEFAULT_ECDH_CURVE);
else if (options.ecdhCurve)
c.context.setECDHCurve(options.ecdhCurve);

if (options.crl) {
if (util.isArray(options.crl)) {
for (var i = 0, len = options.crl.length; i < len; i++) {
Expand Down Expand Up @@ -126,3 +128,27 @@ exports.createSecureContext = function createSecureContext(options, context) {

return c;
};

exports.translatePeerCertificate = function translatePeerCertificate(c) {
if (!c)
return null;

if (c.issuer) c.issuer = tls.parseCertString(c.issuer);
if (c.subject) c.subject = tls.parseCertString(c.subject);
if (c.infoAccess) {
var info = c.infoAccess;
c.infoAccess = {};

// XXX: More key validation?
info.replace(/([^\n:]*):([^\n]*)(?:\n|$)/g, function(all, key, val) {
if (key === '__proto__')
return;

if (c.infoAccess.hasOwnProperty(key))
c.infoAccess[key].push(val);
else
c.infoAccess[key] = [val];
});
}
return c;
};
22 changes: 13 additions & 9 deletions lib/_tls_legacy.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ var events = require('events');
var stream = require('stream');
var tls = require('tls');
var util = require('util');
var common = require('_tls_common');

var Timer = process.binding('timer_wrap').Timer;
var Connection = null;
Expand Down Expand Up @@ -378,15 +379,8 @@ CryptoStream.prototype.__defineGetter__('bytesWritten', function() {
});

CryptoStream.prototype.getPeerCertificate = function() {
if (this.pair.ssl) {
var c = this.pair.ssl.getPeerCertificate();

if (c) {
if (c.issuer) c.issuer = tls.parseCertString(c.issuer);
if (c.subject) c.subject = tls.parseCertString(c.subject);
return c;
}
}
if (this.pair.ssl)
return common.translatePeerCertificate(this.pair.ssl.getPeerCertificate());

return null;
};
Expand Down Expand Up @@ -677,6 +671,11 @@ function onnewsessiondone() {
}


function onocspresponse(resp) {
this.emit('OCSPResponse', resp);
}


/**
* Provides a pair of streams to do encrypted communication.
*/
Expand Down Expand Up @@ -733,6 +732,8 @@ function SecurePair(context, isServer, requestCert, rejectUnauthorized,
this.ssl.onnewsession = onnewsession.bind(this);
this.ssl.lastHandshakeTime = 0;
this.ssl.handshakes = 0;
} else {
this.ssl.onocspresponse = onocspresponse.bind(this);
}

if (process.features.tls_sni) {
Expand Down Expand Up @@ -764,6 +765,9 @@ function SecurePair(context, isServer, requestCert, rejectUnauthorized,
if (self.ssl) {
self.ssl.start();

if (options.requestOCSP)
self.ssl.requestOCSP();

/* In case of cipher suite failures - SSL_accept/SSL_connect may fail */
if (self.ssl && self.ssl.error)
self.error();
Expand Down
Loading

0 comments on commit b3ef289

Please sign in to comment.