mirror of
https://github.com/denoland/deno.git
synced 2025-01-21 21:50:00 -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:
parent
9c348a0acd
commit
b00f076017
4 changed files with 76 additions and 29 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1683,6 +1683,7 @@ dependencies = [
|
||||||
"ripemd",
|
"ripemd",
|
||||||
"rsa",
|
"rsa",
|
||||||
"scrypt",
|
"scrypt",
|
||||||
|
"sec1",
|
||||||
"serde",
|
"serde",
|
||||||
"sha-1",
|
"sha-1",
|
||||||
"sha2",
|
"sha2",
|
||||||
|
|
|
@ -63,6 +63,7 @@ ring.workspace = true
|
||||||
ripemd = "0.1.3"
|
ripemd = "0.1.3"
|
||||||
rsa.workspace = true
|
rsa.workspace = true
|
||||||
scrypt = "0.11.0"
|
scrypt = "0.11.0"
|
||||||
|
sec1 = "0.7"
|
||||||
serde = "1.0.149"
|
serde = "1.0.149"
|
||||||
sha-1 = "0.10.0"
|
sha-1 = "0.10.0"
|
||||||
sha2.workspace = true
|
sha2.workspace = true
|
||||||
|
|
|
@ -373,20 +373,36 @@ pub fn op_node_sign(
|
||||||
|
|
||||||
let oid;
|
let oid;
|
||||||
let pkey = match format {
|
let pkey = match format {
|
||||||
"pem" => {
|
"pem" => match label {
|
||||||
if label == "PRIVATE KEY" {
|
"PRIVATE KEY" => {
|
||||||
let pk_info = pkcs8::PrivateKeyInfo::try_from(doc.as_bytes())?;
|
let pk_info = pkcs8::PrivateKeyInfo::try_from(doc.as_bytes())?;
|
||||||
oid = pk_info.algorithm.oid;
|
oid = pk_info.algorithm.oid;
|
||||||
pk_info.private_key
|
pk_info.private_key
|
||||||
} else if label == "RSA PRIVATE KEY" {
|
}
|
||||||
|
"RSA PRIVATE KEY" => {
|
||||||
oid = RSA_ENCRYPTION_OID;
|
oid = RSA_ENCRYPTION_OID;
|
||||||
doc.as_bytes()
|
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")),
|
_ => return Err(type_error("Unsupported key format")),
|
||||||
};
|
};
|
||||||
|
|
||||||
match oid {
|
match oid {
|
||||||
RSA_ENCRYPTION_OID => {
|
RSA_ENCRYPTION_OID => {
|
||||||
use rsa::pkcs1v15::SigningKey;
|
use rsa::pkcs1v15::SigningKey;
|
||||||
|
@ -419,6 +435,25 @@ pub fn op_node_sign(
|
||||||
.into(),
|
.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")),
|
_ => Err(type_error("Unsupported signing key")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -704,30 +739,32 @@ pub async fn op_node_dsa_generate_async(
|
||||||
fn ec_generate(
|
fn ec_generate(
|
||||||
named_curve: &str,
|
named_curve: &str,
|
||||||
) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
|
) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
|
||||||
use ring::signature::EcdsaKeyPair;
|
use elliptic_curve::sec1::ToEncodedPoint;
|
||||||
use ring::signature::KeyPair;
|
|
||||||
|
|
||||||
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" => {
|
"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" => {
|
"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")),
|
_ => 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()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[op2]
|
#[op2]
|
||||||
|
@ -1363,11 +1400,8 @@ fn parse_private_key(
|
||||||
) -> Result<pkcs8::SecretDocument, AnyError> {
|
) -> Result<pkcs8::SecretDocument, AnyError> {
|
||||||
match format {
|
match format {
|
||||||
"pem" => {
|
"pem" => {
|
||||||
let (label, doc) =
|
let (_, doc) =
|
||||||
pkcs8::SecretDocument::from_pem(std::str::from_utf8(key).unwrap())?;
|
pkcs8::SecretDocument::from_pem(std::str::from_utf8(key).unwrap())?;
|
||||||
if label != "PRIVATE KEY" {
|
|
||||||
return Err(type_error("Invalid PEM label"));
|
|
||||||
}
|
|
||||||
Ok(doc)
|
Ok(doc)
|
||||||
}
|
}
|
||||||
"der" => {
|
"der" => {
|
||||||
|
|
|
@ -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");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
Loading…
Add table
Reference in a new issue