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

fix(ext/crypto): export private x25519 JWK key #27828

Merged
merged 3 commits into from
Jan 27, 2025
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
12 changes: 7 additions & 5 deletions ext/crypto/00_crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions ext/crypto/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
23 changes: 17 additions & 6 deletions ext/crypto/x25519.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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
Expand All @@ -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");
Comment on lines +50 to +51
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any logic in JS that would prevent passing keys of other length. Maybe handle it gracefully and return an error?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is passed from the inner key handle held by JS which should be already validated. If this panics then there is a bug elsewhere during import/generation of the key.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok 👍

BASE64_URL_SAFE_NO_PAD
.encode(x25519_dalek::x25519(private_key, X25519_BASEPOINT_BYTES))
}

const MONTGOMERY_IDENTITY: MontgomeryPoint = MontgomeryPoint([0; 32]);

#[op2(fast)]
Expand Down
17 changes: 17 additions & 0 deletions tests/unit/webcrypto_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
16 changes: 0 additions & 16 deletions tests/wpt/runner/expectation.json
Original file line number Diff line number Diff line change
Expand Up @@ -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])",
Expand Down Expand Up @@ -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])",
Expand Down
Loading