| #include "crypto/crypto_common.h" |
| #include "base_object-inl.h" |
| #include "crypto/crypto_util.h" |
| #include "crypto/crypto_x509.h" |
| #include "env-inl.h" |
| #include "memory_tracker-inl.h" |
| #include "nbytes.h" |
| #include "ncrypto.h" |
| #include "node.h" |
| #include "node_buffer.h" |
| #include "node_crypto.h" |
| #include "node_internals.h" |
| #include "string_bytes.h" |
| #include "v8.h" |
| |
| #include <openssl/ec.h> |
| #include <openssl/ecdh.h> |
| #include <openssl/evp.h> |
| #include <openssl/pem.h> |
| #include <openssl/x509v3.h> |
| #include <openssl/hmac.h> |
| #include <openssl/rand.h> |
| #include <openssl/pkcs12.h> |
| |
| #include <string> |
| #include <unordered_map> |
| |
| namespace node { |
| |
| using v8::Array; |
| using v8::ArrayBuffer; |
| using v8::BackingStore; |
| using v8::Context; |
| using v8::EscapableHandleScope; |
| using v8::Integer; |
| using v8::Local; |
| using v8::MaybeLocal; |
| using v8::Object; |
| using v8::String; |
| 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<const char*>(crandom), |
| kTlsClientRandomSize); |
| line += |
| " " + nbytes::HexEncode(reinterpret_cast<const char*>(secret), secretlen); |
| keylog_cb(ssl.get(), line.c_str()); |
| } |
| |
| MaybeLocal<Value> GetSSLOCSPResponse( |
| Environment* env, |
| SSL* ssl, |
| Local<Value> default_value) { |
| const unsigned char* resp; |
| int len = SSL_get_tlsext_status_ocsp_resp(ssl, &resp); |
| if (resp == nullptr) |
| return default_value; |
| |
| Local<Value> ret; |
| MaybeLocal<Object> maybe_buffer = |
| Buffer::Copy(env, reinterpret_cast<const char*>(resp), len); |
| |
| if (!maybe_buffer.ToLocal(&ret)) |
| return MaybeLocal<Value>(); |
| |
| 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)); |
| } |
| |
| 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; |
| } |
| |
| bool UseSNIContext( |
| const SSLPointer& ssl, BaseObjectPtr<SecureContext> 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<const char*>(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<const char*>(buf + 5); |
| } |
| |
| const char* GetServerName(SSL* ssl) { |
| return SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); |
| } |
| |
| 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<Value> 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); |
| } |
| |
| MaybeLocal<Value> GetValidationErrorCode(Environment* env, int err) { |
| if (err == 0) |
| return Undefined(env->isolate()); |
| return OneByteString(env->isolate(), X509ErrorCode(err)); |
| } |
| |
| MaybeLocal<Value> 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); |
| } |
| |
| namespace { |
| template <typename T> |
| bool Set( |
| Local<Context> context, |
| Local<Object> target, |
| Local<Value> name, |
| MaybeLocal<T> maybe_value) { |
| Local<Value> value; |
| if (!maybe_value.ToLocal(&value)) |
| return false; |
| |
| // Undefined is ignored, but still considered successful |
| if (value->IsUndefined()) |
| return true; |
| |
| return !target->Set(context, name, value).IsNothing(); |
| } |
| |
| template <const char* (*getstr)(const SSL_CIPHER* cipher)> |
| MaybeLocal<Value> GetCipherValue(Environment* env, const SSL_CIPHER* cipher) { |
| if (cipher == nullptr) |
| return Undefined(env->isolate()); |
| |
| return OneByteString(env->isolate(), getstr(cipher)); |
| } |
| |
| constexpr auto GetCipherName = GetCipherValue<SSL_CIPHER_get_name>; |
| constexpr auto GetCipherStandardName = GetCipherValue<SSL_CIPHER_standard_name>; |
| constexpr auto GetCipherVersion = GetCipherValue<SSL_CIPHER_get_version>; |
| |
| StackOfX509 CloneSSLCerts(X509Pointer&& cert, |
| const STACK_OF(X509)* const ssl_certs) { |
| StackOfX509 peer_certs(sk_X509_new(nullptr)); |
| if (!peer_certs) return StackOfX509(); |
| if (cert && !sk_X509_push(peer_certs.get(), cert.release())) |
| return StackOfX509(); |
| for (int i = 0; i < sk_X509_num(ssl_certs); i++) { |
| X509Pointer cert(X509_dup(sk_X509_value(ssl_certs, i))); |
| if (!cert || !sk_X509_push(peer_certs.get(), cert.get())) |
| return StackOfX509(); |
| // `cert` is now managed by the stack. |
| cert.release(); |
| } |
| return peer_certs; |
| } |
| |
| MaybeLocal<Object> AddIssuerChainToObject(X509Pointer* cert, |
| Local<Object> object, |
| StackOfX509&& peer_certs, |
| Environment* const env) { |
| cert->reset(sk_X509_delete(peer_certs.get(), 0)); |
| for (;;) { |
| int i; |
| for (i = 0; i < sk_X509_num(peer_certs.get()); i++) { |
| ncrypto::X509View ca(sk_X509_value(peer_certs.get(), i)); |
| if (!cert->view().isIssuedBy(ca)) continue; |
| |
| Local<Value> ca_info; |
| if (!X509Certificate::toObject(env, ca).ToLocal(&ca_info)) return {}; |
| CHECK(ca_info->IsObject()); |
| |
| if (!Set<Object>(env->context(), |
| object, |
| env->issuercert_string(), |
| ca_info.As<Object>())) { |
| return {}; |
| } |
| object = ca_info.As<Object>(); |
| |
| // NOTE: Intentionally freeing cert that is not used anymore. |
| // Delete cert and continue aggregating issuers. |
| cert->reset(sk_X509_delete(peer_certs.get(), i)); |
| break; |
| } |
| |
| // Issuer not found, break out of the loop. |
| if (i == sk_X509_num(peer_certs.get())) |
| break; |
| } |
| return MaybeLocal<Object>(object); |
| } |
| |
| MaybeLocal<Object> GetLastIssuedCert( |
| X509Pointer* cert, |
| const SSLPointer& ssl, |
| Local<Object> issuer_chain, |
| Environment* const env) { |
| Local<Value> ca_info; |
| while (!cert->view().isIssuedBy(cert->view())) { |
| auto ca = X509Pointer::IssuerFrom(ssl, cert->view()); |
| if (!ca) break; |
| |
| if (!X509Certificate::toObject(env, ca.view()).ToLocal(&ca_info)) return {}; |
| |
| CHECK(ca_info->IsObject()); |
| |
| if (!Set<Object>(env->context(), |
| issuer_chain, |
| env->issuercert_string(), |
| ca_info.As<Object>())) { |
| return {}; |
| } |
| issuer_chain = ca_info.As<Object>(); |
| |
| // For self-signed certificates whose keyUsage field does not include |
| // keyCertSign, X509_check_issued() will return false. Avoid going into an |
| // infinite loop by checking if SSL_CTX_get_issuer() returned the same |
| // certificate. |
| if (cert->get() == ca.get()) break; |
| |
| // Delete previous cert and continue aggregating issuers. |
| *cert = std::move(ca); |
| } |
| return MaybeLocal<Object>(issuer_chain); |
| } |
| |
| } // namespace |
| |
| MaybeLocal<Value> GetCurrentCipherName(Environment* env, |
| const SSLPointer& ssl) { |
| return GetCipherName(env, SSL_get_current_cipher(ssl.get())); |
| } |
| |
| MaybeLocal<Value> GetCurrentCipherVersion(Environment* env, |
| const SSLPointer& ssl) { |
| return GetCipherVersion(env, SSL_get_current_cipher(ssl.get())); |
| } |
| |
| template <MaybeLocal<Value> (*Get)(Environment* env, const SSL_CIPHER* cipher)> |
| MaybeLocal<Value> GetCurrentCipherValue(Environment* env, |
| const SSLPointer& ssl) { |
| return Get(env, SSL_get_current_cipher(ssl.get())); |
| } |
| |
| MaybeLocal<Array> 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<Local<Value>, 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<Object> 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<Array>(); |
| } |
| ciphers[j++] = obj; |
| } |
| Local<Array> ret = Array::New(env->isolate(), ciphers.out(), count); |
| return scope.Escape(ret); |
| } |
| |
| |
| MaybeLocal<Object> GetCipherInfo(Environment* env, const SSLPointer& ssl) { |
| if (SSL_get_current_cipher(ssl.get()) == nullptr) |
| return MaybeLocal<Object>(); |
| EscapableHandleScope scope(env->isolate()); |
| Local<Object> info = Object::New(env->isolate()); |
| |
| if (!Set<Value>(env->context(), |
| info, |
| env->name_string(), |
| GetCurrentCipherValue<GetCipherName>(env, ssl)) || |
| !Set<Value>(env->context(), |
| info, |
| env->standard_name_string(), |
| GetCurrentCipherValue<GetCipherStandardName>(env, ssl)) || |
| !Set<Value>(env->context(), |
| info, |
| env->version_string(), |
| GetCurrentCipherValue<GetCipherVersion>(env, ssl))) { |
| return MaybeLocal<Object>(); |
| } |
| |
| return scope.Escape(info); |
| } |
| |
| MaybeLocal<Object> GetEphemeralKey(Environment* env, const SSLPointer& ssl) { |
| CHECK_EQ(SSL_is_server(ssl.get()), 0); |
| EVP_PKEY* raw_key; |
| |
| EscapableHandleScope scope(env->isolate()); |
| Local<Object> info = Object::New(env->isolate()); |
| if (!SSL_get_peer_tmp_key(ssl.get(), &raw_key)) return scope.Escape(info); |
| |
| Local<Context> context = env->context(); |
| crypto::EVPKeyPointer key(raw_key); |
| |
| int kid = key.id(); |
| switch (kid) { |
| case EVP_PKEY_DH: |
| if (!Set<String>(context, info, env->type_string(), env->dh_string()) || |
| !Set<Integer>(context, |
| info, |
| env->size_string(), |
| Integer::New(env->isolate(), key.bits()))) { |
| return MaybeLocal<Object>(); |
| } |
| break; |
| case EVP_PKEY_EC: |
| case EVP_PKEY_X25519: |
| case EVP_PKEY_X448: |
| { |
| const char* curve_name; |
| if (kid == EVP_PKEY_EC) { |
| OSSL3_CONST EC_KEY* ec = EVP_PKEY_get0_EC_KEY(key.get()); |
| int nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec)); |
| curve_name = OBJ_nid2sn(nid); |
| } else { |
| curve_name = OBJ_nid2sn(kid); |
| } |
| if (!Set<String>( |
| context, info, env->type_string(), env->ecdh_string()) || |
| !Set<String>(context, |
| info, |
| env->name_string(), |
| OneByteString(env->isolate(), curve_name)) || |
| !Set<Integer>(context, |
| info, |
| env->size_string(), |
| Integer::New(env->isolate(), key.bits()))) { |
| return MaybeLocal<Object>(); |
| } |
| } |
| break; |
| } |
| |
| return scope.Escape(info); |
| } |
| |
| MaybeLocal<Object> ECPointToBuffer(Environment* env, |
| const EC_GROUP* group, |
| const EC_POINT* point, |
| point_conversion_form_t form, |
| const char** error) { |
| size_t len = EC_POINT_point2oct(group, point, form, nullptr, 0, nullptr); |
| if (len == 0) { |
| if (error != nullptr) *error = "Failed to get public key length"; |
| return MaybeLocal<Object>(); |
| } |
| |
| std::unique_ptr<BackingStore> bs; |
| { |
| NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data()); |
| bs = ArrayBuffer::NewBackingStore(env->isolate(), len); |
| } |
| |
| len = EC_POINT_point2oct(group, |
| point, |
| form, |
| reinterpret_cast<unsigned char*>(bs->Data()), |
| bs->ByteLength(), |
| nullptr); |
| if (len == 0) { |
| if (error != nullptr) *error = "Failed to get public key"; |
| return MaybeLocal<Object>(); |
| } |
| |
| Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), std::move(bs)); |
| return Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local<Object>()); |
| } |
| |
| MaybeLocal<Value> GetPeerCert( |
| Environment* env, |
| const SSLPointer& ssl, |
| bool abbreviated, |
| bool is_server) { |
| ClearErrorOnReturn clear_error_on_return; |
| |
| // NOTE: This is because of the odd OpenSSL behavior. On client `cert_chain` |
| // contains the `peer_certificate`, but on server it doesn't. |
| X509Pointer cert(is_server ? SSL_get_peer_certificate(ssl.get()) : nullptr); |
| STACK_OF(X509)* ssl_certs = SSL_get_peer_cert_chain(ssl.get()); |
| if (!cert && (ssl_certs == nullptr || sk_X509_num(ssl_certs) == 0)) |
| return Undefined(env->isolate()); |
| |
| // Short result requested. |
| if (abbreviated) { |
| if (cert) { |
| return X509Certificate::toObject(env, cert.view()); |
| } |
| return X509Certificate::toObject( |
| env, ncrypto::X509View(sk_X509_value(ssl_certs, 0))); |
| } |
| |
| StackOfX509 peer_certs = CloneSSLCerts(std::move(cert), ssl_certs); |
| if (peer_certs == nullptr) |
| return Undefined(env->isolate()); |
| |
| // First and main certificate. |
| Local<Value> result; |
| ncrypto::X509View first_cert(sk_X509_value(peer_certs.get(), 0)); |
| CHECK(first_cert); |
| if (!X509Certificate::toObject(env, first_cert).ToLocal(&result)) return {}; |
| CHECK(result->IsObject()); |
| |
| Local<Object> issuer_chain; |
| MaybeLocal<Object> maybe_issuer_chain; |
| |
| maybe_issuer_chain = AddIssuerChainToObject( |
| &cert, result.As<Object>(), std::move(peer_certs), env); |
| if (!maybe_issuer_chain.ToLocal(&issuer_chain)) return {}; |
| |
| maybe_issuer_chain = |
| GetLastIssuedCert( |
| &cert, |
| ssl, |
| issuer_chain, |
| env); |
| |
| issuer_chain.Clear(); |
| if (!maybe_issuer_chain.ToLocal(&issuer_chain)) return {}; |
| |
| // Last certificate should be self-signed. |
| if (cert.view().isIssuedBy(cert.view()) && |
| !Set<Object>(env->context(), |
| issuer_chain, |
| env->issuercert_string(), |
| issuer_chain)) { |
| return {}; |
| } |
| |
| return result; |
| } |
| |
| } // namespace crypto |
| } // namespace node |