%PDF- %PDF-
| Direktori : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/src/crypto/ |
| Current File : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/src/crypto/crypto_common.cc |
#include "crypto/crypto_common.h"
#include "base_object-inl.h"
#include "env-inl.h"
#include "memory_tracker-inl.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 v8::Array;
using v8::ArrayBuffer;
using v8::BackingStore;
using v8::Boolean;
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;
X509Pointer SSL_CTX_get_issuer(SSL_CTX* ctx, X509* cert) {
X509_STORE* store = SSL_CTX_get_cert_store(ctx);
DeleteFnPtr<X509_STORE_CTX, X509_STORE_CTX_free> store_ctx(
X509_STORE_CTX_new());
X509Pointer result;
X509* issuer;
if (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) {
result.reset(issuer);
}
return result;
}
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()));
// All supported versions of TLS/SSL fix the client random to the same size.
constexpr size_t kTlsClientRandomSize = SSL3_RANDOM_SIZE;
unsigned char crandom[kTlsClientRandomSize];
if (keylog_cb == nullptr ||
SSL_get_client_random(ssl.get(), crandom, kTlsClientRandomSize) !=
kTlsClientRandomSize) {
return;
}
std::string line = name;
line += " " + StringBytes::hex_encode(reinterpret_cast<const char*>(crandom),
kTlsClientRandomSize);
line += " " + StringBytes::hex_encode(
reinterpret_cast<const char*>(secret), secretlen);
keylog_cb(ssl.get(), line.c_str());
}
MaybeLocal<Value> GetSSLOCSPResponse(
Environment* env,
SSL* ssl,
Local<Value> default_value) {
const unsigned char* resp;
int len = SSL_get_tlsext_status_ocsp_resp(ssl, &resp);
if (resp == nullptr)
return default_value;
Local<Value> ret;
MaybeLocal<Object> maybe_buffer =
Buffer::Copy(env, reinterpret_cast<const char*>(resp), len);
if (!maybe_buffer.ToLocal(&ret))
return MaybeLocal<Value>();
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<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 == 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<const char*>(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<const char*>(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->ctx().get(), 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<Value> 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<Value> GetValidationErrorCode(Environment* env, int err) {
if (err == 0)
return Undefined(env->isolate());
return OneByteString(env->isolate(), X509ErrorCode(err));
}
MaybeLocal<Value> 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<Object> maybe_cert = X509ToObject(env, cert);
return maybe_cert.FromMaybe<Value>(Local<Value>());
}
Local<Value> ToV8Value(Environment* env, const BIOPointer& bio) {
BUF_MEM* mem;
BIO_get_mem_ptr(bio.get(), &mem);
MaybeLocal<String> ret =
String::NewFromUtf8(
env->isolate(),
mem->data,
NewStringType::kNormal,
mem->length);
CHECK_EQ(BIO_reset(bio.get()), 1);
return ret.FromMaybe(Local<Value>());
}
namespace {
template <typename T>
bool Set(
Local<Context> context,
Local<Object> target,
Local<Value> name,
MaybeLocal<T> maybe_value) {
Local<Value> 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();
}
template <const char* (*getstr)(const SSL_CIPHER* cipher)>
MaybeLocal<Value> GetCipherValue(Environment* env, const SSL_CIPHER* cipher) {
if (cipher == nullptr)
return Undefined(env->isolate());
return OneByteString(env->isolate(), getstr(cipher));
}
constexpr auto GetCipherName = GetCipherValue<SSL_CIPHER_get_name>;
constexpr auto GetCipherStandardName = GetCipherValue<SSL_CIPHER_standard_name>;
constexpr auto GetCipherVersion = GetCipherValue<SSL_CIPHER_get_version>;
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) {
Local<Context> 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<Object> ca_info;
MaybeLocal<Object> maybe_ca_info = X509ToObject(env, ca);
if (!maybe_ca_info.ToLocal(&ca_info))
return MaybeLocal<Object>();
if (!Set<Object>(context, object, env->issuercert_string(), ca_info))
return MaybeLocal<Object>();
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>(object);
}
MaybeLocal<Object> GetLastIssuedCert(
X509Pointer* cert,
const SSLPointer& ssl,
Local<Object> issuer_chain,
Environment* const env) {
Local<Context> context = env->isolate()->GetCurrentContext();
while (X509_check_issued(cert->get(), cert->get()) != X509_V_OK) {
X509Pointer ca;
if (!(ca = SSL_CTX_get_issuer(SSL_get_SSL_CTX(ssl.get()), cert->get())))
break;
Local<Object> ca_info;
MaybeLocal<Object> maybe_ca_info = X509ToObject(env, ca.get());
if (!maybe_ca_info.ToLocal(&ca_info))
return MaybeLocal<Object>();
if (!Set<Object>(context, issuer_chain, env->issuercert_string(), ca_info))
return MaybeLocal<Object>();
issuer_chain = ca_info;
// 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);
}
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 <const char* (*nid2string)(int nid)>
MaybeLocal<Value> GetCurveName(Environment* env, const int nid) {
const char* name = nid2string(nid);
return name != nullptr ?
MaybeLocal<Value>(OneByteString(env->isolate(), name)) :
MaybeLocal<Value>(Undefined(env->isolate()));
}
MaybeLocal<Value> 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<Object>());
}
MaybeLocal<Value> 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<Object> GetPubKey(Environment* env, const RSAPointer& rsa) {
int size = i2d_RSA_PUBKEY(rsa.get(), nullptr);
CHECK_GE(size, 0);
std::unique_ptr<BackingStore> bs;
{
NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
bs = ArrayBuffer::NewBackingStore(env->isolate(), size);
}
unsigned char* serialized = reinterpret_cast<unsigned char*>(bs->Data());
CHECK_GE(i2d_RSA_PUBKEY(rsa.get(), &serialized), 0);
Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), std::move(bs));
return Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local<Object>());
}
MaybeLocal<Value> GetExponentString(
Environment* env,
const BIOPointer& bio,
const BIGNUM* e) {
uint64_t exponent_word = static_cast<uint64_t>(BN_get_word(e));
BIO_printf(bio.get(), "0x%" PRIx64, exponent_word);
return ToV8Value(env, bio);
}
Local<Value> GetBits(Environment* env, const BIGNUM* n) {
return Integer::New(env->isolate(), BN_num_bits(n));
}
MaybeLocal<Value> GetModulusString(
Environment* env,
const BIOPointer& bio,
const BIGNUM* n) {
BN_print(bio.get(), n);
return ToV8Value(env, bio);
}
} // namespace
MaybeLocal<Value> GetRawDERCertificate(Environment* env, X509* cert) {
int size = i2d_X509(cert, nullptr);
std::unique_ptr<BackingStore> bs;
{
NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
bs = ArrayBuffer::NewBackingStore(env->isolate(), size);
}
unsigned char* serialized = reinterpret_cast<unsigned char*>(bs->Data());
CHECK_GE(i2d_X509(cert, &serialized), 0);
Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), std::move(bs));
return Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local<Object>());
}
MaybeLocal<Value> 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.data<unsigned char>());
}
}
return Undefined(env->isolate());
}
MaybeLocal<Value> GetKeyUsage(Environment* env, X509* cert) {
StackOfASN1 eku(static_cast<STACK_OF(ASN1_OBJECT)*>(
X509_get_ext_d2i(cert, NID_ext_key_usage, nullptr, nullptr)));
if (eku) {
const int count = sk_ASN1_OBJECT_num(eku.get());
MaybeStackBuffer<Local<Value>, 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<Value> GetCurrentCipherName(Environment* env,
const SSLPointer& ssl) {
return GetCipherName(env, SSL_get_current_cipher(ssl.get()));
}
MaybeLocal<Value> GetCurrentCipherVersion(Environment* env,
const SSLPointer& ssl) {
return GetCipherVersion(env, SSL_get_current_cipher(ssl.get()));
}
MaybeLocal<Value> 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<Value> GetValidTo(
Environment* env,
X509* cert,
const BIOPointer& bio) {
ASN1_TIME_print(bio.get(), X509_get0_notAfter(cert));
return ToV8Value(env, bio);
}
MaybeLocal<Value> 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<unsigned char>(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<char>(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<const char*>(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<const char*>(name->data), name->length,
true, safe_prefix);
}
// This function emulates the behavior of i2v_GENERAL_NAME in a safer and less
// ambiguous way. "othername:" entries use the GENERAL_NAME_print format.
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<size_t>(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(), "<invalid length=%d>", ip->length);
#else
BIO_printf(out.get(), "<invalid>");
#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) {
// The format that is used here is based on OpenSSL's implementation of
// GENERAL_NAME_print (as of OpenSSL 3.0.1). Earlier versions of Node.js
// instead produced the same format as i2v_GENERAL_NAME, which was somewhat
// awkward, especially when passed to translatePeerCertificate.
bool unicode = true;
const char* prefix = nullptr;
// OpenSSL 1.1.1 does not support othername in GENERAL_NAME_print 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:<unsupported>");
} 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:<unsupported>");
} else if (gen->type == GEN_EDIPARTY) {
// TODO(tniessen): this is what OpenSSL does, implement properly instead
BIO_printf(out.get(), "EdiPartyName:<unsupported>");
} 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<GENERAL_NAMES*>(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<AUTHORITY_INFO_ACCESS*>(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<v8::Value> GetSubjectAltNameString(Environment* env,
X509* cert,
const BIOPointer& bio) {
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<v8::Value> GetInfoAccessString(Environment* env,
X509* cert,
const BIOPointer& bio) {
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<Value> GetIssuerString(Environment* env,
X509* cert,
const BIOPointer& bio) {
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<Value> GetSubject(Environment* env,
X509* cert,
const BIOPointer& bio) {
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 <X509_NAME* get_name(const X509*)>
static MaybeLocal<Value> 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<Object> result =
Object::New(env->isolate(), Null(env->isolate()), nullptr, nullptr, 0);
if (result.IsEmpty()) {
return MaybeLocal<Value>();
}
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<String> v8_name;
if (!String::NewFromUtf8(env->isolate(), type_str).ToLocal(&v8_name)) {
return MaybeLocal<Value>();
}
// 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());
}
auto free_value_str = OnScopeLeave([&]() { OPENSSL_free(value_str); });
Local<String> v8_value;
if (!String::NewFromUtf8(env->isolate(),
reinterpret_cast<const char*>(value_str),
NewStringType::kNormal,
value_str_size)
.ToLocal(&v8_value)) {
return MaybeLocal<Value>();
}
// 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<Value>();
} else if (multiple) {
Local<Value> accum;
if (!result->Get(env->context(), v8_name).ToLocal(&accum)) {
return MaybeLocal<Value>();
}
if (!accum->IsArray()) {
accum = Array::New(env->isolate(), &accum, 1);
if (result->Set(env->context(), v8_name, accum).IsNothing()) {
return MaybeLocal<Value>();
}
}
Local<Array> array = accum.As<Array>();
if (array->Set(env->context(), array->Length(), v8_value).IsNothing()) {
return MaybeLocal<Value>();
}
} else if (result->Set(env->context(), v8_name, v8_value).IsNothing()) {
return MaybeLocal<Value>();
}
}
return result;
}
template <MaybeLocal<Value> (*Get)(Environment* env, const SSL_CIPHER* cipher)>
MaybeLocal<Value> GetCurrentCipherValue(Environment* env,
const SSLPointer& ssl) {
return Get(env, SSL_get_current_cipher(ssl.get()));
}
MaybeLocal<Array> 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<Local<Value>, 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<Object> 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<Array>();
}
ciphers[j++] = obj;
}
Local<Array> ret = Array::New(env->isolate(), ciphers.out(), count);
return scope.Escape(ret);
}
MaybeLocal<Object> GetCipherInfo(Environment* env, const SSLPointer& ssl) {
if (SSL_get_current_cipher(ssl.get()) == nullptr)
return MaybeLocal<Object>();
EscapableHandleScope scope(env->isolate());
Local<Object> info = Object::New(env->isolate());
if (!Set<Value>(env->context(),
info,
env->name_string(),
GetCurrentCipherValue<GetCipherName>(env, ssl)) ||
!Set<Value>(env->context(),
info,
env->standard_name_string(),
GetCurrentCipherValue<GetCipherStandardName>(env, ssl)) ||
!Set<Value>(env->context(),
info,
env->version_string(),
GetCurrentCipherValue<GetCipherVersion>(env, ssl))) {
return MaybeLocal<Object>();
}
return scope.Escape(info);
}
MaybeLocal<Object> GetEphemeralKey(Environment* env, const SSLPointer& ssl) {
CHECK_EQ(SSL_is_server(ssl.get()), 0);
EVP_PKEY* raw_key;
EscapableHandleScope scope(env->isolate());
Local<Object> info = Object::New(env->isolate());
if (!SSL_get_server_tmp_key(ssl.get(), &raw_key))
return scope.Escape(info);
Local<Context> 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<String>(context, info, env->type_string(), env->dh_string()) ||
!Set<Integer>(context,
info,
env->size_string(),
Integer::New(env->isolate(), bits))) {
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) {
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<String>(context,
info,
env->type_string(),
env->ecdh_string()) ||
!Set<String>(context,
info,
env->name_string(),
OneByteString(env->isolate(), curve_name)) ||
!Set<Integer>(context,
info,
env->size_string(),
Integer::New(env->isolate(), bits))) {
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,
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<Object>();
}
std::unique_ptr<BackingStore> bs;
{
NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
bs = ArrayBuffer::NewBackingStore(env->isolate(), len);
}
len = EC_POINT_point2oct(group,
point,
form,
reinterpret_cast<unsigned char*>(bs->Data()),
bs->ByteLength(),
nullptr);
if (len == 0) {
if (error != nullptr) *error = "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;
Local<Object> result;
MaybeLocal<Object> 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<Value>();
}
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<Value>();
Local<Object> issuer_chain;
MaybeLocal<Object> maybe_issuer_chain;
maybe_issuer_chain =
AddIssuerChainToObject(
&cert,
result,
std::move(peer_certs),
env);
if (!maybe_issuer_chain.ToLocal(&issuer_chain))
return MaybeLocal<Value>();
maybe_issuer_chain =
GetLastIssuedCert(
&cert,
ssl,
issuer_chain,
env);
issuer_chain.Clear();
if (!maybe_issuer_chain.ToLocal(&issuer_chain))
return MaybeLocal<Value>();
// Last certificate should be self-signed.
if (X509_check_issued(cert.get(), cert.get()) == X509_V_OK &&
!Set<Object>(env->context(),
issuer_chain,
env->issuercert_string(),
issuer_chain)) {
return MaybeLocal<Value>();
}
return result;
}
MaybeLocal<Object> X509ToObject(
Environment* env,
X509* cert) {
EscapableHandleScope scope(env->isolate());
Local<Context> context = env->context();
Local<Object> info = Object::New(env->isolate());
BIOPointer bio(BIO_new(BIO_s_mem()));
CHECK(bio);
// X509_check_ca() returns a range of values. Only 1 means "is a CA"
auto is_ca = Boolean::New(env->isolate(), 1 == X509_check_ca(cert));
if (!Set<Value>(context,
info,
env->subject_string(),
GetX509NameObject<X509_get_subject_name>(env, cert)) ||
!Set<Value>(context,
info,
env->issuer_string(),
GetX509NameObject<X509_get_issuer_name>(env, cert)) ||
!Set<Value>(context,
info,
env->subjectaltname_string(),
GetSubjectAltNameString(env, cert, bio)) ||
!Set<Value>(context,
info,
env->infoaccess_string(),
GetInfoAccessString(env, cert, bio)) ||
!Set<Boolean>(context, info, env->ca_string(), is_ca)) {
return MaybeLocal<Object>();
}
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<Value>(context,
info,
env->modulus_string(),
GetModulusString(env, bio, n)) ||
!Set<Value>(context, info, env->bits_string(), GetBits(env, n)) ||
!Set<Value>(context,
info,
env->exponent_string(),
GetExponentString(env, bio, e)) ||
!Set<Object>(context,
info,
env->pubkey_string(),
GetPubKey(env, rsa))) {
return MaybeLocal<Object>();
}
} else if (ec) {
const EC_GROUP* group = EC_KEY_get0_group(ec.get());
if (!Set<Value>(context,
info,
env->bits_string(),
GetECGroup(env, group, ec)) ||
!Set<Value>(context,
info,
env->pubkey_string(),
GetECPubKey(env, group, ec))) {
return MaybeLocal<Object>();
}
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<Value>(context,
info,
env->asn1curve_string(),
GetCurveName<OBJ_nid2sn>(env, nid)) ||
!Set<Value>(context,
info,
env->nistcurve_string(),
GetCurveName<EC_curve_nid2nist>(env, nid))) {
return MaybeLocal<Object>();
}
} 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<Value>(context,
info,
env->valid_from_string(),
GetValidFrom(env, cert, bio)) ||
!Set<Value>(context,
info,
env->valid_to_string(),
GetValidTo(env, cert, bio))) {
return MaybeLocal<Object>();
}
// bio is no longer needed
bio.reset();
if (!Set<Value>(context,
info,
env->fingerprint_string(),
GetFingerprintDigest(env, EVP_sha1(), cert)) ||
!Set<Value>(context,
info,
env->fingerprint256_string(),
GetFingerprintDigest(env, EVP_sha256(), cert)) ||
!Set<Value>(context,
info,
env->fingerprint512_string(),
GetFingerprintDigest(env, EVP_sha512(), cert)) ||
!Set<Value>(
context, info, env->ext_key_usage_string(), GetKeyUsage(env, cert)) ||
!Set<Value>(context,
info,
env->serial_number_string(),
GetSerialNumber(env, cert)) ||
!Set<Value>(
context, info, env->raw_string(), GetRawDERCertificate(env, cert))) {
return MaybeLocal<Object>();
}
return scope.Escape(info);
}
} // namespace crypto
} // namespace node