diff --git a/ext/crypto/00_crypto.js b/ext/crypto/00_crypto.js index 5a9f32cb001aeb..e26d48506cc6cc 100644 --- a/ext/crypto/00_crypto.js +++ b/ext/crypto/00_crypto.js @@ -48,6 +48,7 @@ import { op_crypto_verify_ed25519, op_crypto_verify_key, op_crypto_wrap_key, + op_crypto_x25519_public_key, } from "ext:core/ops"; const { ArrayBufferIsView, @@ -4532,17 +4533,18 @@ function exportKeyX25519(format, key, innerKey) { return TypedArrayPrototypeGetBuffer(pkcs8Der); } case "jwk": { - if (key[_type] === "private") { - throw new DOMException("Not implemented", "NotSupportedError"); - } - const x = op_crypto_base64url_encode(innerKey); const jwk = { kty: "OKP", crv: "X25519", - x, "key_ops": key.usages, ext: key[_extractable], }; + if (key[_type] === "private") { + jwk.x = op_crypto_x25519_public_key(innerKey); + jwk.d = op_crypto_base64url_encode(innerKey); + } else { + jwk.x = op_crypto_base64url_encode(innerKey); + } return jwk; } default: diff --git a/ext/crypto/lib.rs b/ext/crypto/lib.rs index 0d6eecb911f368..9a5b71500b63eb 100644 --- a/ext/crypto/lib.rs +++ b/ext/crypto/lib.rs @@ -98,6 +98,7 @@ deno_core::extension!(deno_crypto, op_crypto_base64url_decode, op_crypto_base64url_encode, x25519::op_crypto_generate_x25519_keypair, + x25519::op_crypto_x25519_public_key, x25519::op_crypto_derive_bits_x25519, x25519::op_crypto_import_spki_x25519, x25519::op_crypto_import_pkcs8_x25519, diff --git a/ext/crypto/x25519.rs b/ext/crypto/x25519.rs index 226ed89e40ee74..aee8ae0e298c3a 100644 --- a/ext/crypto/x25519.rs +++ b/ext/crypto/x25519.rs @@ -1,5 +1,6 @@ // Copyright 2018-2025 the Deno authors. MIT license. +use base64::prelude::BASE64_URL_SAFE_NO_PAD; use curve25519_dalek::montgomery::MontgomeryPoint; use deno_core::op2; use deno_core::ToJsBuffer; @@ -20,17 +21,16 @@ pub enum X25519Error { #[error(transparent)] Der(#[from] spki::der::Error), } - +// u-coordinate of the base point. +const X25519_BASEPOINT_BYTES: [u8; 32] = [ + 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, +]; #[op2(fast)] pub fn op_crypto_generate_x25519_keypair( #[buffer] pkey: &mut [u8], #[buffer] pubkey: &mut [u8], ) { - // u-coordinate of the base point. - const X25519_BASEPOINT_BYTES: [u8; 32] = [ - 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, - ]; let mut rng = OsRng; rng.fill_bytes(pkey); // https://www.rfc-editor.org/rfc/rfc7748#section-6.1 @@ -42,6 +42,17 @@ pub fn op_crypto_generate_x25519_keypair( pubkey.copy_from_slice(&x25519_dalek::x25519(pkey, X25519_BASEPOINT_BYTES)); } +#[op2] +#[string] +pub fn op_crypto_x25519_public_key(#[buffer] private_key: &[u8]) -> String { + use base64::Engine; + + let private_key: [u8; 32] = + private_key.try_into().expect("Expected byteLength 32"); + BASE64_URL_SAFE_NO_PAD + .encode(x25519_dalek::x25519(private_key, X25519_BASEPOINT_BYTES)) +} + const MONTGOMERY_IDENTITY: MontgomeryPoint = MontgomeryPoint([0; 32]); #[op2(fast)] diff --git a/tests/unit/webcrypto_test.ts b/tests/unit/webcrypto_test.ts index bc5b307de5e49b..1732bb26350479 100644 --- a/tests/unit/webcrypto_test.ts +++ b/tests/unit/webcrypto_test.ts @@ -2085,3 +2085,20 @@ Deno.test(async function x25519SharedSecret() { assertEquals(sharedSecret1.byteLength, 16); assertEquals(new Uint8Array(sharedSecret1), new Uint8Array(sharedSecret2)); }); + +Deno.test(async function x25519ExportJwk() { + const keyPair = await crypto.subtle.generateKey( + { + name: "X25519", + }, + true, + ["deriveBits"], + ) as CryptoKeyPair; + + const jwk = await crypto.subtle.exportKey("jwk", keyPair.privateKey); + + assertEquals(jwk.kty, "OKP"); + assertEquals(jwk.crv, "X25519"); + assert(jwk.d); + assert(jwk.x); +}); diff --git a/tests/wpt/runner/expectation.json b/tests/wpt/runner/expectation.json index 6d2cc38562fef2..01a4e0b5e9efcc 100644 --- a/tests/wpt/runner/expectation.json +++ b/tests/wpt/runner/expectation.json @@ -845,14 +845,6 @@ "Good parameters: Ed448 bits (jwk, object(crv, d, x, kty), {name: Ed448}, false, [sign])", "Good parameters: Ed448 bits (pkcs8, buffer(73), {name: Ed448}, false, [sign, sign])", "Good parameters: Ed448 bits (jwk, object(crv, d, x, kty), {name: Ed448}, false, [sign, sign])", - "Good parameters: X25519 bits (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveKey])", - "Good parameters with ignored JWK alg: X25519 (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveKey])", - "Good parameters: X25519 bits (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveBits, deriveKey])", - "Good parameters with ignored JWK alg: X25519 (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveBits, deriveKey])", - "Good parameters: X25519 bits (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveBits])", - "Good parameters with ignored JWK alg: X25519 (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveBits])", - "Good parameters: X25519 bits (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveKey, deriveBits, deriveKey, deriveBits])", - "Good parameters with ignored JWK alg: X25519 (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveKey, deriveBits, deriveKey, deriveBits])", "Good parameters: X448 bits (pkcs8, buffer(72), {name: X448}, true, [deriveKey])", "Good parameters: X448 bits (jwk, object(crv, d, x, kty), {name: X448}, true, [deriveKey])", "Good parameters with ignored JWK alg: X448 (jwk, object(crv, d, x, kty), {name: X448}, true, [deriveKey])", @@ -902,14 +894,6 @@ "Good parameters: Ed448 bits (jwk, object(crv, d, x, kty), {name: Ed448}, false, [sign])", "Good parameters: Ed448 bits (pkcs8, buffer(73), {name: Ed448}, false, [sign, sign])", "Good parameters: Ed448 bits (jwk, object(crv, d, x, kty), {name: Ed448}, false, [sign, sign])", - "Good parameters: X25519 bits (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveKey])", - "Good parameters with ignored JWK alg: X25519 (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveKey])", - "Good parameters: X25519 bits (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveBits, deriveKey])", - "Good parameters with ignored JWK alg: X25519 (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveBits, deriveKey])", - "Good parameters: X25519 bits (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveBits])", - "Good parameters with ignored JWK alg: X25519 (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveBits])", - "Good parameters: X25519 bits (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveKey, deriveBits, deriveKey, deriveBits])", - "Good parameters with ignored JWK alg: X25519 (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveKey, deriveBits, deriveKey, deriveBits])", "Good parameters: X448 bits (pkcs8, buffer(72), {name: X448}, true, [deriveKey])", "Good parameters: X448 bits (jwk, object(crv, d, x, kty), {name: X448}, true, [deriveKey])", "Good parameters with ignored JWK alg: X448 (jwk, object(crv, d, x, kty), {name: X448}, true, [deriveKey])",