nodejs/node · #65649

crypto: fix multi-prime RSA JWKs

panva · merged Sep 5, 20267 files · 344 + / 10
deps/ncrypto/ncrypto.cc122 + / 1
@@ -130,6 +130,31 @@ struct OpenSSLBufferDeleter { }; using OpenSSLBufferPointer =     std::unique_ptr<unsigned char, OpenSSLBufferDeleter>;++struct RsaOtherPrimeParamNames {+  const char* factor;+  const char* exponent;+  const char* coefficient;+};++#define RSA_OTHER_PRIME_PARAM_NAMES(prime, coefficient)                        \+  {                                                                            \+    OSSL_PKEY_PARAM_RSA_FACTOR #prime, OSSL_PKEY_PARAM_RSA_EXPONENT #prime,    \+        OSSL_PKEY_PARAM_RSA_COEFFICIENT #coefficient                           \+  }++constexpr std::array<RsaOtherPrimeParamNames, 8> kRsaOtherPrimeParamNames = {{+    RSA_OTHER_PRIME_PARAM_NAMES(3, 2),+    RSA_OTHER_PRIME_PARAM_NAMES(4, 3),+    RSA_OTHER_PRIME_PARAM_NAMES(5, 4),+    RSA_OTHER_PRIME_PARAM_NAMES(6, 5),+    RSA_OTHER_PRIME_PARAM_NAMES(7, 6),+    RSA_OTHER_PRIME_PARAM_NAMES(8, 7),+    RSA_OTHER_PRIME_PARAM_NAMES(9, 8),+    RSA_OTHER_PRIME_PARAM_NAMES(10, 9),+}};++#undef RSA_OTHER_PRIME_PARAM_NAMES #endif  static constexpr int kX509NameFlagsRFC2253WithinUtf8JSON =@@ -3081,6 +3106,19 @@ EVPKeyPointer EVPKeyPointer::NewRSA(const Rsa& rsa) {             bld.get(), OSSL_PKEY_PARAM_RSA_COEFFICIENT1, private_key.qi) != 1) {       return {};     }++    const auto other_prime_infos = rsa.getOtherPrimeInfos();+    if (other_prime_infos.size() > kRsaOtherPrimeParamNames.size()) return {};+    for (size_t i = 0; i < other_prime_infos.size(); i++) {+      const auto& info = other_prime_infos[i];+      const auto& names = kRsaOtherPrimeParamNames[i];+      if (info.r == nullptr || info.d == nullptr || info.t == nullptr ||+          OSSL_PARAM_BLD_push_BN(bld.get(), names.factor, info.r) != 1 ||+          OSSL_PARAM_BLD_push_BN(bld.get(), names.exponent, info.d) != 1 ||+          OSSL_PARAM_BLD_push_BN(bld.get(), names.coefficient, info.t) != 1) {+        return {};+      }+    }     selection = EVP_PKEY_KEYPAIR;   } @@ -6134,6 +6172,11 @@ DataPointer CipherImpl(const EVPKeyPointer& key, } }  // namespace +Rsa::OtherPrimeInfoPointer::OtherPrimeInfoPointer(BignumPointer&& r,+                                                  BignumPointer&& d,+                                                  BignumPointer&& t)+    : r(r.release()), d(d.release()), t(t.release()) {}+ #if NCRYPTO_USE_OPENSSL3_PROVIDER namespace { int DigestAlgorithmIdentifierToNid(const unsigned char* data, size_t size) {@@ -6362,6 +6405,19 @@ Rsa::Rsa(const EVP_PKEY* pkey) : Rsa() {     return;   } +  for (const auto& names : kRsaOtherPrimeParamNames) {+    OtherPrimeInfoPointer info;+    if (!GetOptionalPKeyBnParam(pkey, names.factor, &info.r) ||+        !GetOptionalPKeyBnParam(pkey, names.exponent, &info.d) ||+        !GetOptionalPKeyBnParam(pkey, names.coefficient, &info.t)) {+      return;+    }++    if (!info.r && !info.d && !info.t) break;+    if (!info.r || !info.d || !info.t) return;+    other_prime_infos_.push_back(std::move(info));+  }+   if (type == EVP_PKEY_RSA_PSS) {     MarkPopErrorOnReturn pop_errors;     PssParams params;@@ -6400,6 +6456,35 @@ const Rsa::PrivateKey Rsa::getPrivateKey() const { #endif } +const Rsa::OtherPrimeInfos Rsa::getOtherPrimeInfos() const {+  OtherPrimeInfos infos;+#if NCRYPTO_USE_OPENSSL3_PROVIDER+  infos.reserve(other_prime_infos_.size());+  for (const auto& info : other_prime_infos_) {+    infos.push_back({info.r.get(), info.d.get(), info.t.get()});+  }+#elif NCRYPTO_USE_LEGACY_OPENSSL+  if (rsa_ == nullptr) return infos;+  const int count = RSA_get_multi_prime_extra_count(rsa_);+  if (count <= 0) return infos;++  std::vector<const BIGNUM*> factors(count);+  std::vector<const BIGNUM*> exponents(count);+  std::vector<const BIGNUM*> coefficients(count);+  if (RSA_get0_multi_prime_factors(rsa_, factors.data()) != 1 ||+      RSA_get0_multi_prime_crt_params(+          rsa_, exponents.data(), coefficients.data()) != 1) {+    return {};+  }++  infos.reserve(count);+  for (int i = 0; i < count; i++) {+    infos.push_back({factors[i], exponents[i], coefficients[i]});+  }+#endif+  return infos;+}+ const std::optional<Rsa::PssParams> Rsa::getPssParams() const { #if NCRYPTO_USE_OPENSSL3_PROVIDER   return pss_params_;@@ -6501,15 +6586,20 @@ bool Rsa::setPrivateKey(BignumPointer&& d,                         BignumPointer&& p,                         BignumPointer&& dp,                         BignumPointer&& dq,-                        BignumPointer&& qi) {+                        BignumPointer&& qi,+                        OtherPrimeInfoPointers&& other_prime_infos) { #if NCRYPTO_USE_OPENSSL3_PROVIDER   if (!d || !q || !p || !dp || !dq || !qi) return false;+  for (const auto& info : other_prime_infos) {+    if (!info.r || !info.d || !info.t) return false;+  }   d_.reset(d.release());   q_.reset(q.release());   p_.reset(p.release());   dp_.reset(dp.release());   dq_.reset(dq.release());   qi_.reset(qi.release());+  other_prime_infos_ = std::move(other_prime_infos);   rsa_ = n_ != nullptr && e_ != nullptr;   return rsa_; #else@@ -6531,6 +6621,37 @@ bool Rsa::setPrivateKey(BignumPointer&& d,   dp.release();   dq.release();   qi.release();++#if NCRYPTO_USE_LEGACY_OPENSSL+  if (!other_prime_infos.empty()) {+    std::vector<BIGNUM*> factors;+    std::vector<BIGNUM*> exponents;+    std::vector<BIGNUM*> coefficients;+    factors.reserve(other_prime_infos.size());+    exponents.reserve(other_prime_infos.size());+    coefficients.reserve(other_prime_infos.size());+    for (const auto& info : other_prime_infos) {+      if (!info.r || !info.d || !info.t) return false;+      factors.push_back(info.r.get());+      exponents.push_back(info.d.get());+      coefficients.push_back(info.t.get());+    }+    if (RSA_set0_multi_prime_params(const_cast<RSA*>(rsa_),+                                    factors.data(),+                                    exponents.data(),+                                    coefficients.data(),+                                    static_cast<int>(factors.size())) != 1) {+      return false;+    }+    for (auto& info : other_prime_infos) {+      info.r.release();+      info.d.release();+      info.t.release();+    }+  }+#else+  if (!other_prime_infos.empty()) return false;+#endif   return true; #endif }
deps/ncrypto/ncrypto.h21 + / 1
@@ -724,6 +724,23 @@ class Rsa final {     const BIGNUM* dq;     const BIGNUM* qi;   };+  struct OtherPrimeInfo {+    const BIGNUM* r;+    const BIGNUM* d;+    const BIGNUM* t;+  };+  struct OtherPrimeInfoPointer {+    OtherPrimeInfoPointer() = default;+    OtherPrimeInfoPointer(BignumPointer&& r,+                          BignumPointer&& d,+                          BignumPointer&& t);++    DeleteFnPtr<BIGNUM, BN_clear_free> r;+    DeleteFnPtr<BIGNUM, BN_clear_free> d;+    DeleteFnPtr<BIGNUM, BN_clear_free> t;+  };+  using OtherPrimeInfos = std::vector<OtherPrimeInfo>;+  using OtherPrimeInfoPointers = std::vector<OtherPrimeInfoPointer>;   struct PssParams {     std::string_view digest = "sha1";     std::optional<std::string_view> mgf1_digest = "sha1";@@ -732,6 +749,7 @@ class Rsa final {    const PublicKey getPublicKey() const;   const PrivateKey getPrivateKey() const;+  const OtherPrimeInfos getOtherPrimeInfos() const;   const std::optional<PssParams> getPssParams() const;    bool setPublicKey(BignumPointer&& n, BignumPointer&& e);@@ -740,7 +758,8 @@ class Rsa final {                      BignumPointer&& p,                      BignumPointer&& dp,                      BignumPointer&& dq,-                     BignumPointer&& qi);+                     BignumPointer&& qi,+                     OtherPrimeInfoPointers&& other_prime_infos = {});    using CipherParams = Cipher::CipherParams; @@ -765,6 +784,7 @@ class Rsa final {   DeleteFnPtr<BIGNUM, BN_clear_free> dp_;   DeleteFnPtr<BIGNUM, BN_clear_free> dq_;   DeleteFnPtr<BIGNUM, BN_clear_free> qi_;+  OtherPrimeInfoPointers other_prime_infos_;   std::optional<PssParams> pss_params_; #else   OSSL3_CONST RSA* rsa_;
src/crypto/crypto_rsa.cc99 + / 8
@@ -22,6 +22,7 @@ using ncrypto::EVPKeyPointer; #if NCRYPTO_USE_LEGACY_KEY_TYPES using ncrypto::RSAPointer; #endif+using v8::Array; using v8::ArrayBuffer; using v8::BackingStoreInitializationMode; using v8::FunctionCallbackInfo;@@ -39,6 +40,8 @@ using v8::Value;  namespace crypto { namespace {+constexpr uint32_t kMaxRsaOtherPrimeInfos = 8;+ bool IsRsaPssDigestEncodable(const Digest& digest) { #if NCRYPTO_USE_OPENSSL3_PROVIDER   const int nid = EVP_MD_type(digest.get());@@ -339,6 +342,29 @@ bool ExportJWKRsaKey(Environment* env,             .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;@@ -348,12 +374,13 @@ 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) ||-      !n_value->IsString() ||-      !e_value->IsString()) {+      !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 {};   }@@ -364,6 +391,10 @@ KeyObjectData ImportJWKRsaKey(Environment* env, Local<Object> jwk) {   }    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_OPENSSL3_PROVIDER   ncrypto::Rsa rsa_view;@@ -417,19 +448,79 @@ KeyObjectData ImportJWKRsaKey(Environment* env, Local<Object> jwk) {     ByteSource dq = ByteSource::FromEncodedString(env, dq_value.As<String>());     ByteSource qi = ByteSource::FromEncodedString(env, qi_value.As<String>()); -    if (!rsa_view.setPrivateKey(-            d.ToBN(), q.ToBN(), p.ToBN(), dp.ToBN(), dq.ToBN(), qi.ToBN())) {+    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 {};     } -    // Verify that n == p * q.+    // Verify that n is the product of all prime factors.     const auto& pub = rsa_view.getPublicKey();     const auto& priv = rsa_view.getPrivateKey();-    auto pq = BignumPointer::New();+    auto product = BignumPointer::New();     BN_CTX* ctx = BN_CTX_new();-    bool n_valid = ctx && pq && BN_mul(pq.get(), priv.p, priv.q, ctx) == 1 &&-                   BN_cmp(pq.get(), pub.n) == 0;+    bool n_valid =+        ctx && product && BN_mul(product.get(), priv.p, priv.q, ctx) == 1;+    for (const auto& info : rsa_view.getOtherPrimeInfos()) {+      auto next = BignumPointer::New();+      if (!n_valid || !next ||+          BN_mul(next.get(), product.get(), info.r, ctx) != 1) {+        n_valid = false;+        break;+      }+      product = std::move(next);+    }+    n_valid = n_valid && BN_cmp(product.get(), pub.n) == 0;     BN_CTX_free(ctx);     if (!n_valid) {       THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK RSA key");
src/env_properties.h3 + / 0
@@ -240,12 +240,15 @@   V(jwk_n_string, "n")                                                         \   V(jwk_oct_string, "oct")                                                     \   V(jwk_okp_string, "OKP")                                                     \+  V(jwk_oth_string, "oth")                                                     \   V(jwk_p_string, "p")                                                         \   V(jwk_priv_string, "priv")                                                   \   V(jwk_pub_string, "pub")                                                     \   V(jwk_q_string, "q")                                                         \   V(jwk_qi_string, "qi")                                                       \+  V(jwk_r_string, "r")                                                         \   V(jwk_rsa_string, "RSA")                                                     \+  V(jwk_t_string, "t")                                                         \   V(jwk_x_string, "x")                                                         \   V(jwk_y_string, "y")                                                         \   V(kill_signal_string, "killSignal")                                          \
test/fixtures/keys/Makefile8 + / 0
@@ -70,6 +70,7 @@ all: \   rsa_spkac.spkac \   rsa_spkac_invalid.spkac \   rsa_private_2048.pem \+  rsa_private_2048_3_primes.pem \   rsa_private_4096.pem \   rsa_public_2048.pem \   rsa_public_4096.pem \@@ -927,6 +928,13 @@ rsa_spkac_invalid.spkac: rsa_spkac.spkac rsa_private_2048.pem: 	openssl genrsa -out rsa_private_2048.pem 2048 +rsa_private_2048_3_primes.pem:+	openssl genpkey \+		-algorithm RSA \+		-pkeyopt rsa_keygen_bits:2048 \+		-pkeyopt rsa_keygen_primes:3 \+		-out rsa_private_2048_3_primes.pem+ rsa_private_4096.pem: 	openssl genrsa -out rsa_private_4096.pem 4096 
test/fixtures/keys/rsa_private_2048_3_primes.pemadded29 + / 0
@@ -0,0 +1,29 @@+-----BEGIN PRIVATE KEY-----+MIIE8wIBADANBgkqhkiG9w0BAQEFAASCBN0wggTZAgEBAoIBAQC1Lu9a9Hlj8/OA+J3I2h/1aFYV59SxWb1KzbuRb2nAoOVWCqG8ettkT7kGN6qva3C/oCw2pYPIlRxpX+0Vve0fNRqpSCCXcnN9vlKpU0BrHfpIHuXSleT7vwSq70UV5YwjFhL+g5wcCUVCuz+Fkd8dYb0keqVApSKkcydepk5/BwBwQdkfibs3RuIjROkgIbDKSXrm1lFinqyCBTG+umLcaL9XMhaJRxsVIVqkCM6hs+kEXhIBvlFIqcycECKEV3l71nV0VM3RwlbORIjx+iHpSvNEUixGR0Rwx4WdJNU4GiUJGZACKj+RrLX+mh0odOl5r8Up9TkTgWp4UEdUx+GHbQ6lIrAgMBAAECggEAA8mZY1Nc/kPpQTKR5qXtG+b7RVNku+HT3sBgsbLdSVn5+eaO/vuqfmxUN9cOEGC0mKzmf7XyDU1grKs7LltrYtUpVlGpSPVWjmSTMvauuKDiu+Jqd35+irM+pQerj/AYOs9Lv2lLzzA1ivcSmB0uR2gNyiVvVxBAxydNXiBwCn+v8M++qosREPVN9eEQ+1j+ZWvZ2j/AkT6EDdOl0qbhkLDkaLgDkO4uhAZfNKjwmuVkBoW+BI9bUiQv9vzsSp7AIVXwd8D+NaX0VuDpbcIq/tIfrl2OxwUDuTkYYsvwlIwvAkZ4+yMl57x2YjRJu2u4f4t+fuagqCisbbbE+LywM5r+YEQJWB/Z/lOoAXXCRSBSGhI29+zI+pXWiK4UneitEKTJpdR7i38Var03aciK+q64z4gWDNSBRzYq5eyVWosv0f9WkO+lQFRfOy0HM68dg2KYDCIVnquCwwgaDcCVgZ7lxyKqwirCDYL9VFxRNiBIOXuMLdl+6c4YNpdPTrp/AILBeINJYKBYjpro6fPe0YHcxJpMNf5Izaow17VVAqJKDmXbca7y+SclsgBR6mAnFf6WfFM5rAlYBe2nHp74lphwEVH2tEkyvVFXrotemkv8uyzWliF2w+SbtC2iTlGQCNmFSxvu1J92B9+AB574aI5r6dget+2+zw4uvt2oizi+8b8e09dgsK+PY9DUyB9BQJWBTsT4uPAvW3DYlijyOMsOcV3Vg+rH1AMJ09CPTSwwapYLykMpGJA+pSDPcA8fNrdDNRV9TULRDiyQRhLC+n1tcqVumKFISXEWOIBjmCKTKs9A/9IsZ6EC+VgQG607Arq/ztxJFVKHY4RW0v8Ofx9Q0h+GDcKqqxDlymZfrDsdKJjC1QTofRUR++JJiwshahF2jBtlsPwEjX8/InboaF2f2VlIxzxe2+GpzWeRgzIN9yMIIBDDCCAQgC+VgOChKyWwWrVMfjdfwtFnMA/OjwK4KBtR8VWwAapaiJPFhZRM0jh8EMsC03X7xm9+AbL3hfE3eLU2mRLxw1pPh0RfK2xtKTCZWjgnQDhPznMUUvF+FYxHAlYCZbRvGa1U+Zffy7n7m2Q4+tTD2b8uLZ/jcjess4awvcOs6JQ4tWr07dCroaXPTlahCcBRZdLUK+uRcGvIDc/3L5vTMwpMK8ghIARrbzf3cYXSpl7eOT4wJWAuPXcrk1PE1YuMidPa2v+3OshgH2no6BeVW2vChBDnK9IBstWII/AhjQzDHjbj9S8xjLZA6bqHH89uXc+0Z/2+hF8lLDsruEceLVckmk7wb1eFAvns/XA=+-----END PRIVATE KEY-----
test/parallel/test-crypto-rsa-multiprime-jwk.jsadded62 + / 0
@@ -0,0 +1,62 @@+'use strict';++const common = require('../common');+if (!common.hasCrypto)+  common.skip('missing crypto');++const assert = require('assert');+const fixtures = require('../common/fixtures');+const { hasFIPS } = require('../common/crypto');+const {+  createPrivateKey,+} = require('crypto');+const { subtle } = globalThis.crypto;++if (process.features.openssl_is_boringssl)+  common.skip('multi-prime RSA is not available with BoringSSL');+if (hasFIPS())+  common.skip('multi-prime RSA is not available in FIPS mode');++const privateKey = createPrivateKey(+  fixtures.readKey('rsa_private_2048_3_primes.pem'));+const pkcs8 = privateKey.export({ format: 'der', type: 'pkcs8' });+const jwk = privateKey.export({ format: 'jwk' });++assert.strictEqual(jwk.oth.length, 1);+assert.deepStrictEqual(Object.keys(jwk.oth[0]), ['r', 'd', 't']);++const importedKey = createPrivateKey({ key: jwk, format: 'jwk' });+assert.deepStrictEqual(importedKey.export({ format: 'jwk' }), jwk);+assert.deepStrictEqual(+  importedKey.export({ format: 'der', type: 'pkcs8' }),+  pkcs8);++for (const field of ['r', 'd', 't']) {+  const invalidJwk = {+    ...jwk,+    oth: [{ ...jwk.oth[0] }],+  };+  delete invalidJwk.oth[0][field];+  assert.throws(+    () => createPrivateKey({ key: invalidJwk, format: 'jwk' }),+    { code: 'ERR_CRYPTO_INVALID_JWK' });+}++(async () => {+  const algorithm = { name: 'RSA-PSS', hash: 'SHA-256' };+  const cryptoKey = await subtle.importKey(+    'pkcs8', pkcs8, algorithm, true, ['sign']);+  const exportedJwk = await subtle.exportKey('jwk', cryptoKey);++  const exportedKeyMaterial = { ...exportedJwk };+  delete exportedKeyMaterial.key_ops;+  delete exportedKeyMaterial.ext;+  delete exportedKeyMaterial.alg;+  assert.deepStrictEqual(exportedKeyMaterial, jwk);++  const importedCryptoKey = await subtle.importKey(+    'jwk', exportedJwk, algorithm, true, ['sign']);+  assert.deepStrictEqual(+    Buffer.from(await subtle.exportKey('pkcs8', importedCryptoKey)),+    pkcs8);+})().then(common.mustCall());