diff --git a/src/node_crypto.cc b/src/node_crypto.cc index a21e37408de68a..b3c9149b032bde 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -43,12 +43,14 @@ // StartComAndWoSignData.inc #include "StartComAndWoSignData.inc" -#include #include #include // INT_MAX #include #include #include + +#include +#include #include #define THROW_AND_RETURN_IF_NOT_BUFFER(val, prefix) \ @@ -107,6 +109,12 @@ using v8::String; using v8::Value; +struct StackOfX509Deleter { + void operator()(STACK_OF(X509)* p) const { sk_X509_pop_free(p, X509_free); } +}; + +using StackOfX509 = std::unique_ptr; + #if OPENSSL_VERSION_NUMBER < 0x10100000L static void RSA_get0_key(const RSA* r, const BIGNUM** n, const BIGNUM** e, const BIGNUM** d) { @@ -829,17 +837,15 @@ int SSL_CTX_use_certificate_chain(SSL_CTX* ctx, int ret = 0; unsigned long err = 0; // NOLINT(runtime/int) - // Read extra certs - STACK_OF(X509)* extra_certs = sk_X509_new_null(); - if (extra_certs == nullptr) { + StackOfX509 extra_certs(sk_X509_new_null()); + if (!extra_certs) goto done; - } while ((extra = PEM_read_bio_X509(in, nullptr, NoPasswordCallback, nullptr))) { - if (sk_X509_push(extra_certs, extra)) + if (sk_X509_push(extra_certs.get(), extra)) continue; // Failure, free all certs @@ -857,13 +863,11 @@ int SSL_CTX_use_certificate_chain(SSL_CTX* ctx, goto done; } - ret = SSL_CTX_use_certificate_chain(ctx, x, extra_certs, cert, issuer); + ret = SSL_CTX_use_certificate_chain(ctx, x, extra_certs.get(), cert, issuer); if (!ret) goto done; done: - if (extra_certs != nullptr) - sk_X509_pop_free(extra_certs, X509_free); if (extra != nullptr) X509_free(extra); if (x != nullptr) @@ -1988,109 +1992,128 @@ static Local X509ToObject(Environment* env, X509* cert) { } -// TODO(indutny): Split it into multiple smaller functions +static Local AddIssuerChainToObject(X509** cert, + Local object, + StackOfX509 peer_certs, + Environment* const env) { + Local context = env->isolate()->GetCurrentContext(); + *cert = 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) != X509_V_OK) + continue; + + Local ca_info = X509ToObject(env, ca); + object->Set(context, env->issuercert_string(), ca_info).FromJust(); + object = ca_info; + + // NOTE: Intentionally freeing cert that is not used anymore. + X509_free(*cert); + + // Delete cert and continue aggregating issuers. + *cert = 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(X509** cert, + const STACK_OF(X509)* const ssl_certs) { + StackOfX509 peer_certs(sk_X509_new(nullptr)); + if (*cert != nullptr) + sk_X509_push(peer_certs.get(), *cert); + for (int i = 0; i < sk_X509_num(ssl_certs); i++) { + *cert = X509_dup(sk_X509_value(ssl_certs, i)); + if (*cert == nullptr) + return StackOfX509(); + if (!sk_X509_push(peer_certs.get(), *cert)) + return StackOfX509(); + } + return peer_certs; +} + + +static Local GetLastIssuedCert(X509** cert, + const SSL* const ssl, + Local issuer_chain, + Environment* const env) { + Local context = env->isolate()->GetCurrentContext(); + while (X509_check_issued(*cert, *cert) != X509_V_OK) { + X509* ca; + if (SSL_CTX_get_issuer(SSL_get_SSL_CTX(ssl), *cert, &ca) <= 0) + break; + + Local ca_info = X509ToObject(env, ca); + issuer_chain->Set(context, env->issuercert_string(), ca_info).FromJust(); + issuer_chain = ca_info; + + // NOTE: Intentionally freeing cert that is not used anymore. + X509_free(*cert); + + // Delete cert and continue aggregating issuers. + *cert = ca; + } + return issuer_chain; +} + + template void SSLWrap::GetPeerCertificate( const FunctionCallbackInfo& args) { Base* w; ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); Environment* env = w->ssl_env(); - Local context = env->context(); ClearErrorOnReturn clear_error_on_return; Local result; - Local info; + // 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 + // contains the `peer_certificate`, but on server it doesn't. X509* cert = w->is_server() ? SSL_get_peer_certificate(w->ssl_) : nullptr; STACK_OF(X509)* ssl_certs = SSL_get_peer_cert_chain(w->ssl_); - STACK_OF(X509)* peer_certs = nullptr; - if (cert == nullptr && ssl_certs == nullptr) - goto done; - - if (cert == nullptr && sk_X509_num(ssl_certs) == 0) + if (cert == nullptr && (ssl_certs == nullptr || sk_X509_num(ssl_certs) == 0)) goto done; - // Short result requested + // Short result requested. if (args.Length() < 1 || !args[0]->IsTrue()) { - result = X509ToObject(env, - cert == nullptr ? sk_X509_value(ssl_certs, 0) : cert); + X509* target_cert = cert; + if (target_cert == nullptr) + target_cert = sk_X509_value(ssl_certs, 0); + result = X509ToObject(env, target_cert); goto done; } - // Clone `ssl_certs`, because we are going to destruct it - peer_certs = sk_X509_new(nullptr); - if (cert != nullptr) - sk_X509_push(peer_certs, cert); - for (int i = 0; i < sk_X509_num(ssl_certs); i++) { - cert = X509_dup(sk_X509_value(ssl_certs, i)); - if (cert == nullptr) - goto done; - if (!sk_X509_push(peer_certs, cert)) - goto done; - } - - // First and main certificate - cert = sk_X509_value(peer_certs, 0); - result = X509ToObject(env, cert); - info = result; - - // Put issuer inside the object - cert = sk_X509_delete(peer_certs, 0); - while (sk_X509_num(peer_certs) > 0) { - int i; - for (i = 0; i < sk_X509_num(peer_certs); i++) { - X509* ca = sk_X509_value(peer_certs, i); - if (X509_check_issued(ca, cert) != X509_V_OK) - continue; - - Local ca_info = X509ToObject(env, ca); - info->Set(context, env->issuercert_string(), ca_info).FromJust(); - info = ca_info; - - // NOTE: Intentionally freeing cert that is not used anymore - X509_free(cert); - - // Delete cert and continue aggregating issuers - cert = sk_X509_delete(peer_certs, i); - break; - } - - // Issuer not found, break out of the loop - if (i == sk_X509_num(peer_certs)) - break; - } - - // Last certificate should be self-signed - while (X509_check_issued(cert, cert) != X509_V_OK) { - X509* ca; - if (SSL_CTX_get_issuer(SSL_get_SSL_CTX(w->ssl_), cert, &ca) <= 0) - break; - - Local ca_info = X509ToObject(env, ca); - info->Set(context, env->issuercert_string(), ca_info).FromJust(); - info = ca_info; + if (auto peer_certs = CloneSSLCerts(&cert, ssl_certs)) { + // First and main certificate. + cert = sk_X509_value(peer_certs.get(), 0); + result = X509ToObject(env, cert); - // NOTE: Intentionally freeing cert that is not used anymore - X509_free(cert); + 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, cert) == X509_V_OK) + issuer_chain->Set(env->context(), + env->issuercert_string(), + issuer_chain).FromJust(); - // Delete cert and continue aggregating issuers - cert = ca; + CHECK_NE(cert, nullptr); } - // Self-issued certificate - if (X509_check_issued(cert, cert) == X509_V_OK) - info->Set(context, env->issuercert_string(), info).FromJust(); - - CHECK_NE(cert, nullptr); - done: if (cert != nullptr) X509_free(cert); - if (peer_certs != nullptr) - sk_X509_pop_free(peer_certs, X509_free); if (result.IsEmpty()) result = Object::New(env->isolate()); args.GetReturnValue().Set(result); @@ -2931,25 +2954,23 @@ inline CheckResult CheckWhitelistedServerCert(X509_STORE_CTX* ctx) { unsigned char hash[CNNIC_WHITELIST_HASH_LEN]; unsigned int hashlen = CNNIC_WHITELIST_HASH_LEN; - STACK_OF(X509)* chain = X509_STORE_CTX_get1_chain(ctx); - CHECK_NE(chain, nullptr); - CHECK_GT(sk_X509_num(chain), 0); + StackOfX509 chain(X509_STORE_CTX_get1_chain(ctx)); + CHECK(chain); + CHECK_GT(sk_X509_num(chain.get()), 0); // Take the last cert as root at the first time. - X509* root_cert = sk_X509_value(chain, sk_X509_num(chain)-1); + X509* root_cert = sk_X509_value(chain.get(), sk_X509_num(chain.get())-1); X509_NAME* root_name = X509_get_subject_name(root_cert); if (!IsSelfSigned(root_cert)) { - root_cert = FindRoot(chain); + root_cert = FindRoot(chain.get()); CHECK_NE(root_cert, nullptr); root_name = X509_get_subject_name(root_cert); } - X509* leaf_cert = sk_X509_value(chain, 0); - if (!CheckStartComOrWoSign(root_name, leaf_cert)) { - sk_X509_pop_free(chain, X509_free); + X509* leaf_cert = sk_X509_value(chain.get(), 0); + if (!CheckStartComOrWoSign(root_name, leaf_cert)) return CHECK_CERT_REVOKED; - } // When the cert is issued from either CNNNIC ROOT CA or CNNNIC EV // ROOT CA, check a hash of its leaf cert if it is in the whitelist. @@ -2962,13 +2983,10 @@ inline CheckResult CheckWhitelistedServerCert(X509_STORE_CTX* ctx) { void* result = bsearch(hash, WhitelistedCNNICHashes, arraysize(WhitelistedCNNICHashes), CNNIC_WHITELIST_HASH_LEN, compar); - if (result == nullptr) { - sk_X509_pop_free(chain, X509_free); + if (result == nullptr) return CHECK_CERT_REVOKED; - } } - sk_X509_pop_free(chain, X509_free); return CHECK_OK; }