mirror of
https://github.com/denoland/deno.git
synced 2025-01-21 04:52:26 -05:00
fix(ext/node): support private key export in JWK format (#27325)
Closes https://github.com/denoland/deno/issues/26643 --------- Co-authored-by: Divy Srivastava <dj.srivastava23@gmail.com>
This commit is contained in:
parent
7b491a28df
commit
1cd36009b0
4 changed files with 128 additions and 1 deletions
|
@ -262,6 +262,7 @@ deno_core::extension!(deno_node,
|
|||
ops::crypto::keys::op_node_derive_public_key_from_private_key,
|
||||
ops::crypto::keys::op_node_dh_keys_generate_and_export,
|
||||
ops::crypto::keys::op_node_export_private_key_der,
|
||||
ops::crypto::keys::op_node_export_private_key_jwk,
|
||||
ops::crypto::keys::op_node_export_private_key_pem,
|
||||
ops::crypto::keys::op_node_export_public_key_der,
|
||||
ops::crypto::keys::op_node_export_public_key_pem,
|
||||
|
|
|
@ -26,6 +26,7 @@ use rsa::pkcs1::DecodeRsaPrivateKey as _;
|
|||
use rsa::pkcs1::DecodeRsaPublicKey;
|
||||
use rsa::pkcs1::EncodeRsaPrivateKey as _;
|
||||
use rsa::pkcs1::EncodeRsaPublicKey;
|
||||
use rsa::traits::PrivateKeyParts;
|
||||
use rsa::traits::PublicKeyParts;
|
||||
use rsa::RsaPrivateKey;
|
||||
use rsa::RsaPublicKey;
|
||||
|
@ -255,6 +256,16 @@ impl EcPrivateKey {
|
|||
EcPrivateKey::P384(key) => EcPublicKey::P384(key.public_key()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_jwk(&self) -> Result<JwkEcKey, AsymmetricPrivateKeyJwkError> {
|
||||
match self {
|
||||
EcPrivateKey::P224(_) => {
|
||||
Err(AsymmetricPrivateKeyJwkError::UnsupportedJwkEcCurveP224)
|
||||
}
|
||||
EcPrivateKey::P256(key) => Ok(key.to_jwk()),
|
||||
EcPrivateKey::P384(key) => Ok(key.to_jwk()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://oidref.com/
|
||||
|
@ -1107,6 +1118,16 @@ fn bytes_to_b64(bytes: &[u8]) -> String {
|
|||
BASE64_URL_SAFE_NO_PAD.encode(bytes)
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum AsymmetricPrivateKeyJwkError {
|
||||
#[error("key is not an asymmetric private key")]
|
||||
KeyIsNotAsymmetricPrivateKey,
|
||||
#[error("Unsupported JWK EC curve: P224")]
|
||||
UnsupportedJwkEcCurveP224,
|
||||
#[error("jwk export not implemented for this key type")]
|
||||
JwkExportNotImplementedForKeyType,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum AsymmetricPublicKeyJwkError {
|
||||
#[error("key is not an asymmetric public key")]
|
||||
|
@ -1328,7 +1349,73 @@ pub enum AsymmetricPrivateKeyDerError {
|
|||
UnsupportedKeyType(String),
|
||||
}
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/rfc7518#section-6.3.2
|
||||
fn rsa_private_to_jwk(key: &RsaPrivateKey) -> deno_core::serde_json::Value {
|
||||
let n = key.n();
|
||||
let e = key.e();
|
||||
let d = key.d();
|
||||
let p = &key.primes()[0];
|
||||
let q = &key.primes()[1];
|
||||
let dp = key.dp();
|
||||
let dq = key.dq();
|
||||
let qi = key.crt_coefficient();
|
||||
let oth = &key.primes()[2..];
|
||||
|
||||
let mut obj = deno_core::serde_json::json!({
|
||||
"kty": "RSA",
|
||||
"n": bytes_to_b64(&n.to_bytes_be()),
|
||||
"e": bytes_to_b64(&e.to_bytes_be()),
|
||||
"d": bytes_to_b64(&d.to_bytes_be()),
|
||||
"p": bytes_to_b64(&p.to_bytes_be()),
|
||||
"q": bytes_to_b64(&q.to_bytes_be()),
|
||||
"dp": dp.map(|dp| bytes_to_b64(&dp.to_bytes_be())),
|
||||
"dq": dq.map(|dq| bytes_to_b64(&dq.to_bytes_be())),
|
||||
"qi": qi.map(|qi| bytes_to_b64(&qi.to_bytes_be())),
|
||||
});
|
||||
|
||||
if !oth.is_empty() {
|
||||
obj["oth"] = deno_core::serde_json::json!(oth
|
||||
.iter()
|
||||
.map(|o| o.to_bytes_be())
|
||||
.collect::<Vec<_>>());
|
||||
}
|
||||
|
||||
obj
|
||||
}
|
||||
|
||||
impl AsymmetricPrivateKey {
|
||||
fn export_jwk(
|
||||
&self,
|
||||
) -> Result<deno_core::serde_json::Value, AsymmetricPrivateKeyJwkError> {
|
||||
match self {
|
||||
AsymmetricPrivateKey::Rsa(key) => Ok(rsa_private_to_jwk(key)),
|
||||
AsymmetricPrivateKey::RsaPss(key) => Ok(rsa_private_to_jwk(&key.key)),
|
||||
AsymmetricPrivateKey::Ec(key) => {
|
||||
let jwk = key.to_jwk()?;
|
||||
Ok(deno_core::serde_json::json!(jwk))
|
||||
}
|
||||
AsymmetricPrivateKey::X25519(static_secret) => {
|
||||
let bytes = static_secret.to_bytes();
|
||||
|
||||
Ok(deno_core::serde_json::json!({
|
||||
"kty": "OKP",
|
||||
"crv": "X25519",
|
||||
"d": bytes_to_b64(&bytes),
|
||||
}))
|
||||
}
|
||||
AsymmetricPrivateKey::Ed25519(key) => {
|
||||
let bytes = key.to_bytes();
|
||||
|
||||
Ok(deno_core::serde_json::json!({
|
||||
"kty": "OKP",
|
||||
"crv": "Ed25519",
|
||||
"d": bytes_to_b64(&bytes),
|
||||
}))
|
||||
}
|
||||
_ => Err(AsymmetricPrivateKeyJwkError::JwkExportNotImplementedForKeyType),
|
||||
}
|
||||
}
|
||||
|
||||
fn export_der(
|
||||
&self,
|
||||
typ: &str,
|
||||
|
@ -2329,6 +2416,28 @@ pub fn op_node_export_private_key_pem(
|
|||
Ok(String::from_utf8(out).expect("invalid pem is not possible"))
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ExportPrivateKeyJwkError {
|
||||
#[error(transparent)]
|
||||
AsymmetricPublicKeyJwk(#[from] AsymmetricPrivateKeyJwkError),
|
||||
#[error("very large data")]
|
||||
VeryLargeData,
|
||||
#[error(transparent)]
|
||||
Der(#[from] der::Error),
|
||||
}
|
||||
|
||||
#[op2]
|
||||
#[serde]
|
||||
pub fn op_node_export_private_key_jwk(
|
||||
#[cppgc] handle: &KeyObjectHandle,
|
||||
) -> Result<deno_core::serde_json::Value, ExportPrivateKeyJwkError> {
|
||||
let private_key = handle
|
||||
.as_private_key()
|
||||
.ok_or(AsymmetricPrivateKeyJwkError::KeyIsNotAsymmetricPrivateKey)?;
|
||||
|
||||
Ok(private_key.export_jwk()?)
|
||||
}
|
||||
|
||||
#[op2]
|
||||
#[buffer]
|
||||
pub fn op_node_export_private_key_der(
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
op_node_create_secret_key,
|
||||
op_node_derive_public_key_from_private_key,
|
||||
op_node_export_private_key_der,
|
||||
op_node_export_private_key_jwk,
|
||||
op_node_export_private_key_pem,
|
||||
op_node_export_public_key_der,
|
||||
op_node_export_public_key_jwk,
|
||||
|
@ -791,7 +792,7 @@ export class PrivateKeyObject extends AsymmetricKeyObject {
|
|||
|
||||
export(options: JwkKeyExportOptions | KeyExportOptions<KeyFormat>) {
|
||||
if (options && options.format === "jwk") {
|
||||
notImplemented("jwk private key export not implemented");
|
||||
return op_node_export_private_key_jwk(this[kHandle]);
|
||||
}
|
||||
const {
|
||||
format,
|
||||
|
|
|
@ -700,3 +700,19 @@ Deno.test("generateKeyPair promisify", async () => {
|
|||
assert(publicKey.startsWith("-----BEGIN PUBLIC KEY-----"));
|
||||
assert(privateKey.startsWith("-----BEGIN PRIVATE KEY-----"));
|
||||
});
|
||||
|
||||
Deno.test("RSA export private JWK", function () {
|
||||
// @ts-ignore @types/node broken
|
||||
const { privateKey, publicKey } = generateKeyPairSync("rsa", {
|
||||
modulusLength: 4096,
|
||||
publicKeyEncoding: {
|
||||
format: "jwk",
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
format: "jwk",
|
||||
},
|
||||
});
|
||||
|
||||
assertEquals((privateKey as any).kty, "RSA");
|
||||
assertEquals((privateKey as any).n, (publicKey as any).n);
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue