Skip to content

Commit

Permalink
fix(ext/node): Support private EC key signing (#22914)
Browse files Browse the repository at this point in the history
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`
  • Loading branch information
littledivy authored Mar 14, 2024
1 parent 9c348a0 commit b00f076
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 29 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions ext/node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ ring.workspace = true
ripemd = "0.1.3"
rsa.workspace = true
scrypt = "0.11.0"
sec1 = "0.7"
serde = "1.0.149"
sha-1 = "0.10.0"
sha2.workspace = true
Expand Down
92 changes: 63 additions & 29 deletions ext/node/ops/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,20 +373,36 @@ pub fn op_node_sign(

let oid;
let pkey = match format {
"pem" => {
if label == "PRIVATE KEY" {
"pem" => match label {
"PRIVATE KEY" => {
let pk_info = pkcs8::PrivateKeyInfo::try_from(doc.as_bytes())?;
oid = pk_info.algorithm.oid;
pk_info.private_key
} else if label == "RSA PRIVATE KEY" {
}
"RSA PRIVATE KEY" => {
oid = RSA_ENCRYPTION_OID;
doc.as_bytes()
} else {
return Err(type_error("Invalid PEM label"));
}
}
"EC PRIVATE KEY" => {
let ec_pk = sec1::EcPrivateKey::from_der(doc.as_bytes())?;
match ec_pk.parameters {
Some(sec1::EcParameters::NamedCurve(o)) => {
oid = o;
ec_pk.private_key
}
// https://datatracker.ietf.org/doc/html/rfc5915#section-3
//
// Though the ASN.1 indicates that
// the parameters field is OPTIONAL, implementations that conform to
// this document MUST always include the parameters field.
_ => return Err(type_error("invalid ECPrivateKey params")),
}
}
_ => return Err(type_error("Invalid PEM label")),
},
_ => return Err(type_error("Unsupported key format")),
};

match oid {
RSA_ENCRYPTION_OID => {
use rsa::pkcs1v15::SigningKey;
Expand Down Expand Up @@ -419,6 +435,25 @@ pub fn op_node_sign(
.into(),
)
}
// signature structure encoding is DER by default for DSA and ECDSA.
//
// TODO(@littledivy): Validate public_key if present
ID_SECP256R1_OID => {
let key = p256::ecdsa::SigningKey::from_slice(pkey)?;
Ok(
key
.sign_prehash(digest)
.map(|sig: p256::ecdsa::Signature| sig.to_der().to_vec().into())?,
)
}
ID_SECP384R1_OID => {
let key = p384::ecdsa::SigningKey::from_slice(pkey)?;
Ok(
key
.sign_prehash(digest)
.map(|sig: p384::ecdsa::Signature| sig.to_der().to_vec().into())?,
)
}
_ => Err(type_error("Unsupported signing key")),
}
}
Expand Down Expand Up @@ -704,30 +739,32 @@ pub async fn op_node_dsa_generate_async(
fn ec_generate(
named_curve: &str,
) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
use ring::signature::EcdsaKeyPair;
use ring::signature::KeyPair;
use elliptic_curve::sec1::ToEncodedPoint;

let curve = match named_curve {
let mut rng = rand::thread_rng();
// TODO(@littledivy): Support public key point encoding.
// Default is uncompressed.
match named_curve {
"P-256" | "prime256v1" | "secp256r1" => {
&ring::signature::ECDSA_P256_SHA256_FIXED_SIGNING
let key = p256::SecretKey::random(&mut rng);
let public_key = key.public_key();

Ok((
key.to_bytes().to_vec().into(),
public_key.to_encoded_point(false).as_ref().to_vec().into(),
))
}
"P-384" | "prime384v1" | "secp384r1" => {
&ring::signature::ECDSA_P384_SHA384_FIXED_SIGNING
}
_ => return Err(type_error("Unsupported named curve")),
};

let rng = ring::rand::SystemRandom::new();
let key = p384::SecretKey::random(&mut rng);
let public_key = key.public_key();

let pkcs8 = EcdsaKeyPair::generate_pkcs8(curve, &rng)
.map_err(|_| type_error("Failed to generate EC key"))?;

let public_key = EcdsaKeyPair::from_pkcs8(curve, pkcs8.as_ref(), &rng)
.map_err(|_| type_error("Failed to generate EC key"))?
.public_key()
.as_ref()
.to_vec();
Ok((pkcs8.as_ref().to_vec().into(), public_key.into()))
Ok((
key.to_bytes().to_vec().into(),
public_key.to_encoded_point(false).as_ref().to_vec().into(),
))
}
_ => Err(type_error("Unsupported named curve")),
}
}

#[op2]
Expand Down Expand Up @@ -1363,11 +1400,8 @@ fn parse_private_key(
) -> Result<pkcs8::SecretDocument, AnyError> {
match format {
"pem" => {
let (label, doc) =
let (_, doc) =
pkcs8::SecretDocument::from_pem(std::str::from_utf8(key).unwrap())?;
if label != "PRIVATE KEY" {
return Err(type_error("Invalid PEM label"));
}
Ok(doc)
}
"der" => {
Expand Down
11 changes: 11 additions & 0 deletions tests/unit_node/crypto/crypto_sign_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,14 @@ Deno.test({
}
},
});

Deno.test({
name: "crypto.createSign|sign - EC PRIVATE KEY",
fn() {
const pem = `-----BEGIN EC PRIVATE KEY-----
MDECAQEEIIThPSZ00CNW1UD5Ju9mhplv6SSs3T5objYjlx11gHW9oAoGCCqGSM49
AwEH
-----END EC PRIVATE KEY-----`;
createSign("SHA256").update("test").sign(pem, "base64");
},
});

0 comments on commit b00f076

Please sign in to comment.