Repository: denoland/deno
Pull request: feat(ext/crypto): implement P-521 sign, verify, and ECDH derive

## Summary

- Adds P-521 (secp521r1) ECDSA sign/verify and ECDH deriveBits/deriveKey to WebCrypto, completing the P-521 curve support per [W3C Web Crypto spec](https://www.w3.org/TR/WebCryptoAPI/#dfn-EcKeyGenParams)
- Key generation and import/export were already implemented; this fills in the remaining operations
- Handles short hash digests (SHA-1, SHA-256) that are smaller than P-521's `bits2field` minimum requirement by left-padding

Closes #13449

Changed files:
filename: Cargo.toml
patch: @@ -402,7 +402,7 @@ ocb3 = "0.1.0"
 p224 = { version = "0.13.0", features = ["ecdh"] }
 p256 = { version = "0.13.2", features = ["ecdh", "jwk"] }
 p384 = { version = "0.13.0", features = ["ecdh", "jwk"] }
-p521 = { version = "0.13.3", features = ["ecdh", "jwk"] }
+p521 = { version = "0.13.3", features = ["ecdh", "ecdsa", "jwk", "pkcs8"] }
 pbkdf2 = "0.12.1"
 pkcs8 = "0.10.2"
 ripemd = "0.1.3"

filename: ext/crypto/key.rs
patch: @@ -27,13 +27,16 @@ pub enum CryptoNamedCurve {
   P256,
   #[serde(rename = "P-384")]
   P384,
+  #[serde(rename = "P-521")]
+  P521,
 }
 
 impl From<CryptoNamedCurve> for &RingAlgorithm {
   fn from(curve: CryptoNamedCurve) -> &'static RingAlgorithm {
     match curve {
       CryptoNamedCurve::P256 => &aws_lc_rs::agreement::ECDH_P256,
       CryptoNamedCurve::P384 => &aws_lc_rs::agreement::ECDH_P384,
+      CryptoNamedCurve::P521 => &aws_lc_rs::agreement::ECDH_P521,
     }
   }
 }
@@ -47,6 +50,9 @@ impl From<CryptoNamedCurve> for &EcdsaSigningAlgorithm {
       CryptoNamedCurve::P384 => {
         &aws_lc_rs::signature::ECDSA_P384_SHA384_FIXED_SIGNING
       }
+      CryptoNamedCurve::P521 => {
+        &aws_lc_rs::signature::ECDSA_P521_SHA512_FIXED_SIGNING
+      }
     }
   }
 }
@@ -56,6 +62,7 @@ impl From<CryptoNamedCurve> for &EcdsaVerificationAlgorithm {
     match curve {
       CryptoNamedCurve::P256 => &aws_lc_rs::signature::ECDSA_P256_SHA256_FIXED,
       CryptoNamedCurve::P384 => &aws_lc_rs::signature::ECDSA_P384_SHA384_FIXED,
+      CryptoNamedCurve::P521 => &aws_lc_rs::signature::ECDSA_P521_SHA512_FIXED,
     }
   }
 }

filename: ext/crypto/lib.rs
patch: @@ -26,6 +26,9 @@ use p256::pkcs8::DecodePrivateKey;
 use p384::ecdsa::Signature as P384Signature;
 use p384::ecdsa::SigningKey as P384SigningKey;
 use p384::ecdsa::VerifyingKey as P384VerifyingKey;
+use p521::ecdsa::Signature as P521Signature;
+use p521::ecdsa::SigningKey as P521SigningKey;
+use p521::ecdsa::VerifyingKey as P521VerifyingKey;
 pub use rand;
 use rand::Rng;
 use rand::SeedableRng;
@@ -394,6 +397,32 @@ pub async fn op_crypto_sign_key(
               signing_key.sign_prehash(&prehash)?;
             signature.to_bytes().to_vec()
           }
