diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index ac2d771555126a..ebdda72184b709 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -311,6 +311,87 @@ 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(), + // 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)); + 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; +} + +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 {}; + } + return res; +} + +BignumPointer BignumPointer::NewLShift(size_t length) { + BignumPointer res = New(); + if (!res) return {}; + if (!BN_lshift(res.get(), One(), length)) { + return {}; + } + return res; +} + // ============================================================================ // Utility methods @@ -1005,6 +1086,29 @@ X509View X509View::From(const SSLCtxPointer& ctx) { return X509View(SSL_CTX_get0_certificate(ctx.get())); } +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; + 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::nullopt; +} + X509Pointer X509View::clone() const { ClearErrorOnReturn clear_error_on_return; if (!cert_) return {}; @@ -1050,6 +1154,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::optional X509Pointer::ErrorReason(int32_t err) { + if (err == X509_V_OK) return std::nullopt; + return X509_verify_cert_error_string(err); +} + // ============================================================================ // BIOPointer @@ -1105,6 +1256,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()); @@ -2044,4 +2201,416 @@ 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 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); + + 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) { + cb(TLS13_CIPHERS[i]); + } +} + +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 {}; + 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 {}; + } + + len = (buf[0] << 8) | buf[1]; + if (len + 2 != rem) return {}; + return reinterpret_cast(buf + 3); +} + +const std::string_view SSLPointer::getClientHelloServerName() const { + 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 {}; + } + + len = (*buf << 8) | *(buf + 1); + if (len + 2 != rem) return {}; + rem = len; + + if (rem == 0 || *(buf + 2) != TLSEXT_NAMETYPE_host_name) return {}; + rem--; + if (rem <= 2) return {}; + len = (*(buf + 3) << 8) | *(buf + 4); + if (len + 2 > rem) return {}; + 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); +} + +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; +} + +// ============================================================================ + +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_); +} + +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}"; +} + +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; + } +} + +// ============================================================================ + +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 fffa75ec718fac..c718ae404dd223 100644 --- a/deps/ncrypto/ncrypto.h +++ b/deps/ncrypto/ncrypto.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -195,7 +196,7 @@ template using DeleteFnPtr = typename FunctionDeleter::Pointer; using BignumCtxPointer = DeleteFnPtr; -using CipherCtxPointer = DeleteFnPtr; +using BignumGenCallbackPointer = DeleteFnPtr; using DSAPointer = DeleteFnPtr; using DSASigPointer = DeleteFnPtr; using ECDSASigPointer = DeleteFnPtr; @@ -209,10 +210,10 @@ using HMACCtxPointer = DeleteFnPtr; using NetscapeSPKIPointer = DeleteFnPtr; using PKCS8Pointer = DeleteFnPtr; using RSAPointer = DeleteFnPtr; -using SSLCtxPointer = DeleteFnPtr; -using SSLPointer = 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); @@ -227,6 +228,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; + std::string_view getModeLabel() 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 { @@ -272,6 +307,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); @@ -350,8 +386,28 @@ 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 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, @@ -366,6 +422,53 @@ class BignumPointer final { private: DeleteFnPtr bn_; + + 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 { @@ -551,7 +654,85 @@ 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; + +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; + 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: @@ -565,6 +746,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; } @@ -589,6 +772,8 @@ class X509View final { bool checkPrivateKey(const EVPKeyPointer& pkey) const; bool checkPublicKey(const EVPKeyPointer& pkey) const; + std::optional getFingerprint(const EVP_MD* method) const; + X509Pointer clone() const; enum class CheckMatch { @@ -624,12 +809,17 @@ 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(); X509View view() const; operator X509View() const { return view(); } + static std::string_view ErrorCode(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 d430648aebc540..698f3574e47c3b 100644 --- a/src/crypto/crypto_aes.cc +++ b/src/crypto/crypto_aes.cc @@ -40,43 +40,30 @@ 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()); - 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()) @@ -262,9 +248,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 +262,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; } @@ -480,13 +464,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(); @@ -505,8 +489,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 21e34333609880..8d8914058fc06c 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 @@ -99,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; } @@ -116,7 +85,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; @@ -124,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; @@ -140,38 +105,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; } @@ -198,42 +160,20 @@ 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"); } - 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())); } @@ -363,38 +303,38 @@ 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, int iv_len, unsigned int auth_tag_len) { CHECK(!ctx_); - ctx_.reset(EVP_CIPHER_CTX_new()); + ctx_ = CipherCtxPointer::New(); + CHECK(ctx_); - const int mode = EVP_CIPHER_mode(cipher); - if (mode == EVP_CIPH_WRAP_MODE) - EVP_CIPHER_CTX_set_flags(ctx_.get(), EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); + if (cipher.getMode() == EVP_CIPH_WRAP_MODE) { + 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"); } - if (IsSupportedAuthenticatedMode(cipher)) { + if (cipher.isSupportedAuthenticatedMode()) { CHECK_GE(iv_len, 0); if (!InitAuthenticated(cipher_type, iv_len, auth_tag_len)) 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"); } @@ -405,9 +345,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]; @@ -422,7 +363,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)) { @@ -433,8 +374,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) { @@ -469,12 +415,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 @@ -484,13 +428,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: @@ -553,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)) { @@ -581,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( @@ -604,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; @@ -628,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()); @@ -641,7 +580,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) { @@ -679,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. @@ -689,7 +628,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; } @@ -723,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; @@ -742,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. @@ -761,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) { @@ -797,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; @@ -807,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; } @@ -828,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) { @@ -884,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) { @@ -899,21 +845,23 @@ 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 && 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. 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; } @@ -926,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) { @@ -949,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_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 43a126f863779d..8ea34fe78b2592 100644 --- a/src/crypto/crypto_common.cc +++ b/src/crypto/crypto_common.cc @@ -27,7 +27,7 @@ namespace node { -using v8::Array; +using ncrypto::StackOfX509; using v8::ArrayBuffer; using v8::BackingStore; using v8::Context; @@ -41,54 +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, - 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) { - 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)); @@ -97,153 +49,36 @@ 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.get()); - 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) { - 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; + return sc->ctx().setGroups(groups); } 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).value_or(""); + 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) { - 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 { @@ -366,57 +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) - return MaybeLocal(); + if (ssl.getCipher() == nullptr) return MaybeLocal(); EscapableHandleScope scope(env->isolate()); Local info = Object::New(env->isolate()); @@ -439,15 +239,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 284aadd6cc2cc3..bce577ade78b58 100644 --- a/src/crypto/crypto_common.h +++ b/src/crypto/crypto_common.h @@ -22,26 +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; - -void LogSecret( - const SSLPointer& ssl, - const char* name, - const unsigned char* secret, - size_t secretlen); - -v8::MaybeLocal GetSSLOCSPResponse( - Environment* env, - SSL* ssl, - v8::Local default_value); - -bool SetTLSSession( - const SSLPointer& ssl, - const SSLSessionPointer& session); - SSLSessionPointer GetTLSSession(const unsigned char* buf, size_t length); long VerifyPeerCertificate( // NOLINT(runtime/int) @@ -50,20 +30,8 @@ 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); -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_context.cc b/src/crypto/crypto_context.cc index aa5fc61f19e435..fa96b8d7a51d46 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; @@ -550,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"); } @@ -786,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_random.cc b/src/crypto/crypto_random.cc index 2c90a796e1195b..a6a206455b52c3 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 { @@ -29,21 +27,12 @@ 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 +154,14 @@ 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; + 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 { @@ -207,12 +188,7 @@ 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()); + int ret = params.candidate.isPrime(params.checks, getPrimeCheckCallback(env)); if (ret < 0) return false; ByteSource::Builder buf(1); buf.data()[0] = ret; diff --git a/src/crypto/crypto_tls.cc b/src/crypto/crypto_tls.cc index 9c7ce849521499..f0bb4786abdc71 100644 --- a/src/crypto/crypto_tls.cc +++ b/src/crypto/crypto_tls.cc @@ -217,10 +217,12 @@ int SSLCertCallback(SSL* s, void* arg) { Local info = Object::New(env->isolate()); - const char* servername = GetServerName(s); - Local servername_str = (servername == nullptr) - ? String::Empty(env->isolate()) - : OneByteString(env->isolate(), servername, strlen(servername)); + 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 ocsp = Boolean::New( env->isolate(), SSL_get_tlsext_status_type(s) == TLSEXT_STATUSTYPE_ocsp); @@ -303,6 +305,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 +327,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 @@ -343,8 +359,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( @@ -362,6 +377,19 @@ 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( @@ -372,6 +400,7 @@ std::string GetBIOError() { static_cast(&ret)); return ret; } + } // namespace TLSWrap::TLSWrap(Environment* env, @@ -1330,9 +1359,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); } @@ -1361,8 +1392,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()) @@ -1803,7 +1835,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"); } @@ -1830,15 +1862,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); } 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; diff --git a/src/crypto/crypto_x509.cc b/src/crypto/crypto_x509.cc index 9b9bb7be9a8dac..a19b1d07e02609 100644 --- a/src/crypto/crypto_x509.cc +++ b/src/crypto/crypto_x509.cc @@ -60,34 +60,17 @@ 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); + auto fingerprint = cert.getFingerprint(method); + // Returning an empty string indicates that the digest failed for + // some reason. + if (!fingerprint.has_value()) [[unlikely]] { + return Undefined(env->isolate()); } - return Undefined(env->isolate()); + auto& fp = fingerprint.value(); + return OneByteString(env->isolate(), fp.data(), fp.length()); } template @@ -695,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); } diff --git a/src/quic/tlscontext.cc b/src/quic/tlscontext.cc index fda49710e85938..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( @@ -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 {