Skip to content

Commit bfdca5b

Browse files
authored
feat(ext/crypto): import and export p521 keys (#25789)
Towards #13449
1 parent b155084 commit bfdca5b

File tree

9 files changed

+218
-388
lines changed

9 files changed

+218
-388
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ rustls-webpki = "0.102"
163163
rustyline = "=13.0.0"
164164
saffron = "=0.1.0"
165165
scopeguard = "1.2.0"
166+
sec1 = "0.7"
166167
serde = { version = "1.0.149", features = ["derive"] }
167168
serde_bytes = "0.11"
168169
serde_json = "1.0.85"

ext/crypto/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ p521 = "0.13.3"
3333
rand.workspace = true
3434
ring = { workspace = true, features = ["std"] }
3535
rsa.workspace = true
36+
sec1.workspace = true
3637
serde.workspace = true
3738
serde_bytes.workspace = true
3839
sha1.workspace = true

ext/crypto/export_key.rs

+28-6
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,9 @@ fn export_key_ec(
254254
point.as_ref().to_vec()
255255
}
256256
EcNamedCurve::P521 => {
257-
return Err(data_error("Unsupported named curve"))
257+
let point = key_data.as_ec_public_key_p521()?;
258+
259+
point.as_ref().to_vec()
258260
}
259261
};
260262
Ok(ExportKeyResult::Raw(subject_public_key.into()))
@@ -272,7 +274,9 @@ fn export_key_ec(
272274
point.as_ref().to_vec()
273275
}
274276
EcNamedCurve::P521 => {
275-
return Err(data_error("Unsupported named curve"))
277+
let point = key_data.as_ec_public_key_p521()?;
278+
279+
point.as_ref().to_vec()
276280
}
277281
};
278282

@@ -285,9 +289,10 @@ fn export_key_ec(
285289
oid: elliptic_curve::ALGORITHM_OID,
286290
parameters: Some((&p384::NistP384::OID).into()),
287291
},
288-
EcNamedCurve::P521 => {
289-
return Err(data_error("Unsupported named curve"))
290-
}
292+
EcNamedCurve::P521 => AlgorithmIdentifierOwned {
293+
oid: elliptic_curve::ALGORITHM_OID,
294+
parameters: Some((&p521::NistP521::OID).into()),
295+
},
291296
};
292297

