diff options
author | Fedor Indutny <fedor@indutny.com> | 2014-04-14 21:15:57 +0400 |
---|---|---|
committer | Fedor Indutny <fedor@indutny.com> | 2014-04-18 02:21:16 +0400 |
commit | b3ef289ffb7db476d284866658213f04415ea92d (patch) | |
tree | ece3f973d16849e46ea7736880055482df0616e7 /src | |
parent | 77d1f4a91f2885fd3f39298754ae5b7ee75ad3d1 (diff) | |
download | node-new-b3ef289ffb7db476d284866658213f04415ea92d.tar.gz |
tls: support OCSP on client and server
Diffstat (limited to 'src')
-rw-r--r-- | src/env.h | 4 | ||||
-rw-r--r-- | src/node.cc | 7 | ||||
-rw-r--r-- | src/node_crypto.cc | 170 | ||||
-rw-r--r-- | src/node_crypto.h | 28 | ||||
-rw-r--r-- | src/node_crypto_clienthello.cc | 13 | ||||
-rw-r--r-- | src/node_crypto_clienthello.h | 15 |
6 files changed, 229 insertions, 8 deletions
@@ -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 afa4f2b030..51fd6a8188 100644 --- a/src/node.cc +++ b/src/node.cc @@ -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)); diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 407ad52833..0d685c9f51 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -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); diff --git a/src/node_crypto.h b/src/node_crypto.h index 6605dfdf69..7645eb281a 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<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_; diff --git a/src/node_crypto_clienthello.cc b/src/node_crypto_clienthello.cc index b786942529..c1228c79ac 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 4301d9bb1c..3ebcead0c3 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_; }; |