Repository: denoland/deno
Pull request: fix(ext/node): import RSA JWK keys

Fixes https://github.com/denoland/deno/issues/24129



Changed files:
filename: ext/node/lib.rs
patch: @@ -233,6 +233,7 @@ deno_core::extension!(deno_node,
     ops::crypto::op_node_verify_ed25519,
     ops::crypto::keys::op_node_create_private_key,
     ops::crypto::keys::op_node_create_ed_raw,
+    ops::crypto::keys::op_node_create_rsa_jwk,
     ops::crypto::keys::op_node_create_ec_jwk,
     ops::crypto::keys::op_node_create_public_key,
     ops::crypto::keys::op_node_create_secret_key,

filename: ext/node/ops/crypto/keys.rs
patch: @@ -582,6 +582,61 @@ impl KeyObjectHandle {
     Ok(KeyObjectHandle::AsymmetricPublic(key))
   }
 
+  pub fn new_rsa_jwk(
+    jwk: RsaJwkKey,
+    is_public: bool,
+  ) -> Result<KeyObjectHandle, AnyError> {
+    use base64::prelude::BASE64_URL_SAFE_NO_PAD;
+
+    let n = BASE64_URL_SAFE_NO_PAD.decode(jwk.n.as_bytes())?;
+    let e = BASE64_URL_SAFE_NO_PAD.decode(jwk.e.as_bytes())?;
+
+    if is_public {
+      let public_key = RsaPublicKey::new(
+        rsa::BigUint::from_bytes_be(&n),
+        rsa::BigUint::from_bytes_be(&e),
+      )?;
+
+      Ok(KeyObjectHandle::AsymmetricPublic(AsymmetricPublicKey::Rsa(
+        public_key,
+      )))
+    } else {
+      let d = BASE64_URL_SAFE_NO_PAD.decode(
+        jwk
+          .d
+          .ok_or_else(|| type_error("missing RSA private component"))?
+          .as_bytes(),
+      )?;
+      let p = BASE64_URL_SAFE_NO_PAD.decode(
+        jwk
+          .p
+          .ok_or_else(|| type_error("missing RSA private component"))?
+          .as_bytes(),
+      )?;
+      let q = BASE64_URL_SAFE_NO_PAD.decode(
+        jwk
+          .q
+          .ok_or_else(|| type_error("missing RSA private component"))?
+          .as_bytes(),
+      )?;
+
+      let mut private_key = RsaPrivateKey::from_components(
+        rsa::BigUint::from_bytes_be(&n),
+        rsa::BigUint::from_bytes_be(&e),
+        rsa::BigUint::from_bytes_be(&d),
+        vec![
+          rsa::BigUint::from_bytes_be(&p),
+          rsa::BigUint::from_bytes_be(&q),
+        ],
+      )?;
+      private_key.precompute()?; // precompute CRT params
+
+      Ok(KeyObjectHandle::AsymmetricPrivate(
+        AsymmetricPrivateKey::Rsa(private_key),
+      ))
+    }
+  }
+
   pub fn new_ec_jwk(
     jwk: &JwkEcKey,
     is_public: bool,
@@ -1178,6 +1233,24 @@ pub fn op_node_create_ed_raw(
   KeyObjectHandle::new_ed_raw(curve, key, is_public)
 }
 
+#[derive(serde::Deserialize)]
+pub struct RsaJwkKey {
+  n: String,
+  e: String,
+  d: Option<String>,
+  p: Option<String>,
+  q: Option<String>,
+}
+
+#[op2]
+#[cppgc]
+pub fn op_node_create_rsa_jwk(
+  #[serde] jwk: RsaJwkKey,
+  is_public: bool,
+) -> Result<KeyObjectHandle, AnyError> {
+  KeyObjectHandle::new_rsa_jwk(jwk, is_public)
+}
+
 #[op2]
 #[cppgc]
 pub fn op_node_create_ec_jwk(

filename: ext/node/polyfills/internal/crypto/keys.ts
patch: @@ -16,6 +16,7 @@ import {
   op_node_create_ed_raw,
   op_node_create_private_key,
   op_node_create_public_key,
+  op_node_create_rsa_jwk,
   op_node_create_secret_key,
   op_node_derive_public_key_from_private_key,
   op_node_export_private_key_der,
@@ -324,7 +325,32 @@ function getKeyObjectHandleFromJwk(key, ctx) {
     return op_node_create_ec_jwk(key, isPublic);
   }
 
-  throw new TypeError("rsa jwk imports not implemented");
+  // RSA
+  validateString(key.n, "key.n");
+  validateString(key.e, "key.e");
+
+  const jwk = {
+    kty: key.kty,
+    n: key.n,
+    e: key.e,
+  };
+
+  if (!isPublic) {
+    validateString(key.d, "key.d");
+    validateString(key.p, "key.p");
+    validateString(key.q, "key.q");
+    validateString(key.dp, "key.dp");
+    validateString(key.dq, "key.dq");
+    validateString(key.qi, "key.qi");
+    jwk.d = key.d;
+    jwk.p = key.p;
+    jwk.q = key.q;
+    jwk.dp = key.dp;
+    jwk.dq = key.dq;
+    jwk.qi = key.qi;
+  }
+
+  return op_node_create_rsa_jwk(jwk, isPublic);
 }
 
 export function prepareAsymmetricKey(

filename: tests/unit_node/crypto/crypto_key_test.ts
patch: @@ -440,6 +440,40 @@ Deno.test("create private key with invalid utf-8 string", function () {
   );
 });
 
+Deno.test("RSA JWK import public key", function () {
+  const key = {
+    "kty": "RSA",
+    "alg": "RS256",
+    "n":
+      "5Ddosh0Bze5zy-nQ6gAJFpBfL13muCXrTyKYTps61bmnUxpp3bJnt_2N2MXGfuxBENO0Rbc8DhVPd-lNa4H3XjMwIBdxDAwW32z3pfVr8pHyWxeFtK4SCbvX8B0C6n8ZHigJsvdiCNmoj7_LO_QUzIXmXLFvEXtAqzD_hCr0pJxRIr0BrBjYwL23PkxOYzBR-URcd4Ilji6410Eh9NXycyFzKOcqZ7rjG_PnRyUX1EBZH_PN4RExjJuXYgiqhtU-tDjQFzXLhvwAd5s3ThP9lax27A6MUpjLSKkNy-dG5tlaA0QvECfDzA-5eQjcL_OfvbHlKHQH9zPh-U9Q8gsf3iXmbJrypkalUiTCqnzJu5TgZORSg6zmxNyOCz53YxBHEEaF8yROPwxWDylZfC4fxCRTdoAyFgmFLfMbiepV7AZ24KLj4jfMbGfKpkbPq0xirnSAS-3vbOfkgko5X420AttP8Z1ZBbFSD20Ath_TA9PSHiRCak4AXvOoCZg0t-WuMwzkd_B2V_JZZSTb1yBWrKTL1QzUamqlufjdWuz7M-O2Wkb2cyDSESVNuQyJgDkYb0AOWo0BaN3wbOeT_D4cSrjQoo01xQQCZHQ9SVR4QzUQNAiQcSriqEiptHYhbi6R5_GfGAeMHmlJa4atO2hense0Qk4vDc2fc-sbnQ1jPiE",
+    "e": "AQAB",
+    "key_ops": [
+      "verify",
+    ],
+    "ext": true,
+  };
+
+  const keyObject = createPublicKey({ key, format: "jwk" });
+  const expectedPem = `-----BEGIN PUBLIC KEY-----
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5Ddosh0Bze5zy+nQ6gAJ
+FpBfL13muCXrTyKYTps61bmnUxpp3bJnt/2N2MXGfuxBENO0Rbc8DhVPd+lNa4H3
+XjMwIBdxDAwW32z3pfVr8pHyWxeFtK4SCbvX8B0C6n8ZHigJsvdiCNmoj7/LO/QU
+zIXmXLFvEXtAqzD/hCr0pJxRIr0BrBjYwL23PkxOYzBR+URcd4Ilji6410Eh9NXy
+cyFzKOcqZ7rjG/PnRyUX1EBZH/PN4RExjJuXYgiqhtU+tDjQFzXLhvwAd5s3ThP9
+lax27A6MUpjLSKkNy+dG5tlaA0QvECfDzA+5eQjcL/OfvbHlKHQH9zPh+U9Q8gsf
+3iXmbJrypkalUiTCqnzJu5TgZORSg6zmxNyOCz53YxBHEEaF8yROPwxWDylZfC4f
+xCRTdoAyFgmFLfMbiepV7AZ24KLj4jfMbGfKpkbPq0xirnSAS+3vbOfkgko5X420
+AttP8Z1ZBbFSD20Ath/TA9PSHiRCak4AXvOoCZg0t+WuMwzkd/B2V/JZZSTb1yBW
+rKTL1QzUamqlufjdWuz7M+O2Wkb2cyDSESVNuQyJgDkYb0AOWo0BaN3wbOeT/D4c
+SrjQoo01xQQCZHQ9SVR4QzUQNAiQcSriqEiptHYhbi6R5/GfGAeMHmlJa4atO2he
+nse0Qk4vDc2fc+sbnQ1jPiECAwEAAQ==
+-----END PUBLIC KEY-----
+`;
+
+  const pem = keyObject.export({ format: "pem", type: "spki" });
+  assertEquals(pem, expectedPem);
+});
+
 Deno.test("Ed25519 import jwk public key #1", function () {
   const key = {
     "kty": "OKP",