diff --git a/ext/node/lib.rs b/ext/node/lib.rs index c99467d239121..43a5b158e30e4 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -241,6 +241,8 @@ deno_core::extension!(deno_node, ops::crypto::op_node_ecdh_compute_secret, ops::crypto::op_node_ecdh_compute_public_key, ops::crypto::op_node_ecdh_encode_pubkey, + ops::crypto::op_node_export_rsa_public_pem, + ops::crypto::op_node_export_rsa_spki_der, ops::crypto::x509::op_node_x509_parse, ops::crypto::x509::op_node_x509_ca, ops::crypto::x509::op_node_x509_check_email, diff --git a/ext/node/ops/crypto/mod.rs b/ext/node/ops/crypto/mod.rs index ed1b7fc7571d6..f39fb6d10f5dd 100644 --- a/ext/node/ops/crypto/mod.rs +++ b/ext/node/ops/crypto/mod.rs @@ -42,6 +42,7 @@ use rsa::Oaep; use rsa::Pkcs1v15Encrypt; use rsa::RsaPrivateKey; use rsa::RsaPublicKey; +use spki::EncodePublicKey; mod cipher; mod dh; @@ -681,13 +682,32 @@ pub async fn op_node_generate_rsa_async( spawn_blocking(move || generate_rsa(modulus_length, public_exponent)).await? } +#[op2] +#[string] +pub fn op_node_export_rsa_public_pem( + #[buffer] pkcs1_der: &[u8], +) -> Result { + let public_key = RsaPublicKey::from_pkcs1_der(pkcs1_der)?; + let export = public_key.to_public_key_pem(Default::default())?; + Ok(export) +} + +#[op2] +#[serde] +pub fn op_node_export_rsa_spki_der( + #[buffer] pkcs1_der: &[u8], +) -> Result { + let public_key = RsaPublicKey::from_pkcs1_der(pkcs1_der)?; + let export = public_key.to_public_key_der()?.to_vec(); + Ok(export.into()) +} + fn dsa_generate( modulus_length: usize, divisor_length: usize, ) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> { let mut rng = rand::thread_rng(); use dsa::pkcs8::EncodePrivateKey; - use dsa::pkcs8::EncodePublicKey; use dsa::Components; use dsa::KeySize; use dsa::SigningKey; diff --git a/ext/node/polyfills/internal/crypto/keygen.ts b/ext/node/polyfills/internal/crypto/keygen.ts index f3263aecf1228..dd5d5ad7e44bf 100644 --- a/ext/node/polyfills/internal/crypto/keygen.ts +++ b/ext/node/polyfills/internal/crypto/keygen.ts @@ -7,6 +7,8 @@ import { KeyObject } from "ext:deno_node/internal/crypto/keys.ts"; import { kAesKeyLengths } from "ext:deno_node/internal/crypto/util.ts"; import { + PrivateKeyObject, + PublicKeyObject, SecretKeyObject, setOwnedKey, } from "ext:deno_node/internal/crypto/keys.ts"; @@ -564,8 +566,8 @@ export function generateKeyPair( ) => void, ) { createJob(kAsync, type, options).then(([privateKey, publicKey]) => { - privateKey = new KeyObject("private", setOwnedKey(privateKey)); - publicKey = new KeyObject("public", setOwnedKey(publicKey)); + privateKey = new PrivateKeyObject(setOwnedKey(privateKey), { type }); + publicKey = new PublicKeyObject(setOwnedKey(publicKey), { type }); if (typeof options === "object" && options !== null) { const { publicKeyEncoding, privateKeyEncoding } = options as any; @@ -766,8 +768,8 @@ export function generateKeyPairSync( | KeyPairSyncResult { let [privateKey, publicKey] = createJob(kSync, type, options); - privateKey = new KeyObject("private", setOwnedKey(privateKey)); - publicKey = new KeyObject("public", setOwnedKey(publicKey)); + privateKey = new PrivateKeyObject(setOwnedKey(privateKey), { type }); + publicKey = new PublicKeyObject(setOwnedKey(publicKey), { type }); if (typeof options === "object" && options !== null) { const { publicKeyEncoding, privateKeyEncoding } = options as any; diff --git a/ext/node/polyfills/internal/crypto/keys.ts b/ext/node/polyfills/internal/crypto/keys.ts index 4ab8cac4f6572..8cb9ab6906f11 100644 --- a/ext/node/polyfills/internal/crypto/keys.ts +++ b/ext/node/polyfills/internal/crypto/keys.ts @@ -7,6 +7,8 @@ import { op_node_create_private_key, op_node_create_public_key, + op_node_export_rsa_public_pem, + op_node_export_rsa_spki_der, } from "ext:core/ops"; import { @@ -360,7 +362,7 @@ class AsymmetricKeyObject extends KeyObject { } } -class PrivateKeyObject extends AsymmetricKeyObject { +export class PrivateKeyObject extends AsymmetricKeyObject { constructor(handle: unknown, details: unknown) { super("private", handle, details); } @@ -370,13 +372,35 @@ class PrivateKeyObject extends AsymmetricKeyObject { } } -class PublicKeyObject extends AsymmetricKeyObject { +export class PublicKeyObject extends AsymmetricKeyObject { constructor(handle: unknown, details: unknown) { super("public", handle, details); } - export(_options: unknown) { - notImplemented("crypto.PublicKeyObject.prototype.export"); + export(options: unknown) { + const key = KEY_STORE.get(this[kHandle]); + switch (this.asymmetricKeyType) { + case "rsa": + case "rsa-pss": { + switch (options.format) { + case "pem": + return op_node_export_rsa_public_pem(key); + case "der": { + if (options.type == "pkcs1") { + return key; + } else { + return op_node_export_rsa_spki_der(key); + } + } + default: + throw new TypeError(`exporting ${options.type} is not implemented`); + } + } + default: + throw new TypeError( + `exporting ${this.asymmetricKeyType} is not implemented`, + ); + } } } @@ -414,4 +438,6 @@ export default { prepareSecretKey, setOwnedKey, SecretKeyObject, + PrivateKeyObject, + PublicKeyObject, }; diff --git a/tests/unit_node/crypto/crypto_key_test.ts b/tests/unit_node/crypto/crypto_key_test.ts index c7c741f3e9c8f..0136015728fbc 100644 --- a/tests/unit_node/crypto/crypto_key_test.ts +++ b/tests/unit_node/crypto/crypto_key_test.ts @@ -15,7 +15,7 @@ import { } from "node:crypto"; import { promisify } from "node:util"; import { Buffer } from "node:buffer"; -import { assertEquals, assertThrows } from "@std/assert/mod.ts"; +import { assert, assertEquals, assertThrows } from "@std/assert/mod.ts"; const RUN_SLOW_TESTS = Deno.env.get("SLOW_TESTS") === "1"; @@ -402,3 +402,16 @@ SogaIHQjE81ZkmNtU5gM5Q== `jEwckJ/d5GkF/8TTm+wllq2JNghG/m2JYJIW7vS8Vms53zCTTNSSegTSoIVoxWymwTPw2dTtZi41Lg0O271/WvEmQhiWD2dnjz6D/0F4eyn+QUhcmGCadDFyfp7+8x1XOppSw2YB8vL5WCL0QDdp3TAa/rWI0Hn4OftHMa6HPvatkGs+8XlQOGCCfd3TLg+t1UROgpgmetjoAM67mlwxXMGGu/Tr/EbXnnINKeB0iuSmD1FCxlrgFuYWDKxd79n2jZ74FrS/zto+bqWSI5uUa4Ar7yvXtek1Cu1OFM6vgdN9Y6Po2UD9+IT04EhU03LUDY5paYOO8yohz7p7kqHvpA==`, ); }); + +Deno.test("generate rsa export public key", async function () { + const { publicKey } = await generateKeyPairAsync("rsa", { + modulusLength: 2048, + }); + + const spkiPem = publicKey.export({ format: "pem", type: "spki" }); + assert(typeof spkiPem === "string"); + assert(spkiPem.startsWith("-----BEGIN PUBLIC KEY-----")); + + const der = publicKey.export({ format: "der", type: "spki" }); + assert(der instanceof Uint8Array); +});