current commit
261029.13 · src: make crypto backend checks explicit
#include "crypto/crypto_rsa.h"
#include "async_wrap-inl.h"
#include "base_object-inl.h"
#include "crypto/crypto_bio.h"
#include "crypto/crypto_keys.h"
#include "crypto/crypto_util.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "threadpoolwork-inl.h"
#include "v8.h"

#include <openssl/bn.h>
#include <openssl/rsa.h>

namespace node {

using ncrypto::BignumPointer;
using ncrypto::DataPointer;
using ncrypto::Digest;
using ncrypto::EVPKeyCtxPointer;
using ncrypto::EVPKeyPointer;
using ncrypto::KeyAlgorithm;
#if NCRYPTO_USE_LEGACY_KEY_TYPES
using ncrypto::RSAPointer;
#endif
using v8::Array;
using v8::ArrayBuffer;
using v8::BackingStoreInitializationMode;
using v8::FunctionCallbackInfo;
using v8::Int32;
using v8::Integer;
using v8::JustVoid;
using v8::Local;
using v8::Maybe;
using v8::Nothing;
using v8::Number;
using v8::Object;
using v8::String;
using v8::Uint32;
using v8::Value;

namespace crypto {
namespace {
constexpr uint32_t kMaxRsaOtherPrimeInfos = 8;

bool IsRsaPssDigestEncodable(const Digest& digest) {
#if NCRYPTO_USE_OPENSSL_PROVIDER
  const int nid = EVP_MD_type(digest.get());
  if (nid == NID_undef) return false;

  const ASN1_OBJECT* object = OBJ_nid2obj(nid);
  return object != nullptr && OBJ_length(object) > 0;
#elif NCRYPTO_USE_BORINGSSL
  static_cast<void>(digest);
  return true;
#else
#error "Unsupported crypto backend"
#endif
}
}  // namespace

EVPKeyCtxPointer RsaKeyGenTraits::Setup(RsaKeyPairGenConfig* params) {
  auto ctx = EVPKeyCtxPointer::NewFromAlgorithm(
      params->params.variant == kKeyVariantRSA_PSS ? KeyAlgorithm::RSA_PSS
                                                   : KeyAlgorithm::RSA);

  if (!ctx.initForKeygen() ||
      !ctx.setRsaKeygenBits(params->params.modulus_bits)) {
    return {};
  }

  // 0x10001 is the default RSA exponent.
  if (params->params.exponent != EVPKeyCtxPointer::kDefaultRsaExponent) {
    auto bn = BignumPointer::New();
    if (!bn.setWord(params->params.exponent) ||
        !ctx.setRsaKeygenPubExp(std::move(bn))) {
      return {};
    }
  }

  if (params->params.variant == kKeyVariantRSA_PSS) {
    if (params->params.md && (!IsRsaPssDigestEncodable(params->params.md) ||
                              !ctx.setRsaPssKeygenMd(params->params.md))) {
      return {};
    }

    // OpenSSL does not default the MGF1 hash algorithm to the RSA-PSS
    // hashAlgorithm as recommended by RFC 8017, so set it explicitly.
    auto& mgf1_md = params->params.mgf1_md;
    if (!mgf1_md && params->params.md) {
      mgf1_md = params->params.md;
    }

    if (mgf1_md && (!IsRsaPssDigestEncodable(mgf1_md) ||
                    !ctx.setRsaPssKeygenMgf1Md(mgf1_md))) {
      return {};
    }

    int saltlen = params->params.saltlen;
    if (saltlen < 0 && params->params.md) {
      saltlen = params->params.md.size();
    }

    if (saltlen >= 0 && !ctx.setRsaPssSaltlen(saltlen)) {
      return {};
    }
  }

  return ctx;
}

// Input parameters to the RsaKeyGenJob:
// For key variants RSA-OAEP and RSA-SSA-PKCS1-v1_5
//   1. CryptoJobMode
//   2. Key Variant
//   3. Modulus Bits
//   4. Public Exponent
//   5. Public Format
//   6. Public Type
//   7. Private Format
//   8. Private Type
//   9. Cipher
//   10. Passphrase
//
// For RSA-PSS variant
//   1. CryptoJobMode
//   2. Key Variant
//   3. Modulus Bits
//   4. Public Exponent
//   5. Digest
//   6. mgf1 Digest
//   7. Salt length
//   8. Public Format
//   9. Public Type
//   10. Private Format
//   11. Private Type
//   12. Cipher
//   13. Passphrase
Maybe<void> RsaKeyGenTraits::AdditionalConfig(
    CryptoJobMode mode,
    const FunctionCallbackInfo<Value>& args,
    unsigned int* offset,
    RsaKeyPairGenConfig* params) {
  Environment* env = Environment::GetCurrent(args);

  CHECK(args[*offset]->IsUint32());  // Variant
  CHECK(args[*offset + 1]->IsUint32());  // Modulus bits
  CHECK(args[*offset + 2]->IsUint32());  // Exponent

  params->params.variant =
      static_cast<RSAKeyVariant>(args[*offset].As<Uint32>()->Value());

  CHECK_IMPLIES(params->params.variant != kKeyVariantRSA_PSS,
                static_cast<unsigned int>(args.Length()) >= *offset + 3);
  CHECK_IMPLIES(params->params.variant == kKeyVariantRSA_PSS,
                static_cast<unsigned int>(args.Length()) >= *offset + 6);

  params->params.modulus_bits = args[*offset + 1].As<Uint32>()->Value();
  params->params.exponent = args[*offset + 2].As<Uint32>()->Value();

  *offset += 3;

  if (params->params.variant == kKeyVariantRSA_PSS) {
    if (!args[*offset]->IsUndefined()) {
      CHECK(args[*offset]->IsString());
      Utf8Value digest(env->isolate(), args[*offset]);
      params->params.md = Digest::FromName(*digest);
      if (!params->params.md) {
        THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Invalid digest: %s", digest);
        return Nothing<void>();
      }
    }

    if (!args[*offset + 1]->IsUndefined()) {
      CHECK(args[*offset + 1]->IsString());
      Utf8Value digest(env->isolate(), args[*offset + 1]);
      params->params.mgf1_md = Digest::FromName(*digest);
      if (!params->params.mgf1_md) {
        THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Invalid MGF1 digest: %s", digest);
        return Nothing<void>();
      }
    }

    if (!args[*offset + 2]->IsUndefined()) {
      CHECK(args[*offset + 2]->IsInt32());
      params->params.saltlen = args[*offset + 2].As<Int32>()->Value();
      if (params->params.saltlen < 0) {
        THROW_ERR_OUT_OF_RANGE(
          env,
          "salt length is out of range");
        return Nothing<void>();
      }
    }

    *offset += 3;
  }

  return JustVoid();
}

namespace {
using Cipher_t = DataPointer(const EVPKeyPointer& key,
                             const ncrypto::Rsa::CipherParams& params,
                             const ncrypto::Buffer<const void> in);

template <Cipher_t cipher>
WebCryptoCipherStatus RSA_Cipher(Environment* env,
                                 const KeyObjectData& key_data,
                                 const RSACipherConfig& params,
                                 const ByteSource& in,
                                 ByteSource* out) {
  CHECK_NE(key_data.GetKeyType(), kKeyTypeSecret);
  const auto& m_pkey = key_data.GetAsymmetricKey();
  const ncrypto::Rsa::CipherParams nparams{
      .padding = params.padding,
      .digest = params.digest,
      .mgf1_digest = params.digest,
      .label = params.label,
  };

  auto data = cipher(m_pkey, nparams, in);
  if (!data) return WebCryptoCipherStatus::FAILED;
  DCHECK(!data.isSecure());

  *out = ByteSource::Allocated(data.release());
  return WebCryptoCipherStatus::OK;
}
}  // namespace

RSACipherConfig::RSACipherConfig(RSACipherConfig&& other) noexcept
    : label(std::move(other.label)),
      padding(other.padding),
      digest(other.digest) {}

void RSACipherConfig::MemoryInfo(MemoryTracker* tracker) const {
  tracker->TraitTrackInline(label, "label");
}

Maybe<void> RSACipherTraits::AdditionalConfig(
    CryptoJobMode mode,
    const FunctionCallbackInfo<Value>& args,
    unsigned int offset,
    WebCryptoCipherMode cipher_mode,
    RSACipherConfig* params) {
  Environment* env = Environment::GetCurrent(args);

  params->padding = RSA_PKCS1_OAEP_PADDING;

  CHECK(args[offset]->IsUint32());
  RSAKeyVariant variant =
      static_cast<RSAKeyVariant>(args[offset].As<Uint32>()->Value());

  switch (variant) {
    case kKeyVariantRSA_OAEP: {
      CHECK(args[offset + 1]->IsString());  // digest
      Utf8Value digest(env->isolate(), args[offset + 1]);
      params->digest = Digest::FromName(*digest);
      if (!params->digest) {
        THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Invalid digest: %s", digest);
        return Nothing<void>();
      }

      if (IsAnyBufferSource(args[offset + 2])) {
        ArrayBufferOrViewContents<char> label(args[offset + 2]);
        if (!label.CheckSizeInt32()) [[unlikely]] {
          THROW_ERR_OUT_OF_RANGE(env, "label is too big");
          return Nothing<void>();
        }
        params->label = label.ToCopy();
      }
      break;
    }
    default:
      THROW_ERR_CRYPTO_INVALID_KEYTYPE(env);
      return Nothing<void>();
  }

  return JustVoid();
}

WebCryptoCipherStatus RSACipherTraits::DoCipher(Environment* env,
                                                const KeyObjectData& key_data,
                                                WebCryptoCipherMode cipher_mode,
                                                const RSACipherConfig& params,
                                                const ByteSource& in,
                                                ByteSource* out) {
  switch (cipher_mode) {
    case kWebCryptoCipherEncrypt:
      CHECK_EQ(key_data.GetKeyType(), kKeyTypePublic);
      return RSA_Cipher<ncrypto::Rsa::encrypt>(env, key_data, params, in, out);
    case kWebCryptoCipherDecrypt:
      CHECK_EQ(key_data.GetKeyType(), kKeyTypePrivate);
      return RSA_Cipher<ncrypto::Rsa::decrypt>(env, key_data, params, in, out);
  }
  return WebCryptoCipherStatus::FAILED;
}

bool ExportJWKRsaKey(Environment* env,
                     const KeyObjectData& key,
                     Local<Object> target) {
  const auto& m_pkey = key.GetAsymmetricKey();

  const ncrypto::Rsa rsa = m_pkey;
  if (!rsa ||
      !target
           ->DefineOwnProperty(
               env->context(), env->jwk_kty_string(), env->jwk_rsa_string())
           .FromMaybe(false)) {
    return false;
  }

  auto pub_key = rsa.getPublicKey();

  if (SetEncodedValue(env, target, env->jwk_n_string(), pub_key.n)
          .IsNothing() ||
      SetEncodedValue(env, target, env->jwk_e_string(), pub_key.e)
          .IsNothing()) {
    return false;
  }

  if (key.GetKeyType() == kKeyTypePrivate) {
    auto pvt_key = rsa.getPrivateKey();
    if (pub_key.d == nullptr || pvt_key.p == nullptr || pvt_key.q == nullptr ||
        pvt_key.dp == nullptr || pvt_key.dq == nullptr ||
        pvt_key.qi == nullptr) {
      THROW_ERR_CRYPTO_OPERATION_FAILED(env,
                                        "Failed to export RSA private key");
      return false;
    }
    if (SetEncodedValue(env, target, env->jwk_d_string(), pub_key.d)
            .IsNothing() ||
        SetEncodedValue(env, target, env->jwk_p_string(), pvt_key.p)
            .IsNothing() ||
        SetEncodedValue(env, target, env->jwk_q_string(), pvt_key.q)
            .IsNothing() ||
        SetEncodedValue(env, target, env->jwk_dp_string(), pvt_key.dp)
            .IsNothing() ||
        SetEncodedValue(env, target, env->jwk_dq_string(), pvt_key.dq)
            .IsNothing() ||
        SetEncodedValue(env, target, env->jwk_qi_string(), pvt_key.qi)
            .IsNothing()) {
      return false;
    }

    const auto other_prime_infos = rsa.getOtherPrimeInfos();
    if (!other_prime_infos.empty()) {
      const uint32_t count = static_cast<uint32_t>(other_prime_infos.size());
      Local<Array> oth = Array::New(env->isolate(), count);
      for (uint32_t i = 0; i < count; i++) {
        const auto& info = other_prime_infos[i];
        Local<Object> item = Object::New(env->isolate());
        if (SetEncodedValue(env, item, env->jwk_r_string(), info.r)
                .IsNothing() ||
            SetEncodedValue(env, item, env->jwk_d_string(), info.d)
                .IsNothing() ||
            SetEncodedValue(env, item, env->jwk_t_string(), info.t)
                .IsNothing() ||
            !oth->Set(env->context(), i, item).FromMaybe(false)) {
          return false;
        }
      }
      if (!target->DefineOwnProperty(env->context(), env->jwk_oth_string(), oth)
               .FromMaybe(false)) {
        return false;
      }
    }
  }

  return true;
}

KeyObjectData ImportJWKRsaKey(Environment* env, Local<Object> jwk) {
  Local<Value> n_value;
  Local<Value> e_value;
  Local<Value> d_value;
  Local<Value> oth_value;

  if (!jwk->Get(env->context(), env->jwk_n_string()).ToLocal(&n_value) ||
      !jwk->Get(env->context(), env->jwk_e_string()).ToLocal(&e_value) ||
      !jwk->Get(env->context(), env->jwk_d_string()).ToLocal(&d_value) ||
      !jwk->Get(env->context(), env->jwk_oth_string()).ToLocal(&oth_value) ||
      !n_value->IsString() || !e_value->IsString()) {
    THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK RSA key");
    return {};
  }

  if (!d_value->IsUndefined() && !d_value->IsString()) {
    THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK RSA key");
    return {};
  }

  KeyType type = d_value->IsString() ? kKeyTypePrivate : kKeyTypePublic;
  if (type == kKeyTypePublic && !oth_value->IsUndefined()) {
    THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK RSA key");
    return {};
  }

#if NCRYPTO_USE_OPENSSL_PROVIDER
  ncrypto::Rsa rsa_view;
#elif NCRYPTO_USE_BORINGSSL
  RSAPointer rsa(RSA_new());
  if (!rsa) {
    THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Unable to create RSA pointer");
    return {};
  }

  ncrypto::Rsa rsa_view(rsa.get());
#else
#error "Unsupported crypto backend"
#endif

  ByteSource n = ByteSource::FromEncodedString(env, n_value.As<String>());
  ByteSource e = ByteSource::FromEncodedString(env, e_value.As<String>());

  if (!rsa_view.setPublicKey(n.ToBN(), e.ToBN())) {
    THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK RSA key");
    return {};
  }

  if (type == kKeyTypePrivate) {
    Local<Value> p_value;
    Local<Value> q_value;
    Local<Value> dp_value;
    Local<Value> dq_value;
    Local<Value> qi_value;

    if (!jwk->Get(env->context(), env->jwk_p_string()).ToLocal(&p_value) ||
        !jwk->Get(env->context(), env->jwk_q_string()).ToLocal(&q_value) ||
        !jwk->Get(env->context(), env->jwk_dp_string()).ToLocal(&dp_value) ||
        !jwk->Get(env->context(), env->jwk_dq_string()).ToLocal(&dq_value) ||
        !jwk->Get(env->context(), env->jwk_qi_string()).ToLocal(&qi_value)) {
      THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK RSA key");
      return {};
    }

    if (!p_value->IsString() ||
        !q_value->IsString() ||
        !dp_value->IsString() ||
        !dq_value->IsString() ||
        !qi_value->IsString()) {
      THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK RSA key");
      return {};
    }

    ByteSource d = ByteSource::FromEncodedString(env, d_value.As<String>());
    ByteSource q = ByteSource::FromEncodedString(env, q_value.As<String>());
    ByteSource p = ByteSource::FromEncodedString(env, p_value.As<String>());
    ByteSource dp = ByteSource::FromEncodedString(env, dp_value.As<String>());
    ByteSource dq = ByteSource::FromEncodedString(env, dq_value.As<String>());
    ByteSource qi = ByteSource::FromEncodedString(env, qi_value.As<String>());

    ncrypto::Rsa::OtherPrimeInfoPointers other_prime_infos;
    if (!oth_value->IsUndefined()) {
      if (!oth_value->IsArray()) {
        THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK RSA key");
        return {};
      }

      Local<Array> oth = oth_value.As<Array>();
      const uint32_t length = oth->Length();
      if (length == 0 || length > kMaxRsaOtherPrimeInfos) {
        THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK RSA key");
        return {};
      }
      other_prime_infos.reserve(length);
      for (uint32_t i = 0; i < length; i++) {
        Local<Value> item_value;
        Local<Value> r_value;
        Local<Value> other_d_value;
        Local<Value> t_value;
        if (!oth->Get(env->context(), i).ToLocal(&item_value) ||
            !item_value->IsObject()) {
          THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK RSA key");
          return {};
        }

        Local<Object> item = item_value.As<Object>();
        if (!item->Get(env->context(), env->jwk_r_string()).ToLocal(&r_value) ||
            !item->Get(env->context(), env->jwk_d_string())
                 .ToLocal(&other_d_value) ||
            !item->Get(env->context(), env->jwk_t_string()).ToLocal(&t_value) ||
            !r_value->IsString() || !other_d_value->IsString() ||
            !t_value->IsString()) {
          THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK RSA key");
          return {};
        }

        other_prime_infos.push_back({
            ByteSource::FromEncodedString(env, r_value.As<String>()).ToBN(),
            ByteSource::FromEncodedString(env, other_d_value.As<String>())
                .ToBN(),
            ByteSource::FromEncodedString(env, t_value.As<String>()).ToBN(),
        });
      }
    }

    if (!rsa_view.setPrivateKey(d.ToBN(),
                                q.ToBN(),
                                p.ToBN(),
                                dp.ToBN(),
                                dq.ToBN(),
                                qi.ToBN(),
                                std::move(other_prime_infos))) {
      THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK RSA key");
      return {};
    }

    if (!rsa_view.checkPrimeProduct()) {
      THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK RSA key");
      return {};
    }
  }

#if NCRYPTO_USE_OPENSSL_PROVIDER
  auto pkey = EVPKeyPointer::NewRSA(rsa_view);
#elif NCRYPTO_USE_BORINGSSL
  auto pkey = EVPKeyPointer::NewRSA(std::move(rsa));
#else
#error "Unsupported crypto backend"
#endif
  if (!pkey) {
    THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Unable to create key pointer");
    return {};
  }

  return KeyObjectData::CreateAsymmetric(type, std::move(pkey));
}

bool GetRsaKeyDetail(Environment* env,
                     const KeyObjectData& key,
                     Local<Object> target) {
  const auto& m_pkey = key.GetAsymmetricKey();

  const auto rsa = ncrypto::Rsa::PublicOnly(m_pkey);
  if (!rsa) return false;

  auto pub_key = rsa.getPublicKey();

  if (target
          ->Set(env->context(),
                env->modulus_length_string(),
                Number::New(
                    env->isolate(),
                    static_cast<double>(BignumPointer::GetBitCount(pub_key.n))))
          .IsNothing()) {
    return false;
  }

  auto public_exponent = ArrayBuffer::NewBackingStore(
      env->isolate(),
      BignumPointer::GetByteCount(pub_key.e),
      BackingStoreInitializationMode::kUninitialized);
  CHECK_EQ(BignumPointer::EncodePaddedInto(
               pub_key.e,
               static_cast<unsigned char*>(public_exponent->Data()),
               public_exponent->ByteLength()),
           public_exponent->ByteLength());

  if (target
          ->Set(env->context(),
                env->public_exponent_string(),
                ArrayBuffer::New(env->isolate(), std::move(public_exponent)))
          .IsNothing()) {
    return false;
  }

  if (m_pkey.isA(KeyAlgorithm::RSA_PSS)) {
    // An absent RSASSA-PSS-params sequence means the key is unrestricted.
    // An empty sequence restricts the key to the default parameter values.
    auto maybe_params = rsa.getPssParams();
    if (maybe_params.has_value()) {
      auto& params = maybe_params.value();
      if (target
              ->Set(env->context(),
                    env->hash_algorithm_string(),
                    OneByteString(env->isolate(), params.digest))
              .IsNothing()) {
        return false;
      }

      // If, for some reason, the MGF is not MGF1, then the MGF1 hash function
      // is intentionally not added to the object.
      if (params.mgf1_digest.has_value()) {
        auto digest = params.mgf1_digest.value();
        if (target
                ->Set(env->context(),
                      env->mgf1_hash_algorithm_string(),
                      OneByteString(env->isolate(), digest))
                .IsNothing()) {
          return false;
        }
      }

      if (target
              ->Set(env->context(),
                    env->salt_length_string(),
                    Integer::New(env->isolate(), params.salt_length))
              .IsNothing()) {
        return false;
      }
    }
  }

  return true;
}

namespace RSAAlg {
void Initialize(Environment* env, Local<Object> target) {
  RSAKeyPairGenJob::Initialize(env, target);
  RSACipherJob::Initialize(env, target);

  NODE_DEFINE_CONSTANT(target, kKeyVariantRSA_SSA_PKCS1_v1_5);
  NODE_DEFINE_CONSTANT(target, kKeyVariantRSA_PSS);
  NODE_DEFINE_CONSTANT(target, kKeyVariantRSA_OAEP);
}

void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
  RSAKeyPairGenJob::RegisterExternalReferences(registry);
  RSACipherJob::RegisterExternalReferences(registry);
}
}  // namespace RSAAlg
}  // namespace crypto
}  // namespace node