From 8dd5a11c0d334607ea850ca7faf956b8ce8e6d43 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Tue, 31 Dec 2024 14:02:31 -0800 Subject: [PATCH 01/12] src: move more crypto impl detail to ncrypto dep --- deps/ncrypto/ncrypto.cc | 21 +++++++++++++++++++++ deps/ncrypto/ncrypto.h | 2 ++ src/crypto/crypto_x509.cc | 31 ++++++------------------------- 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index ac2d771555126a..f76b89574be508 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -1005,6 +1005,27 @@ X509View X509View::From(const SSLCtxPointer& ctx) { return X509View(SSL_CTX_get0_certificate(ctx.get())); } +std::string X509View::getFingerprint(const EVP_MD* method) const { + unsigned int md_size; + unsigned char md[EVP_MAX_MD_SIZE]; + static constexpr char hex[] = "0123456789ABCDEF"; + + if (X509_digest(get(), method, md, &md_size)) { + std::string fingerprint((md_size * 3) - 1, 0); + for (unsigned int i = 0; i < md_size; i++) { + auto idx = 3 * i; + fingerprint[idx] = hex[(md[i] & 0xf0) >> 4]; + fingerprint[idx + 1] = hex[(md[i] & 0x0f)]; + if (i == md_size - 1) break; + fingerprint[idx + 2] = ':'; + } + + return fingerprint; + } + + return std::string(); +} + X509Pointer X509View::clone() const { ClearErrorOnReturn clear_error_on_return; if (!cert_) return {}; diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h index fffa75ec718fac..32ff5c4419ca03 100644 --- a/deps/ncrypto/ncrypto.h +++ b/deps/ncrypto/ncrypto.h @@ -589,6 +589,8 @@ class X509View final { bool checkPrivateKey(const EVPKeyPointer& pkey) const; bool checkPublicKey(const EVPKeyPointer& pkey) const; + std::string getFingerprint(const EVP_MD* method) const; + X509Pointer clone() const; enum class CheckMatch { diff --git a/src/crypto/crypto_x509.cc b/src/crypto/crypto_x509.cc index 9b9bb7be9a8dac..da8b07b5e038b2 100644 --- a/src/crypto/crypto_x509.cc +++ b/src/crypto/crypto_x509.cc @@ -60,34 +60,15 @@ void ManagedX509::MemoryInfo(MemoryTracker* tracker) const { } namespace { -void AddFingerprintDigest(const unsigned char* md, - unsigned int md_size, - char fingerprint[3 * EVP_MAX_MD_SIZE]) { - unsigned int i; - static constexpr char hex[] = "0123456789ABCDEF"; - - for (i = 0; i < md_size; i++) { - fingerprint[3 * i] = hex[(md[i] & 0xf0) >> 4]; - fingerprint[(3 * i) + 1] = hex[(md[i] & 0x0f)]; - fingerprint[(3 * i) + 2] = ':'; - } - - DCHECK_GT(md_size, 0); - fingerprint[(3 * (md_size - 1)) + 2] = '\0'; -} - MaybeLocal GetFingerprintDigest(Environment* env, const EVP_MD* method, const ncrypto::X509View& cert) { - unsigned char md[EVP_MAX_MD_SIZE]; - unsigned int md_size; - char fingerprint[EVP_MAX_MD_SIZE * 3]; - - if (X509_digest(cert.get(), method, md, &md_size)) { - AddFingerprintDigest(md, md_size, fingerprint); - return OneByteString(env->isolate(), fingerprint); - } - return Undefined(env->isolate()); + auto fingerprint = cert.getFingerprint(method); + // Returning an empty string indicates that the digest failed for + // some reason. + if (fingerprint == "") return Undefined(env->isolate()); + return OneByteString( + env->isolate(), fingerprint.data(), fingerprint.length()); } template From dd545383941370f8c28f742088259278d0fa8045 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Tue, 31 Dec 2024 14:18:01 -0800 Subject: [PATCH 02/12] src: remove obsolete LogSecret function Nothing was using it. --- src/crypto/crypto_common.cc | 23 ----------------------- src/crypto/crypto_common.h | 6 ------ 2 files changed, 29 deletions(-) diff --git a/src/crypto/crypto_common.cc b/src/crypto/crypto_common.cc index 43a126f863779d..b4fbd8cfa20b24 100644 --- a/src/crypto/crypto_common.cc +++ b/src/crypto/crypto_common.cc @@ -41,29 +41,6 @@ using v8::Undefined; using v8::Value; namespace crypto { -void LogSecret( - const SSLPointer& ssl, - const char* name, - const unsigned char* secret, - size_t secretlen) { - auto keylog_cb = SSL_CTX_get_keylog_callback(SSL_get_SSL_CTX(ssl.get())); - // All supported versions of TLS/SSL fix the client random to the same size. - constexpr size_t kTlsClientRandomSize = SSL3_RANDOM_SIZE; - unsigned char crandom[kTlsClientRandomSize]; - - if (keylog_cb == nullptr || - SSL_get_client_random(ssl.get(), crandom, kTlsClientRandomSize) != - kTlsClientRandomSize) { - return; - } - - std::string line = name; - line += " " + nbytes::HexEncode(reinterpret_cast(crandom), - kTlsClientRandomSize); - line += - " " + nbytes::HexEncode(reinterpret_cast(secret), secretlen); - keylog_cb(ssl.get(), line.c_str()); -} MaybeLocal GetSSLOCSPResponse( Environment* env, diff --git a/src/crypto/crypto_common.h b/src/crypto/crypto_common.h index 284aadd6cc2cc3..5cef67efde9a88 100644 --- a/src/crypto/crypto_common.h +++ b/src/crypto/crypto_common.h @@ -27,12 +27,6 @@ struct StackOfX509Deleter { }; using StackOfX509 = std::unique_ptr; -void LogSecret( - const SSLPointer& ssl, - const char* name, - const unsigned char* secret, - size_t secretlen); - v8::MaybeLocal GetSSLOCSPResponse( Environment* env, SSL* ssl, From 4da29d886f94a4d1cf0f588f16e34b07d7abfe29 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Tue, 31 Dec 2024 14:21:24 -0800 Subject: [PATCH 03/12] src: move StackOfX509 decl to ncrypto --- deps/ncrypto/ncrypto.h | 5 +++++ src/crypto/crypto_common.cc | 1 + src/crypto/crypto_common.h | 5 ----- src/crypto/crypto_context.cc | 1 + 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h index 32ff5c4419ca03..01117868ca8bc6 100644 --- a/deps/ncrypto/ncrypto.h +++ b/deps/ncrypto/ncrypto.h @@ -551,6 +551,11 @@ class DHPointer final { DeleteFnPtr dh_; }; +struct StackOfX509Deleter { + void operator()(STACK_OF(X509) * p) const { sk_X509_pop_free(p, X509_free); } +}; +using StackOfX509 = std::unique_ptr; + class X509Pointer; class X509View final { diff --git a/src/crypto/crypto_common.cc b/src/crypto/crypto_common.cc index b4fbd8cfa20b24..4bdb6c232238b5 100644 --- a/src/crypto/crypto_common.cc +++ b/src/crypto/crypto_common.cc @@ -27,6 +27,7 @@ namespace node { +using ncrypto::StackOfX509; using v8::Array; using v8::ArrayBuffer; using v8::BackingStore; diff --git a/src/crypto/crypto_common.h b/src/crypto/crypto_common.h index 5cef67efde9a88..b49a0e8eb5a9fe 100644 --- a/src/crypto/crypto_common.h +++ b/src/crypto/crypto_common.h @@ -22,11 +22,6 @@ namespace node { namespace crypto { -struct StackOfX509Deleter { - void operator()(STACK_OF(X509)* p) const { sk_X509_pop_free(p, X509_free); } -}; -using StackOfX509 = std::unique_ptr; - v8::MaybeLocal GetSSLOCSPResponse( Environment* env, SSL* ssl, diff --git a/src/crypto/crypto_context.cc b/src/crypto/crypto_context.cc index aa5fc61f19e435..21021094da3c2b 100644 --- a/src/crypto/crypto_context.cc +++ b/src/crypto/crypto_context.cc @@ -21,6 +21,7 @@ namespace node { +using ncrypto::StackOfX509; using v8::Array; using v8::ArrayBufferView; using v8::Boolean; From db9a8d49aa4971d3d51cf1a6077f317bfdc25fb5 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Tue, 31 Dec 2024 14:25:58 -0800 Subject: [PATCH 04/12] src: colocate GetSSLOCSPResponse with callsite Only used one place so move it out of crypto-common --- src/crypto/crypto_common.cc | 19 ------------------- src/crypto/crypto_common.h | 5 ----- src/crypto/crypto_tls.cc | 16 +++++++++++++++- 3 files changed, 15 insertions(+), 25 deletions(-) diff --git a/src/crypto/crypto_common.cc b/src/crypto/crypto_common.cc index 4bdb6c232238b5..eeacbf35186219 100644 --- a/src/crypto/crypto_common.cc +++ b/src/crypto/crypto_common.cc @@ -43,25 +43,6 @@ using v8::Value; namespace crypto { -MaybeLocal GetSSLOCSPResponse( - Environment* env, - SSL* ssl, - Local default_value) { - const unsigned char* resp; - int len = SSL_get_tlsext_status_ocsp_resp(ssl, &resp); - if (resp == nullptr) - return default_value; - - Local ret; - MaybeLocal maybe_buffer = - Buffer::Copy(env, reinterpret_cast(resp), len); - - if (!maybe_buffer.ToLocal(&ret)) - return MaybeLocal(); - - return ret; -} - bool SetTLSSession( const SSLPointer& ssl, const SSLSessionPointer& session) { diff --git a/src/crypto/crypto_common.h b/src/crypto/crypto_common.h index b49a0e8eb5a9fe..e264137ac5767a 100644 --- a/src/crypto/crypto_common.h +++ b/src/crypto/crypto_common.h @@ -22,11 +22,6 @@ namespace node { namespace crypto { -v8::MaybeLocal GetSSLOCSPResponse( - Environment* env, - SSL* ssl, - v8::Local default_value); - bool SetTLSSession( const SSLPointer& ssl, const SSLSessionPointer& session); diff --git a/src/crypto/crypto_tls.cc b/src/crypto/crypto_tls.cc index 9c7ce849521499..2ab407b5c9700a 100644 --- a/src/crypto/crypto_tls.cc +++ b/src/crypto/crypto_tls.cc @@ -303,6 +303,20 @@ int SelectALPNCallback( : SSL_TLSEXT_ERR_ALERT_FATAL; } +MaybeLocal GetSSLOCSPResponse(Environment* env, SSL* ssl) { + const unsigned char* resp; + int len = SSL_get_tlsext_status_ocsp_resp(ssl, &resp); + if (resp == nullptr) return Null(env->isolate()); + + Local ret; + MaybeLocal maybe_buffer = + Buffer::Copy(env, reinterpret_cast(resp), len); + + if (!maybe_buffer.ToLocal(&ret)) return MaybeLocal(); + + return ret; +} + int TLSExtStatusCallback(SSL* s, void* arg) { TLSWrap* w = static_cast(SSL_get_app_data(s)); Environment* env = w->env(); @@ -311,7 +325,7 @@ int TLSExtStatusCallback(SSL* s, void* arg) { if (w->is_client()) { // Incoming response Local arg; - if (GetSSLOCSPResponse(env, s, Null(env->isolate())).ToLocal(&arg)) + if (GetSSLOCSPResponse(env, s).ToLocal(&arg)) w->MakeCallback(env->onocspresponse_string(), 1, &arg); // No async acceptance is possible, so always return 1 to accept the From 4b92b3d7de63ebd20e78c72f7e4f1bf08f3b012d Mon Sep 17 00:00:00 2001 From: James M Snell Date: Tue, 31 Dec 2024 15:02:32 -0800 Subject: [PATCH 05/12] src: move x509 error code and reason to ncrypto --- deps/ncrypto/ncrypto.cc | 47 ++++++++++++++++++++++++++++++++++ deps/ncrypto/ncrypto.h | 3 +++ src/crypto/crypto_common.cc | 51 ++++--------------------------------- src/crypto/crypto_common.h | 2 -- src/crypto/crypto_tls.cc | 19 +++++++++----- 5 files changed, 67 insertions(+), 55 deletions(-) diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index f76b89574be508..af796855abb38b 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -1071,6 +1071,53 @@ X509Pointer X509Pointer::IssuerFrom(const SSL_CTX* ctx, const X509View& cert) { X509Pointer X509Pointer::PeerFrom(const SSLPointer& ssl) { return X509Pointer(SSL_get_peer_certificate(ssl.get())); } + +// When adding or removing errors below, please also update the list in the API +// documentation. See the "OpenSSL Error Codes" section of doc/api/errors.md +// Also *please* update the respective section in doc/api/tls.md as well +std::string_view X509Pointer::ErrorCode(int32_t err) { // NOLINT(runtime/int) +#define CASE(CODE) \ + case X509_V_ERR_##CODE: \ + return #CODE; + switch (err) { + CASE(UNABLE_TO_GET_ISSUER_CERT) + CASE(UNABLE_TO_GET_CRL) + CASE(UNABLE_TO_DECRYPT_CERT_SIGNATURE) + CASE(UNABLE_TO_DECRYPT_CRL_SIGNATURE) + CASE(UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY) + CASE(CERT_SIGNATURE_FAILURE) + CASE(CRL_SIGNATURE_FAILURE) + CASE(CERT_NOT_YET_VALID) + CASE(CERT_HAS_EXPIRED) + CASE(CRL_NOT_YET_VALID) + CASE(CRL_HAS_EXPIRED) + CASE(ERROR_IN_CERT_NOT_BEFORE_FIELD) + CASE(ERROR_IN_CERT_NOT_AFTER_FIELD) + CASE(ERROR_IN_CRL_LAST_UPDATE_FIELD) + CASE(ERROR_IN_CRL_NEXT_UPDATE_FIELD) + CASE(OUT_OF_MEM) + CASE(DEPTH_ZERO_SELF_SIGNED_CERT) + CASE(SELF_SIGNED_CERT_IN_CHAIN) + CASE(UNABLE_TO_GET_ISSUER_CERT_LOCALLY) + CASE(UNABLE_TO_VERIFY_LEAF_SIGNATURE) + CASE(CERT_CHAIN_TOO_LONG) + CASE(CERT_REVOKED) + CASE(INVALID_CA) + CASE(PATH_LENGTH_EXCEEDED) + CASE(INVALID_PURPOSE) + CASE(CERT_UNTRUSTED) + CASE(CERT_REJECTED) + CASE(HOSTNAME_MISMATCH) + } +#undef CASE + return "UNSPECIFIED"; +} + +std::string_view X509Pointer::ErrorReason(int32_t err) { + if (err == X509_V_OK) return ""; + return X509_verify_cert_error_string(err); +} + // ============================================================================ // BIOPointer diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h index 01117868ca8bc6..83ce2458bddaf4 100644 --- a/deps/ncrypto/ncrypto.h +++ b/deps/ncrypto/ncrypto.h @@ -637,6 +637,9 @@ class X509Pointer final { X509View view() const; operator X509View() const { return view(); } + static std::string_view ErrorCode(int32_t err); + static std::string_view ErrorReason(int32_t err); + private: DeleteFnPtr cert_; }; diff --git a/src/crypto/crypto_common.cc b/src/crypto/crypto_common.cc index eeacbf35186219..2d1158313c8522 100644 --- a/src/crypto/crypto_common.cc +++ b/src/crypto/crypto_common.cc @@ -144,58 +144,17 @@ bool SetGroups(SecureContext* sc, const char* groups) { return SSL_CTX_set1_groups_list(sc->ctx().get(), groups) == 1; } -// When adding or removing errors below, please also update the list in the API -// documentation. See the "OpenSSL Error Codes" section of doc/api/errors.md -const char* X509ErrorCode(long err) { // NOLINT(runtime/int) - const char* code = "UNSPECIFIED"; -#define CASE_X509_ERR(CODE) case X509_V_ERR_##CODE: code = #CODE; break; - switch (err) { - // if you modify anything in here, *please* update the respective section in - // doc/api/tls.md as well - CASE_X509_ERR(UNABLE_TO_GET_ISSUER_CERT) - CASE_X509_ERR(UNABLE_TO_GET_CRL) - CASE_X509_ERR(UNABLE_TO_DECRYPT_CERT_SIGNATURE) - CASE_X509_ERR(UNABLE_TO_DECRYPT_CRL_SIGNATURE) - CASE_X509_ERR(UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY) - CASE_X509_ERR(CERT_SIGNATURE_FAILURE) - CASE_X509_ERR(CRL_SIGNATURE_FAILURE) - CASE_X509_ERR(CERT_NOT_YET_VALID) - CASE_X509_ERR(CERT_HAS_EXPIRED) - CASE_X509_ERR(CRL_NOT_YET_VALID) - CASE_X509_ERR(CRL_HAS_EXPIRED) - CASE_X509_ERR(ERROR_IN_CERT_NOT_BEFORE_FIELD) - CASE_X509_ERR(ERROR_IN_CERT_NOT_AFTER_FIELD) - CASE_X509_ERR(ERROR_IN_CRL_LAST_UPDATE_FIELD) - CASE_X509_ERR(ERROR_IN_CRL_NEXT_UPDATE_FIELD) - CASE_X509_ERR(OUT_OF_MEM) - CASE_X509_ERR(DEPTH_ZERO_SELF_SIGNED_CERT) - CASE_X509_ERR(SELF_SIGNED_CERT_IN_CHAIN) - CASE_X509_ERR(UNABLE_TO_GET_ISSUER_CERT_LOCALLY) - CASE_X509_ERR(UNABLE_TO_VERIFY_LEAF_SIGNATURE) - CASE_X509_ERR(CERT_CHAIN_TOO_LONG) - CASE_X509_ERR(CERT_REVOKED) - CASE_X509_ERR(INVALID_CA) - CASE_X509_ERR(PATH_LENGTH_EXCEEDED) - CASE_X509_ERR(INVALID_PURPOSE) - CASE_X509_ERR(CERT_UNTRUSTED) - CASE_X509_ERR(CERT_REJECTED) - CASE_X509_ERR(HOSTNAME_MISMATCH) - } -#undef CASE_X509_ERR - return code; -} - MaybeLocal GetValidationErrorReason(Environment* env, int err) { - if (err == 0) - return Undefined(env->isolate()); - const char* reason = X509_verify_cert_error_string(err); - return OneByteString(env->isolate(), reason); + auto reason = X509Pointer::ErrorReason(err); + if (reason == "") return Undefined(env->isolate()); + return OneByteString(env->isolate(), reason.data(), reason.length()); } MaybeLocal GetValidationErrorCode(Environment* env, int err) { if (err == 0) return Undefined(env->isolate()); - return OneByteString(env->isolate(), X509ErrorCode(err)); + auto error = X509Pointer::ErrorCode(err); + return OneByteString(env->isolate(), error.data(), error.length()); } MaybeLocal GetCert(Environment* env, const SSLPointer& ssl) { diff --git a/src/crypto/crypto_common.h b/src/crypto/crypto_common.h index e264137ac5767a..1067a6a73e114a 100644 --- a/src/crypto/crypto_common.h +++ b/src/crypto/crypto_common.h @@ -46,8 +46,6 @@ v8::MaybeLocal GetClientHelloCiphers( bool SetGroups(SecureContext* sc, const char* groups); -const char* X509ErrorCode(long err); // NOLINT(runtime/int) - v8::MaybeLocal GetValidationErrorReason(Environment* env, int err); v8::MaybeLocal GetValidationErrorCode(Environment* env, int err); diff --git a/src/crypto/crypto_tls.cc b/src/crypto/crypto_tls.cc index 2ab407b5c9700a..3aa163a5e7294c 100644 --- a/src/crypto/crypto_tls.cc +++ b/src/crypto/crypto_tls.cc @@ -386,6 +386,7 @@ std::string GetBIOError() { static_cast(&ret)); return ret; } + } // namespace TLSWrap::TLSWrap(Environment* env, @@ -1844,15 +1845,19 @@ void TLSWrap::VerifyError(const FunctionCallbackInfo& args) { if (x509_verify_error == X509_V_OK) return args.GetReturnValue().SetNull(); - const char* reason = X509_verify_cert_error_string(x509_verify_error); - const char* code = X509ErrorCode(x509_verify_error); + Local reason; + if (!GetValidationErrorReason(env, x509_verify_error).ToLocal(&reason)) { + return; + } + if (reason->IsUndefined()) [[unlikely]] + return; - Local error = - Exception::Error(OneByteString(env->isolate(), reason)) - ->ToObject(env->isolate()->GetCurrentContext()) - .FromMaybe(Local()); + Local error = Exception::Error(reason.As()) + ->ToObject(env->isolate()->GetCurrentContext()) + .FromMaybe(Local()); - if (Set(env, error, env->code_string(), code)) + auto code = X509Pointer::ErrorCode(x509_verify_error); + if (Set(env, error, env->code_string(), code.data())) args.GetReturnValue().Set(error); } From 4d7e96cbd9cfa2ce012984aef82fde1eb4793d3a Mon Sep 17 00:00:00 2001 From: James M Snell Date: Tue, 31 Dec 2024 15:16:22 -0800 Subject: [PATCH 06/12] src: simplify X509Pointer/X509View pointer derefs a bit --- deps/ncrypto/ncrypto.cc | 6 ++++-- deps/ncrypto/ncrypto.h | 6 +++++- src/crypto/crypto_common.cc | 2 +- src/crypto/crypto_context.cc | 5 ++--- src/crypto/crypto_x509.cc | 8 +++++--- 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index af796855abb38b..c9b9d445b0f182 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -1005,12 +1005,14 @@ X509View X509View::From(const SSLCtxPointer& ctx) { return X509View(SSL_CTX_get0_certificate(ctx.get())); } -std::string X509View::getFingerprint(const EVP_MD* method) const { +std::optional X509View::getFingerprint( + const EVP_MD* method) const { unsigned int md_size; unsigned char md[EVP_MAX_MD_SIZE]; static constexpr char hex[] = "0123456789ABCDEF"; if (X509_digest(get(), method, md, &md_size)) { + if (md_size == 0) return std::nullopt; std::string fingerprint((md_size * 3) - 1, 0); for (unsigned int i = 0; i < md_size; i++) { auto idx = 3 * i; @@ -1023,7 +1025,7 @@ std::string X509View::getFingerprint(const EVP_MD* method) const { return fingerprint; } - return std::string(); + return std::nullopt; } X509Pointer X509View::clone() const { diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h index 83ce2458bddaf4..da1548286c590f 100644 --- a/deps/ncrypto/ncrypto.h +++ b/deps/ncrypto/ncrypto.h @@ -570,6 +570,8 @@ class X509View final { NCRYPTO_DISALLOW_MOVE(X509View) inline X509* get() const { return const_cast(cert_); } + inline operator X509*() const { return const_cast(cert_); } + inline operator const X509*() const { return cert_; } inline bool operator==(std::nullptr_t) noexcept { return cert_ == nullptr; } inline operator bool() const { return cert_ != nullptr; } @@ -594,7 +596,7 @@ class X509View final { bool checkPrivateKey(const EVPKeyPointer& pkey) const; bool checkPublicKey(const EVPKeyPointer& pkey) const; - std::string getFingerprint(const EVP_MD* method) const; + std::optional getFingerprint(const EVP_MD* method) const; X509Pointer clone() const; @@ -631,6 +633,8 @@ class X509Pointer final { inline bool operator==(std::nullptr_t) noexcept { return cert_ == nullptr; } inline operator bool() const { return cert_ != nullptr; } inline X509* get() const { return cert_.get(); } + inline operator X509*() const { return cert_.get(); } + inline operator const X509*() const { return cert_.get(); } void reset(X509* cert = nullptr); X509* release(); diff --git a/src/crypto/crypto_common.cc b/src/crypto/crypto_common.cc index 2d1158313c8522..b90415cb33981a 100644 --- a/src/crypto/crypto_common.cc +++ b/src/crypto/crypto_common.cc @@ -83,7 +83,7 @@ bool UseSNIContext( STACK_OF(X509)* chain; int err = SSL_CTX_get0_chain_certs(ctx, &chain); - if (err == 1) err = SSL_use_certificate(ssl.get(), x509.get()); + if (err == 1) err = SSL_use_certificate(ssl.get(), x509); if (err == 1) err = SSL_use_PrivateKey(ssl.get(), pkey); if (err == 1 && chain != nullptr) err = SSL_set1_chain(ssl.get(), chain); return err == 1; diff --git a/src/crypto/crypto_context.cc b/src/crypto/crypto_context.cc index 21021094da3c2b..db6f437759c3ec 100644 --- a/src/crypto/crypto_context.cc +++ b/src/crypto/crypto_context.cc @@ -787,9 +787,8 @@ void SecureContext::SetCACert(const BIOPointer& bio) { while (X509Pointer x509 = X509Pointer(PEM_read_bio_X509_AUX( bio.get(), nullptr, NoPasswordCallback, nullptr))) { CHECK_EQ(1, - X509_STORE_add_cert(GetCertStoreOwnedByThisSecureContext(), - x509.get())); - CHECK_EQ(1, SSL_CTX_add_client_CA(ctx_.get(), x509.get())); + X509_STORE_add_cert(GetCertStoreOwnedByThisSecureContext(), x509)); + CHECK_EQ(1, SSL_CTX_add_client_CA(ctx_.get(), x509)); } } diff --git a/src/crypto/crypto_x509.cc b/src/crypto/crypto_x509.cc index da8b07b5e038b2..4349cc7811d336 100644 --- a/src/crypto/crypto_x509.cc +++ b/src/crypto/crypto_x509.cc @@ -66,9 +66,11 @@ MaybeLocal GetFingerprintDigest(Environment* env, auto fingerprint = cert.getFingerprint(method); // Returning an empty string indicates that the digest failed for // some reason. - if (fingerprint == "") return Undefined(env->isolate()); - return OneByteString( - env->isolate(), fingerprint.data(), fingerprint.length()); + if (!fingerprint.has_value()) [[unlikely]] { + return Undefined(env->isolate()); + } + auto& fp = fingerprint.value(); + return OneByteString(env->isolate(), fp.data(), fp.length()); } template From 1af313182267674d34b3c01927b20650af59d87a Mon Sep 17 00:00:00 2001 From: James M Snell Date: Wed, 1 Jan 2025 14:48:24 -0800 Subject: [PATCH 07/12] src: move prime gen and checks to ncrypto --- deps/ncrypto/ncrypto.cc | 53 +++++++++++++++++++++++++++++++++++++ deps/ncrypto/ncrypto.h | 20 ++++++++++++++ src/crypto/crypto_random.cc | 47 +++++++++----------------------- src/crypto/crypto_util.h | 1 - 4 files changed, 86 insertions(+), 35 deletions(-) diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index c9b9d445b0f182..92c07dbce69216 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -311,6 +311,59 @@ BignumPointer BignumPointer::clone() { return BignumPointer(BN_dup(bn_.get())); } +int BignumPointer::isPrime(int nchecks, + BignumPointer::PrimeCheckCallback innerCb) const { + BignumCtxPointer ctx(BN_CTX_new()); + BignumGenCallbackPointer cb(nullptr); + if (innerCb != nullptr) { + cb = BignumGenCallbackPointer(BN_GENCB_new()); + if (!cb) [[unlikely]] return -1; + BN_GENCB_set(cb.get(), [](int a, int b, BN_GENCB* ctx) mutable -> int { + PrimeCheckCallback& ptr = + *static_cast(BN_GENCB_get_arg(ctx)); + return ptr(a, b) ? 1 : 0; + }, &innerCb); + } + return BN_is_prime_ex(get(), nchecks, ctx.get(), cb.get()); +} + +BignumPointer BignumPointer::NewPrime(const PrimeConfig& params, + PrimeCheckCallback cb) { + BignumPointer prime(BN_new()); + if (!prime || !prime.generate(params, std::move(cb))) { + return {}; + } + return prime; +} + +bool BignumPointer::generate(const PrimeConfig& params, + PrimeCheckCallback innerCb) const { + // BN_generate_prime_ex() calls RAND_bytes_ex() internally. + // Make sure the CSPRNG is properly seeded. + CSPRNG(nullptr, 0); + BignumGenCallbackPointer cb(nullptr); + if (innerCb != nullptr) { + cb = BignumGenCallbackPointer(BN_GENCB_new()); + if (!cb) [[unlikely]] return -1; + BN_GENCB_set(cb.get(), [](int a, int b, BN_GENCB* ctx) mutable -> int { + PrimeCheckCallback& ptr = + *static_cast(BN_GENCB_get_arg(ctx)); + return ptr(a, b) ? 1 : 0; + }, &innerCb); + } + if (BN_generate_prime_ex( + get(), + params.bits, + params.safe ? 1 : 0, + params.add.get(), + params.rem.get(), + cb.get()) == 0) { + return false; + } + + return true; +} + // ============================================================================ // Utility methods diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h index da1548286c590f..5079ee60c95085 100644 --- a/deps/ncrypto/ncrypto.h +++ b/deps/ncrypto/ncrypto.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -195,6 +196,7 @@ template using DeleteFnPtr = typename FunctionDeleter::Pointer; using BignumCtxPointer = DeleteFnPtr; +using BignumGenCallbackPointer = DeleteFnPtr; using CipherCtxPointer = DeleteFnPtr; using DSAPointer = DeleteFnPtr; using DSASigPointer = DeleteFnPtr; @@ -350,6 +352,22 @@ class BignumPointer final { size_t encodeInto(unsigned char* out) const; size_t encodePaddedInto(unsigned char* out, size_t size) const; + using PrimeCheckCallback = std::function; + int isPrime(int checks, + PrimeCheckCallback cb = defaultPrimeCheckCallback) const; + struct PrimeConfig { + int bits; + bool safe = false; + const BignumPointer& add; + const BignumPointer& rem; + }; + + static BignumPointer NewPrime(const PrimeConfig& params, + PrimeCheckCallback cb = defaultPrimeCheckCallback); + + bool generate(const PrimeConfig& params, + PrimeCheckCallback cb = defaultPrimeCheckCallback) const; + static BignumPointer New(); static BignumPointer NewSecure(); static DataPointer Encode(const BIGNUM* bn); @@ -366,6 +384,8 @@ class BignumPointer final { private: DeleteFnPtr bn_; + + static bool defaultPrimeCheckCallback(int, int) { return 1; } }; class EVPKeyPointer final { diff --git a/src/crypto/crypto_random.cc b/src/crypto/crypto_random.cc index 2c90a796e1195b..d81da4fa96264b 100644 --- a/src/crypto/crypto_random.cc +++ b/src/crypto/crypto_random.cc @@ -29,21 +29,13 @@ using v8::Value; namespace crypto { namespace { -using BNGENCBPointer = DeleteFnPtr; - -BNGENCBPointer getBN_GENCB(Environment* env) { +ncrypto::BignumPointer::PrimeCheckCallback getPrimeCheckCallback(Environment* env) { // The callback is used to check if the operation should be stopped. // Currently, the only check we perform is if env->is_stopping() // is true. - BNGENCBPointer cb(BN_GENCB_new()); - BN_GENCB_set( - cb.get(), - [](int a, int b, BN_GENCB* cb) -> int { - Environment* env = static_cast(BN_GENCB_get_arg(cb)); - return env->is_stopping() ? 0 : 1; - }, - env); - return cb; + return [env](int a, int b) -> bool { + return !env->is_stopping(); + }; } } // namespace @@ -165,22 +157,13 @@ Maybe RandomPrimeTraits::AdditionalConfig( bool RandomPrimeTraits::DeriveBits(Environment* env, const RandomPrimeConfig& params, ByteSource* unused) { - // BN_generate_prime_ex() calls RAND_bytes_ex() internally. - // Make sure the CSPRNG is properly seeded. - CHECK(ncrypto::CSPRNG(nullptr, 0)); - - BNGENCBPointer cb = getBN_GENCB(env); - - if (BN_generate_prime_ex(params.prime.get(), - params.bits, - params.safe ? 1 : 0, - params.add.get(), - params.rem.get(), - cb.get()) == 0) { - return false; - } - - return true; + auto cb = getPrimeCheckCallback(env); + return params.prime.generate(BignumPointer::PrimeConfig { + .bits = params.bits, + .safe = params.safe, + .add = params.add, + .rem = params.rem, + }, std::move(cb)); } void CheckPrimeConfig::MemoryInfo(MemoryTracker* tracker) const { @@ -207,12 +190,8 @@ bool CheckPrimeTraits::DeriveBits( Environment* env, const CheckPrimeConfig& params, ByteSource* out) { - - BignumCtxPointer ctx(BN_CTX_new()); - BNGENCBPointer cb = getBN_GENCB(env); - - int ret = BN_is_prime_ex( - params.candidate.get(), params.checks, ctx.get(), cb.get()); + auto cb = getPrimeCheckCallback(env); + int ret = params.candidate.isPrime(params.checks, std::move(cb)); if (ret < 0) return false; ByteSource::Builder buf(1); buf.data()[0] = ret; diff --git a/src/crypto/crypto_util.h b/src/crypto/crypto_util.h index a72c0a2a908294..bd38be97094f1e 100644 --- a/src/crypto/crypto_util.h +++ b/src/crypto/crypto_util.h @@ -66,7 +66,6 @@ using EVPMDCtxPointer = ncrypto::EVPMDCtxPointer; using RSAPointer = ncrypto::RSAPointer; using ECPointer = ncrypto::ECPointer; using BignumPointer = ncrypto::BignumPointer; -using BignumCtxPointer = ncrypto::BignumCtxPointer; using NetscapeSPKIPointer = ncrypto::NetscapeSPKIPointer; using ECGroupPointer = ncrypto::ECGroupPointer; using ECPointPointer = ncrypto::ECPointPointer; From 0b31ee5408096ce4bbb9a5923d5aec5a9ab7cf79 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Wed, 1 Jan 2025 15:41:31 -0800 Subject: [PATCH 08/12] src: move BN_* methods into ncrypto --- deps/ncrypto/ncrypto.cc | 23 +++++++++++++++++++++++ deps/ncrypto/ncrypto.h | 4 ++++ src/crypto/crypto_aes.cc | 12 +++++------- src/crypto/crypto_random.cc | 2 -- src/crypto/crypto_x509.cc | 3 +-- 5 files changed, 33 insertions(+), 11 deletions(-) diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index 92c07dbce69216..4b094538eaaaae 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -364,6 +364,23 @@ bool BignumPointer::generate(const PrimeConfig& params, return true; } +BignumPointer BignumPointer::NewSub(const BignumPointer& a, + const BignumPointer& b) { + BignumPointer res = New(); + if (!BN_sub(res.get(), a.get(), b.get())) { + return {}; + } + return res; +} + +BignumPointer BignumPointer::NewLShift(size_t length) { + BignumPointer res = New(); + if (!BN_lshift(res.get(), One(), length)) { + return {}; + } + return res; +} + // ============================================================================ // Utility methods @@ -1228,6 +1245,12 @@ BIOPointer BIOPointer::NewFp(FILE* fd, int close_flag) { return BIOPointer(BIO_new_fp(fd, close_flag)); } +BIOPointer BIOPointer::New(const BIGNUM* bn) { + auto res = NewMem(); + if (!res || !BN_print(res.get(), bn)) return {}; + return res; +} + int BIOPointer::Write(BIOPointer* bio, std::string_view message) { if (bio == nullptr || !*bio) return 0; return BIO_write(bio->get(), message.data(), message.size()); diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h index 5079ee60c95085..61e10a969e4898 100644 --- a/deps/ncrypto/ncrypto.h +++ b/deps/ncrypto/ncrypto.h @@ -274,6 +274,7 @@ class BIOPointer final { static BIOPointer NewSecMem(); static BIOPointer New(const BIO_METHOD* method); static BIOPointer New(const void* data, size_t len); + static BIOPointer New(const BIGNUM* bn); static BIOPointer NewFile(std::string_view filename, std::string_view mode); static BIOPointer NewFp(FILE* fd, int flags); @@ -370,6 +371,9 @@ class BignumPointer final { static BignumPointer New(); static BignumPointer NewSecure(); + static BignumPointer NewSub(const BignumPointer& a, const BignumPointer& b); + static BignumPointer NewLShift(size_t length); + static DataPointer Encode(const BIGNUM* bn); static DataPointer EncodePadded(const BIGNUM* bn, size_t size); static size_t EncodePaddedInto(const BIGNUM* bn, diff --git a/src/crypto/crypto_aes.cc b/src/crypto/crypto_aes.cc index d430648aebc540..5ac8ee66aef762 100644 --- a/src/crypto/crypto_aes.cc +++ b/src/crypto/crypto_aes.cc @@ -262,9 +262,8 @@ WebCryptoCipherStatus AES_CTR_Cipher(Environment* env, const AESCipherConfig& params, const ByteSource& in, ByteSource* out) { - auto num_counters = BignumPointer::New(); - if (!BN_lshift(num_counters.get(), BignumPointer::One(), params.length)) - return WebCryptoCipherStatus::FAILED; + auto num_counters = BignumPointer::NewLShift(params.length); + if (!num_counters) return WebCryptoCipherStatus::FAILED; BignumPointer current_counter = GetCounter(params); @@ -277,10 +276,9 @@ WebCryptoCipherStatus AES_CTR_Cipher(Environment* env, // be incremented more than there are counter values, we fail. if (num_output > num_counters) return WebCryptoCipherStatus::FAILED; - auto remaining_until_reset = BignumPointer::New(); - if (!BN_sub(remaining_until_reset.get(), - num_counters.get(), - current_counter.get())) { + auto remaining_until_reset = + BignumPointer::NewSub(num_counters, current_counter); + if (!remaining_until_reset) { return WebCryptoCipherStatus::FAILED; } diff --git a/src/crypto/crypto_random.cc b/src/crypto/crypto_random.cc index d81da4fa96264b..beb1e0730736da 100644 --- a/src/crypto/crypto_random.cc +++ b/src/crypto/crypto_random.cc @@ -7,8 +7,6 @@ #include "threadpoolwork-inl.h" #include "v8.h" -#include -#include #include namespace node { diff --git a/src/crypto/crypto_x509.cc b/src/crypto/crypto_x509.cc index 4349cc7811d336..a19b1d07e02609 100644 --- a/src/crypto/crypto_x509.cc +++ b/src/crypto/crypto_x509.cc @@ -678,9 +678,8 @@ MaybeLocal GetPubKey(Environment* env, OSSL3_CONST RSA* rsa) { } MaybeLocal GetModulusString(Environment* env, const BIGNUM* n) { - auto bio = BIOPointer::NewMem(); + auto bio = BIOPointer::New(n); if (!bio) return {}; - BN_print(bio.get(), n); return ToV8Value(env->context(), bio); } From b421159b7ad8a912a58799268cd8136373559ab2 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Wed, 1 Jan 2025 19:00:19 -0800 Subject: [PATCH 09/12] src: move SSLPointer impl to ncrypto --- deps/ncrypto/ncrypto.cc | 181 ++++++++++++++++++++++++++++++++++++ deps/ncrypto/ncrypto.h | 41 +++++++- src/crypto/crypto_cipher.cc | 32 +------ src/crypto/crypto_common.cc | 144 +++------------------------- src/crypto/crypto_common.h | 14 --- src/crypto/crypto_tls.cc | 37 ++++++-- src/quic/tlscontext.cc | 6 +- 7 files changed, 269 insertions(+), 186 deletions(-) diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index 4b094538eaaaae..575be4fee3fd6c 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -2190,4 +2190,185 @@ Result EVPKeyPointer::writePublicKey( return bio; } +// ============================================================================ + +SSLPointer::SSLPointer(SSL* ssl) : ssl_(ssl) {} + +SSLPointer::SSLPointer(SSLPointer&& other) noexcept : ssl_(other.release()) {} + +SSLPointer& SSLPointer::operator=(SSLPointer&& other) noexcept { + if (this == &other) return *this; + this->~SSLPointer(); + return *new (this) SSLPointer(std::move(other)); +} + +SSLPointer::~SSLPointer() { + reset(); +} + +void SSLPointer::reset(SSL* ssl) { + ssl_.reset(ssl); +} + +SSL* SSLPointer::release() { + return ssl_.release(); +} + +SSLPointer SSLPointer::New(const SSLCtxPointer& ctx) { + if (!ctx) return {}; + return SSLPointer(SSL_new(ctx.get())); +} + +void SSLPointer::getCiphers( + std::function cb) const { + if (!ssl_) return; + STACK_OF(SSL_CIPHER)* ciphers = SSL_get_ciphers(get()); + + // TLSv1.3 ciphers aren't listed by EVP. There are only 5, we could just + // document them, but since there are only 5, easier to just add them manually + // and not have to explain their absence in the API docs. They are lower-cased + // because the docs say they will be. + static const char* TLS13_CIPHERS[] = { + "tls_aes_256_gcm_sha384", + "tls_chacha20_poly1305_sha256", + "tls_aes_128_gcm_sha256", + "tls_aes_128_ccm_8_sha256", + "tls_aes_128_ccm_sha256" + }; + + const int n = sk_SSL_CIPHER_num(ciphers); + + for (int i = 0; i < n; ++i) { + const SSL_CIPHER* cipher = sk_SSL_CIPHER_value(ciphers, i); + cb(SSL_CIPHER_get_name(cipher)); + } + + for (unsigned i = 0; i < 5; ++i) { + const char* name = TLS13_CIPHERS[i]; + cb(name); + } +} + +bool SSLPointer::setSession(const SSLSessionPointer& session) { + if (!session || !ssl_) return false; + return SSL_set_session(get(), session.get()) == 1; +} + +bool SSLPointer::setSniContext(const SSLCtxPointer& ctx) const { + if (!ctx) return false; + auto x509 = ncrypto::X509View::From(ctx); + if (!x509) return false; + EVP_PKEY* pkey = SSL_CTX_get0_privatekey(ctx.get()); + STACK_OF(X509)* chain; + + int err = SSL_CTX_get0_chain_certs(ctx.get(), &chain); + if (err == 1) err = SSL_use_certificate(get(), x509); + if (err == 1) err = SSL_use_PrivateKey(get(), pkey); + if (err == 1 && chain != nullptr) err = SSL_set1_chain(get(), chain); + return err == 1; +} + +std::optional SSLPointer::verifyPeerCertificate() const { + if (!ssl_) return std::nullopt; + if (X509Pointer::PeerFrom(*this)) { + return SSL_get_verify_result(get()); + } + + const SSL_CIPHER* curr_cipher = SSL_get_current_cipher(get()); + const SSL_SESSION* sess = SSL_get_session(get()); + // Allow no-cert for PSK authentication in TLS1.2 and lower. + // In TLS1.3 check that session was reused because TLS1.3 PSK + // looks like session resumption. + if (SSL_CIPHER_get_auth_nid(curr_cipher) == NID_auth_psk || + (SSL_SESSION_get_protocol_version(sess) == TLS1_3_VERSION && + SSL_session_reused(get()))) { + return X509_V_OK; + } + + return std::nullopt; +} + +const std::string_view SSLPointer::getClientHelloAlpn() const { + if (ssl_ == nullptr) return std::string_view(); + const unsigned char* buf; + size_t len; + size_t rem; + + if (!SSL_client_hello_get0_ext( + get(), + TLSEXT_TYPE_application_layer_protocol_negotiation, + &buf, + &rem) || + rem < 2) { + return nullptr; + } + + len = (buf[0] << 8) | buf[1]; + if (len + 2 != rem) return nullptr; + return reinterpret_cast(buf + 3); +} + +const std::string_view SSLPointer::getClientHelloServerName() const { + if (ssl_ == nullptr) return std::string_view(); + const unsigned char* buf; + size_t len; + size_t rem; + + if (!SSL_client_hello_get0_ext( + get(), + TLSEXT_TYPE_server_name, + &buf, + &rem) || rem <= 2) { + return nullptr; + } + + len = (*buf << 8) | *(buf + 1); + if (len + 2 != rem) + return nullptr; + rem = len; + + if (rem == 0 || *(buf + 2) != TLSEXT_NAMETYPE_host_name) return nullptr; + rem--; + if (rem <= 2) + return nullptr; + len = (*(buf + 3) << 8) | *(buf + 4); + if (len + 2 > rem) + return nullptr; + return reinterpret_cast(buf + 5); +} + +std::optional SSLPointer::GetServerName( + const SSL* ssl) { + if (ssl == nullptr) return std::nullopt; + auto res = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + if (res == nullptr) return std::nullopt; + return res; +} + +std::optional SSLPointer::getServerName() const { + if (!ssl_) return std::nullopt; + return GetServerName(get()); +} + +X509View SSLPointer::getCertificate() const { + if (!ssl_) return {}; + ClearErrorOnReturn clear_error_on_return; + return ncrypto::X509View(SSL_get_certificate(get())); +} + +const SSL_CIPHER* SSLPointer::getCipher() const { + if (!ssl_) return nullptr; + return SSL_get_current_cipher(get()); +} + +bool SSLPointer::isServer() const { + return SSL_is_server(get()) != 0; +} + +EVPKeyPointer SSLPointer::getPeerTempKey() const { + if (!ssl_) return {}; + EVP_PKEY* raw_key = nullptr; + if (!SSL_get_peer_tmp_key(get(), &raw_key)) return {}; + return EVPKeyPointer(raw_key); +} } // namespace ncrypto diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h index 61e10a969e4898..9e3b86334cdbeb 100644 --- a/deps/ncrypto/ncrypto.h +++ b/deps/ncrypto/ncrypto.h @@ -212,7 +212,6 @@ using NetscapeSPKIPointer = DeleteFnPtr; using PKCS8Pointer = DeleteFnPtr; using RSAPointer = DeleteFnPtr; using SSLCtxPointer = DeleteFnPtr; -using SSLPointer = DeleteFnPtr; using SSLSessionPointer = DeleteFnPtr; struct StackOfXASN1Deleter { @@ -581,6 +580,46 @@ struct StackOfX509Deleter { using StackOfX509 = std::unique_ptr; class X509Pointer; +class X509View; + +class SSLPointer final { + public: + SSLPointer() = default; + explicit SSLPointer(SSL* ssl); + SSLPointer(SSLPointer&& other) noexcept; + SSLPointer& operator=(SSLPointer&& other) noexcept; + NCRYPTO_DISALLOW_COPY(SSLPointer) + ~SSLPointer(); + + inline bool operator==(std::nullptr_t) noexcept { return ssl_ == nullptr; } + inline operator bool() const { return ssl_ != nullptr; } + inline SSL* get() const { return ssl_.get(); } + inline operator SSL*() const { return ssl_.get(); } + void reset(SSL* ssl = nullptr); + SSL* release(); + + bool setSession(const SSLSessionPointer& session); + bool setSniContext(const SSLCtxPointer& ctx) const; + + const std::string_view getClientHelloAlpn() const; + const std::string_view getClientHelloServerName() const; + + std::optional getServerName() const; + X509View getCertificate() const; + EVPKeyPointer getPeerTempKey() const; + const SSL_CIPHER* getCipher() const; + bool isServer() const; + + std::optional verifyPeerCertificate() const; + + void getCiphers(std::function cb) const; + + static SSLPointer New(const SSLCtxPointer& ctx); + static std::optional GetServerName(const SSL* ssl); + + private: + DeleteFnPtr ssl_; +}; class X509View final { public: diff --git a/src/crypto/crypto_cipher.cc b/src/crypto/crypto_cipher.cc index 21e34333609880..075ff3c008fb4b 100644 --- a/src/crypto/crypto_cipher.cc +++ b/src/crypto/crypto_cipher.cc @@ -203,37 +203,15 @@ void CipherBase::GetSSLCiphers(const FunctionCallbackInfo& args) { return ThrowCryptoError(env, ERR_get_error(), "SSL_CTX_new"); } - SSLPointer ssl(SSL_new(ctx.get())); + auto ssl = SSLPointer::New(ctx); if (!ssl) { return ThrowCryptoError(env, ERR_get_error(), "SSL_new"); } - STACK_OF(SSL_CIPHER)* ciphers = SSL_get_ciphers(ssl.get()); - - // TLSv1.3 ciphers aren't listed by EVP. There are only 5, we could just - // document them, but since there are only 5, easier to just add them manually - // and not have to explain their absence in the API docs. They are lower-cased - // because the docs say they will be. - static const char* TLS13_CIPHERS[] = { - "tls_aes_256_gcm_sha384", - "tls_chacha20_poly1305_sha256", - "tls_aes_128_gcm_sha256", - "tls_aes_128_ccm_8_sha256", - "tls_aes_128_ccm_sha256" - }; - - const int n = sk_SSL_CIPHER_num(ciphers); - LocalVector arr(env->isolate(), n + arraysize(TLS13_CIPHERS)); - - for (int i = 0; i < n; ++i) { - const SSL_CIPHER* cipher = sk_SSL_CIPHER_value(ciphers, i); - arr[i] = OneByteString(env->isolate(), SSL_CIPHER_get_name(cipher)); - } - - for (unsigned i = 0; i < arraysize(TLS13_CIPHERS); ++i) { - const char* name = TLS13_CIPHERS[i]; - arr[n + i] = OneByteString(env->isolate(), name); - } + LocalVector arr(env->isolate()); + ssl.getCiphers([&](const std::string_view name) { + arr.push_back(OneByteString(env->isolate(), name.data(), name.length())); + }); args.GetReturnValue().Set(Array::New(env->isolate(), arr.data(), arr.size())); } diff --git a/src/crypto/crypto_common.cc b/src/crypto/crypto_common.cc index b90415cb33981a..c30c8947b934fd 100644 --- a/src/crypto/crypto_common.cc +++ b/src/crypto/crypto_common.cc @@ -28,7 +28,6 @@ namespace node { using ncrypto::StackOfX509; -using v8::Array; using v8::ArrayBuffer; using v8::BackingStore; using v8::Context; @@ -43,12 +42,6 @@ using v8::Value; namespace crypto { -bool SetTLSSession( - const SSLPointer& ssl, - const SSLSessionPointer& session) { - return session != nullptr && SSL_set_session(ssl.get(), session.get()) == 1; -} - SSLSessionPointer GetTLSSession(const unsigned char* buf, size_t length) { return SSLSessionPointer(d2i_SSL_SESSION(nullptr, &buf, length)); } @@ -56,88 +49,12 @@ SSLSessionPointer GetTLSSession(const unsigned char* buf, size_t length) { long VerifyPeerCertificate( // NOLINT(runtime/int) const SSLPointer& ssl, long def) { // NOLINT(runtime/int) - long err = def; // NOLINT(runtime/int) - if (X509Pointer::PeerFrom(ssl)) { - err = SSL_get_verify_result(ssl.get()); - } else { - const SSL_CIPHER* curr_cipher = SSL_get_current_cipher(ssl.get()); - const SSL_SESSION* sess = SSL_get_session(ssl.get()); - // Allow no-cert for PSK authentication in TLS1.2 and lower. - // In TLS1.3 check that session was reused because TLS1.3 PSK - // looks like session resumption. - if (SSL_CIPHER_get_auth_nid(curr_cipher) == NID_auth_psk || - (SSL_SESSION_get_protocol_version(sess) == TLS1_3_VERSION && - SSL_session_reused(ssl.get()))) { - return X509_V_OK; - } - } - return err; + return ssl.verifyPeerCertificate().value_or(def); } bool UseSNIContext( const SSLPointer& ssl, BaseObjectPtr context) { - auto x509 = ncrypto::X509View::From(context->ctx()); - if (!x509) return false; - SSL_CTX* ctx = context->ctx().get(); - EVP_PKEY* pkey = SSL_CTX_get0_privatekey(ctx); - STACK_OF(X509)* chain; - - int err = SSL_CTX_get0_chain_certs(ctx, &chain); - if (err == 1) err = SSL_use_certificate(ssl.get(), x509); - if (err == 1) err = SSL_use_PrivateKey(ssl.get(), pkey); - if (err == 1 && chain != nullptr) err = SSL_set1_chain(ssl.get(), chain); - return err == 1; -} - -const char* GetClientHelloALPN(const SSLPointer& ssl) { - const unsigned char* buf; - size_t len; - size_t rem; - - if (!SSL_client_hello_get0_ext( - ssl.get(), - TLSEXT_TYPE_application_layer_protocol_negotiation, - &buf, - &rem) || - rem < 2) { - return nullptr; - } - - len = (buf[0] << 8) | buf[1]; - if (len + 2 != rem) return nullptr; - return reinterpret_cast(buf + 3); -} - -const char* GetClientHelloServerName(const SSLPointer& ssl) { - const unsigned char* buf; - size_t len; - size_t rem; - - if (!SSL_client_hello_get0_ext( - ssl.get(), - TLSEXT_TYPE_server_name, - &buf, - &rem) || rem <= 2) { - return nullptr; - } - - len = (*buf << 8) | *(buf + 1); - if (len + 2 != rem) - return nullptr; - rem = len; - - if (rem == 0 || *(buf + 2) != TLSEXT_NAMETYPE_host_name) return nullptr; - rem--; - if (rem <= 2) - return nullptr; - len = (*(buf + 3) << 8) | *(buf + 4); - if (len + 2 > rem) - return nullptr; - return reinterpret_cast(buf + 5); -} - -const char* GetServerName(SSL* ssl) { - return SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + return ssl.setSniContext(context->ctx()); } bool SetGroups(SecureContext* sc, const char* groups) { @@ -158,10 +75,10 @@ MaybeLocal GetValidationErrorCode(Environment* env, int err) { } MaybeLocal GetCert(Environment* env, const SSLPointer& ssl) { - ClearErrorOnReturn clear_error_on_return; - ncrypto::X509View cert(SSL_get_certificate(ssl.get())); - if (!cert) return Undefined(env->isolate()); - return X509Certificate::toObject(env, cert); + if (auto cert = ssl.getCertificate()) { + return X509Certificate::toObject(env, cert); + } + return Undefined(env->isolate()); } namespace { @@ -284,56 +201,22 @@ MaybeLocal GetLastIssuedCert( MaybeLocal GetCurrentCipherName(Environment* env, const SSLPointer& ssl) { - return GetCipherName(env, SSL_get_current_cipher(ssl.get())); + return GetCipherName(env, ssl.getCipher()); } MaybeLocal GetCurrentCipherVersion(Environment* env, const SSLPointer& ssl) { - return GetCipherVersion(env, SSL_get_current_cipher(ssl.get())); + return GetCipherVersion(env, ssl.getCipher()); } template (*Get)(Environment* env, const SSL_CIPHER* cipher)> MaybeLocal GetCurrentCipherValue(Environment* env, const SSLPointer& ssl) { - return Get(env, SSL_get_current_cipher(ssl.get())); -} - -MaybeLocal GetClientHelloCiphers( - Environment* env, - const SSLPointer& ssl) { - EscapableHandleScope scope(env->isolate()); - const unsigned char* buf; - size_t len = SSL_client_hello_get0_ciphers(ssl.get(), &buf); - size_t count = len / 2; - MaybeStackBuffer, 16> ciphers(count); - int j = 0; - for (size_t n = 0; n < len; n += 2) { - const SSL_CIPHER* cipher = SSL_CIPHER_find(ssl.get(), buf); - buf += 2; - Local obj = Object::New(env->isolate()); - if (!Set(env->context(), - obj, - env->name_string(), - GetCipherName(env, cipher)) || - !Set(env->context(), - obj, - env->standard_name_string(), - GetCipherStandardName(env, cipher)) || - !Set(env->context(), - obj, - env->version_string(), - GetCipherVersion(env, cipher))) { - return MaybeLocal(); - } - ciphers[j++] = obj; - } - Local ret = Array::New(env->isolate(), ciphers.out(), count); - return scope.Escape(ret); + return Get(env, ssl.getCipher()); } - MaybeLocal GetCipherInfo(Environment* env, const SSLPointer& ssl) { - if (SSL_get_current_cipher(ssl.get()) == nullptr) + if (ssl.getCipher() == nullptr) return MaybeLocal(); EscapableHandleScope scope(env->isolate()); Local info = Object::New(env->isolate()); @@ -357,15 +240,14 @@ MaybeLocal GetCipherInfo(Environment* env, const SSLPointer& ssl) { } MaybeLocal GetEphemeralKey(Environment* env, const SSLPointer& ssl) { - CHECK_EQ(SSL_is_server(ssl.get()), 0); - EVP_PKEY* raw_key; + CHECK(!ssl.isServer()); EscapableHandleScope scope(env->isolate()); Local info = Object::New(env->isolate()); - if (!SSL_get_peer_tmp_key(ssl.get(), &raw_key)) return scope.Escape(info); + crypto::EVPKeyPointer key = ssl.getPeerTempKey(); + if (!key) return scope.Escape(info); Local context = env->context(); - crypto::EVPKeyPointer key(raw_key); int kid = key.id(); switch (kid) { diff --git a/src/crypto/crypto_common.h b/src/crypto/crypto_common.h index 1067a6a73e114a..bce577ade78b58 100644 --- a/src/crypto/crypto_common.h +++ b/src/crypto/crypto_common.h @@ -22,10 +22,6 @@ namespace node { namespace crypto { -bool SetTLSSession( - const SSLPointer& ssl, - const SSLSessionPointer& session); - SSLSessionPointer GetTLSSession(const unsigned char* buf, size_t length); long VerifyPeerCertificate( // NOLINT(runtime/int) @@ -34,16 +30,6 @@ long VerifyPeerCertificate( // NOLINT(runtime/int) bool UseSNIContext(const SSLPointer& ssl, BaseObjectPtr context); -const char* GetClientHelloALPN(const SSLPointer& ssl); - -const char* GetClientHelloServerName(const SSLPointer& ssl); - -const char* GetServerName(SSL* ssl); - -v8::MaybeLocal GetClientHelloCiphers( - Environment* env, - const SSLPointer& ssl); - bool SetGroups(SecureContext* sc, const char* groups); v8::MaybeLocal GetValidationErrorReason(Environment* env, int err); diff --git a/src/crypto/crypto_tls.cc b/src/crypto/crypto_tls.cc index 3aa163a5e7294c..dbc50878b9860c 100644 --- a/src/crypto/crypto_tls.cc +++ b/src/crypto/crypto_tls.cc @@ -217,10 +217,11 @@ int SSLCertCallback(SSL* s, void* arg) { Local info = Object::New(env->isolate()); - const char* servername = GetServerName(s); - Local servername_str = (servername == nullptr) + auto servername = SSLPointer::GetServerName(s); + Local servername_str = !servername.has_value() ? String::Empty(env->isolate()) - : OneByteString(env->isolate(), servername, strlen(servername)); + : OneByteString(env->isolate(), servername.value().data(), + servername.value().length()); Local ocsp = Boolean::New( env->isolate(), SSL_get_tlsext_status_type(s) == TLSEXT_STATUSTYPE_ocsp); @@ -376,6 +377,21 @@ inline bool Set( .IsNothing(); } +inline bool Set( + Environment* env, + Local target, + Local name, + const std::string_view& value, + bool ignore_null = true) { + if (value.empty()) + return ignore_null; + return !target->Set( + env->context(), + name, + OneByteString(env->isolate(), value.data(), value.length())) + .IsNothing(); +} + std::string GetBIOError() { std::string ret; ERR_print_errors_cb( @@ -1345,9 +1361,11 @@ void TLSWrap::GetServername(const FunctionCallbackInfo& args) { CHECK_NOT_NULL(wrap->ssl_); - const char* servername = GetServerName(wrap->ssl_.get()); - if (servername != nullptr) { - args.GetReturnValue().Set(OneByteString(env->isolate(), servername)); + auto servername = wrap->ssl_.getServerName(); + if (servername.has_value()) { + auto& sn = servername.value(); + args.GetReturnValue().Set( + OneByteString(env->isolate(), sn.data(), sn.length())); } else { args.GetReturnValue().Set(false); } @@ -1376,8 +1394,9 @@ int TLSWrap::SelectSNIContextCallback(SSL* s, int* ad, void* arg) { HandleScope handle_scope(env->isolate()); Context::Scope context_scope(env->context()); - const char* servername = GetServerName(s); - if (!Set(env, p->GetOwner(), env->servername_string(), servername)) + auto servername = SSLPointer::GetServerName(s); + if (!servername.has_value() || + !Set(env, p->GetOwner(), env->servername_string(), servername.value())) return SSL_TLSEXT_ERR_NOACK; Local ctx = p->object()->Get(env->context(), env->sni_context_string()) @@ -1818,7 +1837,7 @@ void TLSWrap::SetSession(const FunctionCallbackInfo& args) { if (sess == nullptr) return; // TODO(tniessen): figure out error handling - if (!SetTLSSession(w->ssl_, sess)) + if (!w->ssl_.setSession(sess)) return env->ThrowError("SSL_set_session error"); } diff --git a/src/quic/tlscontext.cc b/src/quic/tlscontext.cc index fda49710e85938..916d6f0efb4c7e 100644 --- a/src/quic/tlscontext.cc +++ b/src/quic/tlscontext.cc @@ -557,7 +557,7 @@ crypto::SSLPointer TLSSession::Initialize( reinterpret_cast(buf.base), buf.len); // The early data will just be ignored if it's invalid. - if (crypto::SetTLSSession(ssl, ticket) && + if (ssl.setSession(ticket) && SSL_SESSION_get_max_early_data(ticket.get()) != 0) { ngtcp2_vec rtp = sessionTicket.transport_params(); if (ngtcp2_conn_decode_and_set_0rtt_transport_params( @@ -622,9 +622,7 @@ MaybeLocal TLSSession::cipher_version(Environment* env) const { } const std::string_view TLSSession::servername() const { - const char* servername = crypto::GetServerName(ssl_.get()); - return servername != nullptr ? std::string_view(servername) - : std::string_view(); + return ssl_.getServerName().value_or(std::string_view()); } const std::string_view TLSSession::protocol() const { From e79a7aa64b276317dd8d8667082603d7d5fc2d94 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 2 Jan 2025 08:56:43 -0800 Subject: [PATCH 10/12] src: move SSLCtxPointer impl to ncrypto --- deps/ncrypto/ncrypto.cc | 42 ++++++++++++++++++++++++++++++++++++ deps/ncrypto/ncrypto.h | 34 ++++++++++++++++++++++++++++- src/crypto/crypto_cipher.cc | 2 +- src/crypto/crypto_common.cc | 2 +- src/crypto/crypto_context.cc | 2 +- src/crypto/crypto_tls.cc | 3 +-- src/quic/tlscontext.cc | 4 ++-- 7 files changed, 81 insertions(+), 8 deletions(-) diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index 575be4fee3fd6c..26285db46d50a7 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -2371,4 +2371,46 @@ EVPKeyPointer SSLPointer::getPeerTempKey() const { if (!SSL_get_peer_tmp_key(get(), &raw_key)) return {}; return EVPKeyPointer(raw_key); } + +SSLCtxPointer::SSLCtxPointer(SSL_CTX* ctx) : ctx_(ctx) {} + +SSLCtxPointer::SSLCtxPointer(SSLCtxPointer&& other) noexcept + : ctx_(other.release()) {} + +SSLCtxPointer& SSLCtxPointer::operator=(SSLCtxPointer&& other) noexcept { + if (this == &other) return *this; + this->~SSLCtxPointer(); + return *new (this) SSLCtxPointer(std::move(other)); +} + +SSLCtxPointer::~SSLCtxPointer() { reset(); } + +void SSLCtxPointer::reset(SSL_CTX* ctx) { + ctx_.reset(ctx); +} + +void SSLCtxPointer::reset(const SSL_METHOD* method) { + ctx_.reset(SSL_CTX_new(method)); +} + +SSL_CTX* SSLCtxPointer::release() { + return ctx_.release(); +} + +SSLCtxPointer SSLCtxPointer::NewServer() { + return SSLCtxPointer(SSL_CTX_new(TLS_server_method())); +} + +SSLCtxPointer SSLCtxPointer::NewClient() { + return SSLCtxPointer(SSL_CTX_new(TLS_client_method())); +} + +SSLCtxPointer SSLCtxPointer::New(const SSL_METHOD* method) { + return SSLCtxPointer(SSL_CTX_new(method)); +} + +bool SSLCtxPointer::setGroups(const char* groups) { + return SSL_CTX_set1_groups_list(get(), groups) == 1; +} + } // namespace ncrypto diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h index 9e3b86334cdbeb..d404ca86407bd9 100644 --- a/deps/ncrypto/ncrypto.h +++ b/deps/ncrypto/ncrypto.h @@ -211,7 +211,6 @@ using HMACCtxPointer = DeleteFnPtr; using NetscapeSPKIPointer = DeleteFnPtr; using PKCS8Pointer = DeleteFnPtr; using RSAPointer = DeleteFnPtr; -using SSLCtxPointer = DeleteFnPtr; using SSLSessionPointer = DeleteFnPtr; struct StackOfXASN1Deleter { @@ -582,6 +581,39 @@ using StackOfX509 = std::unique_ptr; class X509Pointer; class X509View; +class SSLCtxPointer final { + public: + SSLCtxPointer() = default; + explicit SSLCtxPointer(SSL_CTX* ctx); + SSLCtxPointer(SSLCtxPointer&& other) noexcept; + SSLCtxPointer& operator=(SSLCtxPointer&& other) noexcept; + NCRYPTO_DISALLOW_COPY(SSLCtxPointer) + ~SSLCtxPointer(); + + inline bool operator==(std::nullptr_t) const noexcept { + return ctx_ == nullptr; + } + inline operator bool() const { return ctx_ != nullptr; } + inline SSL_CTX* get() const { return ctx_.get(); } + void reset(SSL_CTX* ctx = nullptr); + void reset(const SSL_METHOD* method); + SSL_CTX* release(); + + bool setGroups(const char* groups); + void setStatusCallback(auto callback) { + if (!ctx_) return; + SSL_CTX_set_tlsext_status_cb(get(), callback); + SSL_CTX_set_tlsext_status_arg(get(), nullptr); + } + + static SSLCtxPointer NewServer(); + static SSLCtxPointer NewClient(); + static SSLCtxPointer New(const SSL_METHOD* method = TLS_method()); + + private: + DeleteFnPtr ctx_; +}; + class SSLPointer final { public: SSLPointer() = default; diff --git a/src/crypto/crypto_cipher.cc b/src/crypto/crypto_cipher.cc index 075ff3c008fb4b..7bacc6c684028f 100644 --- a/src/crypto/crypto_cipher.cc +++ b/src/crypto/crypto_cipher.cc @@ -198,7 +198,7 @@ void GetCipherInfo(const FunctionCallbackInfo& args) { void CipherBase::GetSSLCiphers(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - SSLCtxPointer ctx(SSL_CTX_new(TLS_method())); + auto ctx = SSLCtxPointer::New(); if (!ctx) { return ThrowCryptoError(env, ERR_get_error(), "SSL_CTX_new"); } diff --git a/src/crypto/crypto_common.cc b/src/crypto/crypto_common.cc index c30c8947b934fd..f7e5f800cd9900 100644 --- a/src/crypto/crypto_common.cc +++ b/src/crypto/crypto_common.cc @@ -58,7 +58,7 @@ bool UseSNIContext( } bool SetGroups(SecureContext* sc, const char* groups) { - return SSL_CTX_set1_groups_list(sc->ctx().get(), groups) == 1; + return sc->ctx().setGroups(groups); } MaybeLocal GetValidationErrorReason(Environment* env, int err) { diff --git a/src/crypto/crypto_context.cc b/src/crypto/crypto_context.cc index db6f437759c3ec..fa96b8d7a51d46 100644 --- a/src/crypto/crypto_context.cc +++ b/src/crypto/crypto_context.cc @@ -551,7 +551,7 @@ void SecureContext::Init(const FunctionCallbackInfo& args) { } } - sc->ctx_.reset(SSL_CTX_new(method)); + sc->ctx_.reset(method); if (!sc->ctx_) { return ThrowCryptoError(env, ERR_get_error(), "SSL_CTX_new"); } diff --git a/src/crypto/crypto_tls.cc b/src/crypto/crypto_tls.cc index dbc50878b9860c..51ed6466c9391a 100644 --- a/src/crypto/crypto_tls.cc +++ b/src/crypto/crypto_tls.cc @@ -358,8 +358,7 @@ int TLSExtStatusCallback(SSL* s, void* arg) { void ConfigureSecureContext(SecureContext* sc) { // OCSP stapling - SSL_CTX_set_tlsext_status_cb(sc->ctx().get(), TLSExtStatusCallback); - SSL_CTX_set_tlsext_status_arg(sc->ctx().get(), nullptr); + sc->ctx().setStatusCallback(TLSExtStatusCallback); } inline bool Set( diff --git a/src/quic/tlscontext.cc b/src/quic/tlscontext.cc index 916d6f0efb4c7e..8e2995589c9116 100644 --- a/src/quic/tlscontext.cc +++ b/src/quic/tlscontext.cc @@ -245,7 +245,7 @@ crypto::SSLCtxPointer TLSContext::Initialize() { switch (side_) { case Side::SERVER: { static constexpr unsigned char kSidCtx[] = "Node.js QUIC Server"; - ctx.reset(SSL_CTX_new(TLS_server_method())); + ctx = crypto::SSLCtxPointer::NewServer(); CHECK_EQ(ngtcp2_crypto_quictls_configure_server_context(ctx.get()), 0); CHECK_EQ(SSL_CTX_set_max_early_data(ctx.get(), UINT32_MAX), 1); SSL_CTX_set_options(ctx.get(), @@ -276,7 +276,7 @@ crypto::SSLCtxPointer TLSContext::Initialize() { break; } case Side::CLIENT: { - ctx.reset(SSL_CTX_new(TLS_client_method())); + ctx = crypto::SSLCtxPointer::NewClient(); CHECK_EQ(ngtcp2_crypto_quictls_configure_client_context(ctx.get()), 0); SSL_CTX_set_session_cache_mode( From 3dacb828821222aa35b986e52266d4a2879421ec Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 2 Jan 2025 10:16:59 -0800 Subject: [PATCH 11/12] src: move EVP_CIPHER methods to ncrypto --- deps/ncrypto/ncrypto.cc | 179 +++++++++++++++++++++++++++--------- deps/ncrypto/ncrypto.h | 41 ++++++++- src/crypto/crypto_aes.cc | 11 +-- src/crypto/crypto_aes.h | 2 +- src/crypto/crypto_cipher.cc | 152 ++++++++++++------------------ src/crypto/crypto_cipher.h | 4 +- src/crypto/crypto_common.cc | 3 +- src/crypto/crypto_tls.cc | 33 ++++--- 8 files changed, 259 insertions(+), 166 deletions(-) diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index 26285db46d50a7..d687904b677afd 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -312,23 +312,27 @@ BignumPointer BignumPointer::clone() { } int BignumPointer::isPrime(int nchecks, - BignumPointer::PrimeCheckCallback innerCb) const { + BignumPointer::PrimeCheckCallback innerCb) const { BignumCtxPointer ctx(BN_CTX_new()); BignumGenCallbackPointer cb(nullptr); if (innerCb != nullptr) { cb = BignumGenCallbackPointer(BN_GENCB_new()); - if (!cb) [[unlikely]] return -1; - BN_GENCB_set(cb.get(), [](int a, int b, BN_GENCB* ctx) mutable -> int { - PrimeCheckCallback& ptr = - *static_cast(BN_GENCB_get_arg(ctx)); - return ptr(a, b) ? 1 : 0; - }, &innerCb); + if (!cb) [[unlikely]] + return -1; + BN_GENCB_set( + cb.get(), + [](int a, int b, BN_GENCB* ctx) mutable -> int { + PrimeCheckCallback& ptr = + *static_cast(BN_GENCB_get_arg(ctx)); + return ptr(a, b) ? 1 : 0; + }, + &innerCb); } return BN_is_prime_ex(get(), nchecks, ctx.get(), cb.get()); } BignumPointer BignumPointer::NewPrime(const PrimeConfig& params, - PrimeCheckCallback cb) { + PrimeCheckCallback cb) { BignumPointer prime(BN_new()); if (!prime || !prime.generate(params, std::move(cb))) { return {}; @@ -337,27 +341,30 @@ BignumPointer BignumPointer::NewPrime(const PrimeConfig& params, } bool BignumPointer::generate(const PrimeConfig& params, - PrimeCheckCallback innerCb) const { + PrimeCheckCallback innerCb) const { // BN_generate_prime_ex() calls RAND_bytes_ex() internally. // Make sure the CSPRNG is properly seeded. CSPRNG(nullptr, 0); BignumGenCallbackPointer cb(nullptr); if (innerCb != nullptr) { cb = BignumGenCallbackPointer(BN_GENCB_new()); - if (!cb) [[unlikely]] return -1; - BN_GENCB_set(cb.get(), [](int a, int b, BN_GENCB* ctx) mutable -> int { - PrimeCheckCallback& ptr = - *static_cast(BN_GENCB_get_arg(ctx)); - return ptr(a, b) ? 1 : 0; - }, &innerCb); - } - if (BN_generate_prime_ex( - get(), - params.bits, - params.safe ? 1 : 0, - params.add.get(), - params.rem.get(), - cb.get()) == 0) { + if (!cb) [[unlikely]] + return -1; + BN_GENCB_set( + cb.get(), + [](int a, int b, BN_GENCB* ctx) mutable -> int { + PrimeCheckCallback& ptr = + *static_cast(BN_GENCB_get_arg(ctx)); + return ptr(a, b) ? 1 : 0; + }, + &innerCb); + } + if (BN_generate_prime_ex(get(), + params.bits, + params.safe ? 1 : 0, + params.add.get(), + params.rem.get(), + cb.get()) == 0) { return false; } @@ -2228,13 +2235,11 @@ void SSLPointer::getCiphers( // document them, but since there are only 5, easier to just add them manually // and not have to explain their absence in the API docs. They are lower-cased // because the docs say they will be. - static const char* TLS13_CIPHERS[] = { - "tls_aes_256_gcm_sha384", - "tls_chacha20_poly1305_sha256", - "tls_aes_128_gcm_sha256", - "tls_aes_128_ccm_8_sha256", - "tls_aes_128_ccm_sha256" - }; + static const char* TLS13_CIPHERS[] = {"tls_aes_256_gcm_sha384", + "tls_chacha20_poly1305_sha256", + "tls_aes_128_gcm_sha256", + "tls_aes_128_ccm_8_sha256", + "tls_aes_128_ccm_sha256"}; const int n = sk_SSL_CIPHER_num(ciphers); @@ -2259,7 +2264,7 @@ bool SSLPointer::setSniContext(const SSLCtxPointer& ctx) const { auto x509 = ncrypto::X509View::From(ctx); if (!x509) return false; EVP_PKEY* pkey = SSL_CTX_get0_privatekey(ctx.get()); - STACK_OF(X509)* chain; + STACK_OF(X509) * chain; int err = SSL_CTX_get0_chain_certs(ctx.get(), &chain); if (err == 1) err = SSL_use_certificate(get(), x509); @@ -2281,7 +2286,7 @@ std::optional SSLPointer::verifyPeerCertificate() const { // looks like session resumption. if (SSL_CIPHER_get_auth_nid(curr_cipher) == NID_auth_psk || (SSL_SESSION_get_protocol_version(sess) == TLS1_3_VERSION && - SSL_session_reused(get()))) { + SSL_session_reused(get()))) { return X509_V_OK; } @@ -2314,26 +2319,20 @@ const std::string_view SSLPointer::getClientHelloServerName() const { size_t len; size_t rem; - if (!SSL_client_hello_get0_ext( - get(), - TLSEXT_TYPE_server_name, - &buf, - &rem) || rem <= 2) { + if (!SSL_client_hello_get0_ext(get(), TLSEXT_TYPE_server_name, &buf, &rem) || + rem <= 2) { return nullptr; } len = (*buf << 8) | *(buf + 1); - if (len + 2 != rem) - return nullptr; + if (len + 2 != rem) return nullptr; rem = len; if (rem == 0 || *(buf + 2) != TLSEXT_NAMETYPE_host_name) return nullptr; rem--; - if (rem <= 2) - return nullptr; + if (rem <= 2) return nullptr; len = (*(buf + 3) << 8) | *(buf + 4); - if (len + 2 > rem) - return nullptr; + if (len + 2 > rem) return nullptr; return reinterpret_cast(buf + 5); } @@ -2383,7 +2382,9 @@ SSLCtxPointer& SSLCtxPointer::operator=(SSLCtxPointer&& other) noexcept { return *new (this) SSLCtxPointer(std::move(other)); } -SSLCtxPointer::~SSLCtxPointer() { reset(); } +SSLCtxPointer::~SSLCtxPointer() { + reset(); +} void SSLCtxPointer::reset(SSL_CTX* ctx) { ctx_.reset(ctx); @@ -2413,4 +2414,94 @@ bool SSLCtxPointer::setGroups(const char* groups) { return SSL_CTX_set1_groups_list(get(), groups) == 1; } +// ============================================================================ + +const Cipher Cipher::FromName(const char* name) { + return Cipher(EVP_get_cipherbyname(name)); +} + +const Cipher Cipher::FromNid(int nid) { + return Cipher(EVP_get_cipherbynid(nid)); +} + +const Cipher Cipher::FromCtx(const CipherCtxPointer& ctx) { + return Cipher(EVP_CIPHER_CTX_cipher(ctx.get())); +} + +int Cipher::getMode() const { + if (!cipher_) return 0; + return EVP_CIPHER_mode(cipher_); +} + +int Cipher::getIvLength() const { + if (!cipher_) return 0; + return EVP_CIPHER_iv_length(cipher_); +} + +int Cipher::getKeyLength() const { + if (!cipher_) return 0; + return EVP_CIPHER_key_length(cipher_); +} + +int Cipher::getBlockSize() const { + if (!cipher_) return 0; + return EVP_CIPHER_block_size(cipher_); +} + +int Cipher::getNid() const { + if (!cipher_) return 0; + return EVP_CIPHER_nid(cipher_); +} + +const std::string_view Cipher::getModeLabel() const { + if (!cipher_) return {}; + switch (getMode()) { + case EVP_CIPH_CCM_MODE: + return "ccm"; + case EVP_CIPH_CFB_MODE: + return "cfb"; + case EVP_CIPH_CBC_MODE: + return "cbc"; + case EVP_CIPH_CTR_MODE: + return "ctr"; + case EVP_CIPH_ECB_MODE: + return "ecb"; + case EVP_CIPH_GCM_MODE: + return "gcm"; + case EVP_CIPH_OCB_MODE: + return "ocb"; + case EVP_CIPH_OFB_MODE: + return "ofb"; + case EVP_CIPH_WRAP_MODE: + return "wrap"; + case EVP_CIPH_XTS_MODE: + return "xts"; + case EVP_CIPH_STREAM_CIPHER: + return "stream"; + } + return "{unknown}"; +} + +const std::string_view Cipher::getName() const { + if (!cipher_) return {}; + // OBJ_nid2sn(EVP_CIPHER_nid(cipher)) is used here instead of + // EVP_CIPHER_name(cipher) for compatibility with BoringSSL. + return OBJ_nid2sn(getNid()); +} + +bool Cipher::isSupportedAuthenticatedMode() const { + switch (getMode()) { + case EVP_CIPH_CCM_MODE: + case EVP_CIPH_GCM_MODE: +#ifndef OPENSSL_NO_OCB + case EVP_CIPH_OCB_MODE: +#endif + return true; + case EVP_CIPH_STREAM_CIPHER: + return getNid() == NID_chacha20_poly1305; + default: + return false; + } +} + } // namespace ncrypto diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h index d404ca86407bd9..e12fa7f925c08f 100644 --- a/deps/ncrypto/ncrypto.h +++ b/deps/ncrypto/ncrypto.h @@ -227,6 +227,40 @@ struct Buffer { size_t len = 0; }; +class Cipher final { + public: + Cipher() = default; + Cipher(const EVP_CIPHER* cipher) : cipher_(cipher) {} + Cipher(const Cipher&) = default; + Cipher& operator=(const Cipher&) = default; + inline Cipher& operator=(const EVP_CIPHER* cipher) { + cipher_ = cipher; + return *this; + } + NCRYPTO_DISALLOW_MOVE(Cipher) + + inline const EVP_CIPHER* get() const { return cipher_; } + inline operator const EVP_CIPHER*() const { return cipher_; } + inline operator bool() const { return cipher_ != nullptr; } + + int getNid() const; + int getMode() const; + int getIvLength() const; + int getKeyLength() const; + int getBlockSize() const; + const std::string_view getModeLabel() const; + const std::string_view getName() const; + + bool isSupportedAuthenticatedMode() const; + + static const Cipher FromName(const char* name); + static const Cipher FromNid(int nid); + static const Cipher FromCtx(const CipherCtxPointer& ctx); + + private: + const EVP_CIPHER* cipher_ = nullptr; +}; + // A managed pointer to a buffer of data. When destroyed the underlying // buffer will be freed. class DataPointer final { @@ -353,7 +387,7 @@ class BignumPointer final { using PrimeCheckCallback = std::function; int isPrime(int checks, - PrimeCheckCallback cb = defaultPrimeCheckCallback) const; + PrimeCheckCallback cb = defaultPrimeCheckCallback) const; struct PrimeConfig { int bits; bool safe = false; @@ -361,11 +395,12 @@ class BignumPointer final { const BignumPointer& rem; }; - static BignumPointer NewPrime(const PrimeConfig& params, + static BignumPointer NewPrime( + const PrimeConfig& params, PrimeCheckCallback cb = defaultPrimeCheckCallback); bool generate(const PrimeConfig& params, - PrimeCheckCallback cb = defaultPrimeCheckCallback) const; + PrimeCheckCallback cb = defaultPrimeCheckCallback) const; static BignumPointer New(); static BignumPointer NewSecure(); diff --git a/src/crypto/crypto_aes.cc b/src/crypto/crypto_aes.cc index 5ac8ee66aef762..5fc63c9beb48c3 100644 --- a/src/crypto/crypto_aes.cc +++ b/src/crypto/crypto_aes.cc @@ -40,7 +40,7 @@ WebCryptoCipherStatus AES_Cipher(Environment* env, ByteSource* out) { CHECK_EQ(key_data.GetKeyType(), kKeyTypeSecret); - const int mode = EVP_CIPHER_mode(params.cipher); + const int mode = params.cipher.getMode(); CipherCtxPointer ctx(EVP_CIPHER_CTX_new()); EVP_CIPHER_CTX_init(ctx.get()); @@ -478,13 +478,13 @@ Maybe AESCipherTraits::AdditionalConfig( } #undef V - params->cipher = EVP_get_cipherbynid(cipher_nid); - if (params->cipher == nullptr) { + params->cipher = ncrypto::Cipher::FromNid(cipher_nid); + if (!params->cipher) { THROW_ERR_CRYPTO_UNKNOWN_CIPHER(env); return Nothing(); } - int cipher_op_mode = EVP_CIPHER_mode(params->cipher); + int cipher_op_mode = params->cipher.getMode(); if (cipher_op_mode != EVP_CIPH_WRAP_MODE) { if (!ValidateIV(env, mode, args[offset + 1], params)) { return Nothing(); @@ -503,8 +503,7 @@ Maybe AESCipherTraits::AdditionalConfig( UseDefaultIV(params); } - if (params->iv.size() < - static_cast(EVP_CIPHER_iv_length(params->cipher))) { + if (params->iv.size() < static_cast(params->cipher.getIvLength())) { THROW_ERR_CRYPTO_INVALID_IV(env); return Nothing(); } diff --git a/src/crypto/crypto_aes.h b/src/crypto/crypto_aes.h index 3f554ac8c15c60..7bcfa36afdace4 100644 --- a/src/crypto/crypto_aes.h +++ b/src/crypto/crypto_aes.h @@ -38,7 +38,7 @@ enum AESKeyVariant { struct AESCipherConfig final : public MemoryRetainer { CryptoJobMode mode; AESKeyVariant variant; - const EVP_CIPHER* cipher; + ncrypto::Cipher cipher; size_t length; ByteSource iv; // Used for both iv or counter ByteSource additional_data; diff --git a/src/crypto/crypto_cipher.cc b/src/crypto/crypto_cipher.cc index 7bacc6c684028f..27294625037dba 100644 --- a/src/crypto/crypto_cipher.cc +++ b/src/crypto/crypto_cipher.cc @@ -27,26 +27,6 @@ using v8::Value; namespace crypto { namespace { -bool IsSupportedAuthenticatedMode(const EVP_CIPHER* cipher) { - switch (EVP_CIPHER_mode(cipher)) { - case EVP_CIPH_CCM_MODE: - case EVP_CIPH_GCM_MODE: -#ifndef OPENSSL_NO_OCB - case EVP_CIPH_OCB_MODE: -#endif - return true; - case EVP_CIPH_STREAM_CIPHER: - return EVP_CIPHER_nid(cipher) == NID_chacha20_poly1305; - default: - return false; - } -} - -bool IsSupportedAuthenticatedMode(const EVP_CIPHER_CTX* ctx) { - const EVP_CIPHER* cipher = EVP_CIPHER_CTX_cipher(ctx); - return IsSupportedAuthenticatedMode(cipher); -} - bool IsValidGCMTagLength(unsigned int tag_len) { return tag_len == 4 || tag_len == 8 || (tag_len >= 12 && tag_len <= 16); } @@ -59,36 +39,23 @@ void GetCipherInfo(const FunctionCallbackInfo& args) { CHECK(args[1]->IsString() || args[1]->IsInt32()); - const EVP_CIPHER* cipher; - if (args[1]->IsString()) { - Utf8Value name(env->isolate(), args[1]); - cipher = EVP_get_cipherbyname(*name); - } else { - int nid = args[1].As()->Value(); - cipher = EVP_get_cipherbynid(nid); - } + const auto cipher = ([&] { + if (args[1]->IsString()) { + Utf8Value name(env->isolate(), args[1]); + return ncrypto::Cipher::FromName(*name); + } else { + int nid = args[1].As()->Value(); + return ncrypto::Cipher::FromNid(nid); + } + })(); - if (cipher == nullptr) - return; + if (!cipher) return; - int mode = EVP_CIPHER_mode(cipher); - int iv_length = EVP_CIPHER_iv_length(cipher); - int key_length = EVP_CIPHER_key_length(cipher); - int block_length = EVP_CIPHER_block_size(cipher); - const char* mode_label = nullptr; - switch (mode) { - case EVP_CIPH_CBC_MODE: mode_label = "cbc"; break; - case EVP_CIPH_CCM_MODE: mode_label = "ccm"; break; - case EVP_CIPH_CFB_MODE: mode_label = "cfb"; break; - case EVP_CIPH_CTR_MODE: mode_label = "ctr"; break; - case EVP_CIPH_ECB_MODE: mode_label = "ecb"; break; - case EVP_CIPH_GCM_MODE: mode_label = "gcm"; break; - case EVP_CIPH_OCB_MODE: mode_label = "ocb"; break; - case EVP_CIPH_OFB_MODE: mode_label = "ofb"; break; - case EVP_CIPH_WRAP_MODE: mode_label = "wrap"; break; - case EVP_CIPH_XTS_MODE: mode_label = "xts"; break; - case EVP_CIPH_STREAM_CIPHER: mode_label = "stream"; break; - } + int iv_length = cipher.getIvLength(); + int key_length = cipher.getKeyLength(); + int block_length = cipher.getBlockSize(); + auto mode_label = cipher.getModeLabel(); + auto name = cipher.getName(); // If the testKeyLen and testIvLen arguments are specified, // then we will make an attempt to see if they are usable for @@ -116,7 +83,7 @@ void GetCipherInfo(const FunctionCallbackInfo& args) { // For GCM and OCB modes, we'll check by attempting to // set the value. For everything else, just check that // check_len == iv_length. - switch (mode) { + switch (cipher.getMode()) { case EVP_CIPH_CCM_MODE: if (check_len < 7 || check_len > 13) return; @@ -140,38 +107,35 @@ void GetCipherInfo(const FunctionCallbackInfo& args) { } } - if (mode_label != nullptr && - info->Set( - env->context(), - FIXED_ONE_BYTE_STRING(env->isolate(), "mode"), - OneByteString(env->isolate(), mode_label)).IsNothing()) { + if (mode_label.length() && + info->Set(env->context(), + FIXED_ONE_BYTE_STRING(env->isolate(), "mode"), + OneByteString( + env->isolate(), mode_label.data(), mode_label.length())) + .IsNothing()) { return; } - // OBJ_nid2sn(EVP_CIPHER_nid(cipher)) is used here instead of - // EVP_CIPHER_name(cipher) for compatibility with BoringSSL. - if (info->Set( - env->context(), - env->name_string(), - OneByteString( - env->isolate(), - OBJ_nid2sn(EVP_CIPHER_nid(cipher)))).IsNothing()) { + if (info->Set(env->context(), + env->name_string(), + OneByteString(env->isolate(), name.data(), name.length())) + .IsNothing()) { return; } - if (info->Set( - env->context(), - FIXED_ONE_BYTE_STRING(env->isolate(), "nid"), - Int32::New(env->isolate(), EVP_CIPHER_nid(cipher))).IsNothing()) { + if (info->Set(env->context(), + FIXED_ONE_BYTE_STRING(env->isolate(), "nid"), + Int32::New(env->isolate(), cipher.getNid())) + .IsNothing()) { return; } // Stream ciphers do not have a meaningful block size - if (mode != EVP_CIPH_STREAM_CIPHER && - info->Set( - env->context(), - FIXED_ONE_BYTE_STRING(env->isolate(), "blockSize"), - Int32::New(env->isolate(), block_length)).IsNothing()) { + if (cipher.getMode() != EVP_CIPH_STREAM_CIPHER && + info->Set(env->context(), + FIXED_ONE_BYTE_STRING(env->isolate(), "blockSize"), + Int32::New(env->isolate(), block_length)) + .IsNothing()) { return; } @@ -341,7 +305,7 @@ void CipherBase::New(const FunctionCallbackInfo& args) { } void CipherBase::CommonInit(const char* cipher_type, - const EVP_CIPHER* cipher, + const ncrypto::Cipher& cipher, const unsigned char* key, int key_len, const unsigned char* iv, @@ -350,9 +314,9 @@ void CipherBase::CommonInit(const char* cipher_type, CHECK(!ctx_); ctx_.reset(EVP_CIPHER_CTX_new()); - const int mode = EVP_CIPHER_mode(cipher); - if (mode == EVP_CIPH_WRAP_MODE) + if (cipher.getMode() == EVP_CIPH_WRAP_MODE) { EVP_CIPHER_CTX_set_flags(ctx_.get(), EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); + } const bool encrypt = (kind_ == kCipher); if (1 != EVP_CipherInit_ex(ctx_.get(), cipher, nullptr, @@ -361,7 +325,7 @@ void CipherBase::CommonInit(const char* cipher_type, "Failed to initialize cipher"); } - if (IsSupportedAuthenticatedMode(cipher)) { + if (cipher.isSupportedAuthenticatedMode()) { CHECK_GE(iv_len, 0); if (!InitAuthenticated(cipher_type, iv_len, auth_tag_len)) return; @@ -383,9 +347,10 @@ void CipherBase::Init(const char* cipher_type, unsigned int auth_tag_len) { HandleScope scope(env()->isolate()); MarkPopErrorOnReturn mark_pop_error_on_return; - const EVP_CIPHER* const cipher = EVP_get_cipherbyname(cipher_type); - if (cipher == nullptr) + auto cipher = ncrypto::Cipher::FromName(cipher_type); + if (!cipher) { return THROW_ERR_CRYPTO_UNKNOWN_CIPHER(env()); + } unsigned char key[EVP_MAX_KEY_LENGTH]; unsigned char iv[EVP_MAX_IV_LENGTH]; @@ -400,7 +365,7 @@ void CipherBase::Init(const char* cipher_type, iv); CHECK_NE(key_len, 0); - const int mode = EVP_CIPHER_mode(cipher); + const int mode = cipher.getMode(); if (kind_ == kCipher && (mode == EVP_CIPH_CTR_MODE || mode == EVP_CIPH_GCM_MODE || mode == EVP_CIPH_CCM_MODE)) { @@ -411,8 +376,13 @@ void CipherBase::Init(const char* cipher_type, cipher_type); } - CommonInit(cipher_type, cipher, key, key_len, iv, - EVP_CIPHER_iv_length(cipher), auth_tag_len); + CommonInit(cipher_type, + cipher, + key, + key_len, + iv, + cipher.getIvLength(), + auth_tag_len); } void CipherBase::Init(const FunctionCallbackInfo& args) { @@ -447,12 +417,10 @@ void CipherBase::InitIv(const char* cipher_type, HandleScope scope(env()->isolate()); MarkPopErrorOnReturn mark_pop_error_on_return; - const EVP_CIPHER* const cipher = EVP_get_cipherbyname(cipher_type); - if (cipher == nullptr) - return THROW_ERR_CRYPTO_UNKNOWN_CIPHER(env()); + auto cipher = ncrypto::Cipher::FromName(cipher_type); + if (!cipher) return THROW_ERR_CRYPTO_UNKNOWN_CIPHER(env()); - const int expected_iv_len = EVP_CIPHER_iv_length(cipher); - const bool is_authenticated_mode = IsSupportedAuthenticatedMode(cipher); + const int expected_iv_len = cipher.getIvLength(); const bool has_iv = iv_buf.size() > 0; // Throw if no IV was passed and the cipher requires an IV @@ -462,13 +430,12 @@ void CipherBase::InitIv(const char* cipher_type, // Throw if an IV was passed which does not match the cipher's fixed IV length // static_cast for the iv_buf.size() is safe because we've verified // prior that the value is not larger than INT_MAX. - if (!is_authenticated_mode && - has_iv && + if (!cipher.isSupportedAuthenticatedMode() && has_iv && static_cast(iv_buf.size()) != expected_iv_len) { return THROW_ERR_CRYPTO_INVALID_IV(env()); } - if (EVP_CIPHER_nid(cipher) == NID_chacha20_poly1305) { + if (cipher.getNid() == NID_chacha20_poly1305) { CHECK(has_iv); // Check for invalid IV lengths, since OpenSSL does not under some // conditions: @@ -619,7 +586,7 @@ bool CipherBase::CheckCCMMessageLength(int message_len) { bool CipherBase::IsAuthenticatedMode() const { // Check if this cipher operates in an AEAD mode that we support. CHECK(ctx_); - return IsSupportedAuthenticatedMode(ctx_.get()); + return ncrypto::Cipher::FromCtx(ctx_).isSupportedAuthenticatedMode(); } void CipherBase::GetAuthTag(const FunctionCallbackInfo& args) { @@ -667,7 +634,8 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo& args) { } else { // At this point, the tag length is already known and must match the // length of the given authentication tag. - CHECK(IsSupportedAuthenticatedMode(cipher->ctx_.get())); + CHECK( + ncrypto::Cipher::FromCtx(cipher->ctx_).isSupportedAuthenticatedMode()); CHECK_NE(cipher->auth_tag_len_, kNoAuthTagLength); is_valid = cipher->auth_tag_len_ == tag_len; } @@ -885,8 +853,10 @@ bool CipherBase::Final(std::unique_ptr* out) { static_cast(EVP_CIPHER_CTX_block_size(ctx_.get()))); } - if (kind_ == kDecipher && IsSupportedAuthenticatedMode(ctx_.get())) + if (kind_ == kDecipher && + ncrypto::Cipher::FromCtx(ctx_).isSupportedAuthenticatedMode()) { MaybePassAuthTagToOpenSSL(); + } // OpenSSL v1.x doesn't verify the presence of the auth tag so do // it ourselves, see https://github.com/nodejs/node/issues/45874. diff --git a/src/crypto/crypto_cipher.h b/src/crypto/crypto_cipher.h index de436e2e9d2df8..d15a231475d657 100644 --- a/src/crypto/crypto_cipher.h +++ b/src/crypto/crypto_cipher.h @@ -44,7 +44,7 @@ class CipherBase : public BaseObject { static const unsigned kNoAuthTagLength = static_cast(-1); void CommonInit(const char* cipher_type, - const EVP_CIPHER* cipher, + const ncrypto::Cipher& cipher, const unsigned char* key, int key_len, const unsigned char* iv, @@ -85,7 +85,7 @@ class CipherBase : public BaseObject { CipherBase(Environment* env, v8::Local wrap, CipherKind kind); private: - DeleteFnPtr ctx_; + CipherCtxPointer ctx_; const CipherKind kind_; AuthTagState auth_tag_state_; unsigned int auth_tag_len_; diff --git a/src/crypto/crypto_common.cc b/src/crypto/crypto_common.cc index f7e5f800cd9900..b03b592a88f627 100644 --- a/src/crypto/crypto_common.cc +++ b/src/crypto/crypto_common.cc @@ -216,8 +216,7 @@ MaybeLocal GetCurrentCipherValue(Environment* env, } MaybeLocal GetCipherInfo(Environment* env, const SSLPointer& ssl) { - if (ssl.getCipher() == nullptr) - return MaybeLocal(); + if (ssl.getCipher() == nullptr) return MaybeLocal(); EscapableHandleScope scope(env->isolate()); Local info = Object::New(env->isolate()); diff --git a/src/crypto/crypto_tls.cc b/src/crypto/crypto_tls.cc index 51ed6466c9391a..f0bb4786abdc71 100644 --- a/src/crypto/crypto_tls.cc +++ b/src/crypto/crypto_tls.cc @@ -218,10 +218,11 @@ int SSLCertCallback(SSL* s, void* arg) { Local info = Object::New(env->isolate()); auto servername = SSLPointer::GetServerName(s); - Local servername_str = !servername.has_value() - ? String::Empty(env->isolate()) - : OneByteString(env->isolate(), servername.value().data(), - servername.value().length()); + Local servername_str = + !servername.has_value() ? String::Empty(env->isolate()) + : OneByteString(env->isolate(), + servername.value().data(), + servername.value().length()); Local ocsp = Boolean::New( env->isolate(), SSL_get_tlsext_status_type(s) == TLSEXT_STATUSTYPE_ocsp); @@ -376,19 +377,17 @@ inline bool Set( .IsNothing(); } -inline bool Set( - Environment* env, - Local target, - Local name, - const std::string_view& value, - bool ignore_null = true) { - if (value.empty()) - return ignore_null; - return !target->Set( - env->context(), - name, - OneByteString(env->isolate(), value.data(), value.length())) - .IsNothing(); +inline bool Set(Environment* env, + Local target, + Local name, + const std::string_view& value, + bool ignore_null = true) { + if (value.empty()) return ignore_null; + return !target + ->Set(env->context(), + name, + OneByteString(env->isolate(), value.data(), value.length())) + .IsNothing(); } std::string GetBIOError() { From f22a969a542dce191e9ceb8453ce9cd0b5eeac47 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 2 Jan 2025 12:01:30 -0800 Subject: [PATCH 12/12] src: move CipherCtx methods to ncrypto --- deps/ncrypto/ncrypto.cc | 151 +++++++++++++++++++++++++++++++----- deps/ncrypto/ncrypto.h | 54 ++++++++++++- src/crypto/crypto_aes.cc | 114 ++++++++++++--------------- src/crypto/crypto_cipher.cc | 118 ++++++++++++++-------------- src/crypto/crypto_common.cc | 2 +- src/crypto/crypto_random.cc | 25 +++--- 6 files changed, 301 insertions(+), 163 deletions(-) diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index d687904b677afd..ebdda72184b709 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -321,6 +321,8 @@ int BignumPointer::isPrime(int nchecks, return -1; BN_GENCB_set( cb.get(), + // TODO(@jasnell): This could be refactored to allow inlining. + // Not too important right now tho. [](int a, int b, BN_GENCB* ctx) mutable -> int { PrimeCheckCallback& ptr = *static_cast(BN_GENCB_get_arg(ctx)); @@ -374,6 +376,7 @@ bool BignumPointer::generate(const PrimeConfig& params, BignumPointer BignumPointer::NewSub(const BignumPointer& a, const BignumPointer& b) { BignumPointer res = New(); + if (!res) return {}; if (!BN_sub(res.get(), a.get(), b.get())) { return {}; } @@ -382,6 +385,7 @@ BignumPointer BignumPointer::NewSub(const BignumPointer& a, BignumPointer BignumPointer::NewLShift(size_t length) { BignumPointer res = New(); + if (!res) return {}; if (!BN_lshift(res.get(), One(), length)) { return {}; } @@ -1192,8 +1196,8 @@ std::string_view X509Pointer::ErrorCode(int32_t err) { // NOLINT(runtime/int) return "UNSPECIFIED"; } -std::string_view X509Pointer::ErrorReason(int32_t err) { - if (err == X509_V_OK) return ""; +std::optional X509Pointer::ErrorReason(int32_t err) { + if (err == X509_V_OK) return std::nullopt; return X509_verify_cert_error_string(err); } @@ -2235,11 +2239,12 @@ void SSLPointer::getCiphers( // document them, but since there are only 5, easier to just add them manually // and not have to explain their absence in the API docs. They are lower-cased // because the docs say they will be. - static const char* TLS13_CIPHERS[] = {"tls_aes_256_gcm_sha384", - "tls_chacha20_poly1305_sha256", - "tls_aes_128_gcm_sha256", - "tls_aes_128_ccm_8_sha256", - "tls_aes_128_ccm_sha256"}; + static constexpr const char* TLS13_CIPHERS[] = { + "tls_aes_256_gcm_sha384", + "tls_chacha20_poly1305_sha256", + "tls_aes_128_gcm_sha256", + "tls_aes_128_ccm_8_sha256", + "tls_aes_128_ccm_sha256"}; const int n = sk_SSL_CIPHER_num(ciphers); @@ -2249,8 +2254,7 @@ void SSLPointer::getCiphers( } for (unsigned i = 0; i < 5; ++i) { - const char* name = TLS13_CIPHERS[i]; - cb(name); + cb(TLS13_CIPHERS[i]); } } @@ -2265,7 +2269,6 @@ bool SSLPointer::setSniContext(const SSLCtxPointer& ctx) const { if (!x509) return false; EVP_PKEY* pkey = SSL_CTX_get0_privatekey(ctx.get()); STACK_OF(X509) * chain; - int err = SSL_CTX_get0_chain_certs(ctx.get(), &chain); if (err == 1) err = SSL_use_certificate(get(), x509); if (err == 1) err = SSL_use_PrivateKey(get(), pkey); @@ -2294,7 +2297,7 @@ std::optional SSLPointer::verifyPeerCertificate() const { } const std::string_view SSLPointer::getClientHelloAlpn() const { - if (ssl_ == nullptr) return std::string_view(); + if (ssl_ == nullptr) return {}; const unsigned char* buf; size_t len; size_t rem; @@ -2305,34 +2308,34 @@ const std::string_view SSLPointer::getClientHelloAlpn() const { &buf, &rem) || rem < 2) { - return nullptr; + return {}; } len = (buf[0] << 8) | buf[1]; - if (len + 2 != rem) return nullptr; + if (len + 2 != rem) return {}; return reinterpret_cast(buf + 3); } const std::string_view SSLPointer::getClientHelloServerName() const { - if (ssl_ == nullptr) return std::string_view(); + if (ssl_ == nullptr) return {}; const unsigned char* buf; size_t len; size_t rem; if (!SSL_client_hello_get0_ext(get(), TLSEXT_TYPE_server_name, &buf, &rem) || rem <= 2) { - return nullptr; + return {}; } len = (*buf << 8) | *(buf + 1); - if (len + 2 != rem) return nullptr; + if (len + 2 != rem) return {}; rem = len; - if (rem == 0 || *(buf + 2) != TLSEXT_NAMETYPE_host_name) return nullptr; + if (rem == 0 || *(buf + 2) != TLSEXT_NAMETYPE_host_name) return {}; rem--; - if (rem <= 2) return nullptr; + if (rem <= 2) return {}; len = (*(buf + 3) << 8) | *(buf + 4); - if (len + 2 > rem) return nullptr; + if (len + 2 > rem) return {}; return reinterpret_cast(buf + 5); } @@ -2453,7 +2456,7 @@ int Cipher::getNid() const { return EVP_CIPHER_nid(cipher_); } -const std::string_view Cipher::getModeLabel() const { +std::string_view Cipher::getModeLabel() const { if (!cipher_) return {}; switch (getMode()) { case EVP_CIPH_CCM_MODE: @@ -2482,7 +2485,7 @@ const std::string_view Cipher::getModeLabel() const { return "{unknown}"; } -const std::string_view Cipher::getName() const { +std::string_view Cipher::getName() const { if (!cipher_) return {}; // OBJ_nid2sn(EVP_CIPHER_nid(cipher)) is used here instead of // EVP_CIPHER_name(cipher) for compatibility with BoringSSL. @@ -2504,4 +2507,110 @@ bool Cipher::isSupportedAuthenticatedMode() const { } } +// ============================================================================ + +CipherCtxPointer CipherCtxPointer::New() { + auto ret = CipherCtxPointer(EVP_CIPHER_CTX_new()); + if (!ret) return {}; + EVP_CIPHER_CTX_init(ret.get()); + return ret; +} + +CipherCtxPointer::CipherCtxPointer(EVP_CIPHER_CTX* ctx) : ctx_(ctx) {} + +CipherCtxPointer::CipherCtxPointer(CipherCtxPointer&& other) noexcept + : ctx_(other.release()) {} + +CipherCtxPointer& CipherCtxPointer::operator=( + CipherCtxPointer&& other) noexcept { + if (this == &other) return *this; + this->~CipherCtxPointer(); + return *new (this) CipherCtxPointer(std::move(other)); +} + +CipherCtxPointer::~CipherCtxPointer() { + reset(); +} + +void CipherCtxPointer::reset(EVP_CIPHER_CTX* ctx) { + ctx_.reset(ctx); +} + +EVP_CIPHER_CTX* CipherCtxPointer::release() { + return ctx_.release(); +} + +void CipherCtxPointer::setFlags(int flags) { + if (!ctx_) return; + EVP_CIPHER_CTX_set_flags(ctx_.get(), flags); +} + +bool CipherCtxPointer::setKeyLength(size_t length) { + if (!ctx_) return false; + return EVP_CIPHER_CTX_set_key_length(ctx_.get(), length); +} + +bool CipherCtxPointer::setIvLength(size_t length) { + if (!ctx_) return false; + return EVP_CIPHER_CTX_ctrl( + ctx_.get(), EVP_CTRL_AEAD_SET_IVLEN, length, nullptr); +} + +bool CipherCtxPointer::setAeadTag(const Buffer& tag) { + if (!ctx_) return false; + return EVP_CIPHER_CTX_ctrl( + ctx_.get(), EVP_CTRL_AEAD_SET_TAG, tag.len, const_cast(tag.data)); +} + +bool CipherCtxPointer::setAeadTagLength(size_t length) { + if (!ctx_) return false; + return EVP_CIPHER_CTX_ctrl( + ctx_.get(), EVP_CTRL_AEAD_SET_TAG, length, nullptr); +} + +bool CipherCtxPointer::setPadding(bool padding) { + if (!ctx_) return false; + return EVP_CIPHER_CTX_set_padding(ctx_.get(), padding); +} + +int CipherCtxPointer::getBlockSize() const { + if (!ctx_) return 0; + return EVP_CIPHER_CTX_block_size(ctx_.get()); +} + +int CipherCtxPointer::getMode() const { + if (!ctx_) return 0; + return EVP_CIPHER_CTX_mode(ctx_.get()); +} + +int CipherCtxPointer::getNid() const { + if (!ctx_) return 0; + return EVP_CIPHER_CTX_nid(ctx_.get()); +} + +bool CipherCtxPointer::init(const Cipher& cipher, + bool encrypt, + const unsigned char* key, + const unsigned char* iv) { + if (!ctx_) return false; + return EVP_CipherInit_ex( + ctx_.get(), cipher, nullptr, key, iv, encrypt ? 1 : 0) == 1; +} + +bool CipherCtxPointer::update(const Buffer& in, + unsigned char* out, + int* out_len, + bool finalize) { + if (!ctx_) return false; + if (!finalize) { + return EVP_CipherUpdate(ctx_.get(), out, out_len, in.data, in.len) == 1; + } + return EVP_CipherFinal_ex(ctx_.get(), out, out_len) == 1; +} + +bool CipherCtxPointer::getAeadTag(size_t len, unsigned char* out) { + if (!ctx_) return false; + return EVP_CIPHER_CTX_ctrl(ctx_.get(), EVP_CTRL_AEAD_GET_TAG, len, out); +} + } // namespace ncrypto diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h index e12fa7f925c08f..c718ae404dd223 100644 --- a/deps/ncrypto/ncrypto.h +++ b/deps/ncrypto/ncrypto.h @@ -197,7 +197,6 @@ using DeleteFnPtr = typename FunctionDeleter::Pointer; using BignumCtxPointer = DeleteFnPtr; using BignumGenCallbackPointer = DeleteFnPtr; -using CipherCtxPointer = DeleteFnPtr; using DSAPointer = DeleteFnPtr; using DSASigPointer = DeleteFnPtr; using ECDSASigPointer = DeleteFnPtr; @@ -213,6 +212,8 @@ using PKCS8Pointer = DeleteFnPtr; using RSAPointer = DeleteFnPtr; using SSLSessionPointer = DeleteFnPtr; +class CipherCtxPointer; + struct StackOfXASN1Deleter { void operator()(STACK_OF(ASN1_OBJECT) * p) const { sk_ASN1_OBJECT_pop_free(p, ASN1_OBJECT_free); @@ -248,8 +249,8 @@ class Cipher final { int getIvLength() const; int getKeyLength() const; int getBlockSize() const; - const std::string_view getModeLabel() const; - const std::string_view getName() const; + std::string_view getModeLabel() const; + std::string_view getName() const; bool isSupportedAuthenticatedMode() const; @@ -425,6 +426,51 @@ class BignumPointer final { static bool defaultPrimeCheckCallback(int, int) { return 1; } }; +class CipherCtxPointer final { + public: + static CipherCtxPointer New(); + + CipherCtxPointer() = default; + explicit CipherCtxPointer(EVP_CIPHER_CTX* ctx); + CipherCtxPointer(CipherCtxPointer&& other) noexcept; + CipherCtxPointer& operator=(CipherCtxPointer&& other) noexcept; + NCRYPTO_DISALLOW_COPY(CipherCtxPointer) + ~CipherCtxPointer(); + + inline bool operator==(std::nullptr_t) const noexcept { + return ctx_ == nullptr; + } + inline operator bool() const { return ctx_ != nullptr; } + inline EVP_CIPHER_CTX* get() const { return ctx_.get(); } + inline operator EVP_CIPHER_CTX*() const { return ctx_.get(); } + void reset(EVP_CIPHER_CTX* ctx = nullptr); + EVP_CIPHER_CTX* release(); + + void setFlags(int flags); + bool setKeyLength(size_t length); + bool setIvLength(size_t length); + bool setAeadTag(const Buffer& tag); + bool setAeadTagLength(size_t length); + bool setPadding(bool padding); + bool init(const Cipher& cipher, + bool encrypt, + const unsigned char* key = nullptr, + const unsigned char* iv = nullptr); + + int getBlockSize() const; + int getMode() const; + int getNid() const; + + bool update(const Buffer& in, + unsigned char* out, + int* out_len, + bool finalize = false); + bool getAeadTag(size_t len, unsigned char* out); + + private: + DeleteFnPtr ctx_; +}; + class EVPKeyPointer final { public: static EVPKeyPointer New(); @@ -772,7 +818,7 @@ class X509Pointer final { operator X509View() const { return view(); } static std::string_view ErrorCode(int32_t err); - static std::string_view ErrorReason(int32_t err); + static std::optional ErrorReason(int32_t err); private: DeleteFnPtr cert_; diff --git a/src/crypto/crypto_aes.cc b/src/crypto/crypto_aes.cc index 5fc63c9beb48c3..698f3574e47c3b 100644 --- a/src/crypto/crypto_aes.cc +++ b/src/crypto/crypto_aes.cc @@ -42,41 +42,28 @@ WebCryptoCipherStatus AES_Cipher(Environment* env, const int mode = params.cipher.getMode(); - CipherCtxPointer ctx(EVP_CIPHER_CTX_new()); - EVP_CIPHER_CTX_init(ctx.get()); - if (mode == EVP_CIPH_WRAP_MODE) - EVP_CIPHER_CTX_set_flags(ctx.get(), EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); + auto ctx = CipherCtxPointer::New(); + if (mode == EVP_CIPH_WRAP_MODE) { + ctx.setFlags(EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); + } const bool encrypt = cipher_mode == kWebCryptoCipherEncrypt; - if (!EVP_CipherInit_ex( - ctx.get(), - params.cipher, - nullptr, - nullptr, - nullptr, - encrypt)) { + if (!ctx.init(params.cipher, encrypt)) { // Cipher init failed return WebCryptoCipherStatus::FAILED; } - if (mode == EVP_CIPH_GCM_MODE && !EVP_CIPHER_CTX_ctrl( - ctx.get(), - EVP_CTRL_AEAD_SET_IVLEN, - params.iv.size(), - nullptr)) { + if (mode == EVP_CIPH_GCM_MODE && !ctx.setIvLength(params.iv.size())) { return WebCryptoCipherStatus::FAILED; } - if (!EVP_CIPHER_CTX_set_key_length(ctx.get(), - key_data.GetSymmetricKeySize()) || - !EVP_CipherInit_ex( - ctx.get(), - nullptr, - nullptr, + if (!ctx.setKeyLength(key_data.GetSymmetricKeySize()) || + !ctx.init( + ncrypto::Cipher(), + encrypt, reinterpret_cast(key_data.GetSymmetricKey()), - params.iv.data(), - encrypt)) { + params.iv.data())) { return WebCryptoCipherStatus::FAILED; } @@ -84,17 +71,19 @@ WebCryptoCipherStatus AES_Cipher(Environment* env, if (mode == EVP_CIPH_GCM_MODE) { switch (cipher_mode) { - case kWebCryptoCipherDecrypt: + case kWebCryptoCipherDecrypt: { // If in decrypt mode, the auth tag must be set in the params.tag. CHECK(params.tag); - if (!EVP_CIPHER_CTX_ctrl(ctx.get(), - EVP_CTRL_AEAD_SET_TAG, - params.tag.size(), - const_cast(params.tag.data()))) { + ncrypto::Buffer buffer = { + .data = params.tag.data(), + .len = params.tag.size(), + }; + if (!ctx.setAeadTag(buffer)) { return WebCryptoCipherStatus::FAILED; } break; - case kWebCryptoCipherEncrypt: + } + case kWebCryptoCipherEncrypt: { // In decrypt mode, we grab the tag length here. We'll use it to // ensure that that allocated buffer has enough room for both the // final block and the auth tag. Unlike our other AES-GCM implementation @@ -102,23 +91,22 @@ WebCryptoCipherStatus AES_Cipher(Environment* env, // of the generated ciphertext and returned in the same ArrayBuffer. tag_len = params.length; break; + } default: UNREACHABLE(); } } size_t total = 0; - int buf_len = in.size() + EVP_CIPHER_CTX_block_size(ctx.get()) + tag_len; + int buf_len = in.size() + ctx.getBlockSize() + tag_len; int out_len; - if (mode == EVP_CIPH_GCM_MODE && - params.additional_data.size() && - !EVP_CipherUpdate( - ctx.get(), - nullptr, - &out_len, - params.additional_data.data(), - params.additional_data.size())) { + ncrypto::Buffer buffer = { + .data = params.additional_data.data(), + .len = params.additional_data.size(), + }; + if (mode == EVP_CIPH_GCM_MODE && params.additional_data.size() && + !ctx.update(buffer, nullptr, &out_len)) { return WebCryptoCipherStatus::FAILED; } @@ -132,21 +120,20 @@ WebCryptoCipherStatus AES_Cipher(Environment* env, // // Refs: https://github.com/openssl/openssl/commit/420cb707b880e4fb649094241371701013eeb15f // Refs: https://github.com/nodejs/node/pull/38913#issuecomment-866505244 + buffer = { + .data = in.data(), + .len = in.size(), + }; if (in.empty()) { out_len = 0; - } else if (!EVP_CipherUpdate(ctx.get(), - buf.data(), - &out_len, - in.data(), - in.size())) { + } else if (!ctx.update(buffer, buf.data(), &out_len)) { return WebCryptoCipherStatus::FAILED; } total += out_len; CHECK_LE(out_len, buf_len); - out_len = EVP_CIPHER_CTX_block_size(ctx.get()); - if (!EVP_CipherFinal_ex( - ctx.get(), buf.data() + total, &out_len)) { + out_len = ctx.getBlockSize(); + if (!ctx.update({}, buf.data() + total, &out_len, true)) { return WebCryptoCipherStatus::FAILED; } total += out_len; @@ -154,11 +141,9 @@ WebCryptoCipherStatus AES_Cipher(Environment* env, // If using AES_GCM, grab the generated auth tag and append // it to the end of the ciphertext. if (cipher_mode == kWebCryptoCipherEncrypt && mode == EVP_CIPH_GCM_MODE) { - if (!EVP_CIPHER_CTX_ctrl(ctx.get(), - EVP_CTRL_AEAD_GET_TAG, - tag_len, - buf.data() + total)) + if (!ctx.getAeadTag(tag_len, buf.data() + total)) { return WebCryptoCipherStatus::FAILED; + } total += tag_len; } @@ -221,33 +206,34 @@ WebCryptoCipherStatus AES_CTR_Cipher2(const KeyObjectData& key_data, const ByteSource& in, unsigned const char* counter, unsigned char* out) { - CipherCtxPointer ctx(EVP_CIPHER_CTX_new()); + auto ctx = CipherCtxPointer::New(); + if (!ctx) { + return WebCryptoCipherStatus::FAILED; + } const bool encrypt = cipher_mode == kWebCryptoCipherEncrypt; - if (!EVP_CipherInit_ex( - ctx.get(), + if (!ctx.init( params.cipher, - nullptr, + encrypt, reinterpret_cast(key_data.GetSymmetricKey()), - counter, - encrypt)) { + counter)) { // Cipher init failed return WebCryptoCipherStatus::FAILED; } int out_len = 0; int final_len = 0; - if (!EVP_CipherUpdate( - ctx.get(), - out, - &out_len, - in.data(), - in.size())) { + ncrypto::Buffer buffer = { + .data = in.data(), + .len = in.size(), + }; + if (!ctx.update(buffer, out, &out_len)) { return WebCryptoCipherStatus::FAILED; } - if (!EVP_CipherFinal_ex(ctx.get(), out + out_len, &final_len)) + if (!ctx.update({}, out + out_len, &final_len, true)) { return WebCryptoCipherStatus::FAILED; + } out_len += final_len; if (static_cast(out_len) != in.size()) diff --git a/src/crypto/crypto_cipher.cc b/src/crypto/crypto_cipher.cc index 27294625037dba..8d8914058fc06c 100644 --- a/src/crypto/crypto_cipher.cc +++ b/src/crypto/crypto_cipher.cc @@ -66,14 +66,16 @@ void GetCipherInfo(const FunctionCallbackInfo& args) { // Test and input IV or key length to determine if it's acceptable. // If it is, then the getCipherInfo will succeed with the given // values. - CipherCtxPointer ctx(EVP_CIPHER_CTX_new()); - if (!EVP_CipherInit_ex(ctx.get(), cipher, nullptr, nullptr, nullptr, 1)) + auto ctx = CipherCtxPointer::New(); + if (!ctx.init(cipher, true)) { return; + } if (args[2]->IsInt32()) { int check_len = args[2].As()->Value(); - if (!EVP_CIPHER_CTX_set_key_length(ctx.get(), check_len)) + if (!ctx.setKeyLength(check_len)) { return; + } key_length = check_len; } @@ -91,11 +93,7 @@ void GetCipherInfo(const FunctionCallbackInfo& args) { case EVP_CIPH_GCM_MODE: // Fall through case EVP_CIPH_OCB_MODE: - if (!EVP_CIPHER_CTX_ctrl( - ctx.get(), - EVP_CTRL_AEAD_SET_IVLEN, - check_len, - nullptr)) { + if (!ctx.setIvLength(check_len)) { return; } break; @@ -312,15 +310,15 @@ void CipherBase::CommonInit(const char* cipher_type, int iv_len, unsigned int auth_tag_len) { CHECK(!ctx_); - ctx_.reset(EVP_CIPHER_CTX_new()); + ctx_ = CipherCtxPointer::New(); + CHECK(ctx_); if (cipher.getMode() == EVP_CIPH_WRAP_MODE) { - EVP_CIPHER_CTX_set_flags(ctx_.get(), EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); + ctx_.setFlags(EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); } const bool encrypt = (kind_ == kCipher); - if (1 != EVP_CipherInit_ex(ctx_.get(), cipher, nullptr, - nullptr, nullptr, encrypt)) { + if (!ctx_.init(cipher, encrypt)) { return ThrowCryptoError(env(), ERR_get_error(), "Failed to initialize cipher"); } @@ -331,12 +329,12 @@ void CipherBase::CommonInit(const char* cipher_type, return; } - if (!EVP_CIPHER_CTX_set_key_length(ctx_.get(), key_len)) { + if (!ctx_.setKeyLength(key_len)) { ctx_.reset(); return THROW_ERR_CRYPTO_INVALID_KEYLEN(env()); } - if (1 != EVP_CipherInit_ex(ctx_.get(), nullptr, nullptr, key, iv, encrypt)) { + if (!ctx_.init(ncrypto::Cipher(), encrypt, key, iv)) { return ThrowCryptoError(env(), ERR_get_error(), "Failed to initialize cipher"); } @@ -498,15 +496,12 @@ bool CipherBase::InitAuthenticated( CHECK(IsAuthenticatedMode()); MarkPopErrorOnReturn mark_pop_error_on_return; - if (!EVP_CIPHER_CTX_ctrl(ctx_.get(), - EVP_CTRL_AEAD_SET_IVLEN, - iv_len, - nullptr)) { + if (!ctx_.setIvLength(iv_len)) { THROW_ERR_CRYPTO_INVALID_IV(env()); return false; } - const int mode = EVP_CIPHER_CTX_mode(ctx_.get()); + const int mode = ctx_.getMode(); if (mode == EVP_CIPH_GCM_MODE) { if (auth_tag_len != kNoAuthTagLength) { if (!IsValidGCMTagLength(auth_tag_len)) { @@ -526,7 +521,7 @@ bool CipherBase::InitAuthenticated( // length defaults to 16 bytes when encrypting. Unlike GCM, the // authentication tag length also defaults to 16 bytes when decrypting, // whereas GCM would accept any valid authentication tag length. - if (EVP_CIPHER_CTX_nid(ctx_.get()) == NID_chacha20_poly1305) { + if (ctx_.getNid() == NID_chacha20_poly1305) { auth_tag_len = 16; } else { THROW_ERR_CRYPTO_INVALID_AUTH_TAG( @@ -549,8 +544,7 @@ bool CipherBase::InitAuthenticated( } // Tell OpenSSL about the desired length. - if (!EVP_CIPHER_CTX_ctrl(ctx_.get(), EVP_CTRL_AEAD_SET_TAG, auth_tag_len, - nullptr)) { + if (!ctx_.setAeadTagLength(auth_tag_len)) { THROW_ERR_CRYPTO_INVALID_AUTH_TAG( env(), "Invalid authentication tag length: %u", auth_tag_len); return false; @@ -573,7 +567,7 @@ bool CipherBase::InitAuthenticated( bool CipherBase::CheckCCMMessageLength(int message_len) { CHECK(ctx_); - CHECK(EVP_CIPHER_CTX_mode(ctx_.get()) == EVP_CIPH_CCM_MODE); + CHECK(ctx_.getMode() == EVP_CIPH_CCM_MODE); if (message_len > max_message_size_) { THROW_ERR_CRYPTO_INVALID_MESSAGELEN(env()); @@ -624,7 +618,7 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo& args) { } unsigned int tag_len = auth_tag.size(); - const int mode = EVP_CIPHER_CTX_mode(cipher->ctx_.get()); + const int mode = cipher->ctx_.getMode(); bool is_valid; if (mode == EVP_CIPH_GCM_MODE) { // Restrict GCM tag lengths according to NIST 800-38d, page 9. @@ -669,10 +663,11 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo& args) { bool CipherBase::MaybePassAuthTagToOpenSSL() { if (auth_tag_state_ == kAuthTagKnown) { - if (!EVP_CIPHER_CTX_ctrl(ctx_.get(), - EVP_CTRL_AEAD_SET_TAG, - auth_tag_len_, - reinterpret_cast(auth_tag_))) { + ncrypto::Buffer buffer{ + .data = auth_tag_, + .len = auth_tag_len_, + }; + if (!ctx_.setAeadTag(buffer)) { return false; } auth_tag_state_ = kAuthTagPassedToOpenSSL; @@ -688,7 +683,7 @@ bool CipherBase::SetAAD( MarkPopErrorOnReturn mark_pop_error_on_return; int outlen; - const int mode = EVP_CIPHER_CTX_mode(ctx_.get()); + const int mode = ctx_.getMode(); // When in CCM mode, we need to set the authentication tag and the plaintext // length in advance. @@ -707,16 +702,21 @@ bool CipherBase::SetAAD( return false; } + ncrypto::Buffer buffer{ + .data = nullptr, + .len = static_cast(plaintext_len), + }; // Specify the plaintext length. - if (!EVP_CipherUpdate(ctx_.get(), nullptr, &outlen, nullptr, plaintext_len)) + if (!ctx_.update(buffer, nullptr, &outlen)) { return false; + } } - return 1 == EVP_CipherUpdate(ctx_.get(), - nullptr, - &outlen, - data.data(), - data.size()); + ncrypto::Buffer buffer{ + .data = data.data(), + .len = data.size(), + }; + return ctx_.update(buffer, nullptr, &outlen); } void CipherBase::SetAAD(const FunctionCallbackInfo& args) { @@ -743,7 +743,7 @@ CipherBase::UpdateResult CipherBase::Update( return kErrorState; MarkPopErrorOnReturn mark_pop_error_on_return; - const int mode = EVP_CIPHER_CTX_mode(ctx_.get()); + const int mode = ctx_.getMode(); if (mode == EVP_CIPH_CCM_MODE && !CheckCCMMessageLength(len)) return kErrorMessageSize; @@ -753,19 +753,17 @@ CipherBase::UpdateResult CipherBase::Update( if (kind_ == kDecipher && IsAuthenticatedMode()) CHECK(MaybePassAuthTagToOpenSSL()); - const int block_size = EVP_CIPHER_CTX_block_size(ctx_.get()); + const int block_size = ctx_.getBlockSize(); CHECK_GT(block_size, 0); if (len + block_size > INT_MAX) return kErrorState; int buf_len = len + block_size; - // For key wrapping algorithms, get output size by calling - // EVP_CipherUpdate() with null output. + ncrypto::Buffer buffer = { + .data = reinterpret_cast(data), + .len = len, + }; if (kind_ == kCipher && mode == EVP_CIPH_WRAP_MODE && - EVP_CipherUpdate(ctx_.get(), - nullptr, - &buf_len, - reinterpret_cast(data), - len) != 1) { + !ctx_.update(buffer, nullptr, &buf_len)) { return kErrorState; } @@ -774,11 +772,13 @@ CipherBase::UpdateResult CipherBase::Update( *out = ArrayBuffer::NewBackingStore(env()->isolate(), buf_len); } - int r = EVP_CipherUpdate(ctx_.get(), - static_cast((*out)->Data()), - &buf_len, - reinterpret_cast(data), - len); + buffer = { + .data = reinterpret_cast(data), + .len = len, + }; + + bool r = ctx_.update( + buffer, static_cast((*out)->Data()), &buf_len); CHECK_LE(static_cast(buf_len), (*out)->ByteLength()); if (buf_len == 0) { @@ -830,7 +830,7 @@ bool CipherBase::SetAutoPadding(bool auto_padding) { if (!ctx_) return false; MarkPopErrorOnReturn mark_pop_error_on_return; - return EVP_CIPHER_CTX_set_padding(ctx_.get(), auto_padding); + return ctx_.setPadding(auto_padding); } void CipherBase::SetAutoPadding(const FunctionCallbackInfo& args) { @@ -845,12 +845,12 @@ bool CipherBase::Final(std::unique_ptr* out) { if (!ctx_) return false; - const int mode = EVP_CIPHER_CTX_mode(ctx_.get()); + const int mode = ctx_.getMode(); { NoArrayBufferZeroFillScope no_zero_fill_scope(env()->isolate_data()); - *out = ArrayBuffer::NewBackingStore(env()->isolate(), - static_cast(EVP_CIPHER_CTX_block_size(ctx_.get()))); + *out = ArrayBuffer::NewBackingStore( + env()->isolate(), static_cast(ctx_.getBlockSize())); } if (kind_ == kDecipher && @@ -861,7 +861,7 @@ bool CipherBase::Final(std::unique_ptr* out) { // OpenSSL v1.x doesn't verify the presence of the auth tag so do // it ourselves, see https://github.com/nodejs/node/issues/45874. if (OPENSSL_VERSION_NUMBER < 0x30000000L && kind_ == kDecipher && - NID_chacha20_poly1305 == EVP_CIPHER_CTX_nid(ctx_.get()) && + NID_chacha20_poly1305 == ctx_.getNid() && auth_tag_state_ != kAuthTagPassedToOpenSSL) { return false; } @@ -874,9 +874,8 @@ bool CipherBase::Final(std::unique_ptr* out) { *out = ArrayBuffer::NewBackingStore(env()->isolate(), 0); } else { int out_len = (*out)->ByteLength(); - ok = EVP_CipherFinal_ex(ctx_.get(), - static_cast((*out)->Data()), - &out_len) == 1; + ok = ctx_.update( + {}, static_cast((*out)->Data()), &out_len, true); CHECK_LE(static_cast(out_len), (*out)->ByteLength()); if (out_len == 0) { @@ -897,9 +896,8 @@ bool CipherBase::Final(std::unique_ptr* out) { CHECK(mode == EVP_CIPH_GCM_MODE); auth_tag_len_ = sizeof(auth_tag_); } - ok = (1 == EVP_CIPHER_CTX_ctrl(ctx_.get(), EVP_CTRL_AEAD_GET_TAG, - auth_tag_len_, - reinterpret_cast(auth_tag_))); + ok = ctx_.getAeadTag(auth_tag_len_, + reinterpret_cast(auth_tag_)); } } diff --git a/src/crypto/crypto_common.cc b/src/crypto/crypto_common.cc index b03b592a88f627..8ea34fe78b2592 100644 --- a/src/crypto/crypto_common.cc +++ b/src/crypto/crypto_common.cc @@ -62,7 +62,7 @@ bool SetGroups(SecureContext* sc, const char* groups) { } MaybeLocal GetValidationErrorReason(Environment* env, int err) { - auto reason = X509Pointer::ErrorReason(err); + auto reason = X509Pointer::ErrorReason(err).value_or(""); if (reason == "") return Undefined(env->isolate()); return OneByteString(env->isolate(), reason.data(), reason.length()); } diff --git a/src/crypto/crypto_random.cc b/src/crypto/crypto_random.cc index beb1e0730736da..a6a206455b52c3 100644 --- a/src/crypto/crypto_random.cc +++ b/src/crypto/crypto_random.cc @@ -27,13 +27,12 @@ using v8::Value; namespace crypto { namespace { -ncrypto::BignumPointer::PrimeCheckCallback getPrimeCheckCallback(Environment* env) { +ncrypto::BignumPointer::PrimeCheckCallback getPrimeCheckCallback( + Environment* env) { // The callback is used to check if the operation should be stopped. // Currently, the only check we perform is if env->is_stopping() // is true. - return [env](int a, int b) -> bool { - return !env->is_stopping(); - }; + return [env](int a, int b) -> bool { return !env->is_stopping(); }; } } // namespace @@ -155,13 +154,14 @@ Maybe RandomPrimeTraits::AdditionalConfig( bool RandomPrimeTraits::DeriveBits(Environment* env, const RandomPrimeConfig& params, ByteSource* unused) { - auto cb = getPrimeCheckCallback(env); - return params.prime.generate(BignumPointer::PrimeConfig { - .bits = params.bits, - .safe = params.safe, - .add = params.add, - .rem = params.rem, - }, std::move(cb)); + return params.prime.generate( + BignumPointer::PrimeConfig{ + .bits = params.bits, + .safe = params.safe, + .add = params.add, + .rem = params.rem, + }, + getPrimeCheckCallback(env)); } void CheckPrimeConfig::MemoryInfo(MemoryTracker* tracker) const { @@ -188,8 +188,7 @@ bool CheckPrimeTraits::DeriveBits( Environment* env, const CheckPrimeConfig& params, ByteSource* out) { - auto cb = getPrimeCheckCallback(env); - int ret = params.candidate.isPrime(params.checks, std::move(cb)); + int ret = params.candidate.isPrime(params.checks, getPrimeCheckCallback(env)); if (ret < 0) return false; ByteSource::Builder buf(1); buf.data()[0] = ret;