1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-24 15:19:26 -05:00

fix(ext/node): Support private EC key signing (#22914)

Fixes https://github.com/denoland/deno/issues/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`
This commit is contained in:
Divy Srivastava 2024-03-14 06:53:50 -07:00 committed by GitHub
parent 9c348a0acd
commit b00f076017
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 76 additions and 29 deletions

1
Cargo.lock generated
View file

@ -1683,6 +1683,7 @@ dependencies = [
"ripemd",
"rsa",
"scrypt",
"sec1",
"serde",
"sha-1",
"sha2",

View file

@ -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

View file

@ -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;
@ -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")),
}
}
@ -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
let key = p384::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(),
))
}
_ => return Err(type_error("Unsupported named curve")),
};
let rng = ring::rand::SystemRandom::new();
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()))
_ => Err(type_error("Unsupported named curve")),
}
}
#[op2]
@ -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" => {

View file

@ -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");
},
});