/
githubmirror
/
node
Обзор
Документация
Войти
/
githubmirror
/
node
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/crypto/crypto_common.cc
351 строка
11 KB
Filip Skokan
crypto: split OpenSSL 3, BoringSSL, and legacy backends
08 июл 2026, 23:07
Не верифицирован
08 июл 2026, 23:07
db7cc5b
Код
Авторство
О чём код?
#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 ncrypto::ClearErrorOnReturn; using ncrypto::ECKeyPointer; using ncrypto::EVPKeyPointer; using ncrypto::SSLPointer; using ncrypto::SSLSessionPointer; using ncrypto::StackOfX509; using ncrypto::X509Pointer; using ncrypto::X509View; using v8::ArrayBuffer; using v8::BackingStoreInitializationMode; using v8::DictionaryTemplate; using v8::EscapableHandleScope; using v8::Integer; using v8::Local; using v8::MaybeLocal; using v8::Object; using v8::Undefined; using v8::Value; namespace crypto { SSLSessionPointer GetTLSSession(const unsigned char* buf, size_t length) { return SSLSessionPointer(d2i_SSL_SESSION(nullptr, &buf, length)); } MaybeLocal<Value> GetValidationErrorReason(Environment* env, int err) { auto reason = std::string(X509Pointer::ErrorReason(err).value_or("")); if (reason == "") return Undefined(env->isolate()); // Suggest --use-system-ca if the error indicates a certificate issue bool suggest_system_ca = (err == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE) || (err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) || ((err == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT) && !env->options()->use_system_ca); if (suggest_system_ca) { reason.append("; if the root CA is installed locally, " "try running Node.js with --use-system-ca"); } return OneByteString(env->isolate(), reason); } MaybeLocal<Value> GetValidationErrorCode(Environment* env, int err) { if (err == 0) return Undefined(env->isolate()); auto error = X509Pointer::ErrorCode(err); return OneByteString(env->isolate(), error); } MaybeLocal<Value> GetCert(Environment* env, const SSLPointer& ssl) { if (auto cert = ssl.getCertificate()) { return X509Certificate::toObject(env, cert); } return Undefined(env->isolate()); } namespace { 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++) { 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 (object ->Set(env->context(), env->issuercert_string(), ca_info.As<Object>()) .IsNothing()) { 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 (issuer_chain ->Set( env->context(), env->issuercert_string(), ca_info.As<Object>()) .IsNothing()) { 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); } Local<Value> maybeString(Environment* env, std::optional<std::string_view> value) { if (!value.has_value()) return Undefined(env->isolate()); return OneByteString(env->isolate(), value.value()); } } // namespace MaybeLocal<Object> GetCipherInfo(Environment* env, const SSLPointer& ssl) { if (ssl.getCipher() == nullptr) return MaybeLocal<Object>(); EscapableHandleScope scope(env->isolate()); auto tmpl = env->cipherinfo_template(); if (tmpl.IsEmpty()) { static constexpr std::string_view names[] = { "name", "standardName", "version"}; tmpl = DictionaryTemplate::New(env->isolate(), names); env->set_cipherinfo_template(tmpl); } MaybeLocal<Value> values[] = { maybeString(env, ssl.getCipherName()), maybeString(env, ssl.getCipherStandardName()), maybeString(env, ssl.getCipherVersion()), }; return scope.EscapeMaybe(NewDictionaryInstance(env->context(), tmpl, values)); } MaybeLocal<Object> GetEphemeralKey(Environment* env, const SSLPointer& ssl) { CHECK(!ssl.isServer()); EscapableHandleScope scope(env->isolate()); auto tmpl = env->ephemeral_key_template(); if (tmpl.IsEmpty()) { static constexpr std::string_view names[] = {"type", "name", "size"}; tmpl = DictionaryTemplate::New(env->isolate(), names); env->set_ephemeral_key_template(tmpl); } MaybeLocal<Value> values[] = { Undefined(env->isolate()), // type Undefined(env->isolate()), // name Undefined(env->isolate()), // size }; bool found = false; if (EVPKeyPointer key = ssl.getPeerTempKey()) { int kid = key.id(); switch (kid) { case EVP_PKEY_DH: { values[0] = env->dh_string(); values[2] = Integer::New(env->isolate(), key.bits()); found = true; break; } case EVP_PKEY_EC: case EVP_PKEY_X25519: case EVP_PKEY_X448: { const char* curve_name; if (kid == EVP_PKEY_EC) { ECKeyPointer ec(key); if (!ec) break; int nid = EC_GROUP_get_curve_name(ec.getGroup()); if (nid == NID_undef) break; curve_name = OBJ_nid2sn(nid); } else { curve_name = OBJ_nid2sn(kid); } if (curve_name == nullptr) break; values[0] = env->ecdh_string(); values[1] = OneByteString(env->isolate(), curve_name); values[2] = Integer::New(env->isolate(), key.bits()); found = true; break; } } } if (!found) { if (auto name = ssl.getNegotiatedGroup()) { values[0] = env->tls_group_string(); values[1] = OneByteString(env->isolate(), name.value()); } } return scope.EscapeMaybe(NewDictionaryInstance(env->context(), tmpl, values)); } MaybeLocal<Object> ECPointToBuffer(Environment* env, const EC_GROUP* group, const EC_POINT* point, point_conversion_form_t form) { size_t len = EC_POINT_point2oct(group, point, form, nullptr, 0, nullptr); if (len == 0) { THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get public key length"); return MaybeLocal<Object>(); } auto bs = ArrayBuffer::NewBackingStore( env->isolate(), len, BackingStoreInitializationMode::kUninitialized); len = EC_POINT_point2oct(group, point, form, reinterpret_cast<unsigned char*>(bs->Data()), bs->ByteLength(), nullptr); if (len == 0) { THROW_ERR_CRYPTO_OPERATION_FAILED(env, "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 ? X509Pointer::PeerFrom(ssl) : X509Pointer()); 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, 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; 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()) && issuer_chain->Set(env->context(), env->issuercert_string(), issuer_chain) .IsNothing()) { return {}; } return result; } } // namespace crypto } // namespace node