Skip to content

Commit

Permalink
feat(ext/crypto): import and export p521 keys (#25789)
Browse files Browse the repository at this point in the history
Towards #13449
  • Loading branch information
littledivy authored Sep 23, 2024
1 parent b155084 commit bfdca5b
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 388 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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ rustls-webpki = "0.102"
rustyline = "=13.0.0"
saffron = "=0.1.0"
scopeguard = "1.2.0"
sec1 = "0.7"
serde = { version = "1.0.149", features = ["derive"] }
serde_bytes = "0.11"
serde_json = "1.0.85"
Expand Down
1 change: 1 addition & 0 deletions ext/crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ p521 = "0.13.3"
rand.workspace = true
ring = { workspace = true, features = ["std"] }
rsa.workspace = true
sec1.workspace = true
serde.workspace = true
serde_bytes.workspace = true
sha1.workspace = true
Expand Down
34 changes: 28 additions & 6 deletions ext/crypto/export_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,9 @@ fn export_key_ec(
point.as_ref().to_vec()
}
EcNamedCurve::P521 => {
return Err(data_error("Unsupported named curve"))
let point = key_data.as_ec_public_key_p521()?;

point.as_ref().to_vec()
}
};
Ok(ExportKeyResult::Raw(subject_public_key.into()))
Expand All @@ -272,7 +274,9 @@ fn export_key_ec(
point.as_ref().to_vec()
}
EcNamedCurve::P521 => {
return Err(data_error("Unsupported named curve"))
let point = key_data.as_ec_public_key_p521()?;

point.as_ref().to_vec()
}
};

Expand All @@ -285,9 +289,10 @@ fn export_key_ec(
oid: elliptic_curve::ALGORITHM_OID,
parameters: Some((&p384::NistP384::OID).into()),
},
EcNamedCurve::P521 => {
return Err(data_error("Unsupported named curve"))
}
EcNamedCurve::P521 => AlgorithmIdentifierOwned {
oid: elliptic_curve::ALGORITHM_OID,
parameters: Some((&p521::NistP521::OID).into()),
},
};

let alg_id = match algorithm {
Expand Down Expand Up @@ -351,7 +356,24 @@ fn export_key_ec(
))
}
}
EcNamedCurve::P521 => Err(data_error("Unsupported named curve")),
EcNamedCurve::P521 => {
let point = key_data.as_ec_public_key_p521()?;
let coords = point.coordinates();

if let p521::elliptic_curve::sec1::Coordinates::Uncompressed { x, y } =
coords
{
Ok(ExportKeyResult::JwkPublicEc {
x: bytes_to_b64(x),
y: bytes_to_b64(y),
})
} else {
Err(custom_error(
"DOMExceptionOperationError",
"failed to decode public key",
))
}
}
},
ExportKeyFormat::JwkPrivate => {
let private_key = key_data.as_ec_private_key()?;
Expand Down
77 changes: 17 additions & 60 deletions ext/crypto/import_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,12 @@ use deno_core::JsBuffer;
use deno_core::ToJsBuffer;
use elliptic_curve::pkcs8::PrivateKeyInfo;
use p256::pkcs8::EncodePrivateKey;
use ring::signature::EcdsaKeyPair;
use rsa::pkcs1::UintRef;
use rsa::pkcs8::der::Encode;
use serde::Deserialize;
use serde::Serialize;
use spki::der::Decode;

use crate::key::CryptoNamedCurve;
use crate::shared::*;

#[derive(Deserialize)]
Expand Down Expand Up @@ -45,7 +43,9 @@ pub enum KeyData {
y: String,
},
JwkPrivateEc {
#[allow(dead_code)]
x: String,
#[allow(dead_code)]
y: String,
d: String,
},
Expand Down Expand Up @@ -543,9 +543,7 @@ fn import_key_ec_jwk(
raw_data: RustRawKeyData::Public(point_bytes.into()),
})
}
KeyData::JwkPrivateEc { d, x, y } => {
jwt_b64_int_or_err!(private_d, &d, "invalid JWK private key");
let point_bytes = import_key_ec_jwk_to_point(x, y, named_curve)?;
KeyData::JwkPrivateEc { d, .. } => {
let pkcs8_der = match named_curve {
EcNamedCurve::P256 => {
let d = decode_b64url_to_field_bytes::<p256::NistP256>(&d)?;
Expand All @@ -562,27 +560,14 @@ fn import_key_ec_jwk(
.map_err(|_| data_error("invalid JWK private key"))?
}
EcNamedCurve::P521 => {
return Err(data_error("Unsupported named curve"))
}
};
let d = decode_b64url_to_field_bytes::<p521::NistP521>(&d)?;
let pk = p521::SecretKey::from_bytes(&d)?;

// Import using ring, to validate key
let key_alg = match named_curve {
EcNamedCurve::P256 => CryptoNamedCurve::P256.into(),
EcNamedCurve::P384 => CryptoNamedCurve::P256.into(),
EcNamedCurve::P521 => {
return Err(data_error("Unsupported named curve"))
pk.to_pkcs8_der()
.map_err(|_| data_error("invalid JWK private key"))?
}
};

let rng = ring::rand::SystemRandom::new();
let _key_pair = EcdsaKeyPair::from_private_key_and_public_key(
key_alg,
private_d.as_bytes(),
point_bytes.as_ref(),
&rng,
);

Ok(ImportKeyResult::Ec {
raw_data: RustRawKeyData::Private(pkcs8_der.as_bytes().to_vec().into()),
})
Expand Down Expand Up @@ -649,24 +634,15 @@ fn import_key_ec(
})
}
KeyData::Pkcs8(data) => {
// 2-7
// Deserialize PKCS8 - validate structure, extracts named_curve
let named_curve_alg = match named_curve {
EcNamedCurve::P256 | EcNamedCurve::P384 => {
let pk = PrivateKeyInfo::from_der(data.as_ref())
.map_err(|_| data_error("expected valid PKCS#8 data"))?;
pk.algorithm
.parameters
.ok_or_else(|| data_error("malformed parameters"))?
.try_into()
.unwrap()
}
EcNamedCurve::P521 => {
return Err(data_error("Unsupported named curve"))
}
};
let pk = PrivateKeyInfo::from_der(data.as_ref())
.map_err(|_| data_error("expected valid PKCS#8 data"))?;
let named_curve_alg = pk
.algorithm
.parameters
.ok_or_else(|| data_error("malformed parameters"))?
.try_into()
.unwrap();

// 8-9.
let pk_named_curve = match named_curve_alg {
// id-secp256r1
ID_SECP256R1_OID => Some(EcNamedCurve::P256),
Expand All @@ -677,27 +653,8 @@ fn import_key_ec(
_ => None,
};

// 10.
if let Some(pk_named_curve) = pk_named_curve {
let signing_alg = match pk_named_curve {
EcNamedCurve::P256 => CryptoNamedCurve::P256.into(),
EcNamedCurve::P384 => CryptoNamedCurve::P384.into(),
EcNamedCurve::P521 => {
return Err(data_error("Unsupported named curve"))
}
};

let rng = ring::rand::SystemRandom::new();
// deserialize pkcs8 using ring crate, to VALIDATE public key
let _private_key = EcdsaKeyPair::from_pkcs8(signing_alg, &data, &rng)
.map_err(|_| data_error("invalid key"))?;

// 11.
if named_curve != pk_named_curve {
return Err(data_error("curve mismatch"));
}
} else {
return Err(data_error("Unsupported named curve"));
if pk_named_curve != Some(named_curve) {
return Err(data_error("curve mismatch"));
}

Ok(ImportKeyResult::Ec {
Expand Down
17 changes: 17 additions & 0 deletions ext/crypto/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,23 @@ impl V8RawKeyData {
}
}

pub fn as_ec_public_key_p521(&self) -> Result<p521::EncodedPoint, AnyError> {
match self {
V8RawKeyData::Public(data) => {
// public_key is a serialized EncodedPoint
p521::EncodedPoint::from_bytes(data)
.map_err(|_| type_error("expected valid public EC key"))
}
V8RawKeyData::Private(data) => {
let signing_key = p521::SecretKey::from_pkcs8_der(data)
.map_err(|_| type_error("expected valid private EC key"))?;
Ok(signing_key.public_key().to_encoded_point(false))
}
// Should never reach here.
V8RawKeyData::Secret(_) => unreachable!(),
}
}

pub fn as_ec_private_key(&self) -> Result<&[u8], AnyError> {
match self {
V8RawKeyData::Private(data) => Ok(data),
Expand Down
2 changes: 1 addition & 1 deletion ext/node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ ring.workspace = true
ripemd = { version = "0.1.3", features = ["oid"] }
rsa.workspace = true
scrypt = "0.11.0"
sec1 = "0.7"
sec1.workspace = true
serde = "1.0.149"
sha1.workspace = true
sha2.workspace = true
Expand Down
21 changes: 0 additions & 21 deletions tests/unit/webcrypto_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2045,24 +2045,3 @@ Deno.test(async function p521Generate() {
assert(key.privateKey instanceof CryptoKey);
assert(key.publicKey instanceof CryptoKey);
});

Deno.test(async function invalidEcPointDataError() {
await assertRejects(async () => {
await crypto.subtle
.importKey(
"pkcs8",
// deno-fmt-ignore
new Uint8Array([
48, 102, 2, 1, 0, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42, 134,
72, 206, 61, 3, 1, 7, 4, 76, 48, 74, 2, 1, 1, 4, 32, 255, 255, 255, 255,
0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 188, 230, 250, 173,
167, 23, 158, 132, 243, 185, 202, 194, 252, 99, 37, 81, 161, 35, 3, 33, 0,
0, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 188,
230, 250, 173, 167, 23, 158, 132, 243, 185, 202, 194, 252, 99, 37, 81,
]),
{ name: "ECDSA", namedCurve: "P-256" },
true,
["sign"],
);
}, DOMException);
});
Loading

0 comments on commit bfdca5b

Please sign in to comment.