Skip to content

Commit b3ef289

Browse files
committed
tls: support OCSP on client and server
1 parent 77d1f4a commit b3ef289

19 files changed

+622
-145
lines changed

doc/api/tls.markdown

+54
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,10 @@ Construct a new TLSSocket object from existing TCP socket.
408408

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

411+
- `requestOCSP`: Optional, if `true` - OCSP status request extension would
412+
be added to client hello, and `OCSPResponse` event will be emitted on socket
413+
before establishing secure communication
414+
411415
## tls.createSecurePair([context], [isServer], [requestCert], [rejectUnauthorized])
412416

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

510514

515+
### Event: 'OCSPRequest'
516+
517+
`function (certificate, issuer, callback) { }`
518+
519+
Emitted when the client sends a certificate status request. You could parse
520+
server's current certificate to obtain OCSP url and certificate id, and after
521+
obtaining OCSP response invoke `callback(null, resp)`, where `resp` is a
522+
`Buffer` instance. Both `certificate` and `issuer` are a `Buffer`
523+
DER-representations of the primary and issuer's certificates. They could be used
524+
to obtain OCSP certificate id and OCSP endpoint url.
525+
526+
Alternatively, `callback(null, null)` could be called, meaning that there is no
527+
OCSP response.
528+
529+
Calling `callback(err)` will result in a `socket.destroy(err)` call.
530+
531+
Typical flow:
532+
533+
1. Client connects to server and sends `OCSPRequest` to it (via status info
534+
extension in ClientHello.)
535+
2. Server receives request and invokes `OCSPRequest` event listener if present
536+
3. Server grabs OCSP url from either `certificate` or `issuer` and performs an
537+
[OCSP request] to the CA
538+
4. Server receives `OCSPResponse` from CA and sends it back to client via
539+
`callback` argument
540+
5. Client validates the response and either destroys socket or performs a
541+
handshake.
542+
543+
NOTE: `issuer` could be null, if certficiate is self-signed or if issuer is not
544+
in the root certificates list. (You could provide an issuer via `ca` option.)
545+
546+
NOTE: adding this event listener will have an effect only on connections
547+
established after addition of event listener.
548+
549+
NOTE: you may want to use some npm module like [asn1.js] to parse the
550+
certificates.
551+
552+
511553
### server.listen(port, [host], [callback])
512554

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

622+
### Event: 'OCSPResponse'
623+
624+
`function (response) { }`
625+
626+
This event will be emitted if `requestOCSP` option was set. `response` is a
627+
buffer object, containing server's OCSP response.
628+
629+
Traditionally, the `response` is a signed object from the server's CA that
630+
contains information about server's certificate revocation status.
631+
580632
### tlsSocket.encrypted
581633

582634
Static boolean value, always `true`. May be used to distinguish TLS sockets
@@ -711,3 +763,5 @@ The numeric representation of the local port.
711763
[Forward secrecy]: http://en.wikipedia.org/wiki/Perfect_forward_secrecy
712764
[DHE]: https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange
713765
[ECDHE]: https://en.wikipedia.org/wiki/Elliptic_curve_Diffie%E2%80%93Hellman
766+
[asn1.js]: http://npmjs.org/package/asn1.js
767+
[OCSP request]: http://en.wikipedia.org/wiki/OCSP_stapling

lib/_tls_common.js

+38-12
Original file line numberDiff line numberDiff line change
@@ -68,18 +68,8 @@ exports.createSecureContext = function createSecureContext(options, context) {
6868
}
6969
}
7070

71-
if (options.cert) c.context.setCert(options.cert);
72-
73-
if (options.ciphers)
74-
c.context.setCiphers(options.ciphers);
75-
else
76-
c.context.setCiphers(tls.DEFAULT_CIPHERS);
77-
78-
if (util.isUndefined(options.ecdhCurve))
79-
c.context.setECDHCurve(tls.DEFAULT_ECDH_CURVE);
80-
else if (options.ecdhCurve)
81-
c.context.setECDHCurve(options.ecdhCurve);
82-
71+
// NOTE: It's important to add CA before the cert to be able to load
72+
// cert's issuer in C++ code.
8373
if (options.ca) {
8474
if (util.isArray(options.ca)) {
8575
for (var i = 0, len = options.ca.length; i < len; i++) {
@@ -92,6 +82,18 @@ exports.createSecureContext = function createSecureContext(options, context) {
9282
c.context.addRootCerts();
9383
}
9484

85+
if (options.cert) c.context.setCert(options.cert);
86+
87+
if (options.ciphers)
88+
c.context.setCiphers(options.ciphers);
89+
else
90+
c.context.setCiphers(tls.DEFAULT_CIPHERS);
91+
92+
if (util.isUndefined(options.ecdhCurve))
93+
c.context.setECDHCurve(tls.DEFAULT_ECDH_CURVE);
94+
else if (options.ecdhCurve)
95+
c.context.setECDHCurve(options.ecdhCurve);
96+
9597
if (options.crl) {
9698
if (util.isArray(options.crl)) {
9799
for (var i = 0, len = options.crl.length; i < len; i++) {
@@ -126,3 +128,27 @@ exports.createSecureContext = function createSecureContext(options, context) {
126128

127129
return c;
128130
};
131+
132+
exports.translatePeerCertificate = function translatePeerCertificate(c) {
133+
if (!c)
134+
return null;
135+
136+
if (c.issuer) c.issuer = tls.parseCertString(c.issuer);
137+
if (c.subject) c.subject = tls.parseCertString(c.subject);
138+
if (c.infoAccess) {
139+
var info = c.infoAccess;
140+
c.infoAccess = {};
141+
142+
// XXX: More key validation?
143+
info.replace(/([^\n:]*):([^\n]*)(?:\n|$)/g, function(all, key, val) {
144+
if (key === '__proto__')
145+
return;
146+
147+
if (c.infoAccess.hasOwnProperty(key))
148+
c.infoAccess[key].push(val);
149+
else
150+
c.infoAccess[key] = [val];
151+
});
152+
}
153+
return c;
154+
};

lib/_tls_legacy.js

+13-9
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ var events = require('events');
2525
var stream = require('stream');
2626
var tls = require('tls');
2727
var util = require('util');
28+
var common = require('_tls_common');
2829

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

380381
CryptoStream.prototype.getPeerCertificate = function() {
381-
if (this.pair.ssl) {
382-
var c = this.pair.ssl.getPeerCertificate();
383-
384-
if (c) {
385-
if (c.issuer) c.issuer = tls.parseCertString(c.issuer);
386-
if (c.subject) c.subject = tls.parseCertString(c.subject);
387-
return c;
388-
}
389-
}
382+
if (this.pair.ssl)
383+
return common.translatePeerCertificate(this.pair.ssl.getPeerCertificate());
390384

391385
return null;
392386
};
@@ -677,6 +671,11 @@ function onnewsessiondone() {
677671
}
678672

679673

674+
function onocspresponse(resp) {
675+
this.emit('OCSPResponse', resp);
676+
}
677+
678+
680679
/**
681680
* Provides a pair of streams to do encrypted communication.
682681
*/
@@ -733,6 +732,8 @@ function SecurePair(context, isServer, requestCert, rejectUnauthorized,
733732
this.ssl.onnewsession = onnewsession.bind(this);
734733
this.ssl.lastHandshakeTime = 0;
735734
this.ssl.handshakes = 0;
735+
} else {
736+
this.ssl.onocspresponse = onocspresponse.bind(this);
736737
}
737738

738739
if (process.features.tls_sni) {
@@ -764,6 +765,9 @@ function SecurePair(context, isServer, requestCert, rejectUnauthorized,
764765
if (self.ssl) {
765766
self.ssl.start();
766767

768+
if (options.requestOCSP)
769+
self.ssl.requestOCSP();
770+
767771
/* In case of cipher suite failures - SSL_accept/SSL_connect may fail */
768772
if (self.ssl && self.ssl.error)
769773
self.error();

0 commit comments

Comments
 (0)