Skip to content

Commit 6d7cbce

Browse files
littledivynathanwhit
authored andcommitted
fix(ext/node): Support private EC key signing (#22914)
Fixes #18972 Support for web-push VAPID keys & jws signing - Fixes EC keygen to return raw private key and uncompressed public key point. - Support for `EC PRIVATE KEY`
1 parent d66d518 commit 6d7cbce

File tree

4 files changed

+76
-29
lines changed

4 files changed

+76
-29
lines changed

Cargo.lock

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

ext/node/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ ring.workspace = true
6363
ripemd = "0.1.3"
6464
rsa.workspace = true
6565
scrypt = "0.11.0"
66+
sec1 = "0.7"
6667
serde = "1.0.149"
6768
sha-1 = "0.10.0"
6869
sha2.workspace = true

ext/node/ops/crypto/mod.rs

+63-29
Original file line numberDiff line numberDiff line change
@@ -373,20 +373,36 @@ pub fn op_node_sign(
373373

374374
let oid;
375375
let pkey = match format {
376-
"pem" => {
377-
if label == "PRIVATE KEY" {
376+
"pem" => match label {
377+
"PRIVATE KEY" => {
378378
let pk_info = pkcs8::PrivateKeyInfo::try_from(doc.as_bytes())?;
379379
oid = pk_info.algorithm.oid;
380380
pk_info.private_key
381-
} else if label == "RSA PRIVATE KEY" {
381+
}
382+
"RSA PRIVATE KEY" => {
382383
oid = RSA_ENCRYPTION_OID;
383384
doc.as_bytes()
384-
} else {
385-
return Err(type_error("Invalid PEM label"));
386385
}
387-
}
386+
"EC PRIVATE KEY" => {
387+
let ec_pk = sec1::EcPrivateKey::from_der(doc.as_bytes())?;
388+
match ec_pk.parameters {
389+
Some(sec1::EcParameters::NamedCurve(o)) => {
390+
oid = o;
391+
ec_pk.private_key
392+
}
393+
// https://datatracker.ietf.org/doc/html/rfc5915#section-3
394+
//
395+
// Though the ASN.1 indicates that
396+
// the parameters field is OPTIONAL, implementations that conform to
397+
// this document MUST always include the parameters field.
398+
_ => return Err(type_error("invalid ECPrivateKey params")),
399+
}
400+
}
401+
_ => return Err(type_error("Invalid PEM label")),
402+
},
388403
_ => return Err(type_error("Unsupported key format")),
389404
};
405+
390406
match oid {
391407
RSA_ENCRYPTION_OID => {
392408
use rsa::pkcs1v15::SigningKey;
@@ -419,6 +435,25 @@ pub fn op_node_sign(
419435
.into(),
420436
)
421437
}
438+
// signature structure encoding is DER by default for DSA and ECDSA.
439+
//
440+
// TODO(@littledivy): Validate public_key if present
441+
ID_SECP256R1_OID => {
442+
let key = p256::ecdsa::SigningKey::from_slice(pkey)?;
443+
Ok(
444+
key
445+
.sign_prehash(digest)
446+
.map(|sig: p256::ecdsa::Signature| sig.to_der().to_vec().into())?,
447+
)
448+
}
449+
ID_SECP384R1_OID => {
450+
let key = p384::ecdsa::SigningKey::from_slice(pkey)?;
451+
Ok(
452+
key
453+
.sign_prehash(digest)
454+
.map(|sig: p384::ecdsa::Signature| sig.to_der().to_vec().into())?,
455+
)
456+
}
422457
_ => Err(type_error("Unsupported signing key")),
423458
}
424459
}
@@ -704,30 +739,32 @@ pub async fn op_node_dsa_generate_async(
704739
fn ec_generate(
705740
named_curve: &str,
706741
) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
707-
use ring::signature::EcdsaKeyPair;
708-
use ring::signature::KeyPair;
742+
use elliptic_curve::sec1::ToEncodedPoint;
709743

710-
let curve = match named_curve {
744+
let mut rng = rand::thread_rng();
745+
// TODO(@littledivy): Support public key point encoding.
746+
// Default is uncompressed.
747+
match named_curve {
711748
"P-256" | "prime256v1" | "secp256r1" => {
712-
&ring::signature::ECDSA_P256_SHA256_FIXED_SIGNING
749+
let key = p256::SecretKey::random(&mut rng);
750+
let public_key = key.public_key();
751+
752+
Ok((
753+
key.to_bytes().to_vec().into(),
754+
public_key.to_encoded_point(false).as_ref().to_vec().into(),
755+
))
713756
}
714757
"P-384" | "prime384v1" | "secp384r1" => {
715-
&ring::signature::ECDSA_P384_SHA384_FIXED_SIGNING
716-
}
717-
_ => return Err(type_error("Unsupported named curve")),
718-
};
719-
720-
let rng = ring::rand::SystemRandom::new();
758+
let key = p384::SecretKey::random(&mut rng);
759+
let public_key = key.public_key();
721760

722-
let pkcs8 = EcdsaKeyPair::generate_pkcs8(curve, &rng)
723-
.map_err(|_| type_error("Failed to generate EC key"))?;
724-
725-
let public_key = EcdsaKeyPair::from_pkcs8(curve, pkcs8.as_ref(), &rng)
726-
.map_err(|_| type_error("Failed to generate EC key"))?
727-
.public_key()
728-
.as_ref()
729-
.to_vec();
730-
Ok((pkcs8.as_ref().to_vec().into(), public_key.into()))
761+
Ok((
762+
key.to_bytes().to_vec().into(),
763+
public_key.to_encoded_point(false).as_ref().to_vec().into(),
764+
))
765+
}
766+
_ => Err(type_error("Unsupported named curve")),
767+
}
731768
}
732769

733770
#[op2]
@@ -1363,11 +1400,8 @@ fn parse_private_key(
13631400
) -> Result<pkcs8::SecretDocument, AnyError> {
13641401
match format {
13651402
"pem" => {
1366-
let (label, doc) =
1403+
let (_, doc) =
13671404
pkcs8::SecretDocument::from_pem(std::str::from_utf8(key).unwrap())?;
1368-
if label != "PRIVATE KEY" {
1369-
return Err(type_error("Invalid PEM label"));
1370-
}
13711405
Ok(doc)
13721406
}
13731407
"der" => {

tests/unit_node/crypto/crypto_sign_test.ts

+11
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,14 @@ Deno.test({
127127
}
128128
},
129129
});
130+
131+
Deno.test({
132+
name: "crypto.createSign|sign - EC PRIVATE KEY",
133+
fn() {
134+
const pem = `-----BEGIN EC PRIVATE KEY-----
135+
MDECAQEEIIThPSZ00CNW1UD5Ju9mhplv6SSs3T5objYjlx11gHW9oAoGCCqGSM49
136+
AwEH
137+
-----END EC PRIVATE KEY-----`;
138+
createSign("SHA256").update("test").sign(pem, "base64");
139+
},
140+
});

0 commit comments

Comments
 (0)