293298
let alg_id = match algorithm {
@@ -351,7 +356,24 @@ fn export_key_ec(
351356
))
352357
}
353358
}
354-
EcNamedCurve::P521 => Err(data_error("Unsupported named curve")),
359+
EcNamedCurve::P521 => {
360+
let point = key_data.as_ec_public_key_p521()?;
361+
let coords = point.coordinates();
362+
363+
if let p521::elliptic_curve::sec1::Coordinates::Uncompressed { x, y } =
364+
coords
365+
{
366+
Ok(ExportKeyResult::JwkPublicEc {
367+
x: bytes_to_b64(x),
368+
y: bytes_to_b64(y),
369+
})
370+
} else {
371+
Err(custom_error(
372+
"DOMExceptionOperationError",
373+
"failed to decode public key",
374+
))
375+
}
376+
}
355377
},
356378
ExportKeyFormat::JwkPrivate => {
357379
let private_key = key_data.as_ec_private_key()?;

ext/crypto/import_key.rs

+17-60
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,12 @@ use deno_core::JsBuffer;
77
use deno_core::ToJsBuffer;
88
use elliptic_curve::pkcs8::PrivateKeyInfo;
99
use p256::pkcs8::EncodePrivateKey;
10-
use ring::signature::EcdsaKeyPair;
1110
use rsa::pkcs1::UintRef;
1211
use rsa::pkcs8::der::Encode;
1312
use serde::Deserialize;
1413
use serde::Serialize;
1514
use spki::der::Decode;
1615

17-
use crate::key::CryptoNamedCurve;
1816
use crate::shared::*;
1917

2018
#[derive(Deserialize)]
@@ -45,7 +43,9 @@ pub enum KeyData {
4543
y: String,
4644
},
4745
JwkPrivateEc {
46+
#[allow(dead_code)]
4847
x: String,
48+
#[allow(dead_code)]
4949
y: String,
5050
d: String,
5151
},
@@ -543,9 +543,7 @@ fn import_key_ec_jwk(
543543
raw_data: RustRawKeyData::Public(point_bytes.into()),
544544
})
545545
}
546-
KeyData::JwkPrivateEc { d, x, y } => {
547-
jwt_b64_int_or_err!(private_d, &d, "invalid JWK private key");
548-
let point_bytes = import_key_ec_jwk_to_point(x, y, named_curve)?;
546+
KeyData::JwkPrivateEc { d, .. } => {
549547
let pkcs8_der = match named_curve {
550548
EcNamedCurve::P256 => {
551549
let d = decode_b64url_to_field_bytes::<p256::NistP256>(&d)?;
@@ -562,27 +560,14 @@ fn import_key_ec_jwk(
562560
.map_err(|_| data_error("invalid JWK private key"))?
563561
}
564562
EcNamedCurve::P521 => {
565-
return Err(data_error("Unsupported named curve"))
566-
}
567-
};
563+
let d = decode_b64url_to_field_bytes::<p521::NistP521>(&d)?;
564+
let pk = p521::SecretKey::from_bytes(&d)?;
568565

569-
// Import using ring, to validate key
570-
let key_alg = match named_curve {
571-
EcNamedCurve::P256 => CryptoNamedCurve::P256.into(),
572-
EcNamedCurve::P384 => CryptoNamedCurve::P256.into(),
573-
EcNamedCurve::P521 => {
574-
return Err(data_error("Unsupported named curve"))
566+
pk.to_pkcs8_der()
567+
.map_err(|_| data_error("invalid JWK private key"))?
575568
}
576569
};
577570

578-
let rng = ring::rand::SystemRandom::new();
579-
let _key_pair = EcdsaKeyPair::from_private_key_and_public_key(
580-
key_alg,
581-
private_d.as_bytes(),
582-
point_bytes.as_ref(),
583-
&rng,
584-
);
585-
586571
Ok(ImportKeyResult::Ec {
587572
raw_data: RustRawKeyData::Private(pkcs8_der.as_bytes().to_vec().into()),
588573
})
@@ -649,24 +634,15 @@ fn import_key_ec(
649634
})
650635
}
651636
KeyData::Pkcs8(data) => {
652-
// 2-7
653-
// Deserialize PKCS8 - validate structure, extracts named_curve
654-
let named_curve_alg = match named_curve {
655-
EcNamedCurve::P256 | EcNamedCurve::P384 => {
656-
let pk = PrivateKeyInfo::from_der(data.as_ref())
657-
.map_err(|_| data_error("expected valid PKCS#8 data"))?;
658-
pk.algorithm
659-
.parameters
660-
.ok_or_else(|| data_error("malformed parameters"))?
661-
.try_into()
662-
.unwrap()
663-
}
664-
EcNamedCurve::P521 => {
665-
return Err(data_error("Unsupported named curve"))
666-
}
667-
};
637+
let pk = PrivateKeyInfo::from_der(data.as_ref())
638+
.map_err(|_| data_error("expected valid PKCS#8 data"))?;
639+
let named_curve_alg = pk
640+
.algorithm
641+
.parameters
642+
.ok_or_else(|| data_error("malformed parameters"))?
643+
.try_into()
644+
.unwrap();
668645

669-
// 8-9.
670646
let pk_named_curve = match named_curve_alg {
671647
// id-secp256r1
672648
ID_SECP256R1_OID => Some(EcNamedCurve::P256),
@@ -677,27 +653,8 @@ fn import_key_ec(
677653
_ => None,
678654
};
679655

680-
// 10.
681-
if let Some(pk_named_curve) = pk_named_curve {
682-
let signing_alg = match pk_named_curve {
683-
EcNamedCurve::P256 => CryptoNamedCurve::P256.into(),
684-
EcNamedCurve::P384 => CryptoNamedCurve::P384.into(),
685-
EcNamedCurve::P521 => {
686-
return Err(data_error("Unsupported named curve"))
687-
}
688-
};
689-
690-
let rng = ring::rand::SystemRandom::new();
691-
// deserialize pkcs8 using ring crate, to VALIDATE public key
692-
let _private_key = EcdsaKeyPair::from_pkcs8(signing_alg, &data, &rng)
693-
.map_err(|_| data_error("invalid key"))?;
694-
695-
// 11.
696-
if named_curve != pk_named_curve {
697-
return Err(data_error("curve mismatch"));
698-
}
699-
} else {
700-
return Err(data_error("Unsupported named curve"));
656+
if pk_named_curve != Some(named_curve) {
657+
return Err(data_error("curve mismatch"));
701658
}
702659

703660
Ok(ImportKeyResult::Ec {

ext/crypto/shared.rs

+17
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,23 @@ impl V8RawKeyData {
126126
}
127127
}
128128

129+
pub fn as_ec_public_key_p521(&self) -> Result<p521::EncodedPoint, AnyError> {
130+
match self {
131+
V8RawKeyData::Public(data) => {
132+
// public_key is a serialized EncodedPoint
133+
p521::EncodedPoint::from_bytes(data)
134+
.map_err(|_| type_error("expected valid public EC key"))
135+
}
136+
V8RawKeyData::Private(data) => {
137+
let signing_key = p521::SecretKey::from_pkcs8_der(data)
138+
.map_err(|_| type_error("expected valid private EC key"))?;
139+
Ok(signing_key.public_key().to_encoded_point(false))
140+
}
141+
// Should never reach here.
142+
V8RawKeyData::Secret(_) => unreachable!(),
143+
}
144+
}
145+
129146
pub fn as_ec_private_key(&self) -> Result<&[u8], AnyError> {
130147
match self {
131148
V8RawKeyData::Private(data) => Ok(data),

ext/node/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ ring.workspace = true
8181
ripemd = { version = "0.1.3", features = ["oid"] }
8282
rsa.workspace = true
8383
scrypt = "0.11.0"
84-
sec1 = "0.7"
84+
sec1.workspace = true
8585
serde = "1.0.149"
8686
sha1.workspace = true
8787
sha2.workspace = true

tests/unit/webcrypto_test.ts

-21
Original file line numberDiff line numberDiff line change
@@ -2045,24 +2045,3 @@ Deno.test(async function p521Generate() {
20452045
assert(key.privateKey instanceof CryptoKey);
20462046
assert(key.publicKey instanceof CryptoKey);
20472047
});
2048-
2049-
Deno.test(async function invalidEcPointDataError() {
2050-
await assertRejects(async () => {
2051-
await crypto.subtle
2052-
.importKey(
2053-
"pkcs8",
2054-
// deno-fmt-ignore
2055-
new Uint8Array([
2056-
48, 102, 2, 1, 0, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42, 134,
2057-
72, 206, 61, 3, 1, 7, 4, 76, 48, 74, 2, 1, 1, 4, 32, 255, 255, 255, 255,
2058-
0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 188, 230, 250, 173,
2059-
167, 23, 158, 132, 243, 185, 202, 194, 252, 99, 37, 81, 161, 35, 3, 33, 0,
2060-
0, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 188,
2061-
230, 250, 173, 167, 23, 158, 132, 243, 185, 202, 194, 252, 99, 37, 81,
2062-
]),
2063-
{ name: "ECDSA", namedCurve: "P-256" },
2064-
true,
2065-
["sign"],
2066-
);
2067-
}, DOMException);
2068-
});

0 commit comments

Comments
 (0)