blob: d3e441d5a67ebfbf0214cdd8397af19e17143a1d [file] [edit]
#include "crypto/crypto_common.h"
#include "base_object-inl.h"
#include "crypto/crypto_util.h"
#include "crypto/crypto_x509.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "nbytes.h"
#include "ncrypto.h"
#include "node.h"
#include "node_buffer.h"
#include "node_crypto.h"
#include "node_internals.h"
#include "string_bytes.h"
#include "v8.h"
#include <openssl/ec.h>
#include <openssl/ecdh.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/x509v3.h>
#include <openssl/hmac.h>
#include <openssl/rand.h>
#include <openssl/pkcs12.h>
#include <string>
#include <unordered_map>
namespace node {
using ncrypto::ClearErrorOnReturn;
using ncrypto::ECKeyPointer;
using ncrypto::EVPKeyPointer;
using ncrypto::SSLPointer;
using ncrypto::SSLSessionPointer;
using ncrypto::StackOfX509;
using ncrypto::X509Pointer;
using ncrypto::X509View;
using v8::ArrayBuffer;
using v8::BackingStoreInitializationMode;
using v8::Context;
using v8::EscapableHandleScope;
using v8::Integer;
using v8::Local;
using v8::MaybeLocal;
using v8::Object;
using v8::Undefined;
using v8::Value;
namespace crypto {
SSLSessionPointer GetTLSSession(const unsigned char* buf, size_t length) {
return SSLSessionPointer(d2i_SSL_SESSION(nullptr, &buf, length));
}
MaybeLocal<Value> GetValidationErrorReason(Environment* env, int err) {
auto reason = std::string(X509Pointer::ErrorReason(err).value_or(""));
if (reason == "") return Undefined(env->isolate());
// Suggest --use-system-ca if the error indicates a certificate issue
bool suggest_system_ca =
(err == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE) ||
(err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) ||
((err == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT) &&
!per_process::cli_options->use_system_ca);
if (suggest_system_ca) {
reason.append("; if the root CA is installed locally, "
"try running Node.js with --use-system-ca");
}
return OneByteString(env->isolate(), reason);
}
MaybeLocal<Value> GetValidationErrorCode(Environment* env, int err) {
if (err == 0) return Undefined(env->isolate());
auto error = X509Pointer::ErrorCode(err);
return OneByteString(env->isolate(), error);
}
MaybeLocal<Value> GetCert(Environment* env, const SSLPointer& ssl) {
if (auto cert = ssl.getCertificate()) {
return X509Certificate::toObject(env, cert);
}
return Undefined(env->isolate());
}
namespace {
StackOfX509 CloneSSLCerts(X509Pointer&& cert,
const STACK_OF(X509)* const ssl_certs) {
StackOfX509 peer_certs(sk_X509_new(nullptr));
if (!peer_certs) return StackOfX509();
if (cert && !sk_X509_push(peer_certs.get(), cert.release()))
return StackOfX509();
for (int i = 0; i < sk_X509_num(ssl_certs); i++) {
X509Pointer cert(X509_dup(sk_X509_value(ssl_certs, i)));
if (!cert || !sk_X509_push(peer_certs.get(), cert.get()))
return StackOfX509();
// `cert` is now managed by the stack.
cert.release();
}
return peer_certs;
}
MaybeLocal<Object> AddIssuerChainToObject(X509Pointer* cert,
Local<Object> object,
StackOfX509&& peer_certs,
Environment* const env) {
cert->reset(sk_X509_delete(peer_certs.get(), 0));
for (;;) {
int i;
for (i = 0; i < sk_X509_num(peer_certs.get()); i++) {
X509View ca(sk_X509_value(peer_certs.get(), i));
if (!cert->view().isIssuedBy(ca)) continue;
Local<Value> ca_info;
if (!X509Certificate::toObject(env, ca).ToLocal(&ca_info)) return {};
CHECK(ca_info->IsObject());
if (object
->Set(env->context(),
env->issuercert_string(),
ca_info.As<Object>())
.IsNothing()) {
return {};
}
object = ca_info.As<Object>();
// NOTE: Intentionally freeing cert that is not used anymore.
// Delete cert and continue aggregating issuers.
cert->reset(sk_X509_delete(peer_certs.get(), i));
break;
}
// Issuer not found, break out of the loop.
if (i == sk_X509_num(peer_certs.get()))
break;
}
return MaybeLocal<Object>(object);
}
MaybeLocal<Object> GetLastIssuedCert(
X509Pointer* cert,
const SSLPointer& ssl,
Local<Object> issuer_chain,
Environment* const env) {
Local<Value> ca_info;
while (!cert->view().isIssuedBy(cert->view())) {
auto ca = X509Pointer::IssuerFrom(ssl, cert->view());
if (!ca) break;
if (!X509Certificate::toObject(env, ca.view()).ToLocal(&ca_info)) return {};
CHECK(ca_info->IsObject());
if (issuer_chain
->Set(
env->context(), env->issuercert_string(), ca_info.As<Object>())
.IsNothing()) {
return {};
}
issuer_chain = ca_info.As<Object>();
// For self-signed certificates whose keyUsage field does not include
// keyCertSign, X509_check_issued() will return false. Avoid going into an
// infinite loop by checking if SSL_CTX_get_issuer() returned the same
// certificate.
if (cert->get() == ca.get()) break;
// Delete previous cert and continue aggregating issuers.
*cert = std::move(ca);
}
return MaybeLocal<Object>(issuer_chain);
}
Local<Value> maybeString(Environment* env,
std::optional<std::string_view> value) {
if (!value.has_value()) return Undefined(env->isolate());
return OneByteString(env->isolate(), value.value());
}
} // namespace
MaybeLocal<Object> GetCipherInfo(Environment* env, const SSLPointer& ssl) {
if (ssl.getCipher() == nullptr) return MaybeLocal<Object>();
EscapableHandleScope scope(env->isolate());
Local<Object> info = Object::New(env->isolate());
if (info->Set(env->context(),
env->name_string(),
maybeString(env, ssl.getCipherName()))
.IsNothing() ||
info->Set(env->context(),
env->standard_name_string(),
maybeString(env, ssl.getCipherStandardName()))
.IsNothing() ||
info->Set(env->context(),
env->version_string(),
maybeString(env, ssl.getCipherVersion()))
.IsNothing()) {
return MaybeLocal<Object>();
}
return scope.Escape(info);
}
MaybeLocal<Object> GetEphemeralKey(Environment* env, const SSLPointer& ssl) {
CHECK(!ssl.isServer());
EscapableHandleScope scope(env->isolate());
Local<Object> info = Object::New(env->isolate());
EVPKeyPointer key = ssl.getPeerTempKey();
if (!key) return scope.Escape(info);
Local<Context> context = env->context();
int kid = key.id();
switch (kid) {
case EVP_PKEY_DH:
if (info->Set(context, env->type_string(), env->dh_string())
.IsNothing() ||
info->Set(context,
env->size_string(),
Integer::New(env->isolate(), key.bits()))
.IsNothing()) {
return MaybeLocal<Object>();
}
break;
case EVP_PKEY_EC:
case EVP_PKEY_X25519:
case EVP_PKEY_X448:
{
const char* curve_name;
if (kid == EVP_PKEY_EC) {
int nid = ECKeyPointer::GetGroupName(key);
curve_name = OBJ_nid2sn(nid);
} else {
curve_name = OBJ_nid2sn(kid);
}
if (info->Set(context, env->type_string(), env->ecdh_string())
.IsNothing() ||
info->Set(context,
env->name_string(),
OneByteString(env->isolate(), curve_name))
.IsNothing() ||
info->Set(context,
env->size_string(),
Integer::New(env->isolate(), key.bits()))
.IsNothing()) {
return MaybeLocal<Object>();
}
}
break;
}
return scope.Escape(info);
}
MaybeLocal<Object> ECPointToBuffer(Environment* env,
const EC_GROUP* group,
const EC_POINT* point,
point_conversion_form_t form) {
size_t len = EC_POINT_point2oct(group, point, form, nullptr, 0, nullptr);
if (len == 0) {
THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get public key length");
return MaybeLocal<Object>();
}
auto bs = ArrayBuffer::NewBackingStore(
env->isolate(), len, BackingStoreInitializationMode::kUninitialized);
len = EC_POINT_point2oct(group,
point,
form,
reinterpret_cast<unsigned char*>(bs->Data()),
bs->ByteLength(),
nullptr);
if (len == 0) {
THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get public key");
return MaybeLocal<Object>();
}
Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), std::move(bs));
return Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local<Object>());
}
MaybeLocal<Value> GetPeerCert(
Environment* env,
const SSLPointer& ssl,
bool abbreviated,
bool is_server) {
ClearErrorOnReturn clear_error_on_return;
// NOTE: This is because of the odd OpenSSL behavior. On client `cert_chain`
// contains the `peer_certificate`, but on server it doesn't.
X509Pointer cert(is_server ? SSL_get_peer_certificate(ssl.get()) : nullptr);
STACK_OF(X509)* ssl_certs = SSL_get_peer_cert_chain(ssl.get());
if (!cert && (ssl_certs == nullptr || sk_X509_num(ssl_certs) == 0))
return Undefined(env->isolate());
// Short result requested.
if (abbreviated) {
if (cert) {
return X509Certificate::toObject(env, cert.view());
}
return X509Certificate::toObject(env,
X509View(sk_X509_value(ssl_certs, 0)));
}
StackOfX509 peer_certs = CloneSSLCerts(std::move(cert), ssl_certs);
if (peer_certs == nullptr)
return Undefined(env->isolate());
// First and main certificate.
Local<Value> result;
X509View first_cert(sk_X509_value(peer_certs.get(), 0));
CHECK(first_cert);
if (!X509Certificate::toObject(env, first_cert).ToLocal(&result)) return {};
CHECK(result->IsObject());
Local<Object> issuer_chain;
MaybeLocal<Object> maybe_issuer_chain;
maybe_issuer_chain = AddIssuerChainToObject(
&cert, result.As<Object>(), std::move(peer_certs), env);
if (!maybe_issuer_chain.ToLocal(&issuer_chain)) return {};
maybe_issuer_chain =
GetLastIssuedCert(
&cert,
ssl,
issuer_chain,
env);
issuer_chain.Clear();
if (!maybe_issuer_chain.ToLocal(&issuer_chain)) return {};
// Last certificate should be self-signed.
if (cert.view().isIssuedBy(cert.view()) &&
issuer_chain->Set(env->context(), env->issuercert_string(), issuer_chain)
.IsNothing()) {
return {};
}
return result;
}
} // namespace crypto
} // namespace node