From 13d7777a6a528ff0b46501db736231d5169cf782 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Wed, 28 Aug 2024 20:56:11 +0530 Subject: [PATCH] fix(ext/node): import RSA JWK keys (#25267) Fixes https://github.com/denoland/deno/issues/24129 --- ext/node/lib.rs | 1 + ext/node/ops/crypto/keys.rs | 73 ++++++++++++++++++++++ ext/node/polyfills/internal/crypto/keys.ts | 28 ++++++++- tests/unit_node/crypto/crypto_key_test.ts | 34 ++++++++++ 4 files changed, 135 insertions(+), 1 deletion(-) diff --git a/ext/node/lib.rs b/ext/node/lib.rs index bf7db14751..3ec2d26bfa 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -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, diff --git a/ext/node/ops/crypto/keys.rs b/ext/node/ops/crypto/keys.rs index 7334fb8eba..cc011dfadd 100644 --- a/ext/node/ops/crypto/keys.rs +++ b/ext/node/ops/crypto/keys.rs @@ -582,6 +582,61 @@ impl KeyObjectHandle { Ok(KeyObjectHandle::AsymmetricPublic(key)) } + pub fn new_rsa_jwk( + jwk: RsaJwkKey, + is_public: bool, + ) -> Result { + 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, + p: Option, + q: Option, +} + +#[op2] +#[cppgc] +pub fn op_node_create_rsa_jwk( + #[serde] jwk: RsaJwkKey, + is_public: bool, +) -> Result { + KeyObjectHandle::new_rsa_jwk(jwk, is_public) +} + #[op2] #[cppgc] pub fn op_node_create_ec_jwk( diff --git a/ext/node/polyfills/internal/crypto/keys.ts b/ext/node/polyfills/internal/crypto/keys.ts index 49a618b65b..c91c23cc3d 100644 --- a/ext/node/polyfills/internal/crypto/keys.ts +++ b/ext/node/polyfills/internal/crypto/keys.ts @@ -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( diff --git a/tests/unit_node/crypto/crypto_key_test.ts b/tests/unit_node/crypto/crypto_key_test.ts index 5dfab3ca42..1f900e84c0 100644 --- a/tests/unit_node/crypto/crypto_key_test.ts +++ b/tests/unit_node/crypto/crypto_key_test.ts @@ -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",