diff --git a/cli/tests/unit/webcrypto_test.ts b/cli/tests/unit/webcrypto_test.ts index 475efde6c0..a5de9fa367 100644 --- a/cli/tests/unit/webcrypto_test.ts +++ b/cli/tests/unit/webcrypto_test.ts @@ -189,7 +189,7 @@ unitTest(async function testGenerateHMACKey() { assert(key.usages.includes("sign")); }); -unitTest(async function testSignECDSA() { +unitTest(async function testECDSASignVerify() { const key = await window.crypto.subtle.generateKey( { name: "ECDSA", @@ -208,6 +208,56 @@ unitTest(async function testSignECDSA() { ); assert(signature); + assert(signature instanceof ArrayBuffer); + + const verified = await window.crypto.subtle.verify( + { hash: { name: "SHA-384" }, name: "ECDSA" }, + key.publicKey, + signature, + encoded, + ); + assert(verified); +}); + +// Tests the "bad paths" as a temporary replacement for sign_verify/ecdsa WPT. +unitTest(async function testECDSASignVerifyFail() { + const key = await window.crypto.subtle.generateKey( + { + name: "ECDSA", + namedCurve: "P-384", + }, + true, + ["sign", "verify"], + ); + + const encoded = new Uint8Array([1]); + // Signing with a public key (InvalidAccessError) + await assertThrowsAsync(async () => { + await window.crypto.subtle.sign( + { name: "ECDSA", hash: "SHA-384" }, + key.publicKey, + new Uint8Array([1]), + ); + throw new TypeError("unreachable"); + }, DOMException); + + // Do a valid sign for later verifying. + const signature = await window.crypto.subtle.sign( + { name: "ECDSA", hash: "SHA-384" }, + key.privateKey, + encoded, + ); + + // Verifying with a private key (InvalidAccessError) + await assertThrowsAsync(async () => { + await window.crypto.subtle.verify( + { hash: { name: "SHA-384" }, name: "ECDSA" }, + key.privateKey, + signature, + encoded, + ); + throw new TypeError("unreachable"); + }, DOMException); }); // https://github.com/denoland/deno/issues/11313 diff --git a/ext/crypto/00_crypto.js b/ext/crypto/00_crypto.js index a95c0be1bc..0e5a511e57 100644 --- a/ext/crypto/00_crypto.js +++ b/ext/crypto/00_crypto.js @@ -92,6 +92,7 @@ "verify": { "RSASSA-PKCS1-v1_5": null, "RSA-PSS": "RsaPssParams", + "ECDSA": "EcdsaParams", "HMAC": null, }, "importKey": { @@ -1185,6 +1186,25 @@ signature, }, data); } + case "ECDSA": { + // 1. + if (key[_type] !== "public") { + throw new DOMException( + "Key type not supported", + "InvalidAccessError", + ); + } + // 2. + const hash = normalizedAlgorithm.hash.name; + // 3-8. + return await core.opAsync("op_crypto_verify_key", { + key: keyData, + algorithm: "ECDSA", + hash, + signature, + namedCurve: key[_algorithm].namedCurve, + }, data); + } } throw new TypeError("unreachable"); diff --git a/ext/crypto/key.rs b/ext/crypto/key.rs index d2420bfe93..663217887c 100644 --- a/ext/crypto/key.rs +++ b/ext/crypto/key.rs @@ -4,6 +4,7 @@ use ring::agreement::Algorithm as RingAlgorithm; use ring::digest; use ring::hmac::Algorithm as HmacAlgorithm; use ring::signature::EcdsaSigningAlgorithm; +use ring::signature::EcdsaVerificationAlgorithm; use serde::Deserialize; use serde::Serialize; @@ -57,6 +58,15 @@ impl From for &EcdsaSigningAlgorithm { } } +impl From for &EcdsaVerificationAlgorithm { + fn from(curve: CryptoNamedCurve) -> &'static EcdsaVerificationAlgorithm { + match curve { + CryptoNamedCurve::P256 => &ring::signature::ECDSA_P256_SHA256_FIXED, + CryptoNamedCurve::P384 => &ring::signature::ECDSA_P384_SHA384_FIXED, + } + } +} + impl From for HmacAlgorithm { fn from(hash: CryptoHash) -> HmacAlgorithm { match hash { diff --git a/ext/crypto/lib.deno_crypto.d.ts b/ext/crypto/lib.deno_crypto.d.ts index 5169e5c3bd..a62e696328 100644 --- a/ext/crypto/lib.deno_crypto.d.ts +++ b/ext/crypto/lib.deno_crypto.d.ts @@ -175,7 +175,7 @@ interface SubtleCrypto { data: BufferSource, ): Promise; verify( - algorithm: AlgorithmIdentifier | RsaPssParams, + algorithm: AlgorithmIdentifier | RsaPssParams | EcdsaParams, key: CryptoKey, signature: BufferSource, data: BufferSource, diff --git a/ext/crypto/lib.rs b/ext/crypto/lib.rs index 7c4010f53d..f2df7ba10a 100644 --- a/ext/crypto/lib.rs +++ b/ext/crypto/lib.rs @@ -33,6 +33,8 @@ use ring::rand as RingRand; use ring::rand::SecureRandom; use ring::signature::EcdsaKeyPair; use ring::signature::EcdsaSigningAlgorithm; +use ring::signature::EcdsaVerificationAlgorithm; +use ring::signature::KeyPair; use rsa::padding::PaddingScheme; use rsa::pkcs8::FromPrivateKey; use rsa::pkcs8::ToPrivateKey; @@ -407,6 +409,7 @@ pub struct VerifyArg { salt_length: Option, hash: Option, signature: ZeroCopyBuf, + named_curve: Option, } pub async fn op_crypto_verify_key( @@ -528,6 +531,19 @@ pub async fn op_crypto_verify_key( let key = HmacKey::new(hash, &*args.key.data); ring::hmac::verify(&key, data, &*args.signature).is_ok() } + Algorithm::Ecdsa => { + let signing_alg: &EcdsaSigningAlgorithm = + args.named_curve.ok_or_else(not_supported)?.try_into()?; + let verify_alg: &EcdsaVerificationAlgorithm = + args.named_curve.ok_or_else(not_supported)?.try_into()?; + + let private_key = EcdsaKeyPair::from_pkcs8(signing_alg, &*args.key.data)?; + let public_key_bytes = private_key.public_key().as_ref(); + let public_key = + ring::signature::UnparsedPublicKey::new(verify_alg, public_key_bytes); + + public_key.verify(data, &*args.signature).is_ok() + } _ => return Err(type_error("Unsupported algorithm".to_string())), };