tls: support OCSP on client and server
authorFedor Indutny <fedor@indutny.com>
Mon, 14 Apr 2014 17:15:57 +0000 (21:15 +0400)
committerFedor Indutny <fedor@indutny.com>
Thu, 17 Apr 2014 22:21:16 +0000 (02:21 +0400)
19 files changed:
doc/api/tls.markdown
lib/_tls_common.js
lib/_tls_legacy.js
lib/_tls_wrap.js
src/env.h
src/node.cc
src/node_crypto.cc
src/node_crypto.h
src/node_crypto_clienthello.cc
src/node_crypto_clienthello.h
test/fixtures/keys/Makefile
test/fixtures/keys/agent1-cert.pem
test/fixtures/keys/agent1-csr.pem
test/fixtures/keys/agent1-key.pem
test/fixtures/keys/agent1.cnf
test/fixtures/keys/ca1-cert.pem
test/fixtures/keys/ca1-cert.srl
test/fixtures/keys/ca1-key.pem
test/simple/test-tls-ocsp-callback.js [new file with mode: 0644]

index 43f819d..3abbe64 100644 (file)
@@ -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
index ce011bc..4cd06f0 100644 (file)
@@ -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;
+};
index 1d62ed6..09fdd57 100644 (file)
@@ -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();
index e17a064..bdedb51 100644 (file)
@@ -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;
   }
index 95e1df6..4405414 100644 (file)
--- 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")                                           \
index afa4f2b..51fd6a8 100644 (file)
@@ -2447,6 +2447,13 @@ static Handle<Object> 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<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(),
            Boolean::New(env->isolate(), get_builtin_module("crypto") != NULL));
 
index 407ad52..0d685c9 100644 (file)
@@ -143,6 +143,7 @@ template int SSLWrap<TLSCallbacks>::SelectNextProtoCallback(
     unsigned int inlen,
     void* arg);
 #endif
+template int SSLWrap<TLSCallbacks>::TLSExtStatusCallback(SSL* s, void* arg);
 
 
 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, "getTicketKeys", SecureContext::GetTicketKeys);
   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"),
               t->GetFunction());
@@ -469,7 +476,10 @@ void SecureContext::SetKey(const FunctionCallbackInfo<Value>& 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<Value>& 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<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>
 void SSLWrap<Base>::AddMethods(Environment* env, Handle<FunctionTemplate> t) {
   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, "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<Base>::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<Base>::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<Value> argv[] = { hello_obj };
   w->MakeCallback(env->onclienthello_string(), ARRAY_SIZE(argv), argv);
@@ -1042,8 +1116,15 @@ void SSLWrap<Base>::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<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;
       int rv;
 
@@ -1054,7 +1135,7 @@ void SSLWrap<Base>::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<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
 template <class Base>
 void SSLWrap<Base>::SetMaxSendFragment(
@@ -1547,6 +1656,55 @@ void SSLWrap<Base>::SetNPNProtocols(const FunctionCallbackInfo<Value>& args) {
 #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) {
   Connection* conn = static_cast<Connection*>(arg);
 
index 6605dfd..7645eb2 100644 (file)
@@ -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<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)
       : BaseObject(env, wrap),
         ca_store_(NULL),
-        ctx_(NULL) {
+        ctx_(NULL),
+        cert_(NULL),
+        issuer_(NULL) {
     MakeWeak<SecureContext>(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<v8::Value>& args);
   static void GetTLSTicket(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
   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<v8::Object> ocsp_response_;
+#endif  // NODE__HAVE_TLSEXT_STATUS_CB
+
 #ifdef OPENSSL_NPN_NEGOTIATED
   v8::Persistent<v8::Object> npn_protos_;
   v8::Persistent<v8::Value> selected_npn_proto_;
index b786942..c1228c7 100644 (file)
@@ -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;
index 4301d9b..3ebcead 100644 (file)
@@ -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_;
 };
index c063377..31e8484 100644 (file)
@@ -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 \
index 9616eb0..c5bfb18 100644 (file)
@@ -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-----
index 7f1677a..5161712 100644 (file)
@@ -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-----
index 29df321..0854673 100644 (file)
@@ -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-----
index 81d2f09..6904b2e 100644 (file)
@@ -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
index 2f7c03f..638c803 100644 (file)
@@ -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-----
index ccc30a5..a253989 100644 (file)
@@ -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 (file)
index 0000000..fd45586
--- /dev/null
@@ -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);
+  });
+}