Skip to content

Commit

Permalink
fix(ext/node): exporting rsa public keys (#23596)
Browse files Browse the repository at this point in the history
Initial support for exporting rsa public KeyObject.

Current assumption is that RSA keys are stored in pkcs1 der format in
key storage.

Ref #23471 
Ref #18928
Ref #21124
  • Loading branch information
littledivy committed Apr 29, 2024
1 parent 7d93704 commit b02ffec
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 10 deletions.
2 changes: 2 additions & 0 deletions ext/node/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
22 changes: 21 additions & 1 deletion ext/node/ops/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ use rsa::Oaep;
use rsa::Pkcs1v15Encrypt;
use rsa::RsaPrivateKey;
use rsa::RsaPublicKey;
use spki::EncodePublicKey;

mod cipher;
mod dh;
Expand Down Expand Up @@ -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<String, AnyError> {
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<ToJsBuffer, AnyError> {
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;
Expand Down
10 changes: 6 additions & 4 deletions ext/node/polyfills/internal/crypto/keygen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -766,8 +768,8 @@ export function generateKeyPairSync(
| KeyPairSyncResult<string | Buffer, string | Buffer> {
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;
Expand Down
34 changes: 30 additions & 4 deletions ext/node/polyfills/internal/crypto/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
}
Expand All @@ -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`,
);
}
}
}

Expand Down Expand Up @@ -414,4 +438,6 @@ export default {
prepareSecretKey,
setOwnedKey,
SecretKeyObject,
PrivateKeyObject,
PublicKeyObject,
};
15 changes: 14 additions & 1 deletion tests/unit_node/crypto/crypto_key_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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);
});

0 comments on commit b02ffec

Please sign in to comment.