Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ext/crypto): import and export p521 keys #25789

Merged
merged 5 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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