current commit
261025.11 · crypto: remove legacy OpenSSL code paths
#include "crypto/crypto_pkcs12.h"
#include "crypto/crypto_context.h"
#include "crypto/crypto_keys.h"
#include "crypto/crypto_util.h"
#include "crypto/crypto_x509.h"
#include "env-inl.h"
#include "ncrypto.h"
#include "node_errors.h"
#include "util-inl.h"
#include "v8.h"

#include <openssl/pkcs12.h>

#include <string>

namespace node {

using ncrypto::BIOPointer;
using v8::Array;
using v8::ArrayBufferView;
using v8::FunctionCallbackInfo;
using v8::Local;
using v8::LocalVector;
using v8::Object;
using v8::Value;

namespace crypto {

// Parses a PKCS#12 bundle from `bio`. See crypto_pkcs12.h; shared with
// SecureContext::LoadPKCS12(), which backs the TLS `pfx` option.
PKCS12ParseResult ParsePKCS12Bundle(const BIOPointer& bio, const char* pass) {
  using PKCS12Pointer = ncrypto::DeleteFnPtr<PKCS12, PKCS12_free>;

  ncrypto::ClearErrorOnReturn clear_error_on_return;

  if (!bio) {
    return PKCS12ParseResult(PKCS12ParseError::FAILED, ERR_get_error());
  }

  PKCS12* p12_ptr = nullptr;
  if (!d2i_PKCS12_bio(bio.get(), &p12_ptr) || p12_ptr == nullptr) {
    return PKCS12ParseResult(PKCS12ParseError::NOT_RECOGNIZED, ERR_get_error());
  }
  PKCS12Pointer p12(p12_ptr);

  EVP_PKEY* pkey_ptr = nullptr;
  X509* cert_ptr = nullptr;
  STACK_OF(X509)* ca_ptr = nullptr;

  if (!PKCS12_parse(p12.get(), pass, &pkey_ptr, &cert_ptr, &ca_ptr)) {
    unsigned long err = ERR_get_error();  // NOLINT(runtime/int)
#ifdef OPENSSL_IS_BORINGSSL
    // BoringSSL's d2i_PKCS12() only copies the input; it does no ASN.1
    // validation, so malformed input gets this far rather than failing above.
    // PKCS8_R_BAD_PKCS12_DATA is raised only for structural failures -- a
    // wrong passphrase is reported as PKCS8_R_INCORRECT_PASSWORD instead --
    // so it means the same thing the d2i failure does on OpenSSL.
    if (ERR_GET_LIB(err) == ERR_LIB_PKCS8 &&
        ERR_GET_REASON(err) == PKCS8_R_BAD_PKCS12_DATA) {
      return PKCS12ParseResult(PKCS12ParseError::NOT_RECOGNIZED, err);
    }
#endif
#ifndef OPENSSL_IS_BORINGSSL
    // OpenSSL reports algorithms that moved to the legacy provider as a
    // bare "unsupported" error.
    if (ERR_GET_REASON(err) == ERR_R_UNSUPPORTED) {
      return PKCS12ParseResult(PKCS12ParseError::UNSUPPORTED_ALGORITHM, err);
    }
#endif
    return PKCS12ParseResult(PKCS12ParseError::FAILED, err);
  }

  PKCS12Contents contents;
  contents.key.reset(pkey_ptr);
  contents.cert.reset(cert_ptr);
  contents.ca.reset(ca_ptr);
  return PKCS12ParseResult(std::move(contents));
}

namespace {

// Returns [ keyHandle | null, certificate | null,
//           [ ...additionalCertificates ] ].
void ParsePKCS12(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);

  CHECK_GE(args.Length(), 2);

  BIOPointer bio(LoadBIO(env, args[0]));
  if (!bio) {
    return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
                                             "Unable to read PKCS#12 data");
  }

  // PKCS12_parse() takes a NUL-terminated C string; a view is not one, so copy
  // the bytes out.
  std::string pass_storage;
  const char* pass = nullptr;
  if (!args[1]->IsUndefined()) {
    THROW_AND_RETURN_IF_NOT_BUFFER(env, args[1], "passphrase");
    Local<ArrayBufferView> abv = args[1].As<ArrayBufferView>();
    size_t len = abv->ByteLength();
    pass_storage.resize(len);
    if (len > 0) abv->CopyContents(pass_storage.data(), len);
    // PKCS12_parse() has no length parameter, so an embedded NUL byte would
    // truncate the passphrase and open the bundle with only the bytes that
    // precede it. Reject it rather than silently accepting a shorter
    // passphrase than the caller supplied. The JS layer checks this too;
    // this is the backstop.
    if (pass_storage.find('\0') != std::string::npos) {
      return THROW_ERR_INVALID_ARG_VALUE(
          env, "passphrase must not contain null bytes");
    }
    pass = pass_storage.c_str();
  }

  auto parsed = ParsePKCS12Bundle(bio, pass);

  if (!parsed) {
    switch (*parsed.error) {
      case PKCS12ParseError::NOT_RECOGNIZED:
        return THROW_ERR_CRYPTO_OPERATION_FAILED(
            env, "Input is not a valid PKCS#12 structure");
      case PKCS12ParseError::UNSUPPORTED_ALGORITHM:
        return THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(
            env,
            "PKCS#12 bundle uses an unsupported algorithm; it may require "
            "the OpenSSL legacy provider");
      case PKCS12ParseError::FAILED:
        return ThrowCryptoError(env,
                                parsed.openssl_error.value_or(0),
                                "Unable to parse PKCS#12 data");
    }
  }

  Local<Value> key_value = v8::Null(env->isolate());
  if (parsed.value.key) {
    auto data = KeyObjectData::CreateAsymmetric(KeyType::kKeyTypePrivate,
                                                std::move(parsed.value.key));
    Local<Object> handle;
    if (!KeyObjectHandle::Create(env, data).ToLocal(&handle)) return;
    key_value = handle;
  }

  Local<Value> cert_value = v8::Null(env->isolate());
  if (parsed.value.cert) {
    Local<Object> cert;
    if (!X509Certificate::New(env, std::move(parsed.value.cert))
             .ToLocal(&cert)) {
      return;
    }
    cert_value = cert;
  }

  const int ca_count = parsed.value.ca ? sk_X509_num(parsed.value.ca.get()) : 0;
  LocalVector<Value> ca_values(env->isolate());
  ca_values.reserve(ca_count > 0 ? ca_count : 0);

  for (int i = 0; i < ca_count; i++) {
    X509* ca = sk_X509_value(parsed.value.ca.get(), i);
    if (ca == nullptr) continue;
    // New() takes ownership, so hand it a reference of its own rather than
    // the stack's.
    X509_up_ref(ca);
    ncrypto::X509Pointer owned(ca);
    Local<Object> obj;
    if (!X509Certificate::New(env, std::move(owned)).ToLocal(&obj)) return;
    ca_values.push_back(obj);
  }

  Local<Value> result[] = {
      key_value,
      cert_value,
      Array::New(env->isolate(), ca_values.data(), ca_values.size()),
  };

  args.GetReturnValue().Set(
      Array::New(env->isolate(), result, arraysize(result)));
}

}  // anonymous namespace

void PKCS12Parser::Initialize(Environment* env, Local<Object> target) {
  SetMethodNoSideEffect(env->context(), target, "parsePKCS12", ParsePKCS12);
}

void PKCS12Parser::RegisterExternalReferences(
    ExternalReferenceRegistry* registry) {
  registry->Register(ParsePKCS12);
}

}  // namespace crypto
}  // namespace node