current commit
261024.6 · tls,tools: update BoringSSL pin and TLS errors
#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::EVPKeyPointer;
using ncrypto::KeyAlgorithm;
using ncrypto::SSLPointer;
using ncrypto::SSLSessionPointer;
using ncrypto::StackOfX509;
using ncrypto::X509Pointer;
using ncrypto::X509View;
using v8::ArrayBuffer;
using v8::BackingStoreInitializationMode;
using v8::DictionaryTemplate;
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) ||
#ifdef OPENSSL_IS_BORINGSSL
      (err == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) ||
#endif
      (err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) ||
      ((err == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT) &&
       !env->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 (OPENSSL_SIZE_T 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 (;;) {
    OPENSSL_SIZE_T 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());

  auto tmpl = env->cipherinfo_template();
  if (tmpl.IsEmpty()) {
    static constexpr std::string_view names[] = {
        "name", "standardName", "version"};
    tmpl = DictionaryTemplate::New(env->isolate(), names);
    env->set_cipherinfo_template(tmpl);
  }

  MaybeLocal<Value> values[] = {
      maybeString(env, ssl.getCipherName()),
      maybeString(env, ssl.getCipherStandardName()),
      maybeString(env, ssl.getCipherVersion()),
  };

  return scope.EscapeMaybe(NewDictionaryInstance(env->context(), tmpl, values));
}

MaybeLocal<Object> GetEphemeralKey(Environment* env, const SSLPointer& ssl) {
  CHECK(!ssl.isServer());

  EscapableHandleScope scope(env->isolate());

  auto tmpl = env->ephemeral_key_template();
  if (tmpl.IsEmpty()) {
    static constexpr std::string_view names[] = {"type", "name", "size"};
    tmpl = DictionaryTemplate::New(env->isolate(), names);
    env->set_ephemeral_key_template(tmpl);
  }

  MaybeLocal<Value> values[] = {
      Undefined(env->isolate()),  // type
      Undefined(env->isolate()),  // name
      Undefined(env->isolate()),  // size
  };

  bool found = false;
  if (EVPKeyPointer key = ssl.getPeerTempKey()) {
    const auto* algorithm = key.getAlgorithm();
    if (algorithm == &KeyAlgorithm::DH) {
      values[0] = env->dh_string();
      values[2] = Integer::New(env->isolate(), key.bits());
      found = true;
    } else if (algorithm == &KeyAlgorithm::EC ||
               algorithm == &KeyAlgorithm::X25519 ||
               algorithm == &KeyAlgorithm::X448) {
      const char* curve_name = nullptr;
      if (algorithm == &KeyAlgorithm::EC) {
        const int nid = ncrypto::Ec::GetCurveId(key);
        if (nid != NID_undef) curve_name = OBJ_nid2sn(nid);
      } else {
        curve_name = algorithm->name();
      }
      if (curve_name != nullptr) {
        values[0] = env->ecdh_string();
        values[1] = OneByteString(env->isolate(), curve_name);
        values[2] = Integer::New(env->isolate(), key.bits());
        found = true;
      }
    }
  }
  if (!found) {
    if (auto name = ssl.getNegotiatedGroup()) {
      values[0] = env->tls_group_string();
      values[1] = OneByteString(env->isolate(), name.value());
    }
  }

  return scope.EscapeMaybe(NewDictionaryInstance(env->context(), tmpl, values));
}

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 ? X509Pointer::PeerFrom(ssl) : X509Pointer());
  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