+          CryptoNamedCurve::P521 => {
+            let secret_key = p521::SecretKey::from_pkcs8_der(&args.key.data)
+              .map_err(|_| CryptoError::InvalidKeyFormat)?;
+            let signing_key =
+              P521SigningKey::from_bytes(&secret_key.to_bytes())
+                .map_err(|_| CryptoError::InvalidKeyFormat)?;
+            let prehash = match hash {
+              CryptoHash::Sha1 => sha1::Sha1::digest(data).to_vec(),
+              CryptoHash::Sha256 => sha2::Sha256::digest(data).to_vec(),
+              CryptoHash::Sha384 => sha2::Sha384::digest(data).to_vec(),
+              CryptoHash::Sha512 => sha2::Sha512::digest(data).to_vec(),
+            };
+            // P-521 field size is 66 bytes; bits2field requires at least
+            // half that (33 bytes). Left-pad shorter hashes to meet the
+            // minimum.
+            let prehash = if prehash.len() < 33 {
+              let mut padded = vec![0u8; 33 - prehash.len()];
+              padded.extend_from_slice(&prehash);
+              padded
+            } else {
+              prehash
+            };
+            let signature: P521Signature =
+              signing_key.sign_prehash(&prehash)?;
+            signature.to_bytes().to_vec()
+          }
         }
       }
       Algorithm::Hmac => {
@@ -560,6 +589,46 @@ pub async fn op_crypto_verify_key(
               _ => false,
             }
           }
+          CryptoNamedCurve::P521 => {
+            let verifying_key = match args.key.r#type {
+              KeyType::Public => {
+                P521VerifyingKey::from_sec1_bytes(&args.key.data)
+                  .map_err(|_| CryptoError::InvalidKeyFormat)?
+              }
+              KeyType::Private => {
+                let secret_key =
+                  p521::SecretKey::from_pkcs8_der(&args.key.data)
+                    .map_err(|_| CryptoError::InvalidKeyFormat)?;
+                // Construct inner ecdsa signing key to get verifying key
+                let inner_signing_key =
+                  ecdsa::SigningKey::<p521::NistP521>::from(secret_key);
+                P521VerifyingKey::from(*inner_signing_key.verifying_key())
+              }
+              _ => return Err(CryptoError::InvalidKeyFormat),
+            };
+            match P521Signature::from_slice(&args.signature) {
+              Ok(signature) => {
+                let prehash = match hash {
+                  CryptoHash::Sha1 => sha1::Sha1::digest(data).to_vec(),
+                  CryptoHash::Sha256 => sha2::Sha256::digest(data).to_vec(),
+                  CryptoHash::Sha384 => sha2::Sha384::digest(data).to_vec(),
+                  CryptoHash::Sha512 => sha2::Sha512::digest(data).to_vec(),
+                };
+                // P-521 field size is 66 bytes; bits2field requires at least
+                // half that (33 bytes). Left-pad shorter hashes to meet the
+                // minimum.
+                let prehash = if prehash.len() < 33 {
+                  let mut padded = vec![0u8; 33 - prehash.len()];
+                  padded.extend_from_slice(&prehash);
+                  padded
+                } else {
+                  prehash
+                };
+                verifying_key.verify_prehash(&prehash, &signature).is_ok()
+              }
+              _ => false,
+            }
+          }
         }
       }
       _ => return Err(CryptoError::UnsupportedAlgorithm),
@@ -695,6 +764,39 @@ pub async fn op_crypto_derive_bits(
               public_key.as_affine(),
             );
 
+            // raw serialized x-coordinate of the computed point
+            Ok(shared_secret.raw_secret_bytes().to_vec().into())
+          }
+          CryptoNamedCurve::P521 => {
+            let secret_key = p521::SecretKey::from_pkcs8_der(&args.key.data)
+              .map_err(|_| CryptoError::DecodePrivateKey)?;
+
+            let public_key = match public_key.r#type {
+              KeyType::Private => {
+                p521::SecretKey::from_pkcs8_der(&public_key.data)
+                  .map_err(|_| CryptoError::DecodePrivateKey)?
+                  .public_key()
+              }
+              KeyType::Public => {
+                let point = p521::EncodedPoint::from_bytes(public_key.data)
+                  .map_err(|_| CryptoError::DecodePrivateKey)?;
+
+                let pk = p521::PublicKey::from_encoded_point(&point);
+                // pk is a constant time Option.
+                if pk.is_some().into() {
+                  pk.unwrap()
+                } else {
+                  return Err(CryptoError::DecodePrivateKey);
+                }
+              }
+              _ => unreachable!(),
+            };
+
+            let shared_secret = p521::elliptic_curve::ecdh::diffie_hellman(
+              secret_key.to_nonzero_scalar(),
+              public_key.as_affine(),
+            );
+
             // raw serialized x-coordinate of the computed point
             Ok(shared_secret.raw_secret_bytes().to_vec().into())
           }

filename: tests/wpt/runner/expectations/WebCryptoAPI.json
patch: @@ -26,38 +26,10 @@
     ]
   },
   "derive_bits_keys": {
-    "ecdh_bits.https.any.html": {
-      "expectedFailures": [
-        "P-521 good parameters",
-        "P-521 mixed case parameters",
-        "P-521 short result",
-        "P-521 non-multiple of 8 bits",
-        "P-521 no deriveBits usage for base key",
-        "P-521 asking for too many bits"
-      ]
-    },
-    "ecdh_bits.https.any.worker.html": {
-      "expectedFailures": [
-        "P-521 good parameters",
-        "P-521 mixed case parameters",
-        "P-521 short result",
-        "P-521 non-multiple of 8 bits",
-        "P-521 no deriveBits usage for base key",
-        "P-521 asking for too many bits"
-      ]
-    },
-    "ecdh_keys.https.any.html": {
-      "expectedFailures": [
-        "P-521 good parameters",
-        "P-521 mixed case parameters"
-      ]
-    },
-    "ecdh_keys.https.any.worker.html": {
-      "expectedFailures": [
-        "P-521 good parameters",
-        "P-521 mixed case parameters"
-      ]
-    },
+    "ecdh_bits.https.any.html": true,
+    "ecdh_bits.https.any.worker.html": true,
+    "ecdh_keys.https.any.html": true,
+    "ecdh_keys.https.any.worker.html": true,
     "hkdf.https.any.html?1-1000": {
       "expectedFailures": [
         "short derivedKey, normal salt, SHA-384, with normal info with 0 length",
@@ -2231,137 +2203,17 @@
     "ecdsa.https.any.html": {
       "expectedFailures": [
         "ECDSA P-384 with SHA-1 verification",
-        "ECDSA P-521 with SHA-1 verification",
-        "ECDSA P-521 with SHA-256 verification",
-        "ECDSA P-521 with SHA-384 verification",
-        "ECDSA P-521 with SHA-512 verification",
         "ECDSA P-384 with SHA-1 verification with altered signature after call",
-        "ECDSA P-521 with SHA-1 verification with altered signature after call",
-        "ECDSA P-521 with SHA-256 verification with altered signature after call",
-        "ECDSA P-521 with SHA-384 verification with altered signature after call",
-        "ECDSA P-521 with SHA-512 verification with altered signature after call",
         "ECDSA P-384 with SHA-1 with altered plaintext after call",
-        "ECDSA P-521 with SHA-1 with altered plaintext after call",
-        "ECDSA P-521 with SHA-256 with altered plaintext after call",
-        "ECDSA P-521 with SHA-384 with altered plaintext after call",
-        "ECDSA P-521 with SHA-512 with altered plaintext after call",
-        "ECDSA P-384 with SHA-1 round trip",
-        "ECDSA P-521 with SHA-1 round trip",
-        "ECDSA P-521 with SHA-256 round trip",
-        "ECDSA P-521 with SHA-384 round trip",
-        "ECDSA P-521 with SHA-512 round trip",
-        "ECDSA P-521 with SHA-1 verification failure due to altered signature",
-        "ECDSA P-521 with SHA-256 verification failure due to altered signature",
-        "ECDSA P-521 with SHA-384 verification failure due to altered signature",
-        "ECDSA P-521 with SHA-512 verification failure due to altered signature",
-        "ECDSA P-521 with SHA-1 verification failure due to wrong hash",
-        "ECDSA P-521 with SHA-256 verification failure due to wrong hash",
-        "ECDSA P-521 with SHA-384 verification failure due to wrong hash",
-        "ECDSA P-521 with SHA-512 verification failure due to wrong hash",
-        "ECDSA P-521 with SHA-1 verification failure due to shortened signature",
-        "ECDSA P-521 with SHA-256 verification failure due to shortened signature",
-        "ECDSA P-521 with SHA-384 verification failure due to shortened signature",
-        "ECDSA P-521 with SHA-512 verification failure due to shortened signature",
-        "ECDSA P-521 with SHA-1 verification failure due to altered plaintext",
-        "ECDSA P-521 with SHA-256 verification failure due to altered plaintext",
-        "ECDSA P-521 with SHA-384 verification failure due to altered plaintext",
-        "ECDSA P-521 with SHA-512 verification failure due to altered plaintext",
-        "ECDSA P-521 with SHA-1 - The signature was truncated by 1 byte verification",
-        "ECDSA P-521 with SHA-1 - The signature was made using SHA-1, however verification is being done using SHA-256 verification",
-        "ECDSA P-521 with SHA-1 - The signature was made using SHA-1, however verification is being done using SHA-384 verification",
-        "ECDSA P-521 with SHA-1 - The signature was made using SHA-1, however verification is being done using SHA-512 verification",
-        "ECDSA P-521 with SHA-1 - Signature has excess padding verification",
-        "ECDSA P-521 with SHA-1 - The signature is empty verification",
-        "ECDSA P-521 with SHA-1 - The signature is all zeroes verification",
-        "ECDSA P-521 with SHA-256 - The signature was truncated by 1 byte verification",
-        "ECDSA P-521 with SHA-256 - The signature was made using SHA-256, however verification is being done using SHA-1 verification",
-        "ECDSA P-521 with SHA-256 - The signature was made using SHA-256, however verification is being done using SHA-384 verification",
-        "ECDSA P-521 with SHA-256 - The signature was made using SHA-256, however verification is being done using SHA-512 verification",
-        "ECDSA P-521 with SHA-256 - Signature has excess padding verification",
-        "ECDSA P-521 with SHA-256 - The signature is empty verification",
-        "ECDSA P-521 with SHA-256 - The signature is all zeroes verification",
-        "ECDSA P-521 with SHA-384 - The signature was truncated by 1 byte verification",
-        "ECDSA P-521 with SHA-384 - The signature was made using SHA-384, however verification is being done using SHA-1 verification",
-        "ECDSA P-521 with SHA-384 - The signature was made using SHA-384, however verification is being done using SHA-256 verification",
-        "ECDSA P-521 with SHA-384 - The signature was made using SHA-384, however verification is being done using SHA-512 verification",
-        "ECDSA P-521 with SHA-384 - Signature has excess padding verification",
-        "ECDSA P-521 with SHA-384 - The signature is empty verification",
-        "ECDSA P-521 with SHA-384 - The signature is all zeroes verification",
-        "ECDSA P-521 with SHA-512 - The signature was truncated by 1 byte verification",
-        "ECDSA P-521 with SHA-512 - The signature was made using SHA-512, however verification is being done using SHA-1 verification",
-        "ECDSA P-521 with SHA-512 - The signature was made using SHA-512, however verification is being done using SHA-256 verification",
-        "ECDSA P-521 with SHA-512 - The signature was made using SHA-512, however verification is being done using SHA-384 verification",
-        "ECDSA P-521 with SHA-512 - Signature has excess padding verification",
-        "ECDSA P-521 with SHA-512 - The signature is empty verification",
-        "ECDSA P-521 with SHA-512 - The signature is all zeroes verification"
+        "ECDSA P-384 with SHA-1 round trip"
       ]
     },
     "ecdsa.https.any.worker.html": {
       "expectedFailures": [
         "ECDSA P-384 with SHA-1 verification",
-        "ECDSA P-521 with SHA-1 verification",
-        "ECDSA P-521 with SHA-256 verification",
-        "ECDSA P-521 with SHA-384 verification",
-        "ECDSA P-521 with SHA-512 verification",
         "ECDSA P-384 with SHA-1 verification with altered signature after call",
-        "ECDSA P-521 with SHA-1 verification with altered signature after call",
-        "ECDSA P-521 with SHA-256 verification with altered signature after call",
-        "ECDSA P-521 with SHA-384 verification with altered signature after call",
-        "ECDSA P-521 with SHA-512 verification with altered signature after call",
         "ECDSA P-384 with SHA-1 with altered plaintext after call",
-        "ECDSA P-521 with SHA-1 with altered plaintext after call",
-        "ECDSA P-521 with SHA-256 with altered plaintext after call",
-        "ECDSA P-521 with SHA-384 with altered plaintext after call",
-        "ECDSA P-521 with SHA-512 with altered plaintext after call",
-        "ECDSA P-384 with SHA-1 round trip",
-        "ECDSA P-521 with SHA-1 round trip",
-        "ECDSA P-521 with SHA-256 round trip",
-        "ECDSA P-521 with SHA-384 round trip",
-        "ECDSA P-521 with SHA-512 round trip",
-        "ECDSA P-521 with SHA-1 verification failure due to altered signature",
-        "ECDSA P-521 with SHA-256 verification failure due to altered signature",
-        "ECDSA P-521 with SHA-384 verification failure due to altered signature",
-        "ECDSA P-521 with SHA-512 verification failure due to altered signature",
-        "ECDSA P-521 with SHA-1 verification failure due to wrong hash",
-        "ECDSA P-521 with SHA-256 verification failure due to wrong hash",
-        "ECDSA P-521 with SHA-384 verification failure due to wrong hash",
-        "ECDSA P-521 with SHA-512 verification failure due to wrong hash",
-        "ECDSA P-521 with SHA-1 verification failure due to shortened signature",
-        "ECDSA P-521 with SHA-256 verification failure due to shortened signature",
-        "ECDSA P-521 with SHA-384 verification failure due to shortened signature",
-        "ECDSA P-521 with SHA-512 verification failure due to shortened signature",
-        "ECDSA P-521 with SHA-1 verification failure due to altered plaintext",
-        "ECDSA P-521 with SHA-256 verification failure due to altered plaintext",
-        "ECDSA P-521 with SHA-384 verification failure due to altered plaintext",
-        "ECDSA P-521 with SHA-512 verification failure due to altered plaintext",
-        "ECDSA P-521 with SHA-1 - The signature was truncated by 1 byte verification",
-        "ECDSA P-521 with SHA-1 - The signature was made using SHA-1, however verification is being done using SHA-256 verification",
-        "ECDSA P-521 with SHA-1 - The signature was made using SHA-1, however verification is being done using SHA-384 verification",
-        "ECDSA P-521 with SHA-1 - The signature was made using SHA-1, however verification is being done using SHA-512 verification",
-        "ECDSA P-521 with SHA-1 - Signature has excess padding verification",
-        "ECDSA P-521 with SHA-1 - The signature is empty verification",
-        "ECDSA P-521 with SHA-1 - The signature is all zeroes verification",
-        "ECDSA P-521 with SHA-256 - The signature was truncated by 1 byte verification",
-        "ECDSA P-521 with SHA-256 - The signature was made using SHA-256, however verification is being done using SHA-1 verification",
-        "ECDSA P-521 with SHA-256 - The signature was made using SHA-256, however verification is being done using SHA-384 verification",
-        "ECDSA P-521 with SHA-256 - The signature was made using SHA-256, however verification is being done using SHA-512 verification",
-        "ECDSA P-521 with SHA-256 - Signature has excess padding verification",
-        "ECDSA P-521 with SHA-256 - The signature is empty verification",
-        "ECDSA P-521 with SHA-256 - The signature is all zeroes verification",
-        "ECDSA P-521 with SHA-384 - The signature was truncated by 1 byte verification",
-        "ECDSA P-521 with SHA-384 - The signature was made using SHA-384, however verification is being done using SHA-1 verification",
-        "ECDSA P-521 with SHA-384 - The signature was made using SHA-384, however verification is being done using SHA-256 verification",
-        "ECDSA P-521 with SHA-384 - The signature was made using SHA-384, however verification is being done using SHA-512 verification",
-        "ECDSA P-521 with SHA-384 - Signature has excess padding verification",
-        "ECDSA P-521 with SHA-384 - The signature is empty verification",
-        "ECDSA P-521 with SHA-384 - The signature is all zeroes verification",
-        "ECDSA P-521 with SHA-512 - The signature was truncated by 1 byte verification",
-        "ECDSA P-521 with SHA-512 - The signature was made using SHA-512, however verification is being done using SHA-1 verification",
-        "ECDSA P-521 with SHA-512 - The signature was made using SHA-512, however verification is being done using SHA-256 verification",
-        "ECDSA P-521 with SHA-512 - The signature was made using SHA-512, however verification is being done using SHA-384 verification",
-        "ECDSA P-521 with SHA-512 - Signature has excess padding verification",
-        "ECDSA P-521 with SHA-512 - The signature is empty verification",
-        "ECDSA P-521 with SHA-512 - The signature is all zeroes verification"
+        "ECDSA P-384 with SHA-1 round trip"
       ]
     },
     "hmac.https.any.html": true,