diff --git a/doc/api/tls.markdown b/doc/api/tls.markdown index 43f819d81e5d55..3abbe64e478b35 100644 --- a/doc/api/tls.markdown +++ b/doc/api/tls.markdown @@ -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. @@ -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 @@ -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 @@ -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 diff --git a/lib/_tls_common.js b/lib/_tls_common.js index ce011bc7f3c0a8..4cd06f06f860eb 100644 --- a/lib/_tls_common.js +++ b/lib/_tls_common.js @@ -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++) { @@ -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++) { @@ -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; +}; diff --git a/lib/_tls_legacy.js b/lib/_tls_legacy.js index 1d62ed623985c4..09fdd5773898f0 100644 --- a/lib/_tls_legacy.js +++ b/lib/_tls_legacy.js @@ -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; @@ -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; }; @@ -677,6 +671,11 @@ function onnewsessiondone() { } +function onocspresponse(resp) { + this.emit('OCSPResponse', resp); +} + + /** * 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.lastHandshakeTime = 0; this.ssl.handshakes = 0; + } else { + this.ssl.onocspresponse = onocspresponse.bind(this); } if (process.features.tls_sni) { @@ -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(); diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js index e17a0646be7dbc..bdedb51210f3b2 100644 --- a/lib/_tls_wrap.js +++ b/lib/_tls_wrap.js @@ -28,6 +28,7 @@ var crypto = require('crypto'); var net = require('net'); var tls = require('tls'); var util = require('util'); +var common = require('_tls_common'); var Timer = process.binding('timer_wrap').Timer; var tls_wrap = process.binding('tls_wrap'); @@ -73,24 +74,96 @@ function onhandshakedone() { } -function onclienthello(hello) { - var self = this, - onceSession = false, - onceSNI = false; - - function callback(err, session) { - if (onceSession) - return self.destroy(new Error('TLS session callback was called 2 times')); - onceSession = true; +function loadSession(self, hello, cb) { + var once = false; + function onSession(err, session) { + if (once) + return cb(new Error('TLS session callback was called 2 times')); + once = true; if (err) - return self.destroy(err); + return cb(err); // NOTE: That we have disabled OpenSSL's internal session storage in // `node_crypto.cc` and hence its safe to rely on getting servername only // from clienthello or this place. 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 // NOTE: TLS Session ticket doesn't include servername information // @@ -104,42 +177,18 @@ function onclienthello(hello) { // session. // // Therefore we should account session loading when dealing with servername - if (!self._SNICallback) { - self.ssl.endParser(); - } else if (ret && ret.servername) { - self._SNICallback(ret.servername, onSNIResult); - } else if (hello.servername && self._SNICallback) { - 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) - return self.destroy(err); - - // TODO(indutny): eventually disallow raw `SecureContext` - if (context) - self.ssl.sni_context = context.context || context; - - 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(); - } + var servername = session && session.servername || hello.servername; + loadSNI(self, servername, function(err, ctx) { + if (err) + return self.destroy(err); + requestOCSP(self, hello, ctx, function(err) { + if (err) + return self.destroy(err); + + self.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. */ @@ -259,12 +313,14 @@ TLSSocket.prototype._init = function(socket) { if (this.server && (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(); } } else { this.ssl.onhandshakestart = function() {}; this.ssl.onhandshakedone = this._finishInit.bind(this); + this.ssl.onocspresponse = onocspresponse.bind(this); if (options.session) this.ssl.setSession(options.session); @@ -402,6 +458,8 @@ TLSSocket.prototype._finishInit = function() { }; TLSSocket.prototype._start = function() { + if (this._tlsOptions.requestOCSP) + this.ssl.requestOCSP(); this.ssl.start(); }; @@ -416,15 +474,8 @@ TLSSocket.prototype.setSession = function(session) { }; TLSSocket.prototype.getPeerCertificate = function() { - if (this.ssl) { - var c = 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; - } - } + if (this.ssl) + return common.translatePeerCertificate(this.ssl.getPeerCertificate()); return null; }; @@ -794,7 +845,8 @@ exports.connect = function(/* [port, host], options, cb */) { requestCert: true, rejectUnauthorized: options.rejectUnauthorized, session: options.session, - NPNProtocols: NPN.NPNProtocols + NPNProtocols: NPN.NPNProtocols, + requestOCSP: options.requestOCSP }); result = socket; } diff --git a/src/env.h b/src/env.h index 95e1df6e59329c..44054149a4d6ca 100644 --- a/src/env.h +++ b/src/env.h @@ -111,6 +111,7 @@ namespace node { V(hostmaster_string, "hostmaster") \ V(ignore_string, "ignore") \ V(immediate_callback_string, "_immediateCallback") \ + V(infoaccess_string, "infoAccess") \ V(inherit_string, "inherit") \ V(ino_string, "ino") \ V(input_string, "input") \ @@ -136,6 +137,7 @@ namespace node { V(nice_string, "nice") \ V(nlink_string, "nlink") \ V(nsname_string, "nsname") \ + V(ocsp_request_string, "OCSPRequest") \ V(offset_string, "offset") \ V(onchange_string, "onchange") \ V(onclienthello_string, "onclienthello") \ @@ -149,6 +151,7 @@ namespace node { V(onmessage_string, "onmessage") \ V(onnewsession_string, "onnewsession") \ V(onnewsessiondone_string, "onnewsessiondone") \ + V(onocspresponse_string, "onocspresponse") \ V(onread_string, "onread") \ V(onselect_string, "onselect") \ V(onsignal_string, "onsignal") \ @@ -207,6 +210,7 @@ namespace node { V(timestamp_string, "timestamp") \ V(title_string, "title") \ V(tls_npn_string, "tls_npn") \ + V(tls_ocsp_string, "tls_ocsp") \ V(tls_sni_string, "tls_sni") \ V(tls_string, "tls") \ V(tls_ticket_string, "tlsTicket") \ diff --git a/src/node.cc b/src/node.cc index afa4f2b030db26..51fd6a81888412 100644 --- a/src/node.cc +++ b/src/node.cc @@ -2447,6 +2447,13 @@ static Handle GetFeatures(Environment* env) { #endif obj->Set(env->tls_sni_string(), tls_sni); +#if !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_set_tlsext_status_cb) + Local tls_ocsp = True(env->isolate()); +#else + Local 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(), Boolean::New(env->isolate(), get_builtin_module("crypto") != NULL)); diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 407ad5283309ae..0d685c9f516e1a 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -143,6 +143,7 @@ template int SSLWrap::SelectNextProtoCallback( unsigned int inlen, void* arg); #endif +template int SSLWrap::TLSExtStatusCallback(SSL* s, void* arg); static void crypto_threadid_cb(CRYPTO_THREADID* tid) { @@ -283,6 +284,12 @@ void SecureContext::Initialize(Environment* env, Handle target) { NODE_SET_PROTOTYPE_METHOD(t, "loadPKCS12", SecureContext::LoadPKCS12); NODE_SET_PROTOTYPE_METHOD(t, "getTicketKeys", SecureContext::GetTicketKeys); NODE_SET_PROTOTYPE_METHOD(t, "setTicketKeys", SecureContext::SetTicketKeys); + NODE_SET_PROTOTYPE_METHOD(t, + "getCertificate", + SecureContext::GetCertificate); + NODE_SET_PROTOTYPE_METHOD(t, + "getIssuer", + SecureContext::GetCertificate); target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "SecureContext"), t->GetFunction()); @@ -469,7 +476,10 @@ void SecureContext::SetKey(const FunctionCallbackInfo& args) { // sent to the peer in the Certificate message. // // 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; 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 // certificate, since its reference count is increased // 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. @@ -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: if (x != NULL) - X509_free(x); + *cert = x; return ret; } @@ -545,7 +582,10 @@ void SecureContext::SetCert(const FunctionCallbackInfo& args) { if (!bio) 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); @@ -881,6 +921,30 @@ void SecureContext::SetTicketKeys(const FunctionCallbackInfo& args) { } +template +void SecureContext::GetCertificate(const FunctionCallbackInfo& args) { + HandleScope scope(args.GetIsolate()); + SecureContext* wrap = Unwrap(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 buff = Buffer::New(env, size); + unsigned char* serialized = reinterpret_cast( + Buffer::Data(buff)); + i2d_X509(cert, &serialized); + + args.GetReturnValue().Set(buff); +} + + template void SSLWrap::AddMethods(Environment* env, Handle t) { HandleScope scope(env->isolate()); @@ -898,6 +962,8 @@ void SSLWrap::AddMethods(Environment* env, Handle t) { NODE_SET_PROTOTYPE_METHOD(t, "shutdown", Shutdown); NODE_SET_PROTOTYPE_METHOD(t, "getTLSTicket", GetTLSTicket); 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 NODE_SET_PROTOTYPE_METHOD(t, "setMaxSendFragment", SetMaxSendFragment); @@ -926,6 +992,12 @@ void SSLWrap::InitNPN(SecureContext* sc, Base* base) { SSL_CTX_set_next_proto_select_cb(sc->ctx_, SelectNextProtoCallback, base); #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::OnClientHello(void* arg, } hello_obj->Set(env->tls_ticket_string(), Boolean::New(env->isolate(), hello.has_ticket())); + hello_obj->Set(env->ocsp_request_string(), + Boolean::New(env->isolate(), hello.ocsp_request())); Local argv[] = { hello_obj }; w->MakeCallback(env->onclienthello_string(), ARRAY_SIZE(argv), argv); @@ -1042,8 +1116,15 @@ void SSLWrap::GetPeerCertificate( } (void) BIO_reset(bio); - int index = X509_get_ext_by_NID(peer_cert, NID_subject_alt_name, -1); - if (index >= 0) { + int nids[] = { NID_subject_alt_name, NID_info_access }; + Local 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; int rv; @@ -1054,7 +1135,7 @@ void SSLWrap::GetPeerCertificate( assert(rv == 1); BIO_get_mem_ptr(bio, &mem); - info->Set(env->subjectaltname_string(), + info->Set(keys[i], OneByteString(args.GetIsolate(), mem->data, mem->length)); (void) BIO_reset(bio); @@ -1316,6 +1397,34 @@ void SSLWrap::NewSessionDone(const FunctionCallbackInfo& args) { } +template +void SSLWrap::SetOCSPResponse( + const v8::FunctionCallbackInfo& args) { +#ifdef NODE__HAVE_TLSEXT_STATUS_CB + HandleScope scope(args.GetIsolate()); + + Base* w = Unwrap(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()); +#endif // NODE__HAVE_TLSEXT_STATUS_CB +} + + +template +void SSLWrap::RequestOCSP( + const v8::FunctionCallbackInfo& args) { +#ifdef NODE__HAVE_TLSEXT_STATUS_CB + HandleScope scope(args.GetIsolate()); + + Base* w = Unwrap(args.Holder()); + + SSL_set_tlsext_status_type(w->ssl_, TLSEXT_STATUSTYPE_ocsp); +#endif // NODE__HAVE_TLSEXT_STATUS_CB +} + + #ifdef SSL_set_max_send_fragment template void SSLWrap::SetMaxSendFragment( @@ -1547,6 +1656,55 @@ void SSLWrap::SetNPNProtocols(const FunctionCallbackInfo& args) { #endif // OPENSSL_NPN_NEGOTIATED +#ifdef NODE__HAVE_TLSEXT_STATUS_CB +template +int SSLWrap::TLSExtStatusCallback(SSL* s, void* arg) { + Base* w = static_cast(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 arg; + if (resp == NULL) { + arg = Null(env->isolate()); + } else { + arg = Buffer::New( + env, + reinterpret_cast(const_cast(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 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(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) { Connection* conn = static_cast(arg); diff --git a/src/node_crypto.h b/src/node_crypto.h index 6605dfdf6958e2..7645eb281acee6 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -53,6 +53,9 @@ #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 crypto { @@ -74,6 +77,8 @@ class SecureContext : public BaseObject { X509_STORE* ca_store_; SSL_CTX* ctx_; + X509* cert_; + X509* issuer_; static const int kMaxSessionSize = 10 * 1024; @@ -98,10 +103,15 @@ class SecureContext : public BaseObject { static void GetTicketKeys(const v8::FunctionCallbackInfo& args); static void SetTicketKeys(const v8::FunctionCallbackInfo& args); + template + static void GetCertificate(const v8::FunctionCallbackInfo& args); + SecureContext(Environment* env, v8::Local wrap) : BaseObject(env, wrap), ca_store_(NULL), - ctx_(NULL) { + ctx_(NULL), + cert_(NULL), + issuer_(NULL) { MakeWeak(this); } @@ -115,8 +125,14 @@ class SecureContext : public BaseObject { ctx_->cert_store = NULL; } SSL_CTX_free(ctx_); + if (cert_ != NULL) + X509_free(cert_); + if (issuer_ != NULL) + X509_free(issuer_); ctx_ = NULL; ca_store_ = NULL; + cert_ = NULL; + issuer_ = NULL; } else { assert(ca_store_ == NULL); } @@ -157,6 +173,9 @@ class SSLWrap { npn_protos_.Reset(); selected_npn_proto_.Reset(); #endif +#ifdef NODE__HAVE_TLSEXT_STATUS_CB + ocsp_response_.Reset(); +#endif // NODE__HAVE_TLSEXT_STATUS_CB } inline SSL* ssl() const { return ssl_; } @@ -191,6 +210,8 @@ class SSLWrap { static void Shutdown(const v8::FunctionCallbackInfo& args); static void GetTLSTicket(const v8::FunctionCallbackInfo& args); static void NewSessionDone(const v8::FunctionCallbackInfo& args); + static void SetOCSPResponse(const v8::FunctionCallbackInfo& args); + static void RequestOCSP(const v8::FunctionCallbackInfo& args); #ifdef SSL_set_max_send_fragment static void SetMaxSendFragment( @@ -212,6 +233,7 @@ class SSLWrap { unsigned int inlen, void* arg); #endif // OPENSSL_NPN_NEGOTIATED + static int TLSExtStatusCallback(SSL* s, void* arg); inline Environment* ssl_env() const { return env_; @@ -225,6 +247,10 @@ class SSLWrap { bool new_session_wait_; ClientHelloParser hello_parser_; +#ifdef NODE__HAVE_TLSEXT_STATUS_CB + v8::Persistent ocsp_response_; +#endif // NODE__HAVE_TLSEXT_STATUS_CB + #ifdef OPENSSL_NPN_NEGOTIATED v8::Persistent npn_protos_; v8::Persistent selected_npn_proto_; diff --git a/src/node_crypto_clienthello.cc b/src/node_crypto_clienthello.cc index b786942529c8ff..c1228c79ac46e5 100644 --- a/src/node_crypto_clienthello.cc +++ b/src/node_crypto_clienthello.cc @@ -123,6 +123,7 @@ void ClientHelloParser::ParseHeader(const uint8_t* data, size_t avail) { hello.session_id_ = session_id_; hello.session_size_ = session_size_; hello.has_ticket_ = tls_ticket_ != NULL && tls_ticket_size_ != 0; + hello.ocsp_request_ = ocsp_request_; hello.servername_ = servername_; hello.servername_size_ = servername_size_; onhello_cb_(cb_arg_, hello); @@ -159,6 +160,18 @@ void ClientHelloParser::ParseExtension(ClientHelloParser::ExtensionType type, } } 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: tls_ticket_size_ = len; tls_ticket_ = data + len; diff --git a/src/node_crypto_clienthello.h b/src/node_crypto_clienthello.h index 4301d9bb1c0166..3ebcead0c36c7b 100644 --- a/src/node_crypto_clienthello.h +++ b/src/node_crypto_clienthello.h @@ -34,7 +34,14 @@ class ClientHelloParser { ClientHelloParser() : state_(kEnded), onhello_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(); } @@ -48,6 +55,7 @@ class ClientHelloParser { inline bool has_ticket() const { return has_ticket_; } inline uint8_t servername_size() const { return servername_size_; } inline const uint8_t* servername() const { return servername_; } + inline int ocsp_request() const { return ocsp_request_; } private: uint8_t session_size_; @@ -55,6 +63,7 @@ class ClientHelloParser { bool has_ticket_; uint8_t servername_size_; const uint8_t* servername_; + int ocsp_request_; friend class ClientHelloParser; }; @@ -76,6 +85,8 @@ class ClientHelloParser { static const size_t kMaxTLSFrameLen = 16 * 1024 + 5; static const size_t kMaxSSLExFrameLen = 32 * 1024; static const uint8_t kServernameHostname = 0; + static const uint8_t kStatusRequestOCSP = 1; + static const size_t kMinStatusRequestSize = 5; enum ParseState { kWaiting, @@ -99,6 +110,7 @@ class ClientHelloParser { enum ExtensionType { kServerName = 0, + kStatusRequest = 5, kTLSSessionTicket = 35 }; @@ -123,6 +135,7 @@ class ClientHelloParser { const uint8_t* session_id_; uint16_t servername_size_; const uint8_t* servername_; + uint8_t ocsp_request_; uint16_t tls_ticket_size_; const uint8_t* tls_ticket_; }; diff --git a/test/fixtures/keys/Makefile b/test/fixtures/keys/Makefile index c063377d97d99f..31e8484e5ce519 100644 --- a/test/fixtures/keys/Makefile +++ b/test/fixtures/keys/Makefile @@ -30,6 +30,8 @@ agent1-csr.pem: agent1.cnf agent1-key.pem agent1-cert.pem: agent1-csr.pem ca1-cert.pem ca1-key.pem openssl x509 -req \ + -extfile agent1.cnf \ + -extensions v3_ca \ -days 9999 \ -passin "pass:password" \ -in agent1-csr.pem \ diff --git a/test/fixtures/keys/agent1-cert.pem b/test/fixtures/keys/agent1-cert.pem index 9616eb0b03c847..c5bfb18e089e85 100644 --- a/test/fixtures/keys/agent1-cert.pem +++ b/test/fixtures/keys/agent1-cert.pem @@ -1,16 +1,18 @@ -----BEGIN CERTIFICATE----- -MIICbjCCAdcCCQCahKvPuKcqtTANBgkqhkiG9w0BAQUFADB6MQswCQYDVQQGEwJV -UzELMAkGA1UECBMCQ0ExCzAJBgNVBAcTAlNGMQ8wDQYDVQQKEwZKb3llbnQxEDAO -BgNVBAsTB05vZGUuanMxDDAKBgNVBAMTA2NhMTEgMB4GCSqGSIb3DQEJARYRcnlA -dGlueWNsb3Vkcy5vcmcwHhcNMTMwODAxMTExODU5WhcNNDAxMjE2MTExODU5WjB9 -MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExCzAJBgNVBAcTAlNGMQ8wDQYDVQQK -EwZKb3llbnQxEDAOBgNVBAsTB05vZGUuanMxDzANBgNVBAMTBmFnZW50MTEgMB4G -CSqGSIb3DQEJARYRcnlAdGlueWNsb3Vkcy5vcmcwgZ8wDQYJKoZIhvcNAQEBBQAD -gY0AMIGJAoGBAMNQTWAcktNJlmpEbu0xKJzjpI0MJfWZauUg5GXD6/CXRGOEQ/Im -uqG7Ar23LrFK/y2goHCF+/ffJKaFzJ4iuv2nAlly/HTriQJUtP/dxacfqrC5A1GH -EYAA/S1VShPUtpljADZWyEemWBzZacC2SQ5cChkXTmqJ9t3wYBSw/guHAgMBAAEw -DQYJKoZIhvcNAQEFBQADgYEAbuPFhXlMbdYX0XpcPiiRamvO2Qha2GEBRSfqg1Qe -fZo5oRXlOd+QVh4O8A3AFY06ERKE72Ho01B+KM2MwpJk0izQhmC4a0pks0jrBuyW -dGoVczyK8eCtbw3Y2uiALV+60EidhCbOqml+3kIDVF0cXkCYi5FVbHRTls7wL0gR -Fe0= +MIIC1jCCAj+gAwIBAgIJAJqEq8+4pyq+MA0GCSqGSIb3DQEBBQUAMHoxCzAJBgNV +BAYTAlVTMQswCQYDVQQIEwJDQTELMAkGA1UEBxMCU0YxDzANBgNVBAoTBkpveWVu +dDEQMA4GA1UECxMHTm9kZS5qczEMMAoGA1UEAxMDY2ExMSAwHgYJKoZIhvcNAQkB +FhFyeUB0aW55Y2xvdWRzLm9yZzAeFw0xNDA0MTUyMTMxMzFaFw00MTA4MzAyMTMx +MzFaMH0xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTELMAkGA1UEBxMCU0YxDzAN +BgNVBAoTBkpveWVudDEQMA4GA1UECxMHTm9kZS5qczEPMA0GA1UEAxMGYWdlbnQx +MSAwHgYJKoZIhvcNAQkBFhFyeUB0aW55Y2xvdWRzLm9yZzCBnzANBgkqhkiG9w0B +AQEFAAOBjQAwgYkCgYEAuOs3hW8rF+7xx5iB9wjmIgd+HTqRFUeKxG+mWV35Hl6A +3uzYGXwWznqsOomr4a/UkZrxbPGp5Awqa9g72NF97g3Sysq2DW4a3ycXWAeYYcHS +lRxqJGXTjx+vG/0nDCXLBhoDKO00zEccdjGS8xEjjieQQr+KeASmIm0kQmuN5YcC +AwEAAaNhMF8wXQYIKwYBBQUHAQEEUTBPMCMGCCsGAQUFBzABhhdodHRwOi8vb2Nz +cC5ub2RlanMub3JnLzAoBggrBgEFBQcwAoYcaHR0cDovL2NhLm5vZGVqcy5vcmcv +Y2EuY2VydDANBgkqhkiG9w0BAQUFAAOBgQAx6rhnYbPygJwIm6nidyx+ydJQC4Gk +JD+pzbdJkTS+01r+xjVY/Wckn4JAsIlo/MMn055rs2cfdjoQtlj6yjEU6AP/7bfr +Mju4lBxDLACJ2y5/rfj3wO4q4Knd4Q4mPWjlS2SwmkHZ21QOqJ6Ig9ps6HPM7syw +ZYQ3WQ1LOPAxMg== -----END CERTIFICATE----- diff --git a/test/fixtures/keys/agent1-csr.pem b/test/fixtures/keys/agent1-csr.pem index 7f1677a2f57e45..51617129d0dfcf 100644 --- a/test/fixtures/keys/agent1-csr.pem +++ b/test/fixtures/keys/agent1-csr.pem @@ -2,12 +2,12 @@ MIIB4jCCAUsCAQAwfTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMQswCQYDVQQH EwJTRjEPMA0GA1UEChMGSm95ZW50MRAwDgYDVQQLEwdOb2RlLmpzMQ8wDQYDVQQD EwZhZ2VudDExIDAeBgkqhkiG9w0BCQEWEXJ5QHRpbnljbG91ZHMub3JnMIGfMA0G -CSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDUE1gHJLTSZZqRG7tMSic46SNDCX1mWrl -IORlw+vwl0RjhEPyJrqhuwK9ty6xSv8toKBwhfv33ySmhcyeIrr9pwJZcvx064kC -VLT/3cWnH6qwuQNRhxGAAP0tVUoT1LaZYwA2VshHplgc2WnAtkkOXAoZF05qifbd -8GAUsP4LhwIDAQABoCUwIwYJKoZIhvcNAQkHMRYTFEEgY2hhbGxlbmdlIHBhc3N3 -b3JkMA0GCSqGSIb3DQEBBQUAA4GBAFRwfX09wCEqB5fOGTLSAQqK7/Tm47t8TcFy -PsCoHcYSHCSSthknJgdnK9nQaVVVqVpDRgmUFmcWC27JOAFQLt79FqOYNLGrmvR/ -ZaRbz3BBi4TBHClalnyBBzaYJJQz16qbT4j48TmzRQvBGR/gT2FpPoLVDWKU+U6E -oU6hMCpb +CSqGSIb3DQEBAQUAA4GNADCBiQKBgQC46zeFbysX7vHHmIH3COYiB34dOpEVR4rE +b6ZZXfkeXoDe7NgZfBbOeqw6iavhr9SRmvFs8ankDCpr2DvY0X3uDdLKyrYNbhrf +JxdYB5hhwdKVHGokZdOPH68b/ScMJcsGGgMo7TTMRxx2MZLzESOOJ5BCv4p4BKYi +bSRCa43lhwIDAQABoCUwIwYJKoZIhvcNAQkHMRYTFEEgY2hhbGxlbmdlIHBhc3N3 +b3JkMA0GCSqGSIb3DQEBBQUAA4GBAC1pwZvvfYfK8IXYyXLD3N47MEbn/Y8C85Qi +rYVl7y/7ThurCLtWVlS3e7es3Kr8nxjHTjVZW20RZHOmOGfSOkXoL3uuwew1jvCq +ibM2jwPCq1N/I4D94Fzh9LG86Cu8U6PtBlZzgprdK84Fo8U/pFRikPrggApUiPhm +MZeWhDyn -----END CERTIFICATE REQUEST----- diff --git a/test/fixtures/keys/agent1-key.pem b/test/fixtures/keys/agent1-key.pem index 29df321862847e..08546736d546fa 100644 --- a/test/fixtures/keys/agent1-key.pem +++ b/test/fixtures/keys/agent1-key.pem @@ -1,15 +1,15 @@ -----BEGIN RSA PRIVATE KEY----- -MIICXAIBAAKBgQDDUE1gHJLTSZZqRG7tMSic46SNDCX1mWrlIORlw+vwl0RjhEPy -JrqhuwK9ty6xSv8toKBwhfv33ySmhcyeIrr9pwJZcvx064kCVLT/3cWnH6qwuQNR -hxGAAP0tVUoT1LaZYwA2VshHplgc2WnAtkkOXAoZF05qifbd8GAUsP4LhwIDAQAB -AoGAJI+nrFIs+fhQe9wLl8MYAyZp6y1W/b6WUAX0O0iNph/q4WYlAfNWBGhpfvIH -f5C2a+ghoG60WBYhWjq5rvB5aCX/DchIATuaVHgaWcBf7y9NXnWDH9JMtDOTaVI6 -s7inJwjqIJAHbloa82NGuwz/EN4Ncng6wTmf1gbF6UtOqGECQQD15UNAtpRqpGPz -xPAZwT3TkY4gYLlZvqn21r/92P5XVbTJXyBTo9pwY4F7o/pNZAQcq3sPUrZW7T4X -t8nPT4RrAkEAy1bvewVS3U10V8ffzCl7F5WiaTEMa39F4e0QqBKOXdnDS2T1FJZl -VSVSXiVMd4qFQf4IVgBZCwihS1hpPSo8VQJBAL7vpBY27+4S8k4SaUIGbITBLHR1 -xtcqFv5F6NUrTuvv8C7Bf++Sdwb4LU4dmTnI5OyCN09Bsba0B5gRLVKd8zsCQAu4 -AetEHkd0zEy2zzYT+e0dCZQoaH/VgPCJWhlloGDWSQQSWHGMTWC/2uRkH+kPyahI -/LAAKyGQqMMP4FjPE1UCQAyPkF3dJy+KRZSQ2rz0bpBVGoUV31hl+SvMigCy0yUy -QwvJxgN14LQJP+pCcuJGaSdiPsOjxqhPX7KMg3SiSlA= +MIICXgIBAAKBgQC46zeFbysX7vHHmIH3COYiB34dOpEVR4rEb6ZZXfkeXoDe7NgZ +fBbOeqw6iavhr9SRmvFs8ankDCpr2DvY0X3uDdLKyrYNbhrfJxdYB5hhwdKVHGok +ZdOPH68b/ScMJcsGGgMo7TTMRxx2MZLzESOOJ5BCv4p4BKYibSRCa43lhwIDAQAB +AoGBAIXZzPCLDXhffydo3vo/uMT9A26IzCfJB0s1PgYGHaK76TBz4+Bej+uZpD0j +FgVgzs8uhn7DVqQ5oiM5++fvi+Sd+KlyVNgLKe7UTBGYE5Nc9DuDDD0GmJtFvso6 +amsVhECF8sWZVOAwUdrwHhWevp5gJ1cfs3YMTlT9YqdRaWOhAkEA8TJAPfnbEfWf +saDhWCjanW+w2QEYPa6wYFt+I5L2XPTeKR/wEQ3EzM++vCWxSF5LNSaXIdic847p +BcIGi/0r6QJBAMREt5r1c6Wf5mS6i/Jg6AdCEUjy0feRCeKemJDMKxyl5m/cU+rk +p5YBUgwoI8kzc82GEhyg4/NgHQfNcrZdT+8CQFVzChNq21PHgyX46xzCjIDOOwcG +PkJMCyx3/X446JMSJUrIh9Ji4F/3EYmyiNYsodRYsZ5KEYCwFpn1nUAnF1ECQQC/ +uzl54YomJDyX7jzEfLJuVLY6AyvmowN7JN95pFoBVHf2ktBPySuFuKiEQ7oh1Wet +QOn0mZ/VovD5LFSBnkp1AkEAgOMkBCJuOfBDvQwrmAAowWQi//7D2x0fhyKcrF6D +EZYVV125Wodw3zFxmE9p4vb6Hg3X5jSyGMzdE5ZqMgBD7w== -----END RSA PRIVATE KEY----- diff --git a/test/fixtures/keys/agent1.cnf b/test/fixtures/keys/agent1.cnf index 81d2f09fe6f4c5..6904b2e930d899 100644 --- a/test/fixtures/keys/agent1.cnf +++ b/test/fixtures/keys/agent1.cnf @@ -4,6 +4,7 @@ days = 999 distinguished_name = req_distinguished_name attributes = req_attributes prompt = no +x509_extensions = v3_ca [ req_distinguished_name ] C = US @@ -17,3 +18,9 @@ emailAddress = ry@tinyclouds.org [ req_attributes ] 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 diff --git a/test/fixtures/keys/ca1-cert.pem b/test/fixtures/keys/ca1-cert.pem index 2f7c03f8a8ca6d..638c803c7fb3fb 100644 --- a/test/fixtures/keys/ca1-cert.pem +++ b/test/fixtures/keys/ca1-cert.pem @@ -1,15 +1,15 @@ -----BEGIN CERTIFICATE----- -MIICazCCAdQCCQCK8euGRwPfJzANBgkqhkiG9w0BAQUFADB6MQswCQYDVQQGEwJV +MIICazCCAdQCCQC1CQyJn8L/kzANBgkqhkiG9w0BAQUFADB6MQswCQYDVQQGEwJV UzELMAkGA1UECBMCQ0ExCzAJBgNVBAcTAlNGMQ8wDQYDVQQKEwZKb3llbnQxEDAO BgNVBAsTB05vZGUuanMxDDAKBgNVBAMTA2NhMTEgMB4GCSqGSIb3DQEJARYRcnlA -dGlueWNsb3Vkcy5vcmcwHhcNMTMwODAxMTExODU5WhcNNDAxMjE2MTExODU5WjB6 +dGlueWNsb3Vkcy5vcmcwHhcNMTQwNDE1MjEzMTMxWhcNNDEwODMwMjEzMTMxWjB6 MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExCzAJBgNVBAcTAlNGMQ8wDQYDVQQK EwZKb3llbnQxEDAOBgNVBAsTB05vZGUuanMxDDAKBgNVBAMTA2NhMTEgMB4GCSqG SIb3DQEJARYRcnlAdGlueWNsb3Vkcy5vcmcwgZ8wDQYJKoZIhvcNAQEBBQADgY0A -MIGJAoGBAKk8iURIH5aHTpddeVkyMUUkiaP4W9M3x2nBqjvFTw7oP1mJYvab52ed -/2rA7fRt3kZyf7+lRt4OtXG7emsBj2F6d/iHKnWUfdMZl+cQ61Mtx6/DeO3F55aT -QrCeqDpyAOY6FvfhdflZItrEMQa9+PbsbyRBSxDJ/Qs7qhevnlqBAgMBAAEwDQYJ -KoZIhvcNAQEFBQADgYEAZwg19wn9cU9Lb7pNw50qi9DeJhUvo4/Jua8FjikvoKX5 -oQSQ+J/7+83OEuJi2Ii1xH2fAlNN7ZoJzOHY/JU2tx64OmnhEPvnX/nb1/jK3zyn -gwJDHcYG6AU6nHGWRewQpkoYYIQ7YQNx26OGQF0QdAJi2ltKZpQKIv/75XWfKrQ= +MIGJAoGBALBXMk/xFR2GN2v/wKreZKyIitGphxYGoJ2d//s/wM6qqyIW94aiq3sm +1zpmOTPTorT9Pk32A7uKKHfrafB+yA07QXgCYXgzcn17nfFInncDyGdggNFGAO13 +5JuC3JC8pRJpEokkMszpHJxPdR6gKXIT05blUnpwGT/AmYJ8S59lAgMBAAEwDQYJ +KoZIhvcNAQEFBQADgYEAAb+Pye0I+k927Qi2+cUowLS5MtmrEosUbTYwI4rqYSR2 +aiibqmC3Z55N72ktQ2pJKP8I1t3Rk+j8/yIKWzSn5Jd2GT4ZzqbANrdLKeAsfVDK +pnsUR1IV/sdIvuELm+P4kyK5wafJytUjD+A4SH2oWN4EozDR1OidAhJrraXQksw= -----END CERTIFICATE----- diff --git a/test/fixtures/keys/ca1-cert.srl b/test/fixtures/keys/ca1-cert.srl index dc2c049c13ddb1..ab9c1c224ad882 100644 --- a/test/fixtures/keys/ca1-cert.srl +++ b/test/fixtures/keys/ca1-cert.srl @@ -1 +1 @@ -9A84ABCFB8A72AB5 +9A84ABCFB8A72ABE diff --git a/test/fixtures/keys/ca1-key.pem b/test/fixtures/keys/ca1-key.pem index ccc30a5db5da52..a253989934ce10 100644 --- a/test/fixtures/keys/ca1-key.pem +++ b/test/fixtures/keys/ca1-key.pem @@ -1,17 +1,17 @@ -----BEGIN ENCRYPTED PRIVATE KEY----- -MIICxjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIWE2PsdhhEj8CAggA -MBQGCCqGSIb3DQMHBAjAnjC7zH2c+ASCAoB/e9OPJqGdm2yw6CwNrdNF+besbvTB -3aLesJXqttUb7GkIG8D0wmIUfW1BgcROFyvD+Jz31NRES4/KmRpwybEoWOBtpYAZ -AbcLFAJm+RYWO6XMwq9kMnQ7I4QtXZzCywJ7TSFQlDt5BJWvhqp5rbQTj6gTGMLl -CwB12/GhpEviX9kDj49FvlylLAjkVR151nhAil2Nv9O4Ww/WV0y21tscaajFS2sb -2sZ+3iZIZL+PF8qFoBtffJHeEWIKlHtnbjl1BqMtSt67CbS0CJSqtGss3+eVq9Y1 -OzeG8EAwTCP+HwOBgGNRssJxwnz+SaAZnb7te3x3yn7zac6+8PmZLdPcYBps2krS -nDVZUBW0kybi7qGWW02SYdDXKOCBVAqlSILMdhMdArQqF7P3tAac2PJNFLBf/31w -bVeqaXHmijbobcTsR7TV8SUMVPJXcDYg5qSsbGa+PLIPPFmQ/ZSSVXn+V7yVfUT5 -S/HjwBm9jdPj5l9uI6uInD8O1OFXvaP/usjKFAB/B2b3aTjtiBdqXQMAIaFKtVXs -BP3GgXxkVjrFWcVE6BXJUNRpx9EeBLz/7s86I3SUKuGduRe7aAfO0Q76dPqh6mwJ -zvOuPJXlARTU5BVWiV7FP53KQZ6a+35urMOdnsq/Fzf4qg9yZcXp9hxJAspHgn8P -3QKCJ6HnHvl9wpjQLxjLnQgIYBNeYW6vo/hUVRfTruN8VjeKWqoNQVEAOJ5Br4i9 -/Nsjl8aw0kaRXonYtmlSD6lQycckkV7WkhFlXej8Q+mGTE60ut1gJ85cM+JHwTlk -XFDe6qYCSZk17l6MMLNwbYeJjTqSbT0UAYx2lyTtAC4L+L/H8hFedwFp +MIICxjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIJ36Z9tFiv2oCAggA +MBQGCCqGSIb3DQMHBAjCAyUwdFh+7gSCAoD9Sv9009MmLb8+wHhYhxmrPrpeYZOt +W8xfELfal/2XWFe41WV41Qsa2HyN03VeyQjb+cnKvZX6HUCe5zVMUsf+B91yQBqR +jOZgsgoflEC3tFrhW5ogmRnjuok3CbTYLx0f8GMucSRNa9ZPhpfUjrVcCUrKKRJ+ +owk0VxWQkRqMF/8WJJgHxnm8/jLbNKuHbwY8SSQO/pStyKxqx5rBXAA/YCrAx7EZ +aDvc2q9EwRHX+hWIhpIkYna7PwnNAX03Ghv4iiwUlwHoomvgxExdUc9T8HBQu/xP +2pmUqwTglAdRb0tNiYrF+yIesAlqBc1qoi7tv2SM3gTowdw2PeHq5MA/ShV21oC8 +51bc0OxRkEFsHGLIs1v5qzs21JhX0IKvb5LDHAaze2RwODfnCTcQb8jYaFZNmwpt +ZrHgBQBwOHSvZ9CCqInDDtGBhYRlQIQRGgj9Ju5fruRYycn8vE58OcIfF52dwLKB +7sjTFX0O0b58wpiQSJzbHGbjNlQNRXxdk9v1qfP8vx8rFHLoFSScaZomavp4uijU +yLKVwquzEOVOMP8cC+yRk+zkcb9EE2pE1CqG0Mj6MHH1Lha98kakTIAS/VG59/Mp +EqZoRtJ6n/DzscrRpHIm+170ufLGmivmTeOXcMyHv1pGp35c5VbmUTZCxcE7A/0f +WPLVB3lBPgsvR/4NVx9A6mvXbkp2yngdfbvQgzRDG6pfrE1xeiUAyRCts7dR3Q9/ +dNHIM2wsiO3A/8Up1vrY6d/dcJt7cwjHRkx2eQFbpeE+a4CCNZ7gXFcwHLigBH8G +uTTVZf4HJavjXiYlkY3OFnPuz4KzanJzuqluKeIdJwSEkp9Kra/Id0eO -----END ENCRYPTED PRIVATE KEY----- diff --git a/test/simple/test-tls-ocsp-callback.js b/test/simple/test-tls-ocsp-callback.js new file mode 100644 index 00000000000000..fd45586b5ea0c9 --- /dev/null +++ b/test/simple/test-tls-ocsp-callback.js @@ -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); + }); +}