tls: support OCSP on client and server
This commit is contained in:
parent
77d1f4a91f
commit
b3ef289ffb
@ -408,6 +408,10 @@ Construct a new TLSSocket object from existing TCP socket.
|
|||||||
|
|
||||||
- `session`: Optional, a `Buffer` instance, containing TLS session
|
- `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])
|
## tls.createSecurePair([context], [isServer], [requestCert], [rejectUnauthorized])
|
||||||
|
|
||||||
Stability: 0 - Deprecated. Use tls.TLSSocket instead.
|
Stability: 0 - Deprecated. Use tls.TLSSocket instead.
|
||||||
@ -508,6 +512,44 @@ NOTE: adding this event listener will have an effect only on connections
|
|||||||
established after addition of event listener.
|
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])
|
### server.listen(port, [host], [callback])
|
||||||
|
|
||||||
Begin accepting connections on the specified `port` and `host`. If the
|
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
|
|||||||
`tlsSocket.authorizationError`. Also if NPN was used - you can check
|
`tlsSocket.authorizationError`. Also if NPN was used - you can check
|
||||||
`tlsSocket.npnProtocol` for negotiated protocol.
|
`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
|
### tlsSocket.encrypted
|
||||||
|
|
||||||
Static boolean value, always `true`. May be used to distinguish TLS sockets
|
Static boolean value, always `true`. May be used to distinguish TLS sockets
|
||||||
@ -711,3 +763,5 @@ The numeric representation of the local port.
|
|||||||
[Forward secrecy]: http://en.wikipedia.org/wiki/Perfect_forward_secrecy
|
[Forward secrecy]: http://en.wikipedia.org/wiki/Perfect_forward_secrecy
|
||||||
[DHE]: https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange
|
[DHE]: https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange
|
||||||
[ECDHE]: https://en.wikipedia.org/wiki/Elliptic_curve_Diffie%E2%80%93Hellman
|
[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
|
||||||
|
@ -68,18 +68,8 @@ exports.createSecureContext = function createSecureContext(options, context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.cert) c.context.setCert(options.cert);
|
// NOTE: It's important to add CA before the cert to be able to load
|
||||||
|
// cert's issuer in C++ code.
|
||||||
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.ca) {
|
if (options.ca) {
|
||||||
if (util.isArray(options.ca)) {
|
if (util.isArray(options.ca)) {
|
||||||
for (var i = 0, len = options.ca.length; i < len; i++) {
|
for (var i = 0, len = options.ca.length; i < len; i++) {
|
||||||
@ -92,6 +82,18 @@ exports.createSecureContext = function createSecureContext(options, context) {
|
|||||||
c.context.addRootCerts();
|
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 (options.crl) {
|
||||||
if (util.isArray(options.crl)) {
|
if (util.isArray(options.crl)) {
|
||||||
for (var i = 0, len = options.crl.length; i < len; i++) {
|
for (var i = 0, len = options.crl.length; i < len; i++) {
|
||||||
@ -126,3 +128,27 @@ exports.createSecureContext = function createSecureContext(options, context) {
|
|||||||
|
|
||||||
return c;
|
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;
|
||||||
|
};
|
||||||
|
@ -25,6 +25,7 @@ var events = require('events');
|
|||||||
var stream = require('stream');
|
var stream = require('stream');
|
||||||
var tls = require('tls');
|
var tls = require('tls');
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
|
var common = require('_tls_common');
|
||||||
|
|
||||||
var Timer = process.binding('timer_wrap').Timer;
|
var Timer = process.binding('timer_wrap').Timer;
|
||||||
var Connection = null;
|
var Connection = null;
|
||||||
@ -378,15 +379,8 @@ CryptoStream.prototype.__defineGetter__('bytesWritten', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
CryptoStream.prototype.getPeerCertificate = function() {
|
CryptoStream.prototype.getPeerCertificate = function() {
|
||||||
if (this.pair.ssl) {
|
if (this.pair.ssl)
|
||||||
var c = this.pair.ssl.getPeerCertificate();
|
return common.translatePeerCertificate(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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
@ -677,6 +671,11 @@ function onnewsessiondone() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function onocspresponse(resp) {
|
||||||
|
this.emit('OCSPResponse', resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a pair of streams to do encrypted communication.
|
* Provides a pair of streams to do encrypted communication.
|
||||||
*/
|
*/
|
||||||
@ -733,6 +732,8 @@ function SecurePair(context, isServer, requestCert, rejectUnauthorized,
|
|||||||
this.ssl.onnewsession = onnewsession.bind(this);
|
this.ssl.onnewsession = onnewsession.bind(this);
|
||||||
this.ssl.lastHandshakeTime = 0;
|
this.ssl.lastHandshakeTime = 0;
|
||||||
this.ssl.handshakes = 0;
|
this.ssl.handshakes = 0;
|
||||||
|
} else {
|
||||||
|
this.ssl.onocspresponse = onocspresponse.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.features.tls_sni) {
|
if (process.features.tls_sni) {
|
||||||
@ -764,6 +765,9 @@ function SecurePair(context, isServer, requestCert, rejectUnauthorized,
|
|||||||
if (self.ssl) {
|
if (self.ssl) {
|
||||||
self.ssl.start();
|
self.ssl.start();
|
||||||
|
|
||||||
|
if (options.requestOCSP)
|
||||||
|
self.ssl.requestOCSP();
|
||||||
|
|
||||||
/* In case of cipher suite failures - SSL_accept/SSL_connect may fail */
|
/* In case of cipher suite failures - SSL_accept/SSL_connect may fail */
|
||||||
if (self.ssl && self.ssl.error)
|
if (self.ssl && self.ssl.error)
|
||||||
self.error();
|
self.error();
|
||||||
|
158
lib/_tls_wrap.js
158
lib/_tls_wrap.js
@ -28,6 +28,7 @@ var crypto = require('crypto');
|
|||||||
var net = require('net');
|
var net = require('net');
|
||||||
var tls = require('tls');
|
var tls = require('tls');
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
|
var common = require('_tls_common');
|
||||||
|
|
||||||
var Timer = process.binding('timer_wrap').Timer;
|
var Timer = process.binding('timer_wrap').Timer;
|
||||||
var tls_wrap = process.binding('tls_wrap');
|
var tls_wrap = process.binding('tls_wrap');
|
||||||
@ -73,24 +74,96 @@ function onhandshakedone() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function onclienthello(hello) {
|
function loadSession(self, hello, cb) {
|
||||||
var self = this,
|
var once = false;
|
||||||
onceSession = false,
|
function onSession(err, session) {
|
||||||
onceSNI = false;
|
if (once)
|
||||||
|
return cb(new Error('TLS session callback was called 2 times'));
|
||||||
function callback(err, session) {
|
once = true;
|
||||||
if (onceSession)
|
|
||||||
return self.destroy(new Error('TLS session callback was called 2 times'));
|
|
||||||
onceSession = true;
|
|
||||||
|
|
||||||
if (err)
|
if (err)
|
||||||
return self.destroy(err);
|
return cb(err);
|
||||||
|
|
||||||
// NOTE: That we have disabled OpenSSL's internal session storage in
|
// NOTE: That we have disabled OpenSSL's internal session storage in
|
||||||
// `node_crypto.cc` and hence its safe to rely on getting servername only
|
// `node_crypto.cc` and hence its safe to rely on getting servername only
|
||||||
// from clienthello or this place.
|
// from clienthello or this place.
|
||||||
var ret = self.ssl.loadSession(session);
|
var ret = self.ssl.loadSession(session);
|
||||||
|
|
||||||
|
cb(null, ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hello.sessionId.length <= 0 ||
|
||||||
|
hello.tlsTicket ||
|
||||||
|
self.server &&
|
||||||
|
!self.server.emit('resumeSession', hello.sessionId, onSession)) {
|
||||||
|
cb(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function loadSNI(self, servername, cb) {
|
||||||
|
if (!servername || !self._SNICallback)
|
||||||
|
return cb(null);
|
||||||
|
|
||||||
|
var once = false;
|
||||||
|
self._SNICallback(servername, function(err, context) {
|
||||||
|
if (once)
|
||||||
|
return cb(new Error('TLS SNI callback was called 2 times'));
|
||||||
|
once = true;
|
||||||
|
|
||||||
|
if (err)
|
||||||
|
return cb(err);
|
||||||
|
|
||||||
|
// TODO(indutny): eventually disallow raw `SecureContext`
|
||||||
|
if (context)
|
||||||
|
self.ssl.sni_context = context.context || context;
|
||||||
|
|
||||||
|
cb(null, self.ssl.sni_context);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function requestOCSP(self, hello, ctx, cb) {
|
||||||
|
if (!hello.OCSPRequest || !self.server)
|
||||||
|
return cb(null);
|
||||||
|
|
||||||
|
if (!ctx)
|
||||||
|
ctx = self.server._sharedCreds;
|
||||||
|
if (ctx.context)
|
||||||
|
ctx = ctx.context;
|
||||||
|
|
||||||
|
if (self.server.listeners('OCSPRequest').length === 0) {
|
||||||
|
return cb(null);
|
||||||
|
} else {
|
||||||
|
self.server.emit('OCSPRequest',
|
||||||
|
ctx.getCertificate(),
|
||||||
|
ctx.getIssuer(),
|
||||||
|
onOCSP);
|
||||||
|
}
|
||||||
|
|
||||||
|
var once = false;
|
||||||
|
function onOCSP(err, response) {
|
||||||
|
if (once)
|
||||||
|
return cb(new Error('TLS OCSP callback was called 2 times'));
|
||||||
|
once = true;
|
||||||
|
|
||||||
|
if (err)
|
||||||
|
return cb(err);
|
||||||
|
|
||||||
|
if (response)
|
||||||
|
self.ssl.setOCSPResponse(response);
|
||||||
|
cb(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function onclienthello(hello) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
loadSession(self, hello, function(err, session) {
|
||||||
|
if (err)
|
||||||
|
return self.destroy(err);
|
||||||
|
|
||||||
// Servername came from SSL session
|
// Servername came from SSL session
|
||||||
// NOTE: TLS Session ticket doesn't include servername information
|
// NOTE: TLS Session ticket doesn't include servername information
|
||||||
//
|
//
|
||||||
@ -104,42 +177,18 @@ function onclienthello(hello) {
|
|||||||
// session.
|
// session.
|
||||||
//
|
//
|
||||||
// Therefore we should account session loading when dealing with servername
|
// Therefore we should account session loading when dealing with servername
|
||||||
if (!self._SNICallback) {
|
var servername = session && session.servername || hello.servername;
|
||||||
self.ssl.endParser();
|
loadSNI(self, servername, function(err, ctx) {
|
||||||
} else if (ret && ret.servername) {
|
if (err)
|
||||||
self._SNICallback(ret.servername, onSNIResult);
|
return self.destroy(err);
|
||||||
} else if (hello.servername && self._SNICallback) {
|
requestOCSP(self, hello, ctx, function(err) {
|
||||||
self._SNICallback(hello.servername, onSNIResult);
|
|
||||||
} else {
|
|
||||||
self.ssl.endParser();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onSNIResult(err, context) {
|
|
||||||
if (onceSNI)
|
|
||||||
return self.destroy(new Error('TLS SNI callback was called 2 times'));
|
|
||||||
onceSNI = true;
|
|
||||||
|
|
||||||
if (err)
|
if (err)
|
||||||
return self.destroy(err);
|
return self.destroy(err);
|
||||||
|
|
||||||
// TODO(indutny): eventually disallow raw `SecureContext`
|
|
||||||
if (context)
|
|
||||||
self.ssl.sni_context = context.context || context;
|
|
||||||
|
|
||||||
self.ssl.endParser();
|
self.ssl.endParser();
|
||||||
}
|
});
|
||||||
|
});
|
||||||
if (hello.sessionId.length <= 0 ||
|
});
|
||||||
hello.tlsTicket ||
|
|
||||||
this.server &&
|
|
||||||
!this.server.emit('resumeSession', hello.sessionId, callback)) {
|
|
||||||
// Invoke SNI callback, since we've no session to resume
|
|
||||||
if (hello.servername && this._SNICallback)
|
|
||||||
this._SNICallback(hello.servername, onSNIResult);
|
|
||||||
else
|
|
||||||
this.ssl.endParser();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -166,6 +215,11 @@ function onnewsession(key, session) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function onocspresponse(resp) {
|
||||||
|
this.emit('OCSPResponse', resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a wrap of socket stream to do encrypted communication.
|
* Provides a wrap of socket stream to do encrypted communication.
|
||||||
*/
|
*/
|
||||||
@ -259,12 +313,14 @@ TLSSocket.prototype._init = function(socket) {
|
|||||||
|
|
||||||
if (this.server &&
|
if (this.server &&
|
||||||
(this.server.listeners('resumeSession').length > 0 ||
|
(this.server.listeners('resumeSession').length > 0 ||
|
||||||
this.server.listeners('newSession').length > 0)) {
|
this.server.listeners('newSession').length > 0 ||
|
||||||
|
this.server.listeners('OCSPRequest').length > 0)) {
|
||||||
this.ssl.enableSessionCallbacks();
|
this.ssl.enableSessionCallbacks();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.ssl.onhandshakestart = function() {};
|
this.ssl.onhandshakestart = function() {};
|
||||||
this.ssl.onhandshakedone = this._finishInit.bind(this);
|
this.ssl.onhandshakedone = this._finishInit.bind(this);
|
||||||
|
this.ssl.onocspresponse = onocspresponse.bind(this);
|
||||||
|
|
||||||
if (options.session)
|
if (options.session)
|
||||||
this.ssl.setSession(options.session);
|
this.ssl.setSession(options.session);
|
||||||
@ -402,6 +458,8 @@ TLSSocket.prototype._finishInit = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
TLSSocket.prototype._start = function() {
|
TLSSocket.prototype._start = function() {
|
||||||
|
if (this._tlsOptions.requestOCSP)
|
||||||
|
this.ssl.requestOCSP();
|
||||||
this.ssl.start();
|
this.ssl.start();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -416,15 +474,8 @@ TLSSocket.prototype.setSession = function(session) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
TLSSocket.prototype.getPeerCertificate = function() {
|
TLSSocket.prototype.getPeerCertificate = function() {
|
||||||
if (this.ssl) {
|
if (this.ssl)
|
||||||
var c = this.ssl.getPeerCertificate();
|
return common.translatePeerCertificate(this.ssl.getPeerCertificate());
|
||||||
|
|
||||||
if (c) {
|
|
||||||
if (c.issuer) c.issuer = tls.parseCertString(c.issuer);
|
|
||||||
if (c.subject) c.subject = tls.parseCertString(c.subject);
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
@ -794,7 +845,8 @@ exports.connect = function(/* [port, host], options, cb */) {
|
|||||||
requestCert: true,
|
requestCert: true,
|
||||||
rejectUnauthorized: options.rejectUnauthorized,
|
rejectUnauthorized: options.rejectUnauthorized,
|
||||||
session: options.session,
|
session: options.session,
|
||||||
NPNProtocols: NPN.NPNProtocols
|
NPNProtocols: NPN.NPNProtocols,
|
||||||
|
requestOCSP: options.requestOCSP
|
||||||
});
|
});
|
||||||
result = socket;
|
result = socket;
|
||||||
}
|
}
|
||||||
|
@ -111,6 +111,7 @@ namespace node {
|
|||||||
V(hostmaster_string, "hostmaster") \
|
V(hostmaster_string, "hostmaster") \
|
||||||
V(ignore_string, "ignore") \
|
V(ignore_string, "ignore") \
|
||||||
V(immediate_callback_string, "_immediateCallback") \
|
V(immediate_callback_string, "_immediateCallback") \
|
||||||
|
V(infoaccess_string, "infoAccess") \
|
||||||
V(inherit_string, "inherit") \
|
V(inherit_string, "inherit") \
|
||||||
V(ino_string, "ino") \
|
V(ino_string, "ino") \
|
||||||
V(input_string, "input") \
|
V(input_string, "input") \
|
||||||
@ -136,6 +137,7 @@ namespace node {
|
|||||||
V(nice_string, "nice") \
|
V(nice_string, "nice") \
|
||||||
V(nlink_string, "nlink") \
|
V(nlink_string, "nlink") \
|
||||||
V(nsname_string, "nsname") \
|
V(nsname_string, "nsname") \
|
||||||
|
V(ocsp_request_string, "OCSPRequest") \
|
||||||
V(offset_string, "offset") \
|
V(offset_string, "offset") \
|
||||||
V(onchange_string, "onchange") \
|
V(onchange_string, "onchange") \
|
||||||
V(onclienthello_string, "onclienthello") \
|
V(onclienthello_string, "onclienthello") \
|
||||||
@ -149,6 +151,7 @@ namespace node {
|
|||||||
V(onmessage_string, "onmessage") \
|
V(onmessage_string, "onmessage") \
|
||||||
V(onnewsession_string, "onnewsession") \
|
V(onnewsession_string, "onnewsession") \
|
||||||
V(onnewsessiondone_string, "onnewsessiondone") \
|
V(onnewsessiondone_string, "onnewsessiondone") \
|
||||||
|
V(onocspresponse_string, "onocspresponse") \
|
||||||
V(onread_string, "onread") \
|
V(onread_string, "onread") \
|
||||||
V(onselect_string, "onselect") \
|
V(onselect_string, "onselect") \
|
||||||
V(onsignal_string, "onsignal") \
|
V(onsignal_string, "onsignal") \
|
||||||
@ -207,6 +210,7 @@ namespace node {
|
|||||||
V(timestamp_string, "timestamp") \
|
V(timestamp_string, "timestamp") \
|
||||||
V(title_string, "title") \
|
V(title_string, "title") \
|
||||||
V(tls_npn_string, "tls_npn") \
|
V(tls_npn_string, "tls_npn") \
|
||||||
|
V(tls_ocsp_string, "tls_ocsp") \
|
||||||
V(tls_sni_string, "tls_sni") \
|
V(tls_sni_string, "tls_sni") \
|
||||||
V(tls_string, "tls") \
|
V(tls_string, "tls") \
|
||||||
V(tls_ticket_string, "tlsTicket") \
|
V(tls_ticket_string, "tlsTicket") \
|
||||||
|
@ -2447,6 +2447,13 @@ static Handle<Object> GetFeatures(Environment* env) {
|
|||||||
#endif
|
#endif
|
||||||
obj->Set(env->tls_sni_string(), tls_sni);
|
obj->Set(env->tls_sni_string(), tls_sni);
|
||||||
|
|
||||||
|
#if !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_set_tlsext_status_cb)
|
||||||
|
Local<Boolean> tls_ocsp = True(env->isolate());
|
||||||
|
#else
|
||||||
|
Local<Boolean> tls_ocsp = False(env->isolate());
|
||||||
|
#endif // !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_set_tlsext_status_cb)
|
||||||
|
obj->Set(env->tls_ocsp_string(), tls_ocsp);
|
||||||
|
|
||||||
obj->Set(env->tls_string(),
|
obj->Set(env->tls_string(),
|
||||||
Boolean::New(env->isolate(), get_builtin_module("crypto") != NULL));
|
Boolean::New(env->isolate(), get_builtin_module("crypto") != NULL));
|
||||||
|
|
||||||
|
@ -143,6 +143,7 @@ template int SSLWrap<TLSCallbacks>::SelectNextProtoCallback(
|
|||||||
unsigned int inlen,
|
unsigned int inlen,
|
||||||
void* arg);
|
void* arg);
|
||||||
#endif
|
#endif
|
||||||
|
template int SSLWrap<TLSCallbacks>::TLSExtStatusCallback(SSL* s, void* arg);
|
||||||
|
|
||||||
|
|
||||||
static void crypto_threadid_cb(CRYPTO_THREADID* tid) {
|
static void crypto_threadid_cb(CRYPTO_THREADID* tid) {
|
||||||
@ -283,6 +284,12 @@ void SecureContext::Initialize(Environment* env, Handle<Object> target) {
|
|||||||
NODE_SET_PROTOTYPE_METHOD(t, "loadPKCS12", SecureContext::LoadPKCS12);
|
NODE_SET_PROTOTYPE_METHOD(t, "loadPKCS12", SecureContext::LoadPKCS12);
|
||||||
NODE_SET_PROTOTYPE_METHOD(t, "getTicketKeys", SecureContext::GetTicketKeys);
|
NODE_SET_PROTOTYPE_METHOD(t, "getTicketKeys", SecureContext::GetTicketKeys);
|
||||||
NODE_SET_PROTOTYPE_METHOD(t, "setTicketKeys", SecureContext::SetTicketKeys);
|
NODE_SET_PROTOTYPE_METHOD(t, "setTicketKeys", SecureContext::SetTicketKeys);
|
||||||
|
NODE_SET_PROTOTYPE_METHOD(t,
|
||||||
|
"getCertificate",
|
||||||
|
SecureContext::GetCertificate<true>);
|
||||||
|
NODE_SET_PROTOTYPE_METHOD(t,
|
||||||
|
"getIssuer",
|
||||||
|
SecureContext::GetCertificate<false>);
|
||||||
|
|
||||||
target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "SecureContext"),
|
target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "SecureContext"),
|
||||||
t->GetFunction());
|
t->GetFunction());
|
||||||
@ -469,7 +476,10 @@ void SecureContext::SetKey(const FunctionCallbackInfo<Value>& args) {
|
|||||||
// sent to the peer in the Certificate message.
|
// sent to the peer in the Certificate message.
|
||||||
//
|
//
|
||||||
// Taken from OpenSSL - editted for style.
|
// Taken from OpenSSL - editted for style.
|
||||||
int SSL_CTX_use_certificate_chain(SSL_CTX *ctx, BIO *in) {
|
int SSL_CTX_use_certificate_chain(SSL_CTX *ctx,
|
||||||
|
BIO *in,
|
||||||
|
X509** cert,
|
||||||
|
X509** issuer) {
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
X509 *x = NULL;
|
X509 *x = NULL;
|
||||||
|
|
||||||
@ -511,6 +521,11 @@ int SSL_CTX_use_certificate_chain(SSL_CTX *ctx, BIO *in) {
|
|||||||
// added to the chain (while we must free the main
|
// added to the chain (while we must free the main
|
||||||
// certificate, since its reference count is increased
|
// certificate, since its reference count is increased
|
||||||
// by SSL_CTX_use_certificate).
|
// by SSL_CTX_use_certificate).
|
||||||
|
|
||||||
|
// Find issuer
|
||||||
|
if (*issuer != NULL || X509_check_issued(ca, x) != X509_V_OK)
|
||||||
|
continue;
|
||||||
|
*issuer = ca;
|
||||||
}
|
}
|
||||||
|
|
||||||
// When the while loop ends, it's usually just EOF.
|
// When the while loop ends, it's usually just EOF.
|
||||||
@ -524,9 +539,31 @@ int SSL_CTX_use_certificate_chain(SSL_CTX *ctx, BIO *in) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try getting issuer from a cert store
|
||||||
|
if (ret) {
|
||||||
|
if (*issuer == NULL) {
|
||||||
|
X509_STORE* store = SSL_CTX_get_cert_store(ctx);
|
||||||
|
X509_STORE_CTX store_ctx;
|
||||||
|
|
||||||
|
ret = X509_STORE_CTX_init(&store_ctx, store, NULL, NULL);
|
||||||
|
if (!ret)
|
||||||
|
goto end;
|
||||||
|
|
||||||
|
ret = X509_STORE_CTX_get1_issuer(issuer, &store_ctx, x);
|
||||||
|
X509_STORE_CTX_cleanup(&store_ctx);
|
||||||
|
|
||||||
|
ret = ret < 0 ? 0 : 1;
|
||||||
|
// NOTE: get_cert_store doesn't increment reference count,
|
||||||
|
// no need to free `store`
|
||||||
|
} else {
|
||||||
|
// Increment issuer reference count
|
||||||
|
CRYPTO_add(&(*issuer)->references, 1, CRYPTO_LOCK_X509);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
end:
|
end:
|
||||||
if (x != NULL)
|
if (x != NULL)
|
||||||
X509_free(x);
|
*cert = x;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -545,7 +582,10 @@ void SecureContext::SetCert(const FunctionCallbackInfo<Value>& args) {
|
|||||||
if (!bio)
|
if (!bio)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int rv = SSL_CTX_use_certificate_chain(sc->ctx_, bio);
|
int rv = SSL_CTX_use_certificate_chain(sc->ctx_,
|
||||||
|
bio,
|
||||||
|
&sc->cert_,
|
||||||
|
&sc->issuer_);
|
||||||
|
|
||||||
BIO_free_all(bio);
|
BIO_free_all(bio);
|
||||||
|
|
||||||
@ -881,6 +921,30 @@ void SecureContext::SetTicketKeys(const FunctionCallbackInfo<Value>& args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template <bool primary>
|
||||||
|
void SecureContext::GetCertificate(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
HandleScope scope(args.GetIsolate());
|
||||||
|
SecureContext* wrap = Unwrap<SecureContext>(args.Holder());
|
||||||
|
Environment* env = wrap->env();
|
||||||
|
X509* cert;
|
||||||
|
|
||||||
|
if (primary)
|
||||||
|
cert = wrap->cert_;
|
||||||
|
else
|
||||||
|
cert = wrap->issuer_;
|
||||||
|
if (cert == NULL)
|
||||||
|
return args.GetReturnValue().Set(Null(env->isolate()));
|
||||||
|
|
||||||
|
int size = i2d_X509(cert, NULL);
|
||||||
|
Local<Object> buff = Buffer::New(env, size);
|
||||||
|
unsigned char* serialized = reinterpret_cast<unsigned char*>(
|
||||||
|
Buffer::Data(buff));
|
||||||
|
i2d_X509(cert, &serialized);
|
||||||
|
|
||||||
|
args.GetReturnValue().Set(buff);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
template <class Base>
|
template <class Base>
|
||||||
void SSLWrap<Base>::AddMethods(Environment* env, Handle<FunctionTemplate> t) {
|
void SSLWrap<Base>::AddMethods(Environment* env, Handle<FunctionTemplate> t) {
|
||||||
HandleScope scope(env->isolate());
|
HandleScope scope(env->isolate());
|
||||||
@ -898,6 +962,8 @@ void SSLWrap<Base>::AddMethods(Environment* env, Handle<FunctionTemplate> t) {
|
|||||||
NODE_SET_PROTOTYPE_METHOD(t, "shutdown", Shutdown);
|
NODE_SET_PROTOTYPE_METHOD(t, "shutdown", Shutdown);
|
||||||
NODE_SET_PROTOTYPE_METHOD(t, "getTLSTicket", GetTLSTicket);
|
NODE_SET_PROTOTYPE_METHOD(t, "getTLSTicket", GetTLSTicket);
|
||||||
NODE_SET_PROTOTYPE_METHOD(t, "newSessionDone", NewSessionDone);
|
NODE_SET_PROTOTYPE_METHOD(t, "newSessionDone", NewSessionDone);
|
||||||
|
NODE_SET_PROTOTYPE_METHOD(t, "setOCSPResponse", SetOCSPResponse);
|
||||||
|
NODE_SET_PROTOTYPE_METHOD(t, "requestOCSP", RequestOCSP);
|
||||||
|
|
||||||
#ifdef SSL_set_max_send_fragment
|
#ifdef SSL_set_max_send_fragment
|
||||||
NODE_SET_PROTOTYPE_METHOD(t, "setMaxSendFragment", SetMaxSendFragment);
|
NODE_SET_PROTOTYPE_METHOD(t, "setMaxSendFragment", SetMaxSendFragment);
|
||||||
@ -926,6 +992,12 @@ void SSLWrap<Base>::InitNPN(SecureContext* sc, Base* base) {
|
|||||||
SSL_CTX_set_next_proto_select_cb(sc->ctx_, SelectNextProtoCallback, base);
|
SSL_CTX_set_next_proto_select_cb(sc->ctx_, SelectNextProtoCallback, base);
|
||||||
#endif // OPENSSL_NPN_NEGOTIATED
|
#endif // OPENSSL_NPN_NEGOTIATED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef NODE__HAVE_TLSEXT_STATUS_CB
|
||||||
|
// OCSP stapling
|
||||||
|
SSL_CTX_set_tlsext_status_cb(sc->ctx_, TLSExtStatusCallback);
|
||||||
|
SSL_CTX_set_tlsext_status_arg(sc->ctx_, base);
|
||||||
|
#endif // NODE__HAVE_TLSEXT_STATUS_CB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1001,6 +1073,8 @@ void SSLWrap<Base>::OnClientHello(void* arg,
|
|||||||
}
|
}
|
||||||
hello_obj->Set(env->tls_ticket_string(),
|
hello_obj->Set(env->tls_ticket_string(),
|
||||||
Boolean::New(env->isolate(), hello.has_ticket()));
|
Boolean::New(env->isolate(), hello.has_ticket()));
|
||||||
|
hello_obj->Set(env->ocsp_request_string(),
|
||||||
|
Boolean::New(env->isolate(), hello.ocsp_request()));
|
||||||
|
|
||||||
Local<Value> argv[] = { hello_obj };
|
Local<Value> argv[] = { hello_obj };
|
||||||
w->MakeCallback(env->onclienthello_string(), ARRAY_SIZE(argv), argv);
|
w->MakeCallback(env->onclienthello_string(), ARRAY_SIZE(argv), argv);
|
||||||
@ -1042,8 +1116,15 @@ void SSLWrap<Base>::GetPeerCertificate(
|
|||||||
}
|
}
|
||||||
(void) BIO_reset(bio);
|
(void) BIO_reset(bio);
|
||||||
|
|
||||||
int index = X509_get_ext_by_NID(peer_cert, NID_subject_alt_name, -1);
|
int nids[] = { NID_subject_alt_name, NID_info_access };
|
||||||
if (index >= 0) {
|
Local<String> keys[] = { env->subjectaltname_string(),
|
||||||
|
env->infoaccess_string() };
|
||||||
|
CHECK_EQ(ARRAY_SIZE(nids), ARRAY_SIZE(keys));
|
||||||
|
for (unsigned int i = 0; i < ARRAY_SIZE(nids); i++) {
|
||||||
|
int index = X509_get_ext_by_NID(peer_cert, nids[i], -1);
|
||||||
|
if (index < 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
X509_EXTENSION* ext;
|
X509_EXTENSION* ext;
|
||||||
int rv;
|
int rv;
|
||||||
|
|
||||||
@ -1054,7 +1135,7 @@ void SSLWrap<Base>::GetPeerCertificate(
|
|||||||
assert(rv == 1);
|
assert(rv == 1);
|
||||||
|
|
||||||
BIO_get_mem_ptr(bio, &mem);
|
BIO_get_mem_ptr(bio, &mem);
|
||||||
info->Set(env->subjectaltname_string(),
|
info->Set(keys[i],
|
||||||
OneByteString(args.GetIsolate(), mem->data, mem->length));
|
OneByteString(args.GetIsolate(), mem->data, mem->length));
|
||||||
|
|
||||||
(void) BIO_reset(bio);
|
(void) BIO_reset(bio);
|
||||||
@ -1316,6 +1397,34 @@ void SSLWrap<Base>::NewSessionDone(const FunctionCallbackInfo<Value>& args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template <class Base>
|
||||||
|
void SSLWrap<Base>::SetOCSPResponse(
|
||||||
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||||
|
#ifdef NODE__HAVE_TLSEXT_STATUS_CB
|
||||||
|
HandleScope scope(args.GetIsolate());
|
||||||
|
|
||||||
|
Base* w = Unwrap<Base>(args.Holder());
|
||||||
|
if (args.Length() < 1 || !Buffer::HasInstance(args[0]))
|
||||||
|
return w->env()->ThrowTypeError("Must give a Buffer as first argument");
|
||||||
|
|
||||||
|
w->ocsp_response_.Reset(args.GetIsolate(), args[0].As<Object>());
|
||||||
|
#endif // NODE__HAVE_TLSEXT_STATUS_CB
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template <class Base>
|
||||||
|
void SSLWrap<Base>::RequestOCSP(
|
||||||
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||||
|
#ifdef NODE__HAVE_TLSEXT_STATUS_CB
|
||||||
|
HandleScope scope(args.GetIsolate());
|
||||||
|
|
||||||
|
Base* w = Unwrap<Base>(args.Holder());
|
||||||
|
|
||||||
|
SSL_set_tlsext_status_type(w->ssl_, TLSEXT_STATUSTYPE_ocsp);
|
||||||
|
#endif // NODE__HAVE_TLSEXT_STATUS_CB
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifdef SSL_set_max_send_fragment
|
#ifdef SSL_set_max_send_fragment
|
||||||
template <class Base>
|
template <class Base>
|
||||||
void SSLWrap<Base>::SetMaxSendFragment(
|
void SSLWrap<Base>::SetMaxSendFragment(
|
||||||
@ -1547,6 +1656,55 @@ void SSLWrap<Base>::SetNPNProtocols(const FunctionCallbackInfo<Value>& args) {
|
|||||||
#endif // OPENSSL_NPN_NEGOTIATED
|
#endif // OPENSSL_NPN_NEGOTIATED
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef NODE__HAVE_TLSEXT_STATUS_CB
|
||||||
|
template <class Base>
|
||||||
|
int SSLWrap<Base>::TLSExtStatusCallback(SSL* s, void* arg) {
|
||||||
|
Base* w = static_cast<Base*>(arg);
|
||||||
|
Environment* env = w->env();
|
||||||
|
HandleScope handle_scope(env->isolate());
|
||||||
|
|
||||||
|
if (w->is_client()) {
|
||||||
|
// Incoming response
|
||||||
|
const unsigned char* resp;
|
||||||
|
int len = SSL_get_tlsext_status_ocsp_resp(s, &resp);
|
||||||
|
Local<Value> arg;
|
||||||
|
if (resp == NULL) {
|
||||||
|
arg = Null(env->isolate());
|
||||||
|
} else {
|
||||||
|
arg = Buffer::New(
|
||||||
|
env,
|
||||||
|
reinterpret_cast<char*>(const_cast<unsigned char*>(resp)),
|
||||||
|
len);
|
||||||
|
}
|
||||||
|
|
||||||
|
w->MakeCallback(env->onocspresponse_string(), 1, &arg);
|
||||||
|
|
||||||
|
// Somehow, client is expecting different return value here
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
// Outgoing response
|
||||||
|
if (w->ocsp_response_.IsEmpty())
|
||||||
|
return SSL_TLSEXT_ERR_NOACK;
|
||||||
|
|
||||||
|
Local<Object> obj = PersistentToLocal(env->isolate(), w->ocsp_response_);
|
||||||
|
char* resp = Buffer::Data(obj);
|
||||||
|
size_t len = Buffer::Length(obj);
|
||||||
|
|
||||||
|
// OpenSSL takes control of the pointer after accepting it
|
||||||
|
char* data = reinterpret_cast<char*>(malloc(len));
|
||||||
|
assert(data != NULL);
|
||||||
|
memcpy(data, resp, len);
|
||||||
|
|
||||||
|
if (!SSL_set_tlsext_status_ocsp_resp(s, data, len))
|
||||||
|
free(data);
|
||||||
|
w->ocsp_response_.Reset();
|
||||||
|
|
||||||
|
return SSL_TLSEXT_ERR_OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // NODE__HAVE_TLSEXT_STATUS_CB
|
||||||
|
|
||||||
|
|
||||||
void Connection::OnClientHelloParseEnd(void* arg) {
|
void Connection::OnClientHelloParseEnd(void* arg) {
|
||||||
Connection* conn = static_cast<Connection*>(arg);
|
Connection* conn = static_cast<Connection*>(arg);
|
||||||
|
|
||||||
|
@ -53,6 +53,9 @@
|
|||||||
|
|
||||||
#define EVP_F_EVP_DECRYPTFINAL 101
|
#define EVP_F_EVP_DECRYPTFINAL 101
|
||||||
|
|
||||||
|
#if !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_set_tlsext_status_cb)
|
||||||
|
# define NODE__HAVE_TLSEXT_STATUS_CB
|
||||||
|
#endif // !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_set_tlsext_status_cb)
|
||||||
|
|
||||||
namespace node {
|
namespace node {
|
||||||
namespace crypto {
|
namespace crypto {
|
||||||
@ -74,6 +77,8 @@ class SecureContext : public BaseObject {
|
|||||||
|
|
||||||
X509_STORE* ca_store_;
|
X509_STORE* ca_store_;
|
||||||
SSL_CTX* ctx_;
|
SSL_CTX* ctx_;
|
||||||
|
X509* cert_;
|
||||||
|
X509* issuer_;
|
||||||
|
|
||||||
static const int kMaxSessionSize = 10 * 1024;
|
static const int kMaxSessionSize = 10 * 1024;
|
||||||
|
|
||||||
@ -98,10 +103,15 @@ class SecureContext : public BaseObject {
|
|||||||
static void GetTicketKeys(const v8::FunctionCallbackInfo<v8::Value>& args);
|
static void GetTicketKeys(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
static void SetTicketKeys(const v8::FunctionCallbackInfo<v8::Value>& args);
|
static void SetTicketKeys(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
|
||||||
|
template <bool primary>
|
||||||
|
static void GetCertificate(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
|
||||||
SecureContext(Environment* env, v8::Local<v8::Object> wrap)
|
SecureContext(Environment* env, v8::Local<v8::Object> wrap)
|
||||||
: BaseObject(env, wrap),
|
: BaseObject(env, wrap),
|
||||||
ca_store_(NULL),
|
ca_store_(NULL),
|
||||||
ctx_(NULL) {
|
ctx_(NULL),
|
||||||
|
cert_(NULL),
|
||||||
|
issuer_(NULL) {
|
||||||
MakeWeak<SecureContext>(this);
|
MakeWeak<SecureContext>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,8 +125,14 @@ class SecureContext : public BaseObject {
|
|||||||
ctx_->cert_store = NULL;
|
ctx_->cert_store = NULL;
|
||||||
}
|
}
|
||||||
SSL_CTX_free(ctx_);
|
SSL_CTX_free(ctx_);
|
||||||
|
if (cert_ != NULL)
|
||||||
|
X509_free(cert_);
|
||||||
|
if (issuer_ != NULL)
|
||||||
|
X509_free(issuer_);
|
||||||
ctx_ = NULL;
|
ctx_ = NULL;
|
||||||
ca_store_ = NULL;
|
ca_store_ = NULL;
|
||||||
|
cert_ = NULL;
|
||||||
|
issuer_ = NULL;
|
||||||
} else {
|
} else {
|
||||||
assert(ca_store_ == NULL);
|
assert(ca_store_ == NULL);
|
||||||
}
|
}
|
||||||
@ -157,6 +173,9 @@ class SSLWrap {
|
|||||||
npn_protos_.Reset();
|
npn_protos_.Reset();
|
||||||
selected_npn_proto_.Reset();
|
selected_npn_proto_.Reset();
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef NODE__HAVE_TLSEXT_STATUS_CB
|
||||||
|
ocsp_response_.Reset();
|
||||||
|
#endif // NODE__HAVE_TLSEXT_STATUS_CB
|
||||||
}
|
}
|
||||||
|
|
||||||
inline SSL* ssl() const { return ssl_; }
|
inline SSL* ssl() const { return ssl_; }
|
||||||
@ -191,6 +210,8 @@ class SSLWrap {
|
|||||||
static void Shutdown(const v8::FunctionCallbackInfo<v8::Value>& args);
|
static void Shutdown(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
static void GetTLSTicket(const v8::FunctionCallbackInfo<v8::Value>& args);
|
static void GetTLSTicket(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
static void NewSessionDone(const v8::FunctionCallbackInfo<v8::Value>& args);
|
static void NewSessionDone(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
static void SetOCSPResponse(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
static void RequestOCSP(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
|
||||||
#ifdef SSL_set_max_send_fragment
|
#ifdef SSL_set_max_send_fragment
|
||||||
static void SetMaxSendFragment(
|
static void SetMaxSendFragment(
|
||||||
@ -212,6 +233,7 @@ class SSLWrap {
|
|||||||
unsigned int inlen,
|
unsigned int inlen,
|
||||||
void* arg);
|
void* arg);
|
||||||
#endif // OPENSSL_NPN_NEGOTIATED
|
#endif // OPENSSL_NPN_NEGOTIATED
|
||||||
|
static int TLSExtStatusCallback(SSL* s, void* arg);
|
||||||
|
|
||||||
inline Environment* ssl_env() const {
|
inline Environment* ssl_env() const {
|
||||||
return env_;
|
return env_;
|
||||||
@ -225,6 +247,10 @@ class SSLWrap {
|
|||||||
bool new_session_wait_;
|
bool new_session_wait_;
|
||||||
ClientHelloParser hello_parser_;
|
ClientHelloParser hello_parser_;
|
||||||
|
|
||||||
|
#ifdef NODE__HAVE_TLSEXT_STATUS_CB
|
||||||
|
v8::Persistent<v8::Object> ocsp_response_;
|
||||||
|
#endif // NODE__HAVE_TLSEXT_STATUS_CB
|
||||||
|
|
||||||
#ifdef OPENSSL_NPN_NEGOTIATED
|
#ifdef OPENSSL_NPN_NEGOTIATED
|
||||||
v8::Persistent<v8::Object> npn_protos_;
|
v8::Persistent<v8::Object> npn_protos_;
|
||||||
v8::Persistent<v8::Value> selected_npn_proto_;
|
v8::Persistent<v8::Value> selected_npn_proto_;
|
||||||
|
@ -123,6 +123,7 @@ void ClientHelloParser::ParseHeader(const uint8_t* data, size_t avail) {
|
|||||||
hello.session_id_ = session_id_;
|
hello.session_id_ = session_id_;
|
||||||
hello.session_size_ = session_size_;
|
hello.session_size_ = session_size_;
|
||||||
hello.has_ticket_ = tls_ticket_ != NULL && tls_ticket_size_ != 0;
|
hello.has_ticket_ = tls_ticket_ != NULL && tls_ticket_size_ != 0;
|
||||||
|
hello.ocsp_request_ = ocsp_request_;
|
||||||
hello.servername_ = servername_;
|
hello.servername_ = servername_;
|
||||||
hello.servername_size_ = servername_size_;
|
hello.servername_size_ = servername_size_;
|
||||||
onhello_cb_(cb_arg_, hello);
|
onhello_cb_(cb_arg_, hello);
|
||||||
@ -159,6 +160,18 @@ void ClientHelloParser::ParseExtension(ClientHelloParser::ExtensionType type,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case kStatusRequest:
|
||||||
|
// We are ignoring any data, just indicating the presence of extension
|
||||||
|
if (len < kMinStatusRequestSize)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Unknown type, ignore it
|
||||||
|
if (data[0] != kStatusRequestOCSP)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Ignore extensions, they won't work with caching on backend anyway
|
||||||
|
ocsp_request_ = 1;
|
||||||
|
break;
|
||||||
case kTLSSessionTicket:
|
case kTLSSessionTicket:
|
||||||
tls_ticket_size_ = len;
|
tls_ticket_size_ = len;
|
||||||
tls_ticket_ = data + len;
|
tls_ticket_ = data + len;
|
||||||
|
@ -34,7 +34,14 @@ class ClientHelloParser {
|
|||||||
ClientHelloParser() : state_(kEnded),
|
ClientHelloParser() : state_(kEnded),
|
||||||
onhello_cb_(NULL),
|
onhello_cb_(NULL),
|
||||||
onend_cb_(NULL),
|
onend_cb_(NULL),
|
||||||
cb_arg_(NULL) {
|
cb_arg_(NULL),
|
||||||
|
session_size_(0),
|
||||||
|
session_id_(NULL),
|
||||||
|
servername_size_(0),
|
||||||
|
servername_(NULL),
|
||||||
|
ocsp_request_(0),
|
||||||
|
tls_ticket_size_(0),
|
||||||
|
tls_ticket_(NULL) {
|
||||||
Reset();
|
Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,6 +55,7 @@ class ClientHelloParser {
|
|||||||
inline bool has_ticket() const { return has_ticket_; }
|
inline bool has_ticket() const { return has_ticket_; }
|
||||||
inline uint8_t servername_size() const { return servername_size_; }
|
inline uint8_t servername_size() const { return servername_size_; }
|
||||||
inline const uint8_t* servername() const { return servername_; }
|
inline const uint8_t* servername() const { return servername_; }
|
||||||
|
inline int ocsp_request() const { return ocsp_request_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint8_t session_size_;
|
uint8_t session_size_;
|
||||||
@ -55,6 +63,7 @@ class ClientHelloParser {
|
|||||||
bool has_ticket_;
|
bool has_ticket_;
|
||||||
uint8_t servername_size_;
|
uint8_t servername_size_;
|
||||||
const uint8_t* servername_;
|
const uint8_t* servername_;
|
||||||
|
int ocsp_request_;
|
||||||
|
|
||||||
friend class ClientHelloParser;
|
friend class ClientHelloParser;
|
||||||
};
|
};
|
||||||
@ -76,6 +85,8 @@ class ClientHelloParser {
|
|||||||
static const size_t kMaxTLSFrameLen = 16 * 1024 + 5;
|
static const size_t kMaxTLSFrameLen = 16 * 1024 + 5;
|
||||||
static const size_t kMaxSSLExFrameLen = 32 * 1024;
|
static const size_t kMaxSSLExFrameLen = 32 * 1024;
|
||||||
static const uint8_t kServernameHostname = 0;
|
static const uint8_t kServernameHostname = 0;
|
||||||
|
static const uint8_t kStatusRequestOCSP = 1;
|
||||||
|
static const size_t kMinStatusRequestSize = 5;
|
||||||
|
|
||||||
enum ParseState {
|
enum ParseState {
|
||||||
kWaiting,
|
kWaiting,
|
||||||
@ -99,6 +110,7 @@ class ClientHelloParser {
|
|||||||
|
|
||||||
enum ExtensionType {
|
enum ExtensionType {
|
||||||
kServerName = 0,
|
kServerName = 0,
|
||||||
|
kStatusRequest = 5,
|
||||||
kTLSSessionTicket = 35
|
kTLSSessionTicket = 35
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -123,6 +135,7 @@ class ClientHelloParser {
|
|||||||
const uint8_t* session_id_;
|
const uint8_t* session_id_;
|
||||||
uint16_t servername_size_;
|
uint16_t servername_size_;
|
||||||
const uint8_t* servername_;
|
const uint8_t* servername_;
|
||||||
|
uint8_t ocsp_request_;
|
||||||
uint16_t tls_ticket_size_;
|
uint16_t tls_ticket_size_;
|
||||||
const uint8_t* tls_ticket_;
|
const uint8_t* tls_ticket_;
|
||||||
};
|
};
|
||||||
|
2
test/fixtures/keys/Makefile
vendored
2
test/fixtures/keys/Makefile
vendored
@ -30,6 +30,8 @@ agent1-csr.pem: agent1.cnf agent1-key.pem
|
|||||||
|
|
||||||
agent1-cert.pem: agent1-csr.pem ca1-cert.pem ca1-key.pem
|
agent1-cert.pem: agent1-csr.pem ca1-cert.pem ca1-key.pem
|
||||||
openssl x509 -req \
|
openssl x509 -req \
|
||||||
|
-extfile agent1.cnf \
|
||||||
|
-extensions v3_ca \
|
||||||
-days 9999 \
|
-days 9999 \
|
||||||
-passin "pass:password" \
|
-passin "pass:password" \
|
||||||
-in agent1-csr.pem \
|
-in agent1-csr.pem \
|
||||||
|
30
test/fixtures/keys/agent1-cert.pem
vendored
30
test/fixtures/keys/agent1-cert.pem
vendored
@ -1,16 +1,18 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
MIICbjCCAdcCCQCahKvPuKcqtTANBgkqhkiG9w0BAQUFADB6MQswCQYDVQQGEwJV
|
MIIC1jCCAj+gAwIBAgIJAJqEq8+4pyq+MA0GCSqGSIb3DQEBBQUAMHoxCzAJBgNV
|
||||||
UzELMAkGA1UECBMCQ0ExCzAJBgNVBAcTAlNGMQ8wDQYDVQQKEwZKb3llbnQxEDAO
|
BAYTAlVTMQswCQYDVQQIEwJDQTELMAkGA1UEBxMCU0YxDzANBgNVBAoTBkpveWVu
|
||||||
BgNVBAsTB05vZGUuanMxDDAKBgNVBAMTA2NhMTEgMB4GCSqGSIb3DQEJARYRcnlA
|
dDEQMA4GA1UECxMHTm9kZS5qczEMMAoGA1UEAxMDY2ExMSAwHgYJKoZIhvcNAQkB
|
||||||
dGlueWNsb3Vkcy5vcmcwHhcNMTMwODAxMTExODU5WhcNNDAxMjE2MTExODU5WjB9
|
FhFyeUB0aW55Y2xvdWRzLm9yZzAeFw0xNDA0MTUyMTMxMzFaFw00MTA4MzAyMTMx
|
||||||
MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExCzAJBgNVBAcTAlNGMQ8wDQYDVQQK
|
MzFaMH0xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTELMAkGA1UEBxMCU0YxDzAN
|
||||||
EwZKb3llbnQxEDAOBgNVBAsTB05vZGUuanMxDzANBgNVBAMTBmFnZW50MTEgMB4G
|
BgNVBAoTBkpveWVudDEQMA4GA1UECxMHTm9kZS5qczEPMA0GA1UEAxMGYWdlbnQx
|
||||||
CSqGSIb3DQEJARYRcnlAdGlueWNsb3Vkcy5vcmcwgZ8wDQYJKoZIhvcNAQEBBQAD
|
MSAwHgYJKoZIhvcNAQkBFhFyeUB0aW55Y2xvdWRzLm9yZzCBnzANBgkqhkiG9w0B
|
||||||
gY0AMIGJAoGBAMNQTWAcktNJlmpEbu0xKJzjpI0MJfWZauUg5GXD6/CXRGOEQ/Im
|
AQEFAAOBjQAwgYkCgYEAuOs3hW8rF+7xx5iB9wjmIgd+HTqRFUeKxG+mWV35Hl6A
|
||||||
uqG7Ar23LrFK/y2goHCF+/ffJKaFzJ4iuv2nAlly/HTriQJUtP/dxacfqrC5A1GH
|
3uzYGXwWznqsOomr4a/UkZrxbPGp5Awqa9g72NF97g3Sysq2DW4a3ycXWAeYYcHS
|
||||||
EYAA/S1VShPUtpljADZWyEemWBzZacC2SQ5cChkXTmqJ9t3wYBSw/guHAgMBAAEw
|
lRxqJGXTjx+vG/0nDCXLBhoDKO00zEccdjGS8xEjjieQQr+KeASmIm0kQmuN5YcC
|
||||||
DQYJKoZIhvcNAQEFBQADgYEAbuPFhXlMbdYX0XpcPiiRamvO2Qha2GEBRSfqg1Qe
|
AwEAAaNhMF8wXQYIKwYBBQUHAQEEUTBPMCMGCCsGAQUFBzABhhdodHRwOi8vb2Nz
|
||||||
fZo5oRXlOd+QVh4O8A3AFY06ERKE72Ho01B+KM2MwpJk0izQhmC4a0pks0jrBuyW
|
cC5ub2RlanMub3JnLzAoBggrBgEFBQcwAoYcaHR0cDovL2NhLm5vZGVqcy5vcmcv
|
||||||
dGoVczyK8eCtbw3Y2uiALV+60EidhCbOqml+3kIDVF0cXkCYi5FVbHRTls7wL0gR
|
Y2EuY2VydDANBgkqhkiG9w0BAQUFAAOBgQAx6rhnYbPygJwIm6nidyx+ydJQC4Gk
|
||||||
Fe0=
|
JD+pzbdJkTS+01r+xjVY/Wckn4JAsIlo/MMn055rs2cfdjoQtlj6yjEU6AP/7bfr
|
||||||
|
Mju4lBxDLACJ2y5/rfj3wO4q4Knd4Q4mPWjlS2SwmkHZ21QOqJ6Ig9ps6HPM7syw
|
||||||
|
ZYQ3WQ1LOPAxMg==
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
|
16
test/fixtures/keys/agent1-csr.pem
vendored
16
test/fixtures/keys/agent1-csr.pem
vendored
@ -2,12 +2,12 @@
|
|||||||
MIIB4jCCAUsCAQAwfTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMQswCQYDVQQH
|
MIIB4jCCAUsCAQAwfTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMQswCQYDVQQH
|
||||||
EwJTRjEPMA0GA1UEChMGSm95ZW50MRAwDgYDVQQLEwdOb2RlLmpzMQ8wDQYDVQQD
|
EwJTRjEPMA0GA1UEChMGSm95ZW50MRAwDgYDVQQLEwdOb2RlLmpzMQ8wDQYDVQQD
|
||||||
EwZhZ2VudDExIDAeBgkqhkiG9w0BCQEWEXJ5QHRpbnljbG91ZHMub3JnMIGfMA0G
|
EwZhZ2VudDExIDAeBgkqhkiG9w0BCQEWEXJ5QHRpbnljbG91ZHMub3JnMIGfMA0G
|
||||||
CSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDUE1gHJLTSZZqRG7tMSic46SNDCX1mWrl
|
CSqGSIb3DQEBAQUAA4GNADCBiQKBgQC46zeFbysX7vHHmIH3COYiB34dOpEVR4rE
|
||||||
IORlw+vwl0RjhEPyJrqhuwK9ty6xSv8toKBwhfv33ySmhcyeIrr9pwJZcvx064kC
|
b6ZZXfkeXoDe7NgZfBbOeqw6iavhr9SRmvFs8ankDCpr2DvY0X3uDdLKyrYNbhrf
|
||||||
VLT/3cWnH6qwuQNRhxGAAP0tVUoT1LaZYwA2VshHplgc2WnAtkkOXAoZF05qifbd
|
JxdYB5hhwdKVHGokZdOPH68b/ScMJcsGGgMo7TTMRxx2MZLzESOOJ5BCv4p4BKYi
|
||||||
8GAUsP4LhwIDAQABoCUwIwYJKoZIhvcNAQkHMRYTFEEgY2hhbGxlbmdlIHBhc3N3
|
bSRCa43lhwIDAQABoCUwIwYJKoZIhvcNAQkHMRYTFEEgY2hhbGxlbmdlIHBhc3N3
|
||||||
b3JkMA0GCSqGSIb3DQEBBQUAA4GBAFRwfX09wCEqB5fOGTLSAQqK7/Tm47t8TcFy
|
b3JkMA0GCSqGSIb3DQEBBQUAA4GBAC1pwZvvfYfK8IXYyXLD3N47MEbn/Y8C85Qi
|
||||||
PsCoHcYSHCSSthknJgdnK9nQaVVVqVpDRgmUFmcWC27JOAFQLt79FqOYNLGrmvR/
|
rYVl7y/7ThurCLtWVlS3e7es3Kr8nxjHTjVZW20RZHOmOGfSOkXoL3uuwew1jvCq
|
||||||
ZaRbz3BBi4TBHClalnyBBzaYJJQz16qbT4j48TmzRQvBGR/gT2FpPoLVDWKU+U6E
|
ibM2jwPCq1N/I4D94Fzh9LG86Cu8U6PtBlZzgprdK84Fo8U/pFRikPrggApUiPhm
|
||||||
oU6hMCpb
|
MZeWhDyn
|
||||||
-----END CERTIFICATE REQUEST-----
|
-----END CERTIFICATE REQUEST-----
|
||||||
|
26
test/fixtures/keys/agent1-key.pem
vendored
26
test/fixtures/keys/agent1-key.pem
vendored
@ -1,15 +1,15 @@
|
|||||||
-----BEGIN RSA PRIVATE KEY-----
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
MIICXAIBAAKBgQDDUE1gHJLTSZZqRG7tMSic46SNDCX1mWrlIORlw+vwl0RjhEPy
|
MIICXgIBAAKBgQC46zeFbysX7vHHmIH3COYiB34dOpEVR4rEb6ZZXfkeXoDe7NgZ
|
||||||
JrqhuwK9ty6xSv8toKBwhfv33ySmhcyeIrr9pwJZcvx064kCVLT/3cWnH6qwuQNR
|
fBbOeqw6iavhr9SRmvFs8ankDCpr2DvY0X3uDdLKyrYNbhrfJxdYB5hhwdKVHGok
|
||||||
hxGAAP0tVUoT1LaZYwA2VshHplgc2WnAtkkOXAoZF05qifbd8GAUsP4LhwIDAQAB
|
ZdOPH68b/ScMJcsGGgMo7TTMRxx2MZLzESOOJ5BCv4p4BKYibSRCa43lhwIDAQAB
|
||||||
AoGAJI+nrFIs+fhQe9wLl8MYAyZp6y1W/b6WUAX0O0iNph/q4WYlAfNWBGhpfvIH
|
AoGBAIXZzPCLDXhffydo3vo/uMT9A26IzCfJB0s1PgYGHaK76TBz4+Bej+uZpD0j
|
||||||
f5C2a+ghoG60WBYhWjq5rvB5aCX/DchIATuaVHgaWcBf7y9NXnWDH9JMtDOTaVI6
|
FgVgzs8uhn7DVqQ5oiM5++fvi+Sd+KlyVNgLKe7UTBGYE5Nc9DuDDD0GmJtFvso6
|
||||||
s7inJwjqIJAHbloa82NGuwz/EN4Ncng6wTmf1gbF6UtOqGECQQD15UNAtpRqpGPz
|
amsVhECF8sWZVOAwUdrwHhWevp5gJ1cfs3YMTlT9YqdRaWOhAkEA8TJAPfnbEfWf
|
||||||
xPAZwT3TkY4gYLlZvqn21r/92P5XVbTJXyBTo9pwY4F7o/pNZAQcq3sPUrZW7T4X
|
saDhWCjanW+w2QEYPa6wYFt+I5L2XPTeKR/wEQ3EzM++vCWxSF5LNSaXIdic847p
|
||||||
t8nPT4RrAkEAy1bvewVS3U10V8ffzCl7F5WiaTEMa39F4e0QqBKOXdnDS2T1FJZl
|
BcIGi/0r6QJBAMREt5r1c6Wf5mS6i/Jg6AdCEUjy0feRCeKemJDMKxyl5m/cU+rk
|
||||||
VSVSXiVMd4qFQf4IVgBZCwihS1hpPSo8VQJBAL7vpBY27+4S8k4SaUIGbITBLHR1
|
p5YBUgwoI8kzc82GEhyg4/NgHQfNcrZdT+8CQFVzChNq21PHgyX46xzCjIDOOwcG
|
||||||
xtcqFv5F6NUrTuvv8C7Bf++Sdwb4LU4dmTnI5OyCN09Bsba0B5gRLVKd8zsCQAu4
|
PkJMCyx3/X446JMSJUrIh9Ji4F/3EYmyiNYsodRYsZ5KEYCwFpn1nUAnF1ECQQC/
|
||||||
AetEHkd0zEy2zzYT+e0dCZQoaH/VgPCJWhlloGDWSQQSWHGMTWC/2uRkH+kPyahI
|
uzl54YomJDyX7jzEfLJuVLY6AyvmowN7JN95pFoBVHf2ktBPySuFuKiEQ7oh1Wet
|
||||||
/LAAKyGQqMMP4FjPE1UCQAyPkF3dJy+KRZSQ2rz0bpBVGoUV31hl+SvMigCy0yUy
|
QOn0mZ/VovD5LFSBnkp1AkEAgOMkBCJuOfBDvQwrmAAowWQi//7D2x0fhyKcrF6D
|
||||||
QwvJxgN14LQJP+pCcuJGaSdiPsOjxqhPX7KMg3SiSlA=
|
EZYVV125Wodw3zFxmE9p4vb6Hg3X5jSyGMzdE5ZqMgBD7w==
|
||||||
-----END RSA PRIVATE KEY-----
|
-----END RSA PRIVATE KEY-----
|
||||||
|
7
test/fixtures/keys/agent1.cnf
vendored
7
test/fixtures/keys/agent1.cnf
vendored
@ -4,6 +4,7 @@ days = 999
|
|||||||
distinguished_name = req_distinguished_name
|
distinguished_name = req_distinguished_name
|
||||||
attributes = req_attributes
|
attributes = req_attributes
|
||||||
prompt = no
|
prompt = no
|
||||||
|
x509_extensions = v3_ca
|
||||||
|
|
||||||
[ req_distinguished_name ]
|
[ req_distinguished_name ]
|
||||||
C = US
|
C = US
|
||||||
@ -17,3 +18,9 @@ emailAddress = ry@tinyclouds.org
|
|||||||
[ req_attributes ]
|
[ req_attributes ]
|
||||||
challengePassword = A challenge password
|
challengePassword = A challenge password
|
||||||
|
|
||||||
|
[ v3_ca ]
|
||||||
|
authorityInfoAccess = @issuer_info
|
||||||
|
|
||||||
|
[ issuer_info ]
|
||||||
|
OCSP;URI.0 = http://ocsp.nodejs.org/
|
||||||
|
caIssuers;URI.0 = http://ca.nodejs.org/ca.cert
|
||||||
|
16
test/fixtures/keys/ca1-cert.pem
vendored
16
test/fixtures/keys/ca1-cert.pem
vendored
@ -1,15 +1,15 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
MIICazCCAdQCCQCK8euGRwPfJzANBgkqhkiG9w0BAQUFADB6MQswCQYDVQQGEwJV
|
MIICazCCAdQCCQC1CQyJn8L/kzANBgkqhkiG9w0BAQUFADB6MQswCQYDVQQGEwJV
|
||||||
UzELMAkGA1UECBMCQ0ExCzAJBgNVBAcTAlNGMQ8wDQYDVQQKEwZKb3llbnQxEDAO
|
UzELMAkGA1UECBMCQ0ExCzAJBgNVBAcTAlNGMQ8wDQYDVQQKEwZKb3llbnQxEDAO
|
||||||
BgNVBAsTB05vZGUuanMxDDAKBgNVBAMTA2NhMTEgMB4GCSqGSIb3DQEJARYRcnlA
|
BgNVBAsTB05vZGUuanMxDDAKBgNVBAMTA2NhMTEgMB4GCSqGSIb3DQEJARYRcnlA
|
||||||
dGlueWNsb3Vkcy5vcmcwHhcNMTMwODAxMTExODU5WhcNNDAxMjE2MTExODU5WjB6
|
dGlueWNsb3Vkcy5vcmcwHhcNMTQwNDE1MjEzMTMxWhcNNDEwODMwMjEzMTMxWjB6
|
||||||
MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExCzAJBgNVBAcTAlNGMQ8wDQYDVQQK
|
MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExCzAJBgNVBAcTAlNGMQ8wDQYDVQQK
|
||||||
EwZKb3llbnQxEDAOBgNVBAsTB05vZGUuanMxDDAKBgNVBAMTA2NhMTEgMB4GCSqG
|
EwZKb3llbnQxEDAOBgNVBAsTB05vZGUuanMxDDAKBgNVBAMTA2NhMTEgMB4GCSqG
|
||||||
SIb3DQEJARYRcnlAdGlueWNsb3Vkcy5vcmcwgZ8wDQYJKoZIhvcNAQEBBQADgY0A
|
SIb3DQEJARYRcnlAdGlueWNsb3Vkcy5vcmcwgZ8wDQYJKoZIhvcNAQEBBQADgY0A
|
||||||
MIGJAoGBAKk8iURIH5aHTpddeVkyMUUkiaP4W9M3x2nBqjvFTw7oP1mJYvab52ed
|
MIGJAoGBALBXMk/xFR2GN2v/wKreZKyIitGphxYGoJ2d//s/wM6qqyIW94aiq3sm
|
||||||
/2rA7fRt3kZyf7+lRt4OtXG7emsBj2F6d/iHKnWUfdMZl+cQ61Mtx6/DeO3F55aT
|
1zpmOTPTorT9Pk32A7uKKHfrafB+yA07QXgCYXgzcn17nfFInncDyGdggNFGAO13
|
||||||
QrCeqDpyAOY6FvfhdflZItrEMQa9+PbsbyRBSxDJ/Qs7qhevnlqBAgMBAAEwDQYJ
|
5JuC3JC8pRJpEokkMszpHJxPdR6gKXIT05blUnpwGT/AmYJ8S59lAgMBAAEwDQYJ
|
||||||
KoZIhvcNAQEFBQADgYEAZwg19wn9cU9Lb7pNw50qi9DeJhUvo4/Jua8FjikvoKX5
|
KoZIhvcNAQEFBQADgYEAAb+Pye0I+k927Qi2+cUowLS5MtmrEosUbTYwI4rqYSR2
|
||||||
oQSQ+J/7+83OEuJi2Ii1xH2fAlNN7ZoJzOHY/JU2tx64OmnhEPvnX/nb1/jK3zyn
|
aiibqmC3Z55N72ktQ2pJKP8I1t3Rk+j8/yIKWzSn5Jd2GT4ZzqbANrdLKeAsfVDK
|
||||||
gwJDHcYG6AU6nHGWRewQpkoYYIQ7YQNx26OGQF0QdAJi2ltKZpQKIv/75XWfKrQ=
|
pnsUR1IV/sdIvuELm+P4kyK5wafJytUjD+A4SH2oWN4EozDR1OidAhJrraXQksw=
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
|
2
test/fixtures/keys/ca1-cert.srl
vendored
2
test/fixtures/keys/ca1-cert.srl
vendored
@ -1 +1 @@
|
|||||||
9A84ABCFB8A72AB5
|
9A84ABCFB8A72ABE
|
||||||
|
30
test/fixtures/keys/ca1-key.pem
vendored
30
test/fixtures/keys/ca1-key.pem
vendored
@ -1,17 +1,17 @@
|
|||||||
-----BEGIN ENCRYPTED PRIVATE KEY-----
|
-----BEGIN ENCRYPTED PRIVATE KEY-----
|
||||||
MIICxjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIWE2PsdhhEj8CAggA
|
MIICxjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIJ36Z9tFiv2oCAggA
|
||||||
MBQGCCqGSIb3DQMHBAjAnjC7zH2c+ASCAoB/e9OPJqGdm2yw6CwNrdNF+besbvTB
|
MBQGCCqGSIb3DQMHBAjCAyUwdFh+7gSCAoD9Sv9009MmLb8+wHhYhxmrPrpeYZOt
|
||||||
3aLesJXqttUb7GkIG8D0wmIUfW1BgcROFyvD+Jz31NRES4/KmRpwybEoWOBtpYAZ
|
W8xfELfal/2XWFe41WV41Qsa2HyN03VeyQjb+cnKvZX6HUCe5zVMUsf+B91yQBqR
|
||||||
AbcLFAJm+RYWO6XMwq9kMnQ7I4QtXZzCywJ7TSFQlDt5BJWvhqp5rbQTj6gTGMLl
|
jOZgsgoflEC3tFrhW5ogmRnjuok3CbTYLx0f8GMucSRNa9ZPhpfUjrVcCUrKKRJ+
|
||||||
CwB12/GhpEviX9kDj49FvlylLAjkVR151nhAil2Nv9O4Ww/WV0y21tscaajFS2sb
|
owk0VxWQkRqMF/8WJJgHxnm8/jLbNKuHbwY8SSQO/pStyKxqx5rBXAA/YCrAx7EZ
|
||||||
2sZ+3iZIZL+PF8qFoBtffJHeEWIKlHtnbjl1BqMtSt67CbS0CJSqtGss3+eVq9Y1
|
aDvc2q9EwRHX+hWIhpIkYna7PwnNAX03Ghv4iiwUlwHoomvgxExdUc9T8HBQu/xP
|
||||||
OzeG8EAwTCP+HwOBgGNRssJxwnz+SaAZnb7te3x3yn7zac6+8PmZLdPcYBps2krS
|
2pmUqwTglAdRb0tNiYrF+yIesAlqBc1qoi7tv2SM3gTowdw2PeHq5MA/ShV21oC8
|
||||||
nDVZUBW0kybi7qGWW02SYdDXKOCBVAqlSILMdhMdArQqF7P3tAac2PJNFLBf/31w
|
51bc0OxRkEFsHGLIs1v5qzs21JhX0IKvb5LDHAaze2RwODfnCTcQb8jYaFZNmwpt
|
||||||
bVeqaXHmijbobcTsR7TV8SUMVPJXcDYg5qSsbGa+PLIPPFmQ/ZSSVXn+V7yVfUT5
|
ZrHgBQBwOHSvZ9CCqInDDtGBhYRlQIQRGgj9Ju5fruRYycn8vE58OcIfF52dwLKB
|
||||||
S/HjwBm9jdPj5l9uI6uInD8O1OFXvaP/usjKFAB/B2b3aTjtiBdqXQMAIaFKtVXs
|
7sjTFX0O0b58wpiQSJzbHGbjNlQNRXxdk9v1qfP8vx8rFHLoFSScaZomavp4uijU
|
||||||
BP3GgXxkVjrFWcVE6BXJUNRpx9EeBLz/7s86I3SUKuGduRe7aAfO0Q76dPqh6mwJ
|
yLKVwquzEOVOMP8cC+yRk+zkcb9EE2pE1CqG0Mj6MHH1Lha98kakTIAS/VG59/Mp
|
||||||
zvOuPJXlARTU5BVWiV7FP53KQZ6a+35urMOdnsq/Fzf4qg9yZcXp9hxJAspHgn8P
|
EqZoRtJ6n/DzscrRpHIm+170ufLGmivmTeOXcMyHv1pGp35c5VbmUTZCxcE7A/0f
|
||||||
3QKCJ6HnHvl9wpjQLxjLnQgIYBNeYW6vo/hUVRfTruN8VjeKWqoNQVEAOJ5Br4i9
|
WPLVB3lBPgsvR/4NVx9A6mvXbkp2yngdfbvQgzRDG6pfrE1xeiUAyRCts7dR3Q9/
|
||||||
/Nsjl8aw0kaRXonYtmlSD6lQycckkV7WkhFlXej8Q+mGTE60ut1gJ85cM+JHwTlk
|
dNHIM2wsiO3A/8Up1vrY6d/dcJt7cwjHRkx2eQFbpeE+a4CCNZ7gXFcwHLigBH8G
|
||||||
XFDe6qYCSZk17l6MMLNwbYeJjTqSbT0UAYx2lyTtAC4L+L/H8hFedwFp
|
uTTVZf4HJavjXiYlkY3OFnPuz4KzanJzuqluKeIdJwSEkp9Kra/Id0eO
|
||||||
-----END ENCRYPTED PRIVATE KEY-----
|
-----END ENCRYPTED PRIVATE KEY-----
|
||||||
|
109
test/simple/test-tls-ocsp-callback.js
Normal file
109
test/simple/test-tls-ocsp-callback.js
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
// Copyright Joyent, Inc. and other Node contributors.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
// copy of this software and associated documentation files (the
|
||||||
|
// "Software"), to deal in the Software without restriction, including
|
||||||
|
// without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||||
|
// persons to whom the Software is furnished to do so, subject to the
|
||||||
|
// following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included
|
||||||
|
// in all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||||
|
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||||
|
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||||
|
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
var common = require('../common');
|
||||||
|
|
||||||
|
if (!process.features.tls_ocsp) {
|
||||||
|
console.error('Skipping because node compiled without OpenSSL or ' +
|
||||||
|
'with old OpenSSL version.');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
if (!common.opensslCli) {
|
||||||
|
console.error('Skipping because node compiled without OpenSSL CLI.');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
test({ response: false }, function() {
|
||||||
|
test({ response: 'hello world' });
|
||||||
|
});
|
||||||
|
|
||||||
|
function test(testOptions, cb) {
|
||||||
|
var assert = require('assert');
|
||||||
|
var tls = require('tls');
|
||||||
|
var fs = require('fs');
|
||||||
|
var join = require('path').join;
|
||||||
|
var spawn = require('child_process').spawn;
|
||||||
|
|
||||||
|
var keyFile = join(common.fixturesDir, 'keys', 'agent1-key.pem');
|
||||||
|
var certFile = join(common.fixturesDir, 'keys', 'agent1-cert.pem');
|
||||||
|
var caFile = join(common.fixturesDir, 'keys', 'ca1-cert.pem');
|
||||||
|
var key = fs.readFileSync(keyFile);
|
||||||
|
var cert = fs.readFileSync(certFile);
|
||||||
|
var ca = fs.readFileSync(caFile);
|
||||||
|
var options = {
|
||||||
|
key: key,
|
||||||
|
cert: cert,
|
||||||
|
ca: [ca]
|
||||||
|
};
|
||||||
|
var requestCount = 0;
|
||||||
|
var ocspCount = 0;
|
||||||
|
var ocspResponse;
|
||||||
|
var session;
|
||||||
|
|
||||||
|
var server = tls.createServer(options, function(cleartext) {
|
||||||
|
cleartext.on('error', function(er) {
|
||||||
|
// We're ok with getting ECONNRESET in this test, but it's
|
||||||
|
// timing-dependent, and thus unreliable. Any other errors
|
||||||
|
// are just failures, though.
|
||||||
|
if (er.code !== 'ECONNRESET')
|
||||||
|
throw er;
|
||||||
|
});
|
||||||
|
++requestCount;
|
||||||
|
cleartext.end();
|
||||||
|
});
|
||||||
|
server.on('OCSPRequest', function(cert, issuer, callback) {
|
||||||
|
++ocspCount;
|
||||||
|
assert.ok(Buffer.isBuffer(cert));
|
||||||
|
assert.ok(Buffer.isBuffer(issuer));
|
||||||
|
|
||||||
|
// Just to check that async really works there
|
||||||
|
setTimeout(function() {
|
||||||
|
callback(null,
|
||||||
|
testOptions.response ? new Buffer(testOptions.response) : null);
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
server.listen(common.PORT, function() {
|
||||||
|
var client = tls.connect({
|
||||||
|
port: common.PORT,
|
||||||
|
requestOCSP: true,
|
||||||
|
rejectUnauthorized: false
|
||||||
|
}, function() {
|
||||||
|
});
|
||||||
|
client.on('OCSPResponse', function(resp) {
|
||||||
|
ocspResponse = resp;
|
||||||
|
if (resp)
|
||||||
|
client.destroy();
|
||||||
|
});
|
||||||
|
client.on('close', function() {
|
||||||
|
server.close(cb);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('exit', function() {
|
||||||
|
if (testOptions.response) {
|
||||||
|
assert.equal(ocspResponse.toString(), testOptions.response);
|
||||||
|
} else {
|
||||||
|
assert.ok(ocspResponse === null);
|
||||||
|
}
|
||||||
|
assert.equal(requestCount, testOptions.response ? 0 : 1);
|
||||||
|
assert.equal(ocspCount, 1);
|
||||||
|
});
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user