summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDivy Srivastava <dj.srivastava23@gmail.com>2024-08-28 19:54:49 +0530
committerGitHub <noreply@github.com>2024-08-28 19:54:49 +0530
commit553bd7dec328884785da805d8ef4f9c4510e1366 (patch)
treef44d1dac915305bfae9d1b9cdbfcf310f6103c8a
parent14a34a0cd76b1d5e4c19b583a3b6aad7db8a6187 (diff)
fix(ext/node): import EC JWK keys (#25266)
-rw-r--r--Cargo.lock14
-rw-r--r--Cargo.toml6
-rw-r--r--ext/node/lib.rs1
-rw-r--r--ext/node/ops/crypto/keys.rs40
-rw-r--r--ext/node/polyfills/internal/crypto/keys.ts11
-rw-r--r--tests/unit_node/crypto/crypto_key_test.ts51
6 files changed, 116 insertions, 7 deletions
diff --git a/Cargo.lock b/Cargo.lock
index ba81c1ee3..927d3a080 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2694,6 +2694,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
dependencies = [
"base16ct",
+ "base64ct",
"crypto-bigint",
"digest",
"ff",
@@ -2704,6 +2705,8 @@ dependencies = [
"pkcs8",
"rand_core",
"sec1",
+ "serde_json",
+ "serdect",
"subtle",
"zeroize",
]
@@ -6169,6 +6172,7 @@ dependencies = [
"der",
"generic-array",
"pkcs8",
+ "serdect",
"subtle",
"zeroize",
]
@@ -6315,6 +6319,16 @@ dependencies = [
]
[[package]]
+name = "serdect"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177"
+dependencies = [
+ "base16ct",
+ "serde",
+]
+
+[[package]]
name = "sha1"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 101663960..1321a8622 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -106,7 +106,7 @@ deno_cache_dir = "=0.11.0"
deno_package_json = { version = "=0.1.1", default-features = false }
dlopen2 = "0.6.1"
ecb = "=0.1.2"
-elliptic-curve = { version = "0.13.4", features = ["alloc", "arithmetic", "ecdh", "std", "pem"] }
+elliptic-curve = { version = "0.13.4", features = ["alloc", "arithmetic", "ecdh", "std", "pem", "jwk"] }
encoding_rs = "=0.8.33"
fast-socks5 = "0.9.6"
faster-hex = "0.9"
@@ -141,8 +141,8 @@ num-bigint = { version = "0.4", features = ["rand"] }
once_cell = "1.17.1"
os_pipe = { version = "=1.1.5", features = ["io_safety"] }
p224 = { version = "0.13.0", features = ["ecdh"] }
-p256 = { version = "0.13.2", features = ["ecdh"] }
-p384 = { version = "0.13.0", features = ["ecdh"] }
+p256 = { version = "0.13.2", features = ["ecdh", "jwk"] }
+p384 = { version = "0.13.0", features = ["ecdh", "jwk"] }
parking_lot = "0.12.0"
percent-encoding = "2.3.0"
phf = { version = "0.11", features = ["macros"] }
diff --git a/ext/node/lib.rs b/ext/node/lib.rs
index f2c1576cf..1023fced3 100644
--- a/ext/node/lib.rs
+++ b/ext/node/lib.rs
@@ -233,6 +233,7 @@ deno_core::extension!(deno_node,
ops::crypto::op_node_verify_ed25519,
ops::crypto::keys::op_node_create_private_key,
ops::crypto::keys::op_node_create_ed_raw,
+ ops::crypto::keys::op_node_create_ec_jwk,
ops::crypto::keys::op_node_create_public_key,
ops::crypto::keys::op_node_create_secret_key,
ops::crypto::keys::op_node_derive_public_key_from_private_key,
diff --git a/ext/node/ops/crypto/keys.rs b/ext/node/ops/crypto/keys.rs
index 7d7ec140e..eccd08564 100644
--- a/ext/node/ops/crypto/keys.rs
+++ b/ext/node/ops/crypto/keys.rs
@@ -13,6 +13,7 @@ use deno_core::unsync::spawn_blocking;
use deno_core::GarbageCollected;
use deno_core::ToJsBuffer;
use ed25519_dalek::pkcs8::BitStringRef;
+use elliptic_curve::JwkEcKey;
use num_bigint::BigInt;
use num_traits::FromPrimitive as _;
use pkcs8::DecodePrivateKey as _;
@@ -571,6 +572,36 @@ impl KeyObjectHandle {
Ok(KeyObjectHandle::AsymmetricPublic(key))
}
+ pub fn new_ec_jwk(
+ jwk: &JwkEcKey,
+ is_public: bool,
+ ) -> Result<KeyObjectHandle, AnyError> {
+ // https://datatracker.ietf.org/doc/html/rfc7518#section-6.2.1.1
+ let handle = match jwk.crv() {
+ "P-256" if is_public => {
+ KeyObjectHandle::AsymmetricPublic(AsymmetricPublicKey::Ec(
+ EcPublicKey::P256(p256::PublicKey::from_jwk(jwk)?),
+ ))
+ }
+ "P-256" => KeyObjectHandle::AsymmetricPrivate(AsymmetricPrivateKey::Ec(
+ EcPrivateKey::P256(p256::SecretKey::from_jwk(jwk)?),
+ )),
+ "P-384" if is_public => {
+ KeyObjectHandle::AsymmetricPublic(AsymmetricPublicKey::Ec(
+ EcPublicKey::P384(p384::PublicKey::from_jwk(jwk)?),
+ ))
+ }
+ "P-384" => KeyObjectHandle::AsymmetricPrivate(AsymmetricPrivateKey::Ec(
+ EcPrivateKey::P384(p384::SecretKey::from_jwk(jwk)?),
+ )),
+ _ => {
+ return Err(type_error(format!("unsupported curve: {}", jwk.crv())));
+ }
+ };
+
+ Ok(handle)
+ }
+
pub fn new_ed_raw(
curve: &str,
data: &[u8],
@@ -1083,6 +1114,15 @@ pub fn op_node_create_ed_raw(
#[op2]
#[cppgc]
+pub fn op_node_create_ec_jwk(
+ #[serde] jwk: elliptic_curve::JwkEcKey,
+ is_public: bool,
+) -> Result<KeyObjectHandle, AnyError> {
+ KeyObjectHandle::new_ec_jwk(&jwk, is_public)
+}
+
+#[op2]
+#[cppgc]
pub fn op_node_create_public_key(
#[buffer] key: &[u8],
#[string] format: &str,
diff --git a/ext/node/polyfills/internal/crypto/keys.ts b/ext/node/polyfills/internal/crypto/keys.ts
index 97e565023..c2e9d95ee 100644
--- a/ext/node/polyfills/internal/crypto/keys.ts
+++ b/ext/node/polyfills/internal/crypto/keys.ts
@@ -12,6 +12,7 @@ const {
} = primordials;
import {
+ op_node_create_ec_jwk,
op_node_create_ed_raw,
op_node_create_private_key,
op_node_create_public_key,
@@ -311,7 +312,15 @@ function getKeyObjectHandleFromJwk(key, ctx) {
}
if (key.kty === "EC") {
- throw new TypeError("ec jwk imports not implemented");
+ validateString(key.crv, "key.crv");
+ validateString(key.x, "key.x");
+ validateString(key.y, "key.y");
+
+ if (!isPublic) {
+ validateString(key.d, "key.d");
+ }
+
+ return op_node_create_ec_jwk(key, isPublic);
}
throw new TypeError("rsa jwk imports not implemented");
diff --git a/tests/unit_node/crypto/crypto_key_test.ts b/tests/unit_node/crypto/crypto_key_test.ts
index 6c2c9f851..dba9ba062 100644
--- a/tests/unit_node/crypto/crypto_key_test.ts
+++ b/tests/unit_node/crypto/crypto_key_test.ts
@@ -440,7 +440,7 @@ Deno.test("create private key with invalid utf-8 string", function () {
);
});
-Deno.test("Ed25519 jwk public key #1", function () {
+Deno.test("Ed25519 import jwk public key #1", function () {
const key = {
"kty": "OKP",
"crv": "Ed25519",
@@ -460,7 +460,7 @@ MCowBQYDK2VwAyEA11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo=
assertEquals(spkiActual, spkiExpected);
});
-Deno.test("Ed25519 jwk public key #2", function () {
+Deno.test("Ed25519 import jwk public key #2", function () {
const key = {
"kty": "OKP",
"crv": "Ed25519",
@@ -478,7 +478,7 @@ MCowBQYDK2VwAyEA11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo=
assertEquals(spki, spkiExpected);
});
-Deno.test("Ed25519 jwk private key", function () {
+Deno.test("Ed25519 import jwk private key", function () {
const key = {
"kty": "OKP",
"crv": "Ed25519",
@@ -497,3 +497,48 @@ MC4CAQAwBQYDK2VwBCIEIJ1hsZ3v/VpguoRK9JLsLMREScVpezJpGXA7rAMcrn9g
assertEquals(pkcs8Actual, pkcs8Expected);
});
+
+Deno.test("EC import jwk public key", function () {
+ const publicKey = createPublicKey({
+ key: {
+ kty: "EC",
+ x: "_GGuz19zab5J70zyiUK6sAM5mHqUbsY8H6U2TnVlt-k",
+ y: "TcZG5efXZDIhNGDp6XuujoJqOEJU2D2ckjG9nOnSPIQ",
+ crv: "P-256",
+ },
+ format: "jwk",
+ });
+
+ const publicSpki = publicKey.export({ type: "spki", format: "pem" });
+ const spkiExpected = `-----BEGIN PUBLIC KEY-----
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/GGuz19zab5J70zyiUK6sAM5mHqU
+bsY8H6U2TnVlt+lNxkbl59dkMiE0YOnpe66Ogmo4QlTYPZySMb2c6dI8hA==
+-----END PUBLIC KEY-----
+`;
+
+ assertEquals(publicSpki, spkiExpected);
+});
+
+Deno.test("EC import jwk private key", function () {
+ const privateKey = createPrivateKey({
+ key: {
+ kty: "EC",
+ x: "_GGuz19zab5J70zyiUK6sAM5mHqUbsY8H6U2TnVlt-k",
+ y: "TcZG5efXZDIhNGDp6XuujoJqOEJU2D2ckjG9nOnSPIQ",
+ crv: "P-256",
+ d: "Wobjne0GqlB_1NynKu19rsw7zBHa94tKcWIxwIb88m8",
+ },
+ format: "jwk",
+ });
+
+ const privatePkcs8 = privateKey.export({ type: "pkcs8", format: "pem" });
+
+ const pkcs8Expected = `-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgWobjne0GqlB/1Nyn
+Ku19rsw7zBHa94tKcWIxwIb88m+hRANCAAT8Ya7PX3NpvknvTPKJQrqwAzmYepRu
+xjwfpTZOdWW36U3GRuXn12QyITRg6el7ro6CajhCVNg9nJIxvZzp0jyE
+-----END PRIVATE KEY-----
+`;
+
+ assertEquals(privatePkcs8, pkcs8Expected);
+});