From f9a597a1d380e166a9f2ccd838646d8db4232c00 Mon Sep 17 00:00:00 2001 From: David Benjamin Date: Sat, 23 Sep 2017 02:55:19 -0400 Subject: [PATCH] crypto: emulate OpenSSL 1.0 ticket scheme in 1.1 OpenSSL 1.0.x used a 48-byte ticket key, but OpenSSL 1.1.x made it larger by using a larger HMAC-SHA256 key and using AES-256-CBC to encrypt. However, Node's public API exposes the 48-byte key. Implement the ticket key callback to restore the OpenSSL 1.0.x behavior. PR-URL: https://github.com/nodejs/node/pull/16130 Backport-PR-URL: https://github.com/nodejs/node/pull/18622 Reviewed-By: Ben Noordhuis Reviewed-By: Rod Vagg --- src/node_crypto.cc | 61 ++++++++++++++++++++++++++++++++++++++++++++++ src/node_crypto.h | 15 ++++++++++++ 2 files changed, 76 insertions(+) diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 79003e25dbe9f3..d09a78e59525de 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -614,6 +614,19 @@ void SecureContext::Init(const FunctionCallbackInfo& args) { SSL_SESS_CACHE_NO_AUTO_CLEAR); SSL_CTX_sess_set_get_cb(sc->ctx_, SSLWrap::GetSessionCallback); SSL_CTX_sess_set_new_cb(sc->ctx_, SSLWrap::NewSessionCallback); + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + // OpenSSL 1.1.0 changed the ticket key size, but the OpenSSL 1.0.x size was + // exposed in the public API. To retain compatibility, install a callback + // which restores the old algorithm. + if (RAND_bytes(sc->ticket_key_name_, sizeof(sc->ticket_key_name_)) <= 0 || + RAND_bytes(sc->ticket_key_hmac_, sizeof(sc->ticket_key_hmac_)) <= 0 || + RAND_bytes(sc->ticket_key_aes_, sizeof(sc->ticket_key_aes_)) <= 0) { + return env->ThrowError("Error generating ticket keys"); + } + SSL_CTX_set_tlsext_ticket_key_cb(sc->ctx_, + SecureContext::TicketCompatibilityCallback); +#endif } @@ -1288,11 +1301,17 @@ void SecureContext::GetTicketKeys(const FunctionCallbackInfo& args) { ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); Local buff = Buffer::New(wrap->env(), 48).ToLocalChecked(); +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + memcpy(Buffer::Data(buff), wrap->ticket_key_name_, 16); + memcpy(Buffer::Data(buff) + 16, wrap->ticket_key_hmac_, 16); + memcpy(Buffer::Data(buff) + 32, wrap->ticket_key_aes_, 16); +#else if (SSL_CTX_get_tlsext_ticket_keys(wrap->ctx_, Buffer::Data(buff), Buffer::Length(buff)) != 1) { return wrap->env()->ThrowError("Failed to fetch tls ticket keys"); } +#endif args.GetReturnValue().Set(buff); #endif // !def(OPENSSL_NO_TLSEXT) && def(SSL_CTX_get_tlsext_ticket_keys) @@ -1315,11 +1334,17 @@ void SecureContext::SetTicketKeys(const FunctionCallbackInfo& args) { return env->ThrowTypeError("Ticket keys length must be 48 bytes"); } +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + memcpy(wrap->ticket_key_name_, Buffer::Data(args[0]), 16); + memcpy(wrap->ticket_key_hmac_, Buffer::Data(args[0]) + 16, 16); + memcpy(wrap->ticket_key_aes_, Buffer::Data(args[0]) + 32, 16); +#else if (SSL_CTX_set_tlsext_ticket_keys(wrap->ctx_, Buffer::Data(args[0]), Buffer::Length(args[0])) != 1) { return env->ThrowError("Failed to fetch tls ticket keys"); } +#endif args.GetReturnValue().Set(true); #endif // !def(OPENSSL_NO_TLSEXT) && def(SSL_CTX_get_tlsext_ticket_keys) @@ -1430,6 +1455,42 @@ int SecureContext::TicketKeyCallback(SSL* ssl, } +#if OPENSSL_VERSION_NUMBER >= 0x10100000L +int SecureContext::TicketCompatibilityCallback(SSL* ssl, + unsigned char* name, + unsigned char* iv, + EVP_CIPHER_CTX* ectx, + HMAC_CTX* hctx, + int enc) { + SecureContext* sc = static_cast( + SSL_CTX_get_app_data(SSL_get_SSL_CTX(ssl))); + + if (enc) { + memcpy(name, sc->ticket_key_name_, sizeof(sc->ticket_key_name_)); + if (RAND_bytes(iv, 16) <= 0 || + EVP_EncryptInit_ex(ectx, EVP_aes_128_cbc(), nullptr, + sc->ticket_key_aes_, iv) <= 0 || + HMAC_Init_ex(hctx, sc->ticket_key_hmac_, sizeof(sc->ticket_key_hmac_), + EVP_sha256(), nullptr) <= 0) { + return -1; + } + return 1; + } + + if (memcmp(name, sc->ticket_key_name_, sizeof(sc->ticket_key_name_)) != 0) { + // The ticket key name does not match. Discard the ticket. + return 0; + } + + if (EVP_DecryptInit_ex(ectx, EVP_aes_128_cbc(), nullptr, sc->ticket_key_aes_, + iv) <= 0 || + HMAC_Init_ex(hctx, sc->ticket_key_hmac_, sizeof(sc->ticket_key_hmac_), + EVP_sha256(), nullptr) <= 0) { + return -1; + } + return 1; +} +#endif void SecureContext::CtxGetter(Local property, diff --git a/src/node_crypto.h b/src/node_crypto.h index a014f3f06c4d43..41261910b94018 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -103,6 +103,12 @@ class SecureContext : public BaseObject { static const int kTicketKeyNameIndex = 3; static const int kTicketKeyIVIndex = 4; +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + unsigned char ticket_key_name_[16]; + unsigned char ticket_key_aes_[16]; + unsigned char ticket_key_hmac_[16]; +#endif + protected: #if OPENSSL_VERSION_NUMBER < 0x10100000L static const int64_t kExternalSize = sizeof(SSL_CTX); @@ -148,6 +154,15 @@ class SecureContext : public BaseObject { HMAC_CTX* hctx, int enc); +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + static int TicketCompatibilityCallback(SSL* ssl, + unsigned char* name, + unsigned char* iv, + EVP_CIPHER_CTX* ectx, + HMAC_CTX* hctx, + int enc); +#endif + SecureContext(Environment* env, v8::Local wrap) : BaseObject(env, wrap), ctx_(nullptr),