Repository: denoland/deno
Pull request: fix(ext/crypto): fix P-521 EC key export for all formats

## Summary

- **Fixed P-521 key generation** to output PKCS8 DER (matching P-256/P-384 behavior) instead of raw scalar bytes, which broke JWK private, JWK public (from private handle), and SPKI exports
- **Added P-521 JWK private export arm** in the `export_key_ec` match, which was the only curve missing from the `JwkPrivate` handler
- Added regression test for P-521 JWK private key export round-trip

Closes #26431

## Test plan

- [x] `cargo check -p deno_crypto` compiles with no warnings
- [x] All `unit_node::crypto` tests pass (10/10)
- [x] Verified all P-521 export formats work: SPKI, PKCS8, JWK public, JWK private

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Changed files:
filename: ext/crypto/export_key.rs
patch: @@ -410,7 +410,23 @@ fn export_key_ec(
             Err(SharedError::ExpectedValidPublicECKey.into())
           }
         }
-        _ => Err(ExportKeyError::UnsupportedNamedCurve),
+        EcNamedCurve::P521 => {
+          let ec_key = p521::SecretKey::from_pkcs8_der(private_key)
+            .map_err(|_| SharedError::FailedDecodePrivateKey)?;
+
+          let point = ec_key.public_key().to_encoded_point(false);
+          if let elliptic_curve::sec1::Coordinates::Uncompressed { x, y } =
+            point.coordinates()
+          {
+            Ok(ExportKeyResult::JwkPrivateEc {
+              x: bytes_to_b64(x),
+              y: bytes_to_b64(y),
+              d: bytes_to_b64(&ec_key.to_bytes()),
+            })
+          } else {
+            Err(SharedError::ExpectedValidPublicECKey.into())
+          }
+        }
       }
     }
     ExportKeyFormat::JwkSecret => Err(SharedError::UnsupportedFormat.into()),

filename: ext/crypto/generate_key.rs
patch: @@ -5,6 +5,7 @@ use aws_lc_rs::signature::EcdsaKeyPair;
 use deno_core::convert::Uint8Array;
 use deno_core::op2;
 use deno_core::unsync::spawn_blocking;
+use elliptic_curve::pkcs8::EncodePrivateKey;
 use elliptic_curve::rand_core::OsRng;
 use num_traits::FromPrimitive;
 use once_cell::sync::Lazy;
@@ -110,10 +111,13 @@ fn generate_key_rsa(
   Ok(private_key.as_bytes().to_vec())
 }
 
-fn generate_key_ec_p521() -> Vec<u8> {
+fn generate_key_ec_p521() -> Result<Vec<u8>, GenerateKeyError> {
   let mut rng = OsRng;
   let key = p521::SecretKey::random(&mut rng);
-  key.to_nonzero_scalar().to_bytes().to_vec()
+  let pkcs8 = key
+    .to_pkcs8_der()
+    .map_err(|_| GenerateKeyError::FailedECKeyGeneration)?;
+  Ok(pkcs8.as_bytes().to_vec())
 }
 
 fn generate_key_ec(
@@ -126,7 +130,7 @@ fn generate_key_ec(
     EcNamedCurve::P384 => {
       &aws_lc_rs::signature::ECDSA_P384_SHA384_FIXED_SIGNING
     }
-    EcNamedCurve::P521 => return Ok(generate_key_ec_p521()),
+    EcNamedCurve::P521 => return generate_key_ec_p521(),
   };
 
   let rng = aws_lc_rs::rand::SystemRandom::new();

filename: tests/unit/webcrypto_test.ts
patch: @@ -2146,6 +2146,36 @@ Deno.test(async function jwkKeyOpsValidation() {
   assert(publicKey);
 });
 
+// https://github.com/denoland/deno/issues/26431
+Deno.test(async function p521ExportJwkPrivateKeyRoundTrip() {
+  const keyPair = await crypto.subtle.generateKey(
+    { name: "ECDSA", namedCurve: "P-521" },
+    true,
+    ["sign", "verify"],
+  ) as CryptoKeyPair;
+
+  const jwk = await crypto.subtle.exportKey("jwk", keyPair.privateKey);
+  assertEquals(jwk.kty, "EC");
+  assertEquals(jwk.crv, "P-521");
+  assert(jwk.x);
+  assert(jwk.y);
+  assert(jwk.d);
+
+  const imported = await crypto.subtle.importKey(
+    "jwk",
+    jwk,
+    { name: "ECDSA", namedCurve: "P-521" },
+    true,
+    ["sign"],
+  );
+  assertEquals(imported.type, "private");
+
+  const reExported = await crypto.subtle.exportKey("jwk", imported);
+  assertEquals(reExported.x, jwk.x);
+  assertEquals(reExported.y, jwk.y);
+  assertEquals(reExported.d, jwk.d);
+});
+
 Deno.test(async function x25519ExportJwk() {
   const keyPair = await crypto.subtle.generateKey(
     {

filename: tests/wpt/runner/expectation.json
patch: @@ -999,98 +999,10 @@
       "successes_AES-KW.https.any.worker.html": true,
       "successes_HMAC.https.any.html": true,
       "successes_HMAC.https.any.worker.html": true,
-      "successes_ECDH.https.any.html": [
-        "Success: generateKey({name: ECDH, namedCurve: P-521}, false, [deriveKey])",
-        "Success: generateKey({name: ECDH, namedCurve: P-521}, true, [deriveKey])",
-        "Success: generateKey({name: ECDH, namedCurve: P-521}, false, [deriveBits, deriveKey])",
-        "Success: generateKey({name: ECDH, namedCurve: P-521}, true, [deriveBits, deriveKey])",
-        "Success: generateKey({name: ECDH, namedCurve: P-521}, false, [deriveBits])",
-        "Success: generateKey({name: ECDH, namedCurve: P-521}, true, [deriveBits])",
-        "Success: generateKey({name: ECDH, namedCurve: P-521}, false, [deriveKey, deriveBits, deriveKey, deriveBits, deriveKey, deriveBits])",
-        "Success: generateKey({name: ECDH, namedCurve: P-521}, true, [deriveKey, deriveBits, deriveKey, deriveBits, deriveKey, deriveBits])",
-        "Success: generateKey({name: ecdh, namedCurve: P-521}, false, [deriveKey])",
-        "Success: generateKey({name: ecdh, namedCurve: P-521}, true, [deriveKey])",
-        "Success: generateKey({name: ecdh, namedCurve: P-521}, false, [deriveBits, deriveKey])",
-        "Success: generateKey({name: ecdh, namedCurve: P-521}, true, [deriveBits, deriveKey])",
-        "Success: generateKey({name: ecdh, namedCurve: P-521}, false, [deriveBits])",
-        "Success: generateKey({name: ecdh, namedCurve: P-521}, true, [deriveBits])",
-        "Success: generateKey({name: ecdh, namedCurve: P-521}, false, [deriveKey, deriveBits, deriveKey, deriveBits, deriveKey, deriveBits])",
-        "Success: generateKey({name: ecdh, namedCurve: P-521}, true, [deriveKey, deriveBits, deriveKey, deriveBits, deriveKey, deriveBits])",
-        "Success: generateKey({name: Ecdh, namedCurve: P-521}, false, [deriveKey])",
-        "Success: generateKey({name: Ecdh, namedCurve: P-521}, true, [deriveKey])",
-        "Success: generateKey({name: Ecdh, namedCurve: P-521}, false, [deriveBits, deriveKey])",
-        "Success: generateKey({name: Ecdh, namedCurve: P-521}, true, [deriveBits, deriveKey])",
-        "Success: generateKey({name: Ecdh, namedCurve: P-521}, false, [deriveBits])",
-        "Success: generateKey({name: Ecdh, namedCurve: P-521}, true, [deriveBits])",
-        "Success: generateKey({name: Ecdh, namedCurve: P-521}, false, [deriveKey, deriveBits, deriveKey, deriveBits, deriveKey, deriveBits])",
-        "Success: generateKey({name: Ecdh, namedCurve: P-521}, true, [deriveKey, deriveBits, deriveKey, deriveBits, deriveKey, deriveBits])"
-      ],
-      "successes_ECDH.https.any.worker.html": [
-        "Success: generateKey({name: ECDH, namedCurve: P-521}, false, [deriveKey])",
-        "Success: generateKey({name: ECDH, namedCurve: P-521}, true, [deriveKey])",
-        "Success: generateKey({name: ECDH, namedCurve: P-521}, false, [deriveBits, deriveKey])",
-        "Success: generateKey({name: ECDH, namedCurve: P-521}, true, [deriveBits, deriveKey])",
-        "Success: generateKey({name: ECDH, namedCurve: P-521}, false, [deriveBits])",
-        "Success: generateKey({name: ECDH, namedCurve: P-521}, true, [deriveBits])",
-        "Success: generateKey({name: ECDH, namedCurve: P-521}, false, [deriveKey, deriveBits, deriveKey, deriveBits, deriveKey, deriveBits])",
-        "Success: generateKey({name: ECDH, namedCurve: P-521}, true, [deriveKey, deriveBits, deriveKey, deriveBits, deriveKey, deriveBits])",
-        "Success: generateKey({name: ecdh, namedCurve: P-521}, false, [deriveKey])",
-        "Success: generateKey({name: ecdh, namedCurve: P-521}, true, [deriveKey])",
-        "Success: generateKey({name: ecdh, namedCurve: P-521}, false, [deriveBits, deriveKey])",
-        "Success: generateKey({name: ecdh, namedCurve: P-521}, true, [deriveBits, deriveKey])",
-        "Success: generateKey({name: ecdh, namedCurve: P-521}, false, [deriveBits])",
-        "Success: generateKey({name: ecdh, namedCurve: P-521}, true, [deriveBits])",
-        "Success: generateKey({name: ecdh, namedCurve: P-521}, false, [deriveKey, deriveBits, deriveKey, deriveBits, deriveKey, deriveBits])",
-        "Success: generateKey({name: ecdh, namedCurve: P-521}, true, [deriveKey, deriveBits, deriveKey, deriveBits, deriveKey, deriveBits])",
-        "Success: generateKey({name: Ecdh, namedCurve: P-521}, false, [deriveKey])",
-        "Success: generateKey({name: Ecdh, namedCurve: P-521}, true, [deriveKey])",
-        "Success: generateKey({name: Ecdh, namedCurve: P-521}, false, [deriveBits, deriveKey])",
-        "Success: generateKey({name: Ecdh, namedCurve: P-521}, true, [deriveBits, deriveKey])",
-        "Success: generateKey({name: Ecdh, namedCurve: P-521}, false, [deriveBits])",
-        "Success: generateKey({name: Ecdh, namedCurve: P-521}, true, [deriveBits])",
-        "Success: generateKey({name: Ecdh, namedCurve: P-521}, false, [deriveKey, deriveBits, deriveKey, deriveBits, deriveKey, deriveBits])",
-        "Success: generateKey({name: Ecdh, namedCurve: P-521}, true, [deriveKey, deriveBits, deriveKey, deriveBits, deriveKey, deriveBits])"
-      ],
-      "successes_ECDSA.https.any.html": [
-        "Success: generateKey({name: ECDSA, namedCurve: P-521}, false, [sign])",
-        "Success: generateKey({name: ECDSA, namedCurve: P-521}, true, [sign])",
-        "Success: generateKey({name: ECDSA, namedCurve: P-521}, false, [verify, sign])",
-        "Success: generateKey({name: ECDSA, namedCurve: P-521}, true, [verify, sign])",
-        "Success: generateKey({name: ECDSA, namedCurve: P-521}, false, [sign, verify, sign, sign, verify])",
-        "Success: generateKey({name: ECDSA, namedCurve: P-521}, true, [sign, verify, sign, sign, verify])",
-        "Success: generateKey({name: ecdsa, namedCurve: P-521}, false, [sign])",
-        "Success: generateKey({name: ecdsa, namedCurve: P-521}, true, [sign])",
-        "Success: generateKey({name: ecdsa, namedCurve: P-521}, false, [verify, sign])",
-        "Success: generateKey({name: ecdsa, namedCurve: P-521}, true, [verify, sign])",
-        "Success: generateKey({name: ecdsa, namedCurve: P-521}, false, [sign, verify, sign, sign, verify])",
-        "Success: generateKey({name: ecdsa, namedCurve: P-521}, true, [sign, verify, sign, sign, verify])",
-        "Success: generateKey({name: Ecdsa, namedCurve: P-521}, false, [sign])",
-        "Success: generateKey({name: Ecdsa, namedCurve: P-521}, true, [sign])",
-        "Success: generateKey({name: Ecdsa, namedCurve: P-521}, false, [verify, sign])",
-        "Success: generateKey({name: Ecdsa, namedCurve: P-521}, true, [verify, sign])",
-        "Success: generateKey({name: Ecdsa, namedCurve: P-521}, false, [sign, verify, sign, sign, verify])",
-        "Success: generateKey({name: Ecdsa, namedCurve: P-521}, true, [sign, verify, sign, sign, verify])"
-      ],
-      "successes_ECDSA.https.any.worker.html": [
-        "Success: generateKey({name: ECDSA, namedCurve: P-521}, false, [sign])",
-        "Success: generateKey({name: ECDSA, namedCurve: P-521}, true, [sign])",
-        "Success: generateKey({name: ECDSA, namedCurve: P-521}, false, [verify, sign])",
-        "Success: generateKey({name: ECDSA, namedCurve: P-521}, true, [verify, sign])",
-        "Success: generateKey({name: ECDSA, namedCurve: P-521}, false, [sign, verify, sign, sign, verify])",
-        "Success: generateKey({name: ECDSA, namedCurve: P-521}, true, [sign, verify, sign, sign, verify])",
-        "Success: generateKey({name: ecdsa, namedCurve: P-521}, false, [sign])",
-        "Success: generateKey({name: ecdsa, namedCurve: P-521}, true, [sign])",
-        "Success: generateKey({name: ecdsa, namedCurve: P-521}, false, [verify, sign])",
-        "Success: generateKey({name: ecdsa, namedCurve: P-521}, true, [verify, sign])",
-        "Success: generateKey({name: ecdsa, namedCurve: P-521}, false, [sign, verify, sign, sign, verify])",
-        "Success: generateKey({name: ecdsa, namedCurve: P-521}, true, [sign, verify, sign, sign, verify])",
-        "Success: generateKey({name: Ecdsa, namedCurve: P-521}, false, [sign])",
-        "Success: generateKey({name: Ecdsa, namedCurve: P-521}, true, [sign])",
-        "Success: generateKey({name: Ecdsa, namedCurve: P-521}, false, [verify, sign])",
-        "Success: generateKey({name: Ecdsa, namedCurve: P-521}, true, [verify, sign])",
-        "Success: generateKey({name: Ecdsa, namedCurve: P-521}, false, [sign, verify, sign, sign, verify])",
-        "Success: generateKey({name: Ecdsa, namedCurve: P-521}, true, [sign, verify, sign, sign, verify])"
-      ],
+      "successes_ECDH.https.any.html": true,
+      "successes_ECDH.https.any.worker.html": true,
+      "successes_ECDSA.https.any.html": true,
+      "successes_ECDSA.https.any.worker.html": true,
       "successes_RSA-OAEP.https.any.html?1-10": true,
       "successes_RSA-OAEP.https.any.html?101-110": true,
       "successes_RSA-OAEP.https.any.html?11-20": true,
@@ -1244,14 +1156,8 @@
         "Good parameters: P-521 bits (raw, buffer(67, compressed), {name: ECDSA, namedCurve: P-521}, true, [])",
         "Good parameters: P-521 bits (spki, buffer(90, compressed), {name: ECDSA, namedCurve: P-521}, true, [verify, verify])",
         "Good parameters: P-521 bits (raw, buffer(67, compressed), {name: ECDSA, namedCurve: P-521}, true, [verify, verify])",
-        "Good parameters: P-521 bits (jwk, object(kty, crv, x, y, d), {name: ECDSA, namedCurve: P-521}, true, [sign])",
-        "Good parameters: P-521 bits (jwk, object(kty, crv, x, y, d), {name: ECDSA, namedCurve: P-521}, true, [sign, sign])",
         "Good parameters: P-521 bits (spki, buffer(90, compressed), {name: ECDH, namedCurve: P-521}, true, [])",
-        "Good parameters: P-521 bits (raw, buffer(67, compressed), {name: ECDH, namedCurve: P-521}, true, [])",
-        "Good parameters: P-521 bits (jwk, object(kty, crv, x, y, d), {name: ECDH, namedCurve: P-521}, true, [deriveKey])",
-        "Good parameters: P-521 bits (jwk, object(kty, crv, x, y, d), {name: ECDH, namedCurve: P-521}, true, [deriveBits, deriveKey])",
-        "Good parameters: P-521 bits (jwk, object(kty, crv, x, y, d), {name: ECDH, namedCurve: P-521}, true, [deriveBits])",
-        "Good parameters: P-521 bits (jwk, object(kty, crv, x, y, d), {name: ECDH, namedCurve: P-521}, true, [deriveKey, deriveBits, deriveKey, deriveBits])"
+        "Good parameters: P-521 bits (raw, buffer(67, compressed), {name: ECDH, namedCurve: P-521}, true, [])"
       ],
       "ec_importKey.https.any.worker.html": [
         "Good parameters: P-521 bits (spki, buffer(90, compressed), {name: ECDSA, namedCurve: P-521}, true, [verify])",
@@ -1260,14 +1166,8 @@
         "Good parameters: P-521 bits (raw, buffer(67, compressed), {name: ECDSA, namedCurve: P-521}, true, [])",
         "Good parameters: P-521 bits (spki, buffer(90, compressed), {name: ECDSA, namedCurve: P-521}, true, [verify, verify])",
         "Good parameters: P-521 bits (raw, buffer(67, compressed), {name: ECDSA, namedCurve: P-521}, true, [verify, verify])",
-        "Good parameters: P-521 bits (jwk, object(kty, crv, x, y, d), {name: ECDSA, namedCurve: P-521}, true, [sign])",
-        "Good parameters: P-521 bits (jwk, object(kty, crv, x, y, d), {name: ECDSA, namedCurve: P-521}, true, [sign, sign])",
         "Good parameters: P-521 bits (spki, buffer(90, compressed), {name: ECDH, namedCurve: P-521}, true, [])",
-        "Good parameters: P-521 bits (raw, buffer(67, compressed), {name: ECDH, namedCurve: P-521}, true, [])",
-        "Good parameters: P-521 bits (jwk, object(kty, crv, x, y, d), {name: ECDH, namedCurve: P-521}, true, [deriveKey])",
-        "Good parameters: P-521 bits (jwk, object(kty, crv, x, y, d), {name: ECDH, namedCurve: P-521}, true, [deriveBits, deriveKey])",
-        "Good parameters: P-521 bits (jwk, object(kty, crv, x, y, d), {name: ECDH, namedCurve: P-521}, true, [deriveBits])",
-        "Good parameters: P-521 bits (jwk, object(kty, crv, x, y, d), {name: ECDH, namedCurve: P-521}, true, [deriveKey, deriveBits, deriveKey, deriveBits])"
+        "Good parameters: P-521 bits (raw, buffer(67, compressed), {name: ECDH, namedCurve: P-521}, true, [])"
       ],
       "rsa_importKey.https.any.html": true,
       "rsa_importKey.https.any.worker.html": true,