#include "allocated_buffer-inl.h" #include "base_object-inl.h" #include "env-inl.h" #include "node_buffer.h" #include "node_crypto.h" #include "crypto/crypto_common.h" #include "node.h" #include "node_internals.h" #include "node_url.h" #include "string_bytes.h" #include "memory_tracker-inl.h" #include "v8.h" #include #include #include #include #include #include #include #include #include #include namespace node { using v8::Array; using v8::ArrayBuffer; using v8::ArrayBufferView; using v8::BackingStore; using v8::Context; using v8::EscapableHandleScope; using v8::Integer; using v8::Local; using v8::MaybeLocal; using v8::NewStringType; using v8::Object; using v8::String; using v8::Undefined; using v8::Value; namespace crypto { static constexpr int kX509NameFlagsMultiline = ASN1_STRFLGS_ESC_2253 | ASN1_STRFLGS_ESC_CTRL | ASN1_STRFLGS_UTF8_CONVERT | XN_FLAG_SEP_MULTILINE | XN_FLAG_FN_SN; static constexpr int kX509NameFlagsRFC2253WithinUtf8JSON = XN_FLAG_RFC2253 & ~ASN1_STRFLGS_ESC_MSB & ~ASN1_STRFLGS_ESC_CTRL; bool SSL_CTX_get_issuer(SSL_CTX* ctx, X509* cert, X509** issuer) { X509_STORE* store = SSL_CTX_get_cert_store(ctx); DeleteFnPtr store_ctx( X509_STORE_CTX_new()); return store_ctx.get() != nullptr && X509_STORE_CTX_init(store_ctx.get(), store, nullptr, nullptr) == 1 && X509_STORE_CTX_get1_issuer(issuer, store_ctx.get(), cert) == 1; } 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())); unsigned char crandom[32]; if (keylog_cb == nullptr || SSL_get_client_random(ssl.get(), crandom, 32) != 32) { return; } std::string line = name; line += " " + StringBytes::hex_encode( reinterpret_cast(crandom), 32); line += " " + StringBytes::hex_encode( reinterpret_cast(secret), secretlen); keylog_cb(ssl.get(), line.c_str()); } bool SetALPN(const SSLPointer& ssl, const std::string& alpn) { return SSL_set_alpn_protos( ssl.get(), reinterpret_cast(alpn.c_str()), alpn.length()) == 0; } bool SetALPN(const SSLPointer& ssl, Local alpn) { if (!alpn->IsArrayBufferView()) return false; ArrayBufferViewContents protos(alpn.As()); return SSL_set_alpn_protos(ssl.get(), protos.data(), protos.length()) == 0; } 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)); } long VerifyPeerCertificate( // NOLINT(runtime/int) const SSLPointer& ssl, long def) { // NOLINT(runtime/int) long err = def; // NOLINT(runtime/int) if (X509* peer_cert = SSL_get_peer_certificate(ssl.get())) { X509_free(peer_cert); 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 context) { SSL_CTX* ctx = context->ctx_.get(); X509* x509 = SSL_CTX_get0_certificate(ctx); EVP_PKEY* pkey = SSL_CTX_get0_privatekey(ctx); STACK_OF(X509)* chain; int err = SSL_CTX_get0_chain_certs(ctx, &chain); if (err == 1) err = SSL_use_certificate(ssl.get(), x509); if (err == 1) err = SSL_use_PrivateKey(ssl.get(), pkey); if (err == 1 && chain != nullptr) err = SSL_set1_chain(ssl.get(), chain); return err == 1; } const char* GetClientHelloALPN(const SSLPointer& ssl) { const unsigned char* buf; size_t len; size_t rem; if (!SSL_client_hello_get0_ext( ssl.get(), TLSEXT_TYPE_application_layer_protocol_negotiation, &buf, &rem) || rem < 2) { return nullptr; } len = (buf[0] << 8) | buf[1]; if (len + 2 != rem) return nullptr; return reinterpret_cast(buf + 3); } const char* GetClientHelloServerName(const SSLPointer& ssl) { const unsigned char* buf; size_t len; size_t rem; if (!SSL_client_hello_get0_ext( ssl.get(), TLSEXT_TYPE_server_name, &buf, &rem) || rem <= 2) { return nullptr; } len = (*buf << 8) | *(buf + 1); if (len + 2 != rem) return nullptr; rem = len; if (rem == 0 || *(buf + 2) != TLSEXT_NAMETYPE_host_name) return nullptr; rem--; if (rem <= 2) return nullptr; len = (*(buf + 3) << 8) | *(buf + 4); if (len + 2 > rem) return nullptr; return reinterpret_cast(buf + 5); } const char* GetServerName(SSL* ssl) { return SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); } bool SetGroups(SecureContext* sc, const char* groups) { return SSL_CTX_set1_groups_list(**sc, groups) == 1; } const char* X509ErrorCode(long err) { // NOLINT(runtime/int) const char* code = "UNSPECIFIED"; #define CASE_X509_ERR(CODE) case X509_V_ERR_##CODE: code = #CODE; break; switch (err) { // if you modify anything in here, *please* update the respective section in // doc/api/tls.md as well CASE_X509_ERR(UNABLE_TO_GET_ISSUER_CERT) CASE_X509_ERR(UNABLE_TO_GET_CRL) CASE_X509_ERR(UNABLE_TO_DECRYPT_CERT_SIGNATURE) CASE_X509_ERR(UNABLE_TO_DECRYPT_CRL_SIGNATURE) CASE_X509_ERR(UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY) CASE_X509_ERR(CERT_SIGNATURE_FAILURE) CASE_X509_ERR(CRL_SIGNATURE_FAILURE) CASE_X509_ERR(CERT_NOT_YET_VALID) CASE_X509_ERR(CERT_HAS_EXPIRED) CASE_X509_ERR(CRL_NOT_YET_VALID) CASE_X509_ERR(CRL_HAS_EXPIRED) CASE_X509_ERR(ERROR_IN_CERT_NOT_BEFORE_FIELD) CASE_X509_ERR(ERROR_IN_CERT_NOT_AFTER_FIELD) CASE_X509_ERR(ERROR_IN_CRL_LAST_UPDATE_FIELD) CASE_X509_ERR(ERROR_IN_CRL_NEXT_UPDATE_FIELD) CASE_X509_ERR(OUT_OF_MEM) CASE_X509_ERR(DEPTH_ZERO_SELF_SIGNED_CERT) CASE_X509_ERR(SELF_SIGNED_CERT_IN_CHAIN) CASE_X509_ERR(UNABLE_TO_GET_ISSUER_CERT_LOCALLY) CASE_X509_ERR(UNABLE_TO_VERIFY_LEAF_SIGNATURE) CASE_X509_ERR(CERT_CHAIN_TOO_LONG) CASE_X509_ERR(CERT_REVOKED) CASE_X509_ERR(INVALID_CA) CASE_X509_ERR(PATH_LENGTH_EXCEEDED) CASE_X509_ERR(INVALID_PURPOSE) CASE_X509_ERR(CERT_UNTRUSTED) CASE_X509_ERR(CERT_REJECTED) CASE_X509_ERR(HOSTNAME_MISMATCH) } #undef CASE_X509_ERR return code; } MaybeLocal GetValidationErrorReason(Environment* env, int err) { if (err == 0) return Undefined(env->isolate()); const char* reason = X509_verify_cert_error_string(err); return OneByteString(env->isolate(), reason); } MaybeLocal GetValidationErrorCode(Environment* env, int err) { if (err == 0) return Undefined(env->isolate()); return OneByteString(env->isolate(), X509ErrorCode(err)); } MaybeLocal GetCert(Environment* env, const SSLPointer& ssl) { ClearErrorOnReturn clear_error_on_return; X509* cert = SSL_get_certificate(ssl.get()); if (cert == nullptr) return Undefined(env->isolate()); MaybeLocal maybe_cert = X509ToObject(env, cert); return maybe_cert.FromMaybe(Local()); } Local ToV8Value(Environment* env, const BIOPointer& bio) { BUF_MEM* mem; BIO_get_mem_ptr(bio.get(), &mem); MaybeLocal ret = String::NewFromUtf8( env->isolate(), mem->data, NewStringType::kNormal, mem->length); CHECK_EQ(BIO_reset(bio.get()), 1); return ret.FromMaybe(Local()); } namespace { template bool Set( Local context, Local target, Local name, MaybeLocal maybe_value) { Local 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(); } MaybeLocal GetCipherValue(Environment* env, const SSL_CIPHER* cipher, const char* (*getstr)(const SSL_CIPHER* cipher)) { if (cipher == nullptr) return Undefined(env->isolate()); return OneByteString(env->isolate(), getstr(cipher)); } MaybeLocal GetCipherName(Environment* env, const SSL_CIPHER* cipher) { return GetCipherValue(env, cipher, SSL_CIPHER_get_name); } MaybeLocal GetCipherStandardName( Environment* env, const SSL_CIPHER* cipher) { return GetCipherValue(env, cipher, SSL_CIPHER_standard_name); } MaybeLocal GetCipherVersion(Environment* env, const SSL_CIPHER* cipher) { return GetCipherValue(env, cipher, SSL_CIPHER_get_version); } StackOfX509 CloneSSLCerts(X509Pointer&& cert, const STACK_OF(X509)* const ssl_certs) { StackOfX509 peer_certs(sk_X509_new(nullptr)); if (cert) sk_X509_push(peer_certs.get(), cert.release()); 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 AddIssuerChainToObject( X509Pointer* cert, Local object, StackOfX509&& peer_certs, Environment* const env) { Local context = env->isolate()->GetCurrentContext(); cert->reset(sk_X509_delete(peer_certs.get(), 0)); for (;;) { int i; for (i = 0; i < sk_X509_num(peer_certs.get()); i++) { X509* ca = sk_X509_value(peer_certs.get(), i); if (X509_check_issued(ca, cert->get()) != X509_V_OK) continue; Local ca_info; MaybeLocal maybe_ca_info = X509ToObject(env, ca); if (!maybe_ca_info.ToLocal(&ca_info)) return MaybeLocal(); if (!Set(context, object, env->issuercert_string(), ca_info)) return MaybeLocal(); object = ca_info; // 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); } MaybeLocal GetLastIssuedCert( X509Pointer* cert, const SSLPointer& ssl, Local issuer_chain, Environment* const env) { Local context = env->isolate()->GetCurrentContext(); while (X509_check_issued(cert->get(), cert->get()) != X509_V_OK) { X509* ca; if (SSL_CTX_get_issuer(SSL_get_SSL_CTX(ssl.get()), cert->get(), &ca) <= 0) break; Local ca_info; MaybeLocal maybe_ca_info = X509ToObject(env, ca); if (!maybe_ca_info.ToLocal(&ca_info)) return MaybeLocal(); if (!Set(context, issuer_chain, env->issuercert_string(), ca_info)) return MaybeLocal(); issuer_chain = ca_info; // Take the value of cert->get() before the call to cert->reset() // in order to compare it to ca after and provide a way to exit this loop // in case it gets stuck. X509* value_before_reset = cert->get(); // Delete previous cert and continue aggregating issuers. cert->reset(ca); if (value_before_reset == ca) break; } return MaybeLocal(issuer_chain); } void AddFingerprintDigest( const unsigned char* md, unsigned int md_size, char fingerprint[3 * EVP_MAX_MD_SIZE]) { unsigned int i; const 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'; } template MaybeLocal GetCurveName(Environment* env, const int nid) { const char* name = nid2string(nid); return name != nullptr ? MaybeLocal(OneByteString(env->isolate(), name)) : MaybeLocal(Undefined(env->isolate())); } MaybeLocal GetECPubKey( Environment* env, const EC_GROUP* group, const ECPointer& ec) { const EC_POINT* pubkey = EC_KEY_get0_public_key(ec.get()); if (pubkey == nullptr) return Undefined(env->isolate()); return ECPointToBuffer( env, group, pubkey, EC_KEY_get_conv_form(ec.get()), nullptr).FromMaybe(Local()); } MaybeLocal GetECGroup( Environment* env, const EC_GROUP* group, const ECPointer& ec) { if (group == nullptr) return Undefined(env->isolate()); int bits = EC_GROUP_order_bits(group); if (bits <= 0) return Undefined(env->isolate()); return Integer::New(env->isolate(), bits); } MaybeLocal GetPubKey(Environment* env, const RSAPointer& rsa) { int size = i2d_RSA_PUBKEY(rsa.get(), nullptr); CHECK_GE(size, 0); std::unique_ptr bs; { NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data()); bs = ArrayBuffer::NewBackingStore(env->isolate(), size); } unsigned char* serialized = reinterpret_cast(bs->Data()); CHECK_GE(i2d_RSA_PUBKEY(rsa.get(), &serialized), 0); Local ab = ArrayBuffer::New(env->isolate(), std::move(bs)); return Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local()); } MaybeLocal GetExponentString( Environment* env, const BIOPointer& bio, const BIGNUM* e) { uint64_t exponent_word = static_cast(BN_get_word(e)); BIO_printf(bio.get(), "0x%" PRIx64, exponent_word); return ToV8Value(env, bio); } Local GetBits(Environment* env, const BIGNUM* n) { return Integer::New(env->isolate(), BN_num_bits(n)); } MaybeLocal GetModulusString( Environment* env, const BIOPointer& bio, const BIGNUM* n) { BN_print(bio.get(), n); return ToV8Value(env, bio); } } // namespace MaybeLocal GetRawDERCertificate(Environment* env, X509* cert) { int size = i2d_X509(cert, nullptr); std::unique_ptr bs; { NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data()); bs = ArrayBuffer::NewBackingStore(env->isolate(), size); } unsigned char* serialized = reinterpret_cast(bs->Data()); CHECK_GE(i2d_X509(cert, &serialized), 0); Local ab = ArrayBuffer::New(env->isolate(), std::move(bs)); return Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local()); } MaybeLocal GetSerialNumber(Environment* env, X509* cert) { if (ASN1_INTEGER* serial_number = X509_get_serialNumber(cert)) { BignumPointer bn(ASN1_INTEGER_to_BN(serial_number, nullptr)); if (bn) { char* data = BN_bn2hex(bn.get()); ByteSource buf = ByteSource::Allocated(data, strlen(data)); if (buf) return OneByteString(env->isolate(), buf.get()); } } return Undefined(env->isolate()); } MaybeLocal GetKeyUsage(Environment* env, X509* cert) { StackOfASN1 eku(static_cast( X509_get_ext_d2i(cert, NID_ext_key_usage, nullptr, nullptr))); if (eku) { const int count = sk_ASN1_OBJECT_num(eku.get()); MaybeStackBuffer, 16> ext_key_usage(count); char buf[256]; int j = 0; for (int i = 0; i < count; i++) { if (OBJ_obj2txt(buf, sizeof(buf), sk_ASN1_OBJECT_value(eku.get(), i), 1) >= 0) { ext_key_usage[j++] = OneByteString(env->isolate(), buf); } } return Array::New(env->isolate(), ext_key_usage.out(), count); } return Undefined(env->isolate()); } MaybeLocal GetFingerprintDigest( Environment* env, const EVP_MD* method, X509* cert) { unsigned char md[EVP_MAX_MD_SIZE]; unsigned int md_size; char fingerprint[EVP_MAX_MD_SIZE * 3]; if (X509_digest(cert, method, md, &md_size)) { AddFingerprintDigest(md, md_size, fingerprint); return OneByteString(env->isolate(), fingerprint); } return Undefined(env->isolate()); } MaybeLocal GetValidTo( Environment* env, X509* cert, const BIOPointer& bio) { ASN1_TIME_print(bio.get(), X509_get0_notAfter(cert)); return ToV8Value(env, bio); } MaybeLocal GetValidFrom( Environment* env, X509* cert, const BIOPointer& bio) { ASN1_TIME_print(bio.get(), X509_get0_notBefore(cert)); return ToV8Value(env, bio); } static inline bool IsSafeAltName(const char* name, size_t length, bool utf8) { for (size_t i = 0; i < length; i++) { char c = name[i]; switch (c) { case '"': case '\\': // These mess with encoding rules. // Fall through. case ',': // Commas make it impossible to split the list of subject alternative // names unambiguously, which is why we have to escape. // Fall through. case '\'': // Single quotes are unlikely to appear in any legitimate values, but they // could be used to make a value look like it was escaped (i.e., enclosed // in single/double quotes). return false; default: if (utf8) { // In UTF8 strings, we require escaping for any ASCII control character, // but NOT for non-ASCII characters. Note that all bytes of any code // point that consists of more than a single byte have their MSB set. if (static_cast(c) < ' ' || c == '\x7f') { return false; } } else { // Check if the char is a control character or non-ASCII character. Note // that char may or may not be a signed type. Regardless, non-ASCII // values will always be outside of this range. if (c < ' ' || c > '~') { return false; } } } } return true; } static inline void PrintAltName(const BIOPointer& out, const char* name, size_t length, bool utf8, const char* safe_prefix) { if (IsSafeAltName(name, length, utf8)) { // For backward-compatibility, append "safe" names without any // modifications. if (safe_prefix != nullptr) { BIO_printf(out.get(), "%s:", safe_prefix); } BIO_write(out.get(), name, length); } else { // If a name is not "safe", we cannot embed it without special // encoding. This does not usually happen, but we don't want to hide // it from the user either. We use JSON compatible escaping here. BIO_write(out.get(), "\"", 1); if (safe_prefix != nullptr) { BIO_printf(out.get(), "%s:", safe_prefix); } for (size_t j = 0; j < length; j++) { char c = static_cast(name[j]); if (c == '\\') { BIO_write(out.get(), "\\\\", 2); } else if (c == '"') { BIO_write(out.get(), "\\\"", 2); } else if ((c >= ' ' && c != ',' && c <= '~') || (utf8 && (c & 0x80))) { // Note that the above condition explicitly excludes commas, which means // that those are encoded as Unicode escape sequences in the "else" // block. That is not strictly necessary, and Node.js itself would parse // it correctly either way. We only do this to account for third-party // code that might be splitting the string at commas (as Node.js itself // used to do). BIO_write(out.get(), &c, 1); } else { // Control character or non-ASCII character. We treat everything as // Latin-1, which corresponds to the first 255 Unicode code points. const char hex[] = "0123456789abcdef"; char u[] = { '\\', 'u', '0', '0', hex[(c & 0xf0) >> 4], hex[c & 0x0f] }; BIO_write(out.get(), u, sizeof(u)); } } BIO_write(out.get(), "\"", 1); } } static inline void PrintLatin1AltName(const BIOPointer& out, const ASN1_IA5STRING* name, const char* safe_prefix = nullptr) { PrintAltName(out, reinterpret_cast(name->data), name->length, false, safe_prefix); } static inline void PrintUtf8AltName(const BIOPointer& out, const ASN1_UTF8STRING* name, const char* safe_prefix = nullptr) { PrintAltName(out, reinterpret_cast(name->data), name->length, true, safe_prefix); } // This function currently emulates the behavior of i2v_GENERAL_NAME in a safer // and less ambiguous way. // TODO(tniessen): gradually improve the format in the next major version(s) static bool PrintGeneralName(const BIOPointer& out, const GENERAL_NAME* gen) { if (gen->type == GEN_DNS) { ASN1_IA5STRING* name = gen->d.dNSName; BIO_write(out.get(), "DNS:", 4); // Note that the preferred name syntax (see RFCs 5280 and 1034) with // wildcards is a subset of what we consider "safe", so spec-compliant DNS // names will never need to be escaped. PrintLatin1AltName(out, name); } else if (gen->type == GEN_EMAIL) { ASN1_IA5STRING* name = gen->d.rfc822Name; BIO_write(out.get(), "email:", 6); PrintLatin1AltName(out, name); } else if (gen->type == GEN_URI) { ASN1_IA5STRING* name = gen->d.uniformResourceIdentifier; BIO_write(out.get(), "URI:", 4); // The set of "safe" names was designed to include just about any URI, // with a few exceptions, most notably URIs that contains commas (see // RFC 2396). In other words, most legitimate URIs will not require // escaping. PrintLatin1AltName(out, name); } else if (gen->type == GEN_DIRNAME) { // Earlier versions of Node.js used X509_NAME_oneline to print the X509_NAME // object. The format was non standard and should be avoided. The use of // X509_NAME_oneline is discouraged by OpenSSL but was required for backward // compatibility. Conveniently, X509_NAME_oneline produced ASCII and the // output was unlikely to contains commas or other characters that would // require escaping. However, it SHOULD NOT produce ASCII output since an // RFC5280 AttributeValue may be a UTF8String. // Newer versions of Node.js have since switched to X509_NAME_print_ex to // produce a better format at the cost of backward compatibility. The new // format may contain Unicode characters and it is likely to contain commas, // which require escaping. Fortunately, the recently safeguarded function // PrintAltName handles all of that safely. BIO_printf(out.get(), "DirName:"); BIOPointer tmp(BIO_new(BIO_s_mem())); CHECK(tmp); if (X509_NAME_print_ex(tmp.get(), gen->d.dirn, 0, kX509NameFlagsRFC2253WithinUtf8JSON) < 0) { return false; } char* oline = nullptr; long n_bytes = BIO_get_mem_data(tmp.get(), &oline); // NOLINT(runtime/int) CHECK_GE(n_bytes, 0); CHECK_IMPLIES(n_bytes != 0, oline != nullptr); PrintAltName(out, oline, static_cast(n_bytes), true, nullptr); } else if (gen->type == GEN_IPADD) { BIO_printf(out.get(), "IP Address:"); const ASN1_OCTET_STRING* ip = gen->d.ip; const unsigned char* b = ip->data; if (ip->length == 4) { BIO_printf(out.get(), "%d.%d.%d.%d", b[0], b[1], b[2], b[3]); } else if (ip->length == 16) { for (unsigned int j = 0; j < 8; j++) { uint16_t pair = (b[2 * j] << 8) | b[2 * j + 1]; BIO_printf(out.get(), (j == 0) ? "%X" : ":%X", pair); } } else { #if OPENSSL_VERSION_MAJOR >= 3 BIO_printf(out.get(), "", ip->length); #else BIO_printf(out.get(), ""); #endif } } else if (gen->type == GEN_RID) { // Unlike OpenSSL's default implementation, never print the OID as text and // instead always print its numeric representation. char oline[256]; OBJ_obj2txt(oline, sizeof(oline), gen->d.rid, true); BIO_printf(out.get(), "Registered ID:%s", oline); } else if (gen->type == GEN_OTHERNAME) { // TODO(tniessen): the format that is used here is based on OpenSSL's // implementation of i2v_GENERAL_NAME (as of OpenSSL 3.0.1), mostly for // backward compatibility. It is somewhat awkward, especially when passed to // translatePeerCertificate, and should be changed in the future, probably // to the format used by GENERAL_NAME_print (in a major release). bool unicode = true; const char* prefix = nullptr; // OpenSSL 1.1.1 does not support othername in i2v_GENERAL_NAME and may not // define these NIDs. #if OPENSSL_VERSION_MAJOR >= 3 int nid = OBJ_obj2nid(gen->d.otherName->type_id); switch (nid) { case NID_id_on_SmtpUTF8Mailbox: prefix = " SmtpUTF8Mailbox:"; break; case NID_XmppAddr: prefix = " XmppAddr:"; break; case NID_SRVName: prefix = " SRVName:"; unicode = false; break; case NID_ms_upn: prefix = " UPN:"; break; case NID_NAIRealm: prefix = " NAIRealm:"; break; } #endif // OPENSSL_VERSION_MAJOR >= 3 int val_type = gen->d.otherName->value->type; if (prefix == nullptr || (unicode && val_type != V_ASN1_UTF8STRING) || (!unicode && val_type != V_ASN1_IA5STRING)) { BIO_printf(out.get(), "othername:"); } else { BIO_printf(out.get(), "othername:"); if (unicode) { PrintUtf8AltName(out, gen->d.otherName->value->value.utf8string, prefix); } else { PrintLatin1AltName(out, gen->d.otherName->value->value.ia5string, prefix); } } } else if (gen->type == GEN_X400) { // TODO(tniessen): this is what OpenSSL does, implement properly instead BIO_printf(out.get(), "X400Name:"); } else if (gen->type == GEN_EDIPARTY) { // TODO(tniessen): this is what OpenSSL does, implement properly instead BIO_printf(out.get(), "EdiPartyName:"); } else { // This is safe because X509V3_EXT_d2i would have returned nullptr in this // case already. UNREACHABLE(); } return true; } bool SafeX509SubjectAltNamePrint(const BIOPointer& out, X509_EXTENSION* ext) { const X509V3_EXT_METHOD* method = X509V3_EXT_get(ext); CHECK(method == X509V3_EXT_get_nid(NID_subject_alt_name)); GENERAL_NAMES* names = static_cast(X509V3_EXT_d2i(ext)); if (names == nullptr) return false; bool ok = true; for (int i = 0; i < sk_GENERAL_NAME_num(names); i++) { GENERAL_NAME* gen = sk_GENERAL_NAME_value(names, i); if (i != 0) BIO_write(out.get(), ", ", 2); if (!(ok = PrintGeneralName(out, gen))) { break; } } sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); return ok; } bool SafeX509InfoAccessPrint(const BIOPointer& out, X509_EXTENSION* ext) { const X509V3_EXT_METHOD* method = X509V3_EXT_get(ext); CHECK(method == X509V3_EXT_get_nid(NID_info_access)); AUTHORITY_INFO_ACCESS* descs = static_cast(X509V3_EXT_d2i(ext)); if (descs == nullptr) return false; bool ok = true; for (int i = 0; i < sk_ACCESS_DESCRIPTION_num(descs); i++) { ACCESS_DESCRIPTION* desc = sk_ACCESS_DESCRIPTION_value(descs, i); if (i != 0) BIO_write(out.get(), "\n", 1); char objtmp[80]; i2t_ASN1_OBJECT(objtmp, sizeof(objtmp), desc->method); BIO_printf(out.get(), "%s - ", objtmp); if (!(ok = PrintGeneralName(out, desc->location))) { break; } } sk_ACCESS_DESCRIPTION_pop_free(descs, ACCESS_DESCRIPTION_free); #if OPENSSL_VERSION_MAJOR < 3 BIO_write(out.get(), "\n", 1); #endif return ok; } v8::MaybeLocal GetSubjectAltNameString( Environment* env, const BIOPointer& bio, X509* cert) { int index = X509_get_ext_by_NID(cert, NID_subject_alt_name, -1); if (index < 0) return Undefined(env->isolate()); X509_EXTENSION* ext = X509_get_ext(cert, index); CHECK_NOT_NULL(ext); if (!SafeX509SubjectAltNamePrint(bio, ext)) { CHECK_EQ(BIO_reset(bio.get()), 1); return v8::Null(env->isolate()); } return ToV8Value(env, bio); } v8::MaybeLocal GetInfoAccessString( Environment* env, const BIOPointer& bio, X509* cert) { int index = X509_get_ext_by_NID(cert, NID_info_access, -1); if (index < 0) return Undefined(env->isolate()); X509_EXTENSION* ext = X509_get_ext(cert, index); CHECK_NOT_NULL(ext); if (!SafeX509InfoAccessPrint(bio, ext)) { CHECK_EQ(BIO_reset(bio.get()), 1); return v8::Null(env->isolate()); } return ToV8Value(env, bio); } MaybeLocal GetIssuerString( Environment* env, const BIOPointer& bio, X509* cert) { X509_NAME* issuer_name = X509_get_issuer_name(cert); if (X509_NAME_print_ex( bio.get(), issuer_name, 0, kX509NameFlagsMultiline) <= 0) { CHECK_EQ(BIO_reset(bio.get()), 1); return Undefined(env->isolate()); } return ToV8Value(env, bio); } MaybeLocal GetSubject( Environment* env, const BIOPointer& bio, X509* cert) { if (X509_NAME_print_ex( bio.get(), X509_get_subject_name(cert), 0, kX509NameFlagsMultiline) <= 0) { CHECK_EQ(BIO_reset(bio.get()), 1); return Undefined(env->isolate()); } return ToV8Value(env, bio); } template static MaybeLocal GetX509NameObject(Environment* env, X509* cert) { X509_NAME* name = get_name(cert); CHECK_NOT_NULL(name); int cnt = X509_NAME_entry_count(name); CHECK_GE(cnt, 0); Local result = Object::New(env->isolate(), Null(env->isolate()), nullptr, nullptr, 0); if (result.IsEmpty()) { return MaybeLocal(); } for (int i = 0; i < cnt; i++) { X509_NAME_ENTRY* entry = X509_NAME_get_entry(name, i); CHECK_NOT_NULL(entry); // We intentionally ignore the value of X509_NAME_ENTRY_set because the // representation as an object does not allow grouping entries into sets // anyway, and multi-value RDNs are rare, i.e., the vast majority of // Relative Distinguished Names contains a single type-value pair only. const ASN1_OBJECT* type = X509_NAME_ENTRY_get_object(entry); const ASN1_STRING* value = X509_NAME_ENTRY_get_data(entry); // If OpenSSL knows the type, use the short name of the type as the key, and // the numeric representation of the type's OID otherwise. int type_nid = OBJ_obj2nid(type); char type_buf[80]; const char* type_str; if (type_nid != NID_undef) { type_str = OBJ_nid2sn(type_nid); CHECK_NOT_NULL(type_str); } else { OBJ_obj2txt(type_buf, sizeof(type_buf), type, true); type_str = type_buf; } Local v8_name; if (!String::NewFromUtf8(env->isolate(), type_str).ToLocal(&v8_name)) { return MaybeLocal(); } // The previous implementation used X509_NAME_print_ex, which escapes some // characters in the value. The old implementation did not decode/unescape // values correctly though, leading to ambiguous and incorrect // representations. The new implementation only converts to Unicode and does // not escape anything. unsigned char* value_str; int value_str_size = ASN1_STRING_to_UTF8(&value_str, value); if (value_str_size < 0) { return Undefined(env->isolate()); } Local v8_value; if (!String::NewFromUtf8(env->isolate(), reinterpret_cast(value_str), NewStringType::kNormal, value_str_size).ToLocal(&v8_value)) { OPENSSL_free(value_str); return MaybeLocal(); } OPENSSL_free(value_str); // For backward compatibility, we only create arrays if multiple values // exist for the same key. That is not great but there is not much we can // change here without breaking things. Note that this creates nested data // structures, yet still does not allow representing Distinguished Names // accurately. bool multiple; if (!result->HasOwnProperty(env->context(), v8_name).To(&multiple)) { return MaybeLocal(); } else if (multiple) { Local accum; if (!result->Get(env->context(), v8_name).ToLocal(&accum)) { return MaybeLocal(); } if (!accum->IsArray()) { accum = Array::New(env->isolate(), &accum, 1); if (result->Set(env->context(), v8_name, accum).IsNothing()) { return MaybeLocal(); } } Local array = accum.As(); if (array->Set(env->context(), array->Length(), v8_value).IsNothing()) { return MaybeLocal(); } } else if (result->Set(env->context(), v8_name, v8_value).IsNothing()) { return MaybeLocal(); } } return result; } MaybeLocal GetCipherName(Environment* env, const SSLPointer& ssl) { return GetCipherName(env, SSL_get_current_cipher(ssl.get())); } MaybeLocal GetCipherStandardName( Environment* env, const SSLPointer& ssl) { return GetCipherStandardName(env, SSL_get_current_cipher(ssl.get())); } MaybeLocal GetCipherVersion(Environment* env, const SSLPointer& ssl) { return GetCipherVersion(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); } MaybeLocal GetCipherInfo(Environment* env, const SSLPointer& ssl) { if (SSL_get_current_cipher(ssl.get()) == nullptr) return MaybeLocal(); EscapableHandleScope scope(env->isolate()); Local info = Object::New(env->isolate()); if (!Set(env->context(), info, env->name_string(), GetCipherName(env, ssl)) || !Set(env->context(), info, env->standard_name_string(), GetCipherStandardName(env, ssl)) || !Set(env->context(), info, env->version_string(), GetCipherVersion(env, ssl))) { return MaybeLocal(); } return scope.Escape(info); } MaybeLocal GetEphemeralKey(Environment* env, const SSLPointer& ssl) { CHECK_EQ(SSL_is_server(ssl.get()), 0); EVP_PKEY* raw_key; EscapableHandleScope scope(env->isolate()); Local info = Object::New(env->isolate()); if (!SSL_get_server_tmp_key(ssl.get(), &raw_key)) return scope.Escape(info); Local context = env->context(); crypto::EVPKeyPointer key(raw_key); int kid = EVP_PKEY_id(key.get()); int bits = EVP_PKEY_bits(key.get()); switch (kid) { case EVP_PKEY_DH: if (!Set(context, info, env->type_string(), env->dh_string()) || !Set(context, info, env->size_string(), Integer::New(env->isolate(), bits))) { return MaybeLocal(); } break; case EVP_PKEY_EC: case EVP_PKEY_X25519: case EVP_PKEY_X448: { const char* curve_name; if (kid == EVP_PKEY_EC) { ECKeyPointer ec(EVP_PKEY_get1_EC_KEY(key.get())); int nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec.get())); curve_name = OBJ_nid2sn(nid); } else { curve_name = OBJ_nid2sn(kid); } if (!Set(context, info, env->type_string(), env->ecdh_string()) || !Set(context, info, env->name_string(), OneByteString(env->isolate(), curve_name)) || !Set(context, info, env->size_string(), Integer::New(env->isolate(), bits))) { return MaybeLocal(); } } break; } return scope.Escape(info); } MaybeLocal 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(); } std::unique_ptr bs; { NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data()); bs = ArrayBuffer::NewBackingStore(env->isolate(), len); } len = EC_POINT_point2oct(group, point, form, reinterpret_cast(bs->Data()), bs->ByteLength(), nullptr); if (len == 0) { if (error != nullptr) *error = "Failed to get public key"; return MaybeLocal(); } Local ab = ArrayBuffer::New(env->isolate(), std::move(bs)); return Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local()); } MaybeLocal GetPeerCert( Environment* env, const SSLPointer& ssl, bool abbreviated, bool is_server) { ClearErrorOnReturn clear_error_on_return; Local result; MaybeLocal maybe_cert; // 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) { maybe_cert = X509ToObject(env, cert ? cert.get() : sk_X509_value(ssl_certs, 0)); return maybe_cert.ToLocal(&result) ? result : MaybeLocal(); } StackOfX509 peer_certs = CloneSSLCerts(std::move(cert), ssl_certs); if (peer_certs == nullptr) return Undefined(env->isolate()); // First and main certificate. X509Pointer first_cert(sk_X509_value(peer_certs.get(), 0)); CHECK(first_cert); maybe_cert = X509ToObject(env, first_cert.release()); if (!maybe_cert.ToLocal(&result)) return MaybeLocal(); Local issuer_chain; MaybeLocal maybe_issuer_chain; maybe_issuer_chain = AddIssuerChainToObject( &cert, result, std::move(peer_certs), env); if (!maybe_issuer_chain.ToLocal(&issuer_chain)) return MaybeLocal(); maybe_issuer_chain = GetLastIssuedCert( &cert, ssl, issuer_chain, env); issuer_chain.Clear(); if (!maybe_issuer_chain.ToLocal(&issuer_chain)) return MaybeLocal(); // Last certificate should be self-signed. if (X509_check_issued(cert.get(), cert.get()) == X509_V_OK && !Set(env->context(), issuer_chain, env->issuercert_string(), issuer_chain)) { return MaybeLocal(); } return result; } MaybeLocal X509ToObject( Environment* env, X509* cert, bool names_as_string) { EscapableHandleScope scope(env->isolate()); Local context = env->context(); Local info = Object::New(env->isolate()); BIOPointer bio(BIO_new(BIO_s_mem())); CHECK(bio); if (names_as_string) { // TODO(tniessen): this branch should not have to exist. It is only here // because toLegacyObject() does not actually return a legacy object, and // instead represents subject and issuer as strings. if (!Set(context, info, env->subject_string(), GetSubject(env, bio, cert)) || !Set(context, info, env->issuer_string(), GetIssuerString(env, bio, cert))) { return MaybeLocal(); } } else { if (!Set(context, info, env->subject_string(), GetX509NameObject(env, cert)) || !Set(context, info, env->issuer_string(), GetX509NameObject(env, cert))) { return MaybeLocal(); } } if (!Set(context, info, env->subjectaltname_string(), GetSubjectAltNameString(env, bio, cert)) || !Set(context, info, env->infoaccess_string(), GetInfoAccessString(env, bio, cert))) { return MaybeLocal(); } EVPKeyPointer pkey(X509_get_pubkey(cert)); RSAPointer rsa; ECPointer ec; if (pkey) { switch (EVP_PKEY_id(pkey.get())) { case EVP_PKEY_RSA: rsa.reset(EVP_PKEY_get1_RSA(pkey.get())); break; case EVP_PKEY_EC: ec.reset(EVP_PKEY_get1_EC_KEY(pkey.get())); break; } } if (rsa) { const BIGNUM* n; const BIGNUM* e; RSA_get0_key(rsa.get(), &n, &e, nullptr); if (!Set(context, info, env->modulus_string(), GetModulusString(env, bio, n)) || !Set(context, info, env->bits_string(), GetBits(env, n)) || !Set(context, info, env->exponent_string(), GetExponentString(env, bio, e)) || !Set(context, info, env->pubkey_string(), GetPubKey(env, rsa))) { return MaybeLocal(); } } else if (ec) { const EC_GROUP* group = EC_KEY_get0_group(ec.get()); if (!Set(context, info, env->bits_string(), GetECGroup(env, group, ec)) || !Set(context, info, env->pubkey_string(), GetECPubKey(env, group, ec))) { return MaybeLocal(); } const int nid = EC_GROUP_get_curve_name(group); if (nid != 0) { // Curve is well-known, get its OID and NIST nick-name (if it has one). if (!Set(context, info, env->asn1curve_string(), GetCurveName(env, nid)) || !Set(context, info, env->nistcurve_string(), GetCurveName(env, nid))) { return MaybeLocal(); } } else { // Unnamed curves can be described by their mathematical properties, // but aren't used much (at all?) with X.509/TLS. Support later if needed. } } // pkey, rsa, and ec pointers are no longer needed. pkey.reset(); rsa.reset(); ec.reset(); if (!Set(context, info, env->valid_from_string(), GetValidFrom(env, cert, bio)) || !Set(context, info, env->valid_to_string(), GetValidTo(env, cert, bio))) { return MaybeLocal(); } // bio is no longer needed bio.reset(); if (!Set(context, info, env->fingerprint_string(), GetFingerprintDigest(env, EVP_sha1(), cert)) || !Set(context, info, env->fingerprint256_string(), GetFingerprintDigest(env, EVP_sha256(), cert)) || !Set(context, info, env->fingerprint512_string(), GetFingerprintDigest(env, EVP_sha512(), cert)) || !Set(context, info, env->ext_key_usage_string(), GetKeyUsage(env, cert)) || !Set(context, info, env->serial_number_string(), GetSerialNumber(env, cert)) || !Set(context, info, env->raw_string(), GetRawDERCertificate(env, cert))) { return MaybeLocal(); } return scope.Escape(info); } } // namespace crypto } // namespace node