From e1b0c3d641a97b0f35f72143d71826a328e92c43 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Fri, 28 Feb 2020 17:13:08 -0800 Subject: [PATCH] src: add node_crypto_common and refactor Two things in one on this commit: (a) For the QUIC implementation, we need to separate out various bits from node_crypto.cc to allow them to be reused. That's where this commit starts. (b) Quite a bit of the node_crypto.cc code was just messy in terms of it's organization and lack of error handling and use of Local vs. MaybeLocal. This cleans that up a bit and hopefully makes certain parts a bit more manageable also. Signed-off-by: James M Snell --- node.gyp | 2 + src/env.h | 2 + src/node_crypto.cc | 637 +-------------------- src/node_crypto.h | 13 + src/node_crypto_common.cc | 1110 +++++++++++++++++++++++++++++++++++++ src/node_crypto_common.h | 139 +++++ src/string_bytes.cc | 12 +- src/string_bytes.h | 9 + 8 files changed, 1313 insertions(+), 611 deletions(-) create mode 100644 src/node_crypto_common.cc create mode 100644 src/node_crypto_common.h diff --git a/node.gyp b/node.gyp index b941eff56570e4..60aff022842bd6 100644 --- a/node.gyp +++ b/node.gyp @@ -823,9 +823,11 @@ [ 'node_use_openssl=="true"', { 'sources': [ 'src/node_crypto.cc', + 'src/node_crypto_common.cc', 'src/node_crypto_bio.cc', 'src/node_crypto_clienthello.cc', 'src/node_crypto.h', + 'src/node_crypto_common.h', 'src/node_crypto_bio.h', 'src/node_crypto_clienthello.h', 'src/node_crypto_clienthello-inl.h', diff --git a/src/env.h b/src/env.h index f02c8e9775ff7e..fc25f7a5b64411 100644 --- a/src/env.h +++ b/src/env.h @@ -206,6 +206,7 @@ constexpr size_t kFsStatsBufferLength = V(dest_string, "dest") \ V(destroyed_string, "destroyed") \ V(detached_string, "detached") \ + V(dh_string, "DH") \ V(dns_a_string, "A") \ V(dns_aaaa_string, "AAAA") \ V(dns_cname_string, "CNAME") \ @@ -219,6 +220,7 @@ constexpr size_t kFsStatsBufferLength = V(done_string, "done") \ V(dot_string, ".") \ V(duration_string, "duration") \ + V(ecdh_string, "ECDH") \ V(emit_warning_string, "emitWarning") \ V(empty_object_string, "{}") \ V(encoding_string, "encoding") \ diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 0b48821cf6c8af..fdaf91acdc0a1c 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -22,6 +22,7 @@ #include "node_crypto.h" #include "node_buffer.h" #include "node_crypto_bio.h" +#include "node_crypto_common.h" #include "node_crypto_clienthello-inl.h" #include "node_crypto_groups.h" #include "node_errors.h" @@ -59,11 +60,6 @@ #include #include -static const int X509_NAME_FLAGS = ASN1_STRFLGS_ESC_CTRL - | ASN1_STRFLGS_UTF8_CONVERT - | XN_FLAG_SEP_MULTILINE - | XN_FLAG_FN_SN; - namespace node { namespace crypto { @@ -75,7 +71,6 @@ using v8::Boolean; using v8::ConstructorBehavior; using v8::Context; using v8::DontDelete; -using v8::EscapableHandleScope; using v8::Exception; using v8::External; using v8::False; @@ -110,24 +105,6 @@ using v8::Value; # define IS_OCB_MODE(mode) ((mode) == EVP_CIPH_OCB_MODE) #endif -struct StackOfX509Deleter { - void operator()(STACK_OF(X509)* p) const { sk_X509_pop_free(p, X509_free); } -}; -using StackOfX509 = std::unique_ptr; - -struct StackOfXASN1Deleter { - void operator()(STACK_OF(ASN1_OBJECT)* p) const { - sk_ASN1_OBJECT_pop_free(p, ASN1_OBJECT_free); - } -}; -using StackOfASN1 = std::unique_ptr; - -// OPENSSL_free is a macro, so we need a wrapper function. -struct OpenSSLBufferDeleter { - void operator()(char* pointer) const { OPENSSL_free(pointer); } -}; -using OpenSSLBuffer = std::unique_ptr; - static const char* const root_certs[] = { #include "node_root_certs.h" // NOLINT(build/include_order) }; @@ -386,7 +363,7 @@ void ThrowCryptoError(Environment* env, unsigned long err, // NOLINT(runtime/int) // Default, only used if there is no SSL `err` which can // be used to create a long-style message string. - const char* message = nullptr) { + const char* message) { char message_buffer[128] = {0}; if (err != 0 || message == nullptr) { ERR_error_string_n(err, message_buffer, sizeof(message_buffer)); @@ -453,15 +430,6 @@ bool EntropySource(unsigned char* buffer, size_t length) { return RAND_bytes(buffer, length) != -1; } - -template -static T* MallocOpenSSL(size_t count) { - void* mem = OPENSSL_malloc(MultiplyWithOverflowCheck(count, sizeof(T))); - CHECK_IMPLIES(mem == nullptr, count == 0); - return static_cast(mem); -} - - void SecureContext::Initialize(Environment* env, Local target) { Local t = env->NewFunctionTemplate(New); t->InstanceTemplate()->SetInternalFieldCount( @@ -831,16 +799,6 @@ void SecureContext::SetEngineKey(const FunctionCallbackInfo& args) { } #endif // !OPENSSL_NO_ENGINE -int 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; -} - - int SSL_CTX_use_certificate_chain(SSL_CTX* ctx, X509Pointer&& x, STACK_OF(X509)* extra_certs, @@ -1893,381 +1851,6 @@ void SSLWrap::OnClientHello(void* arg, w->MakeCallback(env->onclienthello_string(), arraysize(argv), argv); } - -static bool SafeX509ExtPrint(BIO* out, X509_EXTENSION* ext) { - const X509V3_EXT_METHOD* method = X509V3_EXT_get(ext); - - if (method != X509V3_EXT_get_nid(NID_subject_alt_name)) - return false; - - GENERAL_NAMES* names = static_cast(X509V3_EXT_d2i(ext)); - if (names == nullptr) - return false; - - 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, ", ", 2); - - if (gen->type == GEN_DNS) { - ASN1_IA5STRING* name = gen->d.dNSName; - - BIO_write(out, "DNS:", 4); - BIO_write(out, name->data, name->length); - } else { - STACK_OF(CONF_VALUE)* nval = i2v_GENERAL_NAME( - const_cast(method), gen, nullptr); - if (nval == nullptr) - return false; - X509V3_EXT_val_prn(out, nval, 0, 0); - sk_CONF_VALUE_pop_free(nval, X509V3_conf_free); - } - } - sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); - - return true; -} - - -static void AddFingerprintDigest(const unsigned char* md, - unsigned int md_size, - char (*fingerprint)[3 * EVP_MAX_MD_SIZE + 1]) { - 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] = ':'; - } - - if (md_size > 0) { - (*fingerprint)[(3*(md_size-1))+2] = '\0'; - } else { - (*fingerprint)[0] = '\0'; - } -} - - -static 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(); - } - AllocatedBuffer buf = env->AllocateManaged(len); - len = EC_POINT_point2oct(group, - point, - form, - reinterpret_cast(buf.data()), - buf.size(), - nullptr); - if (len == 0) { - if (error != nullptr) *error = "Failed to get public key"; - return MaybeLocal(); - } - return buf.ToBuffer(); -} - - -static Local X509ToObject(Environment* env, X509* cert) { - EscapableHandleScope scope(env->isolate()); - Local context = env->context(); - Local info = Object::New(env->isolate()); - - BIOPointer bio(BIO_new(BIO_s_mem())); - BUF_MEM* mem; - if (X509_NAME_print_ex(bio.get(), - X509_get_subject_name(cert), - 0, - X509_NAME_FLAGS) > 0) { - BIO_get_mem_ptr(bio.get(), &mem); - info->Set(context, env->subject_string(), - String::NewFromUtf8(env->isolate(), mem->data, - NewStringType::kNormal, - mem->length).ToLocalChecked()).Check(); - } - USE(BIO_reset(bio.get())); - - X509_NAME* issuer_name = X509_get_issuer_name(cert); - if (X509_NAME_print_ex(bio.get(), issuer_name, 0, X509_NAME_FLAGS) > 0) { - BIO_get_mem_ptr(bio.get(), &mem); - info->Set(context, env->issuer_string(), - String::NewFromUtf8(env->isolate(), mem->data, - NewStringType::kNormal, - mem->length).ToLocalChecked()).Check(); - } - USE(BIO_reset(bio.get())); - - int nids[] = { NID_subject_alt_name, NID_info_access }; - Local keys[] = { env->subjectaltname_string(), - env->infoaccess_string() }; - CHECK_EQ(arraysize(nids), arraysize(keys)); - for (size_t i = 0; i < arraysize(nids); i++) { - int index = X509_get_ext_by_NID(cert, nids[i], -1); - if (index < 0) - continue; - - X509_EXTENSION* ext = X509_get_ext(cert, index); - CHECK_NOT_NULL(ext); - - if (!SafeX509ExtPrint(bio.get(), ext) && - X509V3_EXT_print(bio.get(), ext, 0, 0) != 1) { - info->Set(context, keys[i], Null(env->isolate())).Check(); - USE(BIO_reset(bio.get())); - continue; - } - - BIO_get_mem_ptr(bio.get(), &mem); - info->Set(context, keys[i], - String::NewFromUtf8(env->isolate(), mem->data, - NewStringType::kNormal, - mem->length).ToLocalChecked()).Check(); - - USE(BIO_reset(bio.get())); - } - - 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); - BN_print(bio.get(), n); - BIO_get_mem_ptr(bio.get(), &mem); - info->Set(context, env->modulus_string(), - String::NewFromUtf8(env->isolate(), mem->data, - NewStringType::kNormal, - mem->length).ToLocalChecked()).Check(); - USE(BIO_reset(bio.get())); - - int bits = BN_num_bits(n); - info->Set(context, env->bits_string(), - Integer::New(env->isolate(), bits)).Check(); - - uint64_t exponent_word = static_cast(BN_get_word(e)); - uint32_t lo = static_cast(exponent_word); - uint32_t hi = static_cast(exponent_word >> 32); - if (hi == 0) { - BIO_printf(bio.get(), "0x%x", lo); - } else { - BIO_printf(bio.get(), "0x%x%08x", hi, lo); - } - BIO_get_mem_ptr(bio.get(), &mem); - info->Set(context, env->exponent_string(), - String::NewFromUtf8(env->isolate(), mem->data, - NewStringType::kNormal, - mem->length).ToLocalChecked()).Check(); - USE(BIO_reset(bio.get())); - - int size = i2d_RSA_PUBKEY(rsa.get(), nullptr); - CHECK_GE(size, 0); - Local pubbuff = Buffer::New(env, size).ToLocalChecked(); - unsigned char* pubserialized = - reinterpret_cast(Buffer::Data(pubbuff)); - i2d_RSA_PUBKEY(rsa.get(), &pubserialized); - info->Set(env->context(), env->pubkey_string(), pubbuff).Check(); - } else if (ec) { - const EC_GROUP* group = EC_KEY_get0_group(ec.get()); - if (group != nullptr) { - int bits = EC_GROUP_order_bits(group); - if (bits > 0) { - info->Set(context, env->bits_string(), - Integer::New(env->isolate(), bits)).Check(); - } - } - - const EC_POINT* pubkey = EC_KEY_get0_public_key(ec.get()); - Local buf; - if (pubkey != nullptr && - ECPointToBuffer( - env, group, pubkey, EC_KEY_get_conv_form(ec.get()), nullptr) - .ToLocal(&buf)) { - info->Set(context, env->pubkey_string(), buf).Check(); - } - - 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 (const char* sn = OBJ_nid2sn(nid)) { - info->Set(context, env->asn1curve_string(), - OneByteString(env->isolate(), sn)).Check(); - } - - if (const char* nist = EC_curve_nid2nist(nid)) { - info->Set(context, env->nistcurve_string(), - OneByteString(env->isolate(), nist)).Check(); - } - } 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.reset(); - rsa.reset(); - ec.reset(); - - ASN1_TIME_print(bio.get(), X509_get0_notBefore(cert)); - BIO_get_mem_ptr(bio.get(), &mem); - info->Set(context, env->valid_from_string(), - String::NewFromUtf8(env->isolate(), mem->data, - NewStringType::kNormal, - mem->length).ToLocalChecked()).Check(); - USE(BIO_reset(bio.get())); - - ASN1_TIME_print(bio.get(), X509_get0_notAfter(cert)); - BIO_get_mem_ptr(bio.get(), &mem); - info->Set(context, env->valid_to_string(), - String::NewFromUtf8(env->isolate(), mem->data, - NewStringType::kNormal, - mem->length).ToLocalChecked()).Check(); - bio.reset(); - - unsigned char md[EVP_MAX_MD_SIZE]; - unsigned int md_size; - char fingerprint[EVP_MAX_MD_SIZE * 3 + 1]; - if (X509_digest(cert, EVP_sha1(), md, &md_size)) { - AddFingerprintDigest(md, md_size, &fingerprint); - info->Set(context, env->fingerprint_string(), - OneByteString(env->isolate(), fingerprint)).Check(); - } - if (X509_digest(cert, EVP_sha256(), md, &md_size)) { - AddFingerprintDigest(md, md_size, &fingerprint); - info->Set(context, env->fingerprint256_string(), - OneByteString(env->isolate(), fingerprint)).Check(); - } - - 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); - } - } - - eku.reset(); - info->Set(context, env->ext_key_usage_string(), - Array::New(env->isolate(), ext_key_usage.out(), count)).Check(); - } - - if (ASN1_INTEGER* serial_number = X509_get_serialNumber(cert)) { - BignumPointer bn(ASN1_INTEGER_to_BN(serial_number, nullptr)); - if (bn) { - OpenSSLBuffer buf(BN_bn2hex(bn.get())); - if (buf) { - info->Set(context, env->serial_number_string(), - OneByteString(env->isolate(), buf.get())).Check(); - } - } - } - - // Raw DER certificate - int size = i2d_X509(cert, nullptr); - Local buff = Buffer::New(env, size).ToLocalChecked(); - unsigned char* serialized = reinterpret_cast( - Buffer::Data(buff)); - i2d_X509(cert, &serialized); - info->Set(context, env->raw_string(), buff).Check(); - - return scope.Escape(info); -} - - -static Local 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 = X509ToObject(env, ca); - object->Set(context, env->issuercert_string(), ca_info).Check(); - 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 object; -} - - -static 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; -} - - -static Local 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 = X509ToObject(env, ca); - issuer_chain->Set(context, env->issuercert_string(), ca_info).Check(); - issuer_chain = ca_info; - - // Delete previous cert and continue aggregating issuers. - cert->reset(ca); - } - return issuer_chain; -} - - template void SSLWrap::GetPeerCertificate( const FunctionCallbackInfo& args) { @@ -2275,44 +1858,11 @@ void SSLWrap::GetPeerCertificate( ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); Environment* env = w->ssl_env(); - ClearErrorOnReturn clear_error_on_return; + bool abbreviated = args.Length() < 1 || !args[0]->IsTrue(); - Local result; - // Used to build the issuer certificate chain. - Local issuer_chain; - - // 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( - w->is_server() ? SSL_get_peer_certificate(w->ssl_.get()) : nullptr); - STACK_OF(X509)* ssl_certs = SSL_get_peer_cert_chain(w->ssl_.get()); - if (!cert && (ssl_certs == nullptr || sk_X509_num(ssl_certs) == 0)) - goto done; - - // Short result requested. - if (args.Length() < 1 || !args[0]->IsTrue()) { - result = X509ToObject(env, cert ? cert.get() : sk_X509_value(ssl_certs, 0)); - goto done; - } - - if (auto peer_certs = CloneSSLCerts(std::move(cert), ssl_certs)) { - // First and main certificate. - X509Pointer cert(sk_X509_value(peer_certs.get(), 0)); - CHECK(cert); - result = X509ToObject(env, cert.release()); - - issuer_chain = - AddIssuerChainToObject(&cert, result, std::move(peer_certs), env); - issuer_chain = GetLastIssuedCert(&cert, w->ssl_, issuer_chain, env); - // Last certificate should be self-signed. - if (X509_check_issued(cert.get(), cert.get()) == X509_V_OK) - issuer_chain->Set(env->context(), - env->issuercert_string(), - issuer_chain).Check(); - } - - done: - args.GetReturnValue().Set(result); + Local ret; + if (GetPeerCert(env, w->ssl_, abbreviated, w->is_server()).ToLocal(&ret)) + args.GetReturnValue().Set(ret); } @@ -2323,16 +1873,9 @@ void SSLWrap::GetCertificate( ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); Environment* env = w->ssl_env(); - ClearErrorOnReturn clear_error_on_return; - - Local result; - - X509* cert = SSL_get_certificate(w->ssl_.get()); - - if (cert != nullptr) - result = X509ToObject(env, cert); - - args.GetReturnValue().Set(result); + Local ret; + if (GetCert(env, w->ssl_).ToLocal(&ret)) + args.GetReturnValue().Set(ret); } @@ -2411,22 +1954,16 @@ void SSLWrap::SetSession(const FunctionCallbackInfo& args) { Base* w; ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); - if (args.Length() < 1) { + if (args.Length() < 1) return THROW_ERR_MISSING_ARGS(env, "Session argument is mandatory"); - } THROW_AND_RETURN_IF_NOT_BUFFER(env, args[0], "Session"); - ArrayBufferViewContents sbuf(args[0].As()); - - const unsigned char* p = sbuf.data(); - SSLSessionPointer sess(d2i_SSL_SESSION(nullptr, &p, sbuf.length())); + SSLSessionPointer sess = GetTLSSession(args[0]); if (sess == nullptr) return; - int r = SSL_set_session(w->ssl_.get(), sess.get()); - - if (!r) + if (!SetTLSSession(w->ssl_, sess)) return env->ThrowError("SSL_set_session error"); } @@ -2542,7 +2079,6 @@ void SSLWrap::GetEphemeralKeyInfo( Base* w; ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); Environment* env = Environment::GetCurrent(args); - Local context = env->context(); CHECK(w->ssl_); @@ -2550,51 +2086,12 @@ void SSLWrap::GetEphemeralKeyInfo( if (w->is_server()) return args.GetReturnValue().SetNull(); - Local info = Object::New(env->isolate()); + Local ret; + if (GetEphemeralKey(env, w->ssl_).ToLocal(&ret)) + args.GetReturnValue().Set(ret); - EVP_PKEY* raw_key; - if (SSL_get_server_tmp_key(w->ssl_.get(), &raw_key)) { - EVPKeyPointer key(raw_key); - int kid = EVP_PKEY_id(key.get()); - switch (kid) { - case EVP_PKEY_DH: - info->Set(context, env->type_string(), - FIXED_ONE_BYTE_STRING(env->isolate(), "DH")).Check(); - info->Set(context, env->size_string(), - Integer::New(env->isolate(), EVP_PKEY_bits(key.get()))) - .Check(); - break; - case EVP_PKEY_EC: - case EVP_PKEY_X25519: - case EVP_PKEY_X448: - { - const char* curve_name; - if (kid == EVP_PKEY_EC) { - EC_KEY* ec = EVP_PKEY_get1_EC_KEY(key.get()); - int nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec)); - curve_name = OBJ_nid2sn(nid); - EC_KEY_free(ec); - } else { - curve_name = OBJ_nid2sn(kid); - } - info->Set(context, env->type_string(), - FIXED_ONE_BYTE_STRING(env->isolate(), "ECDH")).Check(); - info->Set(context, env->name_string(), - OneByteString(args.GetIsolate(), - curve_name)).Check(); - info->Set(context, env->size_string(), - Integer::New(env->isolate(), - EVP_PKEY_bits(key.get()))).Check(); - } - break; - default: - break; - } - } // TODO(@sam-github) semver-major: else return ThrowCryptoError(env, // ERR_get_error()) - - return args.GetReturnValue().Set(info); } @@ -2624,58 +2121,14 @@ void SSLWrap::VerifyError(const FunctionCallbackInfo& args) { // peer certificate is questionable but it's compatible with what was // here before. long x509_verify_error = // NOLINT(runtime/int) - X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT; - if (X509* peer_cert = SSL_get_peer_certificate(w->ssl_.get())) { - X509_free(peer_cert); - x509_verify_error = SSL_get_verify_result(w->ssl_.get()); - } else { - const SSL_CIPHER* curr_cipher = SSL_get_current_cipher(w->ssl_.get()); - const SSL_SESSION* sess = SSL_get_session(w->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. Is there a better way? - if (SSL_CIPHER_get_auth_nid(curr_cipher) == NID_auth_psk || - (SSL_SESSION_get_protocol_version(sess) == TLS1_3_VERSION && - SSL_session_reused(w->ssl_.get()))) - return args.GetReturnValue().SetNull(); - } + VerifyPeerCertificate(w->ssl_, X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT); 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 = reason; -#define CASE_X509_ERR(CODE) case X509_V_ERR_##CODE: code = #CODE; break; - switch (x509_verify_error) { - 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) - } -#undef CASE_X509_ERR + code = X509ErrorCode(x509_verify_error); Isolate* isolate = args.GetIsolate(); Local reason_string = OneByteString(isolate, reason); @@ -2693,23 +2146,14 @@ void SSLWrap::GetCipher(const FunctionCallbackInfo& args) { Base* w; ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); Environment* env = w->ssl_env(); - Local context = env->context(); const SSL_CIPHER* c = SSL_get_current_cipher(w->ssl_.get()); if (c == nullptr) return; - Local info = Object::New(env->isolate()); - const char* cipher_name = SSL_CIPHER_get_name(c); - info->Set(context, env->name_string(), - OneByteString(args.GetIsolate(), cipher_name)).Check(); - const char* cipher_standard_name = SSL_CIPHER_standard_name(c); - info->Set(context, env->standard_name_string(), - OneByteString(args.GetIsolate(), cipher_standard_name)).Check(); - const char* cipher_version = SSL_CIPHER_get_version(c); - info->Set(context, env->version_string(), - OneByteString(args.GetIsolate(), cipher_version)).Check(); - args.GetReturnValue().Set(info); + Local ret; + if (GetCipherInfo(env, w->ssl_).ToLocal(&ret)) + args.GetReturnValue().Set(ret); } @@ -2904,10 +2348,7 @@ void SSLWrap::SetALPNProtocols(const FunctionCallbackInfo& args) { return env->ThrowTypeError("Must give a Buffer as first argument"); if (w->is_client()) { - ArrayBufferViewContents alpn_protos(args[0]); - int r = SSL_set_alpn_protos( - w->ssl_.get(), alpn_protos.data(), alpn_protos.length()); - CHECK_EQ(r, 0); + CHECK(SetALPN(w->ssl_, args[0])); } else { CHECK( w->object()->SetPrivate( @@ -2930,18 +2371,10 @@ int SSLWrap::TLSExtStatusCallback(SSL* s, void* arg) { if (w->is_client()) { // Incoming response - const unsigned char* resp; - int len = SSL_get_tlsext_status_ocsp_resp(s, &resp); Local arg; - if (resp == nullptr) { - arg = Null(env->isolate()); - } else { - arg = - Buffer::Copy(env, reinterpret_cast(resp), len) - .ToLocalChecked(); - } - - w->MakeCallback(env->onocspresponse_string(), 1, &arg); + MaybeLocal ret = GetSSLOCSPResponse(env, s, Null(env->isolate())); + if (ret.ToLocal(&arg)) + w->MakeCallback(env->onocspresponse_string(), 1, &arg); // No async acceptance is possible, so always return 1 to accept the // response. The listener for 'OCSPResponse' event has no control over @@ -3000,7 +2433,7 @@ int SSLWrap::SSLCertCallback(SSL* s, void* arg) { Local info = Object::New(env->isolate()); - const char* servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name); + const char* servername = GetServerName(s); if (servername == nullptr) { info->Set(context, env->servername_string(), @@ -3049,23 +2482,7 @@ void SSLWrap::CertCbDone(const FunctionCallbackInfo& args) { // Store the SNI context for later use. w->sni_context_ = BaseObjectPtr(sc); - int rv; - - // NOTE: reference count is not increased by this API methods - X509* x509 = SSL_CTX_get0_certificate(sc->ctx_.get()); - EVP_PKEY* pkey = SSL_CTX_get0_privatekey(sc->ctx_.get()); - STACK_OF(X509)* chain; - - rv = SSL_CTX_get0_chain_certs(sc->ctx_.get(), &chain); - if (rv) - rv = SSL_use_certificate(w->ssl_.get(), x509); - if (rv) - rv = SSL_use_PrivateKey(w->ssl_.get(), pkey); - if (rv && chain != nullptr) - rv = SSL_set1_chain(w->ssl_.get(), chain); - if (rv) - rv = w->SetCACerts(sc); - if (!rv) { + if (UseSNIContext(w->ssl_, sc) && !w->SetCACerts(sc)) { // Not clear why sometimes we throw error, and sometimes we call // onerror(). Both cause .destroy(), but onerror does a bit more. unsigned long err = ERR_get_error(); // NOLINT(runtime/int) @@ -4255,7 +3672,7 @@ void CipherBase::InitIv(const FunctionCallbackInfo& args) { reinterpret_cast(key.get()), key.size(), iv_buf.data(), - iv_len, + static_cast(iv_len), auth_tag_len); } diff --git a/src/node_crypto.h b/src/node_crypto.h index 655605290b092f..772a34a7da7699 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -90,6 +90,8 @@ class SecureContext final : public BaseObject { static void Initialize(Environment* env, v8::Local target); + SSL_CTX* operator*() const { return ctx_.get(); } + // TODO(joyeecheung): track the memory used by OpenSSL types SET_NO_MEMORY_INFO() SET_MEMORY_INFO_NAME(SecureContext) @@ -779,6 +781,17 @@ void SetEngine(const v8::FunctionCallbackInfo& args); #endif // !OPENSSL_NO_ENGINE void InitCrypto(v8::Local target); +void ThrowCryptoError(Environment* env, + unsigned long err, // NOLINT(runtime/int) + const char* message = nullptr); + +template +inline T* MallocOpenSSL(size_t count) { + void* mem = OPENSSL_malloc(MultiplyWithOverflowCheck(count, sizeof(T))); + CHECK_IMPLIES(mem == nullptr, count == 0); + return static_cast(mem); +} + } // namespace crypto } // namespace node diff --git a/src/node_crypto_common.cc b/src/node_crypto_common.cc new file mode 100644 index 00000000000000..197bc5cd5913a4 --- /dev/null +++ b/src/node_crypto_common.cc @@ -0,0 +1,1110 @@ +#include "env-inl.h" +#include "node_buffer.h" +#include "node_crypto.h" +#include "node_crypto_common.h" +#include "node.h" +#include "node_internals.h" +#include "node_url.h" +#include "string_bytes.h" +#include "v8.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace node { + +using v8::Array; +using v8::ArrayBufferView; +using v8::Context; +using v8::EscapableHandleScope; +using v8::Integer; +using v8::Local; +using v8::MaybeLocal; +using v8::NewStringType; +using v8::Null; +using v8::Object; +using v8::String; +using v8::Value; + +namespace crypto { + +static constexpr int X509_NAME_FLAGS = + ASN1_STRFLGS_ESC_CTRL | + ASN1_STRFLGS_UTF8_CONVERT | + XN_FLAG_SEP_MULTILINE | + XN_FLAG_FN_SN; + +int 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 unsigned char* buf, + size_t length) { + SSLSessionPointer s(d2i_SSL_SESSION(nullptr, &buf, length)); + return s == nullptr ? false : SetTLSSession(ssl, s); +} + +bool SetTLSSession( + const SSLPointer& ssl, + const SSLSessionPointer& session) { + return session != nullptr && SSL_set_session(ssl.get(), session.get()) == 1; +} + +SSLSessionPointer GetTLSSession(Local val) { + if (!val->IsArrayBufferView()) + return SSLSessionPointer(); + ArrayBufferViewContents sbuf(val.As()); + return GetTLSSession(sbuf.data(), sbuf.length()); +} + +SSLSessionPointer GetTLSSession(const unsigned char* buf, size_t length) { + return SSLSessionPointer(d2i_SSL_SESSION(nullptr, &buf, length)); +} + +std::unordered_multimap +GetCertificateAltNames(X509* cert) { + std::unordered_multimap map; + BIOPointer bio(BIO_new(BIO_s_mem())); + BUF_MEM* mem; + int idx = X509_get_ext_by_NID(cert, NID_subject_alt_name, -1); + if (idx < 0) // There is no subject alt name + return map; + + X509_EXTENSION* ext = X509_get_ext(cert, idx); + CHECK_NOT_NULL(ext); + const X509V3_EXT_METHOD* method = X509V3_EXT_get(ext); + CHECK_EQ(method, X509V3_EXT_get_nid(NID_subject_alt_name)); + + GENERAL_NAMES* names = static_cast(X509V3_EXT_d2i(ext)); + if (names == nullptr) // There are no names + return map; + + for (int i = 0; i < sk_GENERAL_NAME_num(names); i++) { + USE(BIO_reset(bio.get())); + GENERAL_NAME* gen = sk_GENERAL_NAME_value(names, i); + if (gen->type == GEN_DNS) { + ASN1_IA5STRING* name = gen->d.dNSName; + BIO_write(bio.get(), name->data, name->length); + BIO_get_mem_ptr(bio.get(), &mem); + map.emplace("dns", std::string(mem->data, mem->length)); + } else { + STACK_OF(CONF_VALUE)* nval = i2v_GENERAL_NAME( + const_cast(method), gen, nullptr); + if (nval == nullptr) + continue; + X509V3_EXT_val_prn(bio.get(), nval, 0, 0); + sk_CONF_VALUE_pop_free(nval, X509V3_conf_free); + BIO_get_mem_ptr(bio.get(), &mem); + std::string value(mem->data, mem->length); + if (value.compare(0, 11, "IP Address:") == 0) { + map.emplace("ip", value.substr(11)); + } else if (value.compare(0, 4, "URI:") == 0) { + url::URL url(value.substr(4)); + if (url.flags() & url::URL_FLAGS_CANNOT_BE_BASE || + url.flags() & url::URL_FLAGS_FAILED) { + continue; // Skip this one + } + map.emplace("uri", url.host()); + } + } + } + sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); + return map; +} + +std::string GetCertificateCN(X509* cert) { + X509_NAME* subject = X509_get_subject_name(cert); + if (subject != nullptr) { + int nid = OBJ_txt2nid("CN"); + int idx = X509_NAME_get_index_by_NID(subject, nid, -1); + if (idx != -1) { + X509_NAME_ENTRY* cn = X509_NAME_get_entry(subject, idx); + if (cn != nullptr) { + ASN1_STRING* cn_str = X509_NAME_ENTRY_get_data(cn); + if (cn_str != nullptr) { + return std::string(reinterpret_cast( + ASN1_STRING_get0_data(cn_str))); + } + } + } + } + return std::string(); +} + +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; +} + +int UseSNIContext(const SSLPointer& ssl, SecureContext* 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; +} + +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) { + 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) { + const char* reason = X509_verify_cert_error_string(err); + return OneByteString(env->isolate(), reason); +} + +MaybeLocal GetValidationErrorCode(Environment* env, int err) { + 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()); + + Local ret; + MaybeLocal maybe_cert = X509ToObject(env, cert); + return maybe_cert.ToLocal(&ret) ? ret : MaybeLocal(); +} + +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(); +} + +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); + USE(BIO_reset(bio.get())); + return ret.FromMaybe(Local()); +} + +MaybeLocal GetCipherName( + Environment* env, + const SSL_CIPHER* cipher) { + if (cipher == nullptr) + return Undefined(env->isolate()); + + return OneByteString(env->isolate(), SSL_CIPHER_get_name(cipher)); +} + +MaybeLocal GetCipherStandardName( + Environment* env, + const SSL_CIPHER* cipher) { + if (cipher == nullptr) + return Undefined(env->isolate()); + + return OneByteString(env->isolate(), SSL_CIPHER_standard_name(cipher)); +} + +MaybeLocal GetCipherVersion( + Environment* env, + const SSL_CIPHER* cipher) { + if (cipher == nullptr) + return Undefined(env->isolate()); + + return OneByteString(env->isolate(), SSL_CIPHER_get_version(cipher)); +} + +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; + + // Delete previous cert and continue aggregating issuers. + cert->reset(ca); + } + return MaybeLocal(issuer_chain); +} + +MaybeLocal GetRawDERCertificate(Environment* env, X509* cert) { + int size = i2d_X509(cert, nullptr); + + AllocatedBuffer buffer = env->AllocateManaged(size); + unsigned char* serialized = + reinterpret_cast(buffer.data()); + i2d_X509(cert, &serialized); + return buffer.ToBuffer(); +} + +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) { + OpenSSLBuffer buf(BN_bn2hex(bn.get())); + 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()); +} + +void AddFingerprintDigest( + const unsigned char* md, + unsigned int md_size, + char (*fingerprint)[3 * EVP_MAX_MD_SIZE + 1]) { + 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] = ':'; + } + + if (md_size > 0) { + (*fingerprint)[(3*(md_size-1))+2] = '\0'; + } else { + (*fingerprint)[0] = '\0'; + } +} + +bool SafeX509ExtPrint(const BIOPointer& out, X509_EXTENSION* ext) { + const X509V3_EXT_METHOD* method = X509V3_EXT_get(ext); + + if (method != X509V3_EXT_get_nid(NID_subject_alt_name)) + return false; + + GENERAL_NAMES* names = static_cast(X509V3_EXT_d2i(ext)); + if (names == nullptr) + return false; + + 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 (gen->type == GEN_DNS) { + ASN1_IA5STRING* name = gen->d.dNSName; + + BIO_write(out.get(), "DNS:", 4); + BIO_write(out.get(), name->data, name->length); + } else { + STACK_OF(CONF_VALUE)* nval = i2v_GENERAL_NAME( + const_cast(method), gen, nullptr); + if (nval == nullptr) + return false; + X509V3_EXT_val_prn(out.get(), nval, 0, 0); + sk_CONF_VALUE_pop_free(nval, X509V3_conf_free); + } + } + sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); + + return true; +} + +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 + 1]; + + 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); +} + +MaybeLocal GetCurveASN1Name(Environment* env, const int nid) { + const char* nist = OBJ_nid2sn(nid); + return nist != nullptr ? + MaybeLocal(OneByteString(env->isolate(), nist)) : + MaybeLocal(Undefined(env->isolate())); +} + +MaybeLocal GetCurveNistName(Environment* env, const int nid) { + const char* nist = EC_curve_nid2nist(nid); + return nist != nullptr ? + MaybeLocal(OneByteString(env->isolate(), nist)) : + 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); + + AllocatedBuffer buffer = env->AllocateManaged(size); + unsigned char* serialized = + reinterpret_cast(buffer.data()); + i2d_RSA_PUBKEY(rsa.get(), &serialized); + return buffer.ToBuffer(); +} + +MaybeLocal GetExponentString( + Environment* env, + const BIOPointer& bio, + const BIGNUM* e) { + uint64_t exponent_word = static_cast(BN_get_word(e)); + uint32_t lo = static_cast(exponent_word); + uint32_t hi = static_cast(exponent_word >> 32); + if (hi == 0) + BIO_printf(bio.get(), "0x%x", lo); + else + BIO_printf(bio.get(), "0x%x%08x", hi, lo); + + 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); +} + +template +MaybeLocal GetInfoString( + Environment* env, + const BIOPointer& bio, + X509* cert) { + int index = X509_get_ext_by_NID(cert, nid, -1); + if (index < 0) + return Undefined(env->isolate()); + + X509_EXTENSION* ext = X509_get_ext(cert, index); + CHECK_NOT_NULL(ext); + + if (!SafeX509ExtPrint(bio, ext) && + X509V3_EXT_print(bio.get(), ext, 0, 0) != 1) { + USE(BIO_reset(bio.get())); + return 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, X509_NAME_FLAGS) <= 0) { + USE(BIO_reset(bio.get())); + 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, + X509_NAME_FLAGS) <= 0) { + USE(BIO_reset(bio.get())); + return Undefined(env->isolate()); + } + + return ToV8Value(env, bio); +} +} // namespace + +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) { + 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(); + } + AllocatedBuffer buf = env->AllocateManaged(len); + len = EC_POINT_point2oct(group, + point, + form, + reinterpret_cast(buf.data()), + buf.size(), + nullptr); + if (len == 0) { + if (error != nullptr) *error = "Failed to get public key"; + return MaybeLocal(); + } + return buf.ToBuffer(); +} + +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()).ToLocalChecked(); + 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) { + EscapableHandleScope scope(env->isolate()); + Local context = env->context(); + Local info = Object::New(env->isolate()); + + BIOPointer bio(BIO_new(BIO_s_mem())); + + if (!Set(context, + info, + env->subject_string(), + GetSubject(env, bio, cert)) || + !Set(context, + info, + env->issuer_string(), + GetIssuerString(env, bio, cert)) || + !Set(context, + info, + env->subjectaltname_string(), + GetInfoString(env, bio, cert)) || + !Set(context, + info, + env->infoaccess_string(), + GetInfoString(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(), + GetCurveASN1Name(env, nid)) || + !Set(context, + info, + env->nistcurve_string(), + GetCurveNistName(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->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 diff --git a/src/node_crypto_common.h b/src/node_crypto_common.h new file mode 100644 index 00000000000000..e42e249ef2ba2e --- /dev/null +++ b/src/node_crypto_common.h @@ -0,0 +1,139 @@ +#ifndef SRC_NODE_CRYPTO_COMMON_H_ +#define SRC_NODE_CRYPTO_COMMON_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "env.h" +#include "node_crypto.h" +#include "v8.h" +#include +#include + +#include +#include + +namespace node { +namespace crypto { + +// OPENSSL_free is a macro, so we need a wrapper function. +struct OpenSSLBufferDeleter { + void operator()(char* pointer) const { OPENSSL_free(pointer); } +}; +using OpenSSLBuffer = std::unique_ptr; + +struct StackOfX509Deleter { + void operator()(STACK_OF(X509)* p) const { sk_X509_pop_free(p, X509_free); } +}; +using StackOfX509 = std::unique_ptr; + +struct StackOfXASN1Deleter { + void operator()(STACK_OF(ASN1_OBJECT)* p) const { + sk_ASN1_OBJECT_pop_free(p, ASN1_OBJECT_free); + } +}; +using StackOfASN1 = std::unique_ptr; + +int SSL_CTX_get_issuer(SSL_CTX* ctx, X509* cert, X509** issuer); + +void LogSecret( + const SSLPointer& ssl, + const char* name, + const unsigned char* secret, + size_t secretlen); + +bool SetALPN(const SSLPointer& ssl, const std::string& alpn); + +bool SetALPN(const SSLPointer& ssl, v8::Local alpn); + +v8::MaybeLocal GetSSLOCSPResponse( + Environment* env, + SSL* ssl, + v8::Local default_value); + +bool SetTLSSession( + const SSLPointer& ssl, + const unsigned char* buf, + size_t length); + +bool SetTLSSession( + const SSLPointer& ssl, + const SSLSessionPointer& session); + +SSLSessionPointer GetTLSSession(v8::Local val); + +SSLSessionPointer GetTLSSession(const unsigned char* buf, size_t length); + +std::unordered_multimap +GetCertificateAltNames(X509* cert); + +std::string GetCertificateCN(X509* cert); + +long VerifyPeerCertificate( // NOLINT(runtime/int) + const SSLPointer& ssl, + long def = X509_V_ERR_UNSPECIFIED); // NOLINT(runtime/int) + +int UseSNIContext(const SSLPointer& ssl, SecureContext* 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); + +v8::MaybeLocal GetCert(Environment* env, const SSLPointer& ssl); + +v8::MaybeLocal GetCipherName( + Environment* env, + const SSLPointer& ssl); + +v8::MaybeLocal GetCipherStandardName( + Environment* env, + const SSLPointer& ssl); + +v8::MaybeLocal GetCipherVersion( + Environment* env, + const SSLPointer& ssl); + +v8::MaybeLocal GetCipherInfo( + Environment* env, + const SSLPointer& ssl); + +v8::MaybeLocal GetEphemeralKey( + Environment* env, + const SSLPointer& ssl); + +v8::MaybeLocal GetPeerCert( + Environment* env, + const SSLPointer& ssl, + bool abbreviated = false, + bool is_server = false); + +v8::MaybeLocal ECPointToBuffer( + Environment* env, + const EC_GROUP* group, + const EC_POINT* point, + point_conversion_form_t form, + const char** error); + +v8::MaybeLocal X509ToObject( + Environment* env, + X509* cert); + +} // namespace crypto +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_NODE_CRYPTO_COMMON_H_ diff --git a/src/string_bytes.cc b/src/string_bytes.cc index f8d7243e5d6c36..7ee87a8ebe8e30 100644 --- a/src/string_bytes.cc +++ b/src/string_bytes.cc @@ -587,7 +587,11 @@ static void force_ascii(const char* src, char* dst, size_t len) { } -static size_t hex_encode(const char* src, size_t slen, char* dst, size_t dlen) { +size_t StringBytes::hex_encode( + const char* src, + size_t slen, + char* dst, + size_t dlen) { // We know how much we'll write, just make sure that there's space. CHECK(dlen >= slen * 2 && "not enough space provided for hex encode"); @@ -603,6 +607,12 @@ static size_t hex_encode(const char* src, size_t slen, char* dst, size_t dlen) { return dlen; } +std::string StringBytes::hex_encode(const char* src, size_t slen) { + size_t dlen = slen * 2; + std::string dst(dlen, '\0'); + hex_encode(src, slen, &dst[0], dlen); + return dst; +} #define CHECK_BUFLEN_IN_RANGE(len) \ do { \ diff --git a/src/string_bytes.h b/src/string_bytes.h index 5ef05fc48cd50d..69bb828e018cb0 100644 --- a/src/string_bytes.h +++ b/src/string_bytes.h @@ -29,6 +29,8 @@ #include "v8.h" #include "env-inl.h" +#include + namespace node { class StringBytes { @@ -97,6 +99,13 @@ class StringBytes { enum encoding encoding, v8::Local* error); + static size_t hex_encode(const char* src, + size_t slen, + char* dst, + size_t dlen); + + static std::string hex_encode(const char* src, size_t slen); + private: static size_t WriteUCS2(v8::Isolate* isolate, char* buf,