From 6cf8243a70e26843c7770f7b29b1a33b822d1c26 Mon Sep 17 00:00:00 2001 From: Haikel Guemar Date: Tue, 26 Jul 2016 22:50:22 +0200 Subject: [PATCH 2/3] Use openssl 1.0.1 Based on Solaris patches from upstream #2783 https://github.com/nodejs/node/issues/2783 --- doc/api/tls.md | 6 ++ src/node_constants.cc | 5 ++ src/node_crypto.cc | 201 ++++++++++++++++++++++++++++++++++++++++++++++---- src/node_crypto.h | 16 ++++ src/tls_wrap.cc | 8 ++ 5 files changed, 223 insertions(+), 13 deletions(-) diff --git a/doc/api/tls.md b/doc/api/tls.md index 7feaff2..97ad7a5 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -114,6 +114,12 @@ handshake extensions: * SNI - Allows the use of one TLS server for multiple hostnames with different SSL certificates. + **NOTE**: dueto a design flaw in node **SNI cannot be + used on the server side**, even so all parameters in related functions are + accepted for compatibility reasons. And thus the related events will not + fire unless one aranges this explicitly. This may change, when the OS + provides OpenSSL v1.0.2 or better and node gets linked to this version. + *Note*: Use of ALPN is recommended over NPN. The NPN extension has never been formally defined or documented and generally not recommended for use. diff --git a/src/node_constants.cc b/src/node_constants.cc index 2e6be8d..239eadb 100644 --- a/src/node_constants.cc +++ b/src/node_constants.cc @@ -14,7 +14,10 @@ #include #if HAVE_OPENSSL +# include +# ifndef OPENSSL_NO_EC # include +# endif # include # ifndef OPENSSL_NO_ENGINE # include @@ -976,12 +979,14 @@ void DefineOpenSSLConstants(Local target) { #if HAVE_OPENSSL // NOTE: These are not defines +# ifndef OPENSSL_NO_EC NODE_DEFINE_CONSTANT(target, POINT_CONVERSION_COMPRESSED); NODE_DEFINE_CONSTANT(target, POINT_CONVERSION_UNCOMPRESSED); NODE_DEFINE_CONSTANT(target, POINT_CONVERSION_HYBRID); #endif +#endif } void DefineSystemConstants(Local target) { diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 9cf216f..888a0f8 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -24,6 +24,82 @@ #include #include +#ifndef SSL_get_server_tmp_key +/* + 1.0.2 SSL_get_server_tmp_key(s, pk) "backport". BAD HACK!!! + NOTE: This imports "foreign" knowledge and thus will break, when SESS_CERT + or CERT_PKEY change, which is definitely the case for the later for + all OpenSSL lib vers != 1.0.1. So don't try to bind to something else! + */ +# define SSL_PKEY_NUM 8 +typedef struct cert_pkey_st { + X509 *x509; + EVP_PKEY *privatekey; + /* Digest to use when signing */ + const EVP_MD *digest; +} CERT_PKEY; + +typedef struct sess_cert_st { + STACK_OF(X509) *cert_chain; /* as received from peer (not for SSL2) */ + /* The 'peer_...' members are used only by clients. */ + int peer_cert_type; + CERT_PKEY *peer_key; /* points to an element of peer_pkeys (never + * NULL!) */ + CERT_PKEY peer_pkeys[SSL_PKEY_NUM]; + /* + * Obviously we don't have the private keys of these, so maybe we + * shouldn't even use the CERT_PKEY type here. + */ +# ifndef OPENSSL_NO_RSA + RSA *peer_rsa_tmp; /* not used for SSL 2 */ +# endif +# ifndef OPENSSL_NO_DH + DH *peer_dh_tmp; /* not used for SSL 2 */ +# endif +# ifndef OPENSSL_NO_ECDH + EC_KEY *peer_ecdh_tmp; +# endif + int references; /* actually always 1 at the moment */ +} SESS_CERT; + +static long SSL_get_server_tmp_key(SSL *s, void *parg) { + if (s->server || !s->session || !s->session->sess_cert) + return 0; + else { + SESS_CERT *sc; + EVP_PKEY *ptmp; + int rv = 0; + sc = s->session->sess_cert; +#if !defined(OPENSSL_NO_RSA) && !defined(OPENSSL_NO_DH) && !defined(OPENSSL_NO_EC) && !defined(OPENSSL_NO_ECDH) + if (!sc->peer_rsa_tmp && !sc->peer_dh_tmp && !sc->peer_ecdh_tmp) + return 0; +#endif + ptmp = EVP_PKEY_new(); + if (!ptmp) + return 0; + if (0) ; +#ifndef OPENSSL_NO_RSA + else if (sc->peer_rsa_tmp) + rv = EVP_PKEY_set1_RSA(ptmp, sc->peer_rsa_tmp); +#endif +#ifndef OPENSSL_NO_DH + else if (sc->peer_dh_tmp) + rv = EVP_PKEY_set1_DH(ptmp, sc->peer_dh_tmp); +#endif +#ifndef OPENSSL_NO_ECDH + else if (sc->peer_ecdh_tmp) + rv = EVP_PKEY_set1_EC_KEY(ptmp, sc->peer_ecdh_tmp); +#endif + if (rv) { + *(EVP_PKEY **)parg = ptmp; + return 1; + } + EVP_PKEY_free(ptmp); + return 0; + } +} +#endif /* SSL_get_server_tmp_key */ + #define THROW_AND_RETURN_IF_NOT_STRING_OR_BUFFER(val, prefix) \ do { \ if (!Buffer::HasInstance(val) && !val->IsString()) { \ @@ -160,7 +236,11 @@ template int SSLWrap::TLSExtStatusCallback(SSL* s, void* arg); #endif template void SSLWrap::DestroySSL(); +#if OPENSSL_VERSION_NUMBER >= 0x10002000L template int SSLWrap::SSLCertCallback(SSL* s, void* arg); +#else +template int SSLWrap::SSLCertCallback(SSL* s, X509 **x509, EVP_PKEY **pkey); +#endif template void SSLWrap::WaitForCertCb(CertCb cb, void* arg); #ifdef TLSEXT_TYPE_application_layer_protocol_negotiation @@ -280,8 +360,12 @@ void SecureContext::Initialize(Environment* env, Local target) { env->SetProtoMethod(t, "addCRL", SecureContext::AddCRL); env->SetProtoMethod(t, "addRootCerts", SecureContext::AddRootCerts); env->SetProtoMethod(t, "setCiphers", SecureContext::SetCiphers); +#ifndef OPENSSL_NO_ECDH env->SetProtoMethod(t, "setECDHCurve", SecureContext::SetECDHCurve); +#endif +#ifndef OPENSSL_NO_DH env->SetProtoMethod(t, "setDHParam", SecureContext::SetDHParam); +#endif env->SetProtoMethod(t, "setOptions", SecureContext::SetOptions); env->SetProtoMethod(t, "setSessionIdContext", SecureContext::SetSessionIdContext); @@ -515,8 +599,20 @@ int SSL_CTX_use_certificate_chain(SSL_CTX* ctx, for (int i = 0; i < sk_X509_num(extra_certs); i++) { X509* ca = sk_X509_value(extra_certs, i); - // NOTE: Increments reference count on `ca` - r = SSL_CTX_add1_chain_cert(ctx, ca); +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + // If ctx->cert->key != NULL create ctx->cert->key->chain if not + // already there, push 'ca' to this chain and finally increment the ca + // reference count by 1 (this is the diff between *_add1_* and *_add0_* + // - the later increments by 0 ;-)) and return 1. Otherwise or if + // something fails in between, return 0. + r = SSL_CTX_add1_chain_cert(ctx, ca); +#else + // Create ctx->extra_certs if not already there, just push 'ca' to this + // chain and return 1. If something fails, return 0. + // NOTE: 1.0.1- does not support multiple certs having its own chain in + // a single context. There is just one: extra_chain! + r = SSL_CTX_add_extra_chain_cert(ctx, ca); +#endif if (!r) { ret = 0; @@ -795,6 +891,7 @@ void SecureContext::SetCiphers(const FunctionCallbackInfo& args) { } +#ifndef OPENSSL_NO_ECDH void SecureContext::SetECDHCurve(const FunctionCallbackInfo& args) { SecureContext* sc; ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder()); @@ -822,8 +919,10 @@ void SecureContext::SetECDHCurve(const FunctionCallbackInfo& args) { EC_KEY_free(ecdh); } +#endif +#ifndef OPENSSL_NO_DH void SecureContext::SetDHParam(const FunctionCallbackInfo& args) { SecureContext* sc; ASSIGN_OR_RETURN_UNWRAP(&sc, args.This()); @@ -862,6 +961,7 @@ void SecureContext::SetDHParam(const FunctionCallbackInfo& args) { if (!r) return env->ThrowTypeError("Error setting temp DH parameter"); } +#endif void SecureContext::SetOptions(const FunctionCallbackInfo& args) { @@ -1872,6 +1972,7 @@ void SSLWrap::GetEphemeralKeyInfo( info->Set(env->size_string(), Integer::New(env->isolate(), EVP_PKEY_bits(key))); break; +#ifndef OPENSSL_NO_ECDH case EVP_PKEY_EC: { EC_KEY* ec = EVP_PKEY_get1_EC_KEY(key); @@ -1884,6 +1985,7 @@ void SSLWrap::GetEphemeralKeyInfo( info->Set(env->size_string(), Integer::New(env->isolate(), EVP_PKEY_bits(key))); } +#endif } EVP_PKEY_free(key); } @@ -2301,7 +2403,12 @@ void SSLWrap::WaitForCertCb(CertCb cb, void* arg) { template +#if OPENSSL_VERSION_NUMBER >= 0x10002000L int SSLWrap::SSLCertCallback(SSL* s, void* arg) { +#else +/* NOTE: For now this callback gets usually never called dueto design flaws */ +int SSLWrap::SSLCertCallback(SSL* s, X509 **x509, EVP_PKEY **pkey) { +#endif Base* w = static_cast(SSL_get_app_data(s)); if (!w->is_server()) @@ -2375,19 +2482,53 @@ void SSLWrap::CertCbDone(const FunctionCallbackInfo& args) { w->sni_context_.Reset(env->isolate(), ctx); int rv; + X509* x509; + EVP_PKEY* pkey; + STACK_OF(X509)* chain; // NOTE: reference count is not increased by this API methods - X509* x509 = SSL_CTX_get0_certificate(sc->ctx_); - EVP_PKEY* pkey = SSL_CTX_get0_privatekey(sc->ctx_); - STACK_OF(X509)* chain; +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + x509 = SSL_CTX_get0_certificate(sc->ctx_); + pkey = SSL_CTX_get0_privatekey(sc->ctx_); + rv = SSL_CTX_get0_chain_certs(sc->ctx_, &chain); +#else + SSL *ssl = SSL_new(sc->ctx_); + rv = SSL_CTX_get_extra_chain_certs(sc->ctx_, &chain); + if (ssl) { + SSL_set_connect_state(ssl); /* just cleanup/reset state - cheap */ + x509 = SSL_get_certificate(ssl); + SSL_free(ssl); + } else { + x509 = NULL; + pkey = NULL; + } +#endif - rv = SSL_CTX_get0_chain_certs(sc->ctx_, &chain); - if (rv) - rv = SSL_use_certificate(w->ssl_, x509); - if (rv) - rv = SSL_use_PrivateKey(w->ssl_, pkey); - if (rv && chain != nullptr) - rv = SSL_set1_chain(w->ssl_, chain); + if (rv) + rv = SSL_use_certificate(w->ssl_, x509); + if (rv) + rv = SSL_use_PrivateKey(w->ssl_, pkey); + if (rv && chain != nullptr) { +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + // replaces w->ssl_->cert->key->chain with a copy of the given chain, + // which is allowed to be NULL + rv = SSL_set1_chain(w->ssl_, chain); +#else + // just replace the extra chain with the given chain - 1.0.1- does not + // support chain per cert + SSL_CTX_clear_extra_chain_certs(w->ssl_->ctx); + if (chain != NULL) { + int i; + SSL_CTX* ctx = w->ssl_->ctx; + for (i = 0; i < sk_X509_num(chain); i++) { + // can't do anything: however others might be ok and still + // satisfy requirements + SSL_CTX_add_extra_chain_cert(ctx, sk_X509_value(chain,i)); + } + } + rv = 1; +#endif + } if (rv) rv = w->SetCACerts(sc); if (!rv) { @@ -2451,10 +2592,14 @@ void SSLWrap::SetSNIContext(SecureContext* sc) { template int SSLWrap::SetCACerts(SecureContext* sc) { +#if OPENSSL_VERSION_NUMBER >= 0x10002000L int err = SSL_set1_verify_cert_store(ssl_, SSL_CTX_get_cert_store(sc->ctx_)); if (err != 1) return err; - +#else + // there is no ssl_->cert->verify_store in <= 1.0.1. So no need to: free the + // old store, set the new one to it and increment its ref count. +#endif STACK_OF(X509_NAME)* list = SSL_dup_CA_list( SSL_CTX_get_client_CA_list(sc->ctx_)); @@ -2732,7 +2877,11 @@ inline int VerifyCallback(int preverify_ok, X509_STORE_CTX* ctx) { SSL* ssl = static_cast( X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx())); +#if OPENSSL_VERSION_NUMBER >= 0x10002000L if (SSL_is_server(ssl)) +#else + if (ssl->server) +#endif return 1; // Client needs to check if the server cert is listed in the @@ -2815,7 +2964,21 @@ void Connection::New(const FunctionCallbackInfo& args) { InitNPN(sc); +#if OPENSSL_VERSION_NUMBER >= 0x10002000L SSL_set_cert_cb(conn->ssl_, SSLWrap::SSLCertCallback, conn); +#else + /* 1.0.1 and less have no general cert callback. The closest for a client is + SSL_CTX_set_client_cert_cb(conn->ssl_->ctx, SSLWrap::SSLCertCallback); + but on the client it is not needed/used by this implementation. Since this + the SSLCertCallback actually calls lib/_tls_wrap.js:oncertcb(), which in + turn loadSNI() and this the actual SNICallback of the JSON object, sets + the context and finally requestOCSP() and certCbDone(). Not sure, why + the SNICallback of the JSON object, doesn't get invoked via + SelectSNIContextCallback_() - design flaw because lets do 2 things at once + (i.e. do SNICallback and attach the certs ca chain), however, this means + no server side support for the SNI TLS/OCSP_state extension anymore. + */ +#endif #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB if (is_server) { @@ -4404,6 +4567,7 @@ void PublicKeyCipher::Cipher(const FunctionCallbackInfo& args) { } +#ifndef OPENSSL_NO_DH void DiffieHellman::Initialize(Environment* env, Local target) { Local t = env->NewFunctionTemplate(New); @@ -4805,8 +4969,10 @@ bool DiffieHellman::VerifyContext() { verifyError_ = codes; return true; } +#endif +#ifndef OPENSSL_NO_ECDH void ECDH::Initialize(Environment* env, Local target) { HandleScope scope(env->isolate()); @@ -5034,6 +5200,7 @@ void ECDH::SetPrivateKey(const FunctionCallbackInfo& args) { EC_POINT_free(pub); } +#endif void ECDH::SetPublicKey(const FunctionCallbackInfo& args) { @@ -5587,6 +5754,7 @@ void GetHashes(const FunctionCallbackInfo& args) { } +# ifndef OPENSSL_NO_EC void GetCurves(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); const size_t num_curves = EC_get_builtin_curves(nullptr, 0); @@ -5611,6 +5779,7 @@ void GetCurves(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(arr); } +#endif bool VerifySpkac(const char* data, unsigned int len) { @@ -5886,8 +6055,12 @@ void InitCrypto(Local target, SecureContext::Initialize(env, target); Connection::Initialize(env, target); CipherBase::Initialize(env, target); +# ifndef OPENSSL_NO_EC DiffieHellman::Initialize(env, target); +#endif +#ifndef OPENSSL_NO_ECDH ECDH::Initialize(env, target); +#endif Hmac::Initialize(env, target); Hash::Initialize(env, target); Sign::Initialize(env, target); @@ -5906,7 +6079,9 @@ void InitCrypto(Local target, env->SetMethod(target, "getSSLCiphers", GetSSLCiphers); env->SetMethod(target, "getCiphers", GetCiphers); env->SetMethod(target, "getHashes", GetHashes); +# ifndef OPENSSL_NO_EC env->SetMethod(target, "getCurves", GetCurves); +#endif env->SetMethod(target, "publicEncrypt", PublicKeyCipher::Cipher +# ifndef OPENSSL_NO_EC #include +# endif +# ifndef OPENSSL_NO_ECDH #include +# endif #ifndef OPENSSL_NO_ENGINE # include #endif // !OPENSSL_NO_ENGINE @@ -101,8 +105,12 @@ class SecureContext : public BaseObject { static void AddCRL(const v8::FunctionCallbackInfo& args); static void AddRootCerts(const v8::FunctionCallbackInfo& args); static void SetCiphers(const v8::FunctionCallbackInfo& args); +#ifndef OPENSSL_NO_ECDH static void SetECDHCurve(const v8::FunctionCallbackInfo& args); +#endif +# ifndef OPENSSL_NO_DH static void SetDHParam(const v8::FunctionCallbackInfo& args); +#endif static void SetOptions(const v8::FunctionCallbackInfo& args); static void SetSessionIdContext( const v8::FunctionCallbackInfo& args); @@ -283,7 +291,11 @@ class SSLWrap { unsigned int inlen, void* arg); static int TLSExtStatusCallback(SSL* s, void* arg); +#if OPENSSL_VERSION_NUMBER >= 0x10002000L static int SSLCertCallback(SSL* s, void* arg); +#else + static int SSLCertCallback(SSL* s, X509 **x509, EVP_PKEY **pkey); +#endif static void SSLGetter(v8::Local property, const v8::PropertyCallbackInfo& info); @@ -645,6 +657,7 @@ class PublicKeyCipher { static void Cipher(const v8::FunctionCallbackInfo& args); }; +#ifndef OPENSSL_NO_DH class DiffieHellman : public BaseObject { public: ~DiffieHellman() override { @@ -690,7 +703,9 @@ class DiffieHellman : public BaseObject { int verifyError_; DH* dh; }; +#endif +# ifndef OPENSSL_NO_ECDH class ECDH : public BaseObject { public: ~ECDH() override { @@ -727,6 +742,7 @@ class ECDH : public BaseObject { EC_KEY* key_; const EC_GROUP* group_; }; +#endif bool EntropySource(unsigned char* buffer, size_t length); #ifndef OPENSSL_NO_ENGINE diff --git a/src/tls_wrap.cc b/src/tls_wrap.cc index 7c5df11..3e06621 100644 --- a/src/tls_wrap.cc +++ b/src/tls_wrap.cc @@ -142,7 +142,15 @@ void TLSWrap::InitSSL() { InitNPN(sc_); +#if OPENSSL_VERSION_NUMBER >= 0x10002000L SSL_set_cert_cb(ssl_, SSLWrap::SSLCertCallback, this); +#else + /* 1.0.1 and less have at most for the client side the function + SSL_CTX_set_client_cert_cb(ssl_->ctx, SSLWrap::SSLCertCallback); + but on the client it is not needed/used by this implementation. + For more info see comments in src/node_crypto.cc Connection::New(). + */ +#endif if (is_server()) { SSL_set_accept_state(ssl_); -- 2.9.0