diff --git a/cli/tests/unit/webcrypto_test.ts b/cli/tests/unit/webcrypto_test.ts index b507a0c58d9fbd..4af31789a8a262 100644 --- a/cli/tests/unit/webcrypto_test.ts +++ b/cli/tests/unit/webcrypto_test.ts @@ -1,5 +1,54 @@ import { assert, assertEquals, unitTest } from "./test_util.ts"; +// TODO(@littledivy): Remove this when we enable WPT for sign_verify +unitTest(async function testSignVerify() { + const subtle = window.crypto.subtle; + assert(subtle); + for (const algorithm of ["RSA-PSS", "RSASSA-PKCS1-v1_5"]) { + for ( + const hash of [ + "SHA-1", + "SHA-256", + "SHA-384", + "SHA-512", + ] + ) { + const keyPair = await subtle.generateKey( + { + name: algorithm, + modulusLength: 2048, + publicExponent: new Uint8Array([1, 0, 1]), + hash, + }, + true, + ["sign", "verify"], + ); + + const data = new Uint8Array([1, 2, 3]); + const signAlgorithm = { name: algorithm, saltLength: 32 }; + + const signature = await subtle.sign( + signAlgorithm, + keyPair.privateKey, + data, + ); + + assert(signature); + assert(signature.byteLength > 0); + assert(signature.byteLength % 8 == 0); + assert(signature instanceof ArrayBuffer); + + const verified = await subtle.verify( + signAlgorithm, + keyPair.publicKey, + signature, + data, + ); + assert(verified); + } + } +}); + unitTest(async function testGenerateRSAKey() { const subtle = window.crypto.subtle; assert(subtle); diff --git a/extensions/crypto/00_crypto.js b/extensions/crypto/00_crypto.js index 936bf7cffdf467..896a11570fdf98 100644 --- a/extensions/crypto/00_crypto.js +++ b/extensions/crypto/00_crypto.js @@ -65,6 +65,10 @@ "ECDSA": "EcdsaParams", "HMAC": null, }, + "verify": { + "RSASSA-PKCS1-v1_5": null, + "RSA-PSS": "RsaPssParams", + }, }; // See https://www.w3.org/TR/WebCryptoAPI/#dfn-normalize-an-algorithm @@ -410,6 +414,113 @@ throw new TypeError("unreachable"); } + /** + * @param {string} algorithm + * @param {CryptoKey} key + * @param {BufferSource} signature + * @param {BufferSource} data + * @returns {Promise} + */ + async verify(algorithm, key, signature, data) { + webidl.assertBranded(this, SubtleCrypto); + const prefix = "Failed to execute 'verify' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 4, { prefix }); + algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + prefix, + context: "Argument 1", + }); + key = webidl.converters.CryptoKey(key, { + prefix, + context: "Argument 2", + }); + signature = webidl.converters.BufferSource(signature, { + prefix, + context: "Argument 3", + }); + data = webidl.converters.BufferSource(data, { + prefix, + context: "Argument 4", + }); + + // 2. + if (ArrayBuffer.isView(signature)) { + signature = new Uint8Array( + signature.buffer, + signature.byteOffset, + signature.byteLength, + ); + } else { + signature = new Uint8Array(signature); + } + signature = signature.slice(); + + // 3. + if (ArrayBuffer.isView(data)) { + data = new Uint8Array(data.buffer, data.byteOffset, data.byteLength); + } else { + data = new Uint8Array(data); + } + data = data.slice(); + + const normalizedAlgorithm = normalizeAlgorithm(algorithm, "verify"); + + const handle = key[_handle]; + const keyData = KEY_STORE.get(handle); + + if (normalizedAlgorithm.name !== key[_algorithm].name) { + throw new DOMException( + "Verifying algorithm doesn't match key algorithm.", + "InvalidAccessError", + ); + } + + if (!key[_usages].includes("verify")) { + throw new DOMException( + "Key does not support the 'verify' operation.", + "InvalidAccessError", + ); + } + + switch (normalizedAlgorithm.name) { + case "RSASSA-PKCS1-v1_5": { + if (key[_type] !== "public") { + throw new DOMException( + "Key type not supported", + "InvalidAccessError", + ); + } + + const hashAlgorithm = key[_algorithm].hash.name; + return await core.opAsync("op_crypto_verify_key", { + key: keyData, + algorithm: "RSASSA-PKCS1-v1_5", + hash: hashAlgorithm, + signature, + }, data); + } + case "RSA-PSS": { + if (key[_type] !== "public") { + throw new DOMException( + "Key type not supported", + "InvalidAccessError", + ); + } + + const hashAlgorithm = key[_algorithm].hash.name; + const saltLength = normalizedAlgorithm.saltLength; + return await core.opAsync("op_crypto_verify_key", { + key: keyData, + algorithm: "RSA-PSS", + hash: hashAlgorithm, + saltLength, + signature, + }, data); + } + } + + throw new TypeError("unreachable"); + } + /** * @param {string} algorithm * @param {boolean} extractable diff --git a/extensions/crypto/lib.deno_crypto.d.ts b/extensions/crypto/lib.deno_crypto.d.ts index 0798af59f506ad..d4d42e58100992 100644 --- a/extensions/crypto/lib.deno_crypto.d.ts +++ b/extensions/crypto/lib.deno_crypto.d.ts @@ -111,6 +111,34 @@ interface SubtleCrypto { | DataView | ArrayBuffer, ): Promise; + verify( + algorithm: AlgorithmIdentifier | RsaPssParams, + key: CryptoKey, + signature: + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint16Array + | Uint32Array + | Uint8ClampedArray + | Float32Array + | Float64Array + | DataView + | ArrayBuffer, + data: + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint16Array + | Uint32Array + | Uint8ClampedArray + | Float32Array + | Float64Array + | DataView + | ArrayBuffer, + ): Promise; digest( algorithm: AlgorithmIdentifier, data: diff --git a/extensions/crypto/lib.rs b/extensions/crypto/lib.rs index 66e576c6a74323..d390afdab41869 100644 --- a/extensions/crypto/lib.rs +++ b/extensions/crypto/lib.rs @@ -34,7 +34,9 @@ use ring::signature::EcdsaSigningAlgorithm; use rsa::padding::PaddingScheme; use rsa::BigUint; use rsa::PrivateKeyEncoding; +use rsa::PublicKey; use rsa::RSAPrivateKey; +use rsa::RSAPublicKey; use sha1::Sha1; use sha2::Digest; use sha2::Sha256; @@ -70,6 +72,7 @@ pub fn init(maybe_seed: Option) -> Extension { ), ("op_crypto_generate_key", op_async(op_crypto_generate_key)), ("op_crypto_sign_key", op_async(op_crypto_sign_key)), + ("op_crypto_verify_key", op_async(op_crypto_verify_key)), ("op_crypto_subtle_digest", op_async(op_crypto_subtle_digest)), ("op_crypto_random_uuid", op_sync(op_crypto_random_uuid)), ]) @@ -378,6 +381,136 @@ pub async fn op_crypto_sign_key( Ok(signature.into()) } +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct VerifyArg { + key: KeyData, + algorithm: Algorithm, + salt_length: Option, + hash: Option, + signature: ZeroCopyBuf, +} + +pub async fn op_crypto_verify_key( + _state: Rc>, + args: VerifyArg, + zero_copy: Option, +) -> Result { + let zero_copy = zero_copy.ok_or_else(null_opbuf)?; + let data = &*zero_copy; + let algorithm = args.algorithm; + + let verification = match algorithm { + Algorithm::RsassaPkcs1v15 => { + let public_key: RSAPublicKey = + RSAPrivateKey::from_pkcs8(&*args.key.data)?.to_public_key(); + let (padding, hashed) = match args + .hash + .ok_or_else(|| type_error("Missing argument hash".to_string()))? + { + CryptoHash::Sha1 => { + let mut hasher = Sha1::new(); + hasher.update(&data); + ( + PaddingScheme::PKCS1v15Sign { + hash: Some(rsa::hash::Hash::SHA1), + }, + hasher.finalize()[..].to_vec(), + ) + } + CryptoHash::Sha256 => { + let mut hasher = Sha256::new(); + hasher.update(&data); + ( + PaddingScheme::PKCS1v15Sign { + hash: Some(rsa::hash::Hash::SHA2_256), + }, + hasher.finalize()[..].to_vec(), + ) + } + CryptoHash::Sha384 => { + let mut hasher = Sha384::new(); + hasher.update(&data); + ( + PaddingScheme::PKCS1v15Sign { + hash: Some(rsa::hash::Hash::SHA2_384), + }, + hasher.finalize()[..].to_vec(), + ) + } + CryptoHash::Sha512 => { + let mut hasher = Sha512::new(); + hasher.update(&data); + ( + PaddingScheme::PKCS1v15Sign { + hash: Some(rsa::hash::Hash::SHA2_512), + }, + hasher.finalize()[..].to_vec(), + ) + } + }; + + public_key + .verify(padding, &hashed, &*args.signature) + .is_ok() + } + Algorithm::RsaPss => { + let salt_len = args + .salt_length + .ok_or_else(|| type_error("Missing argument saltLength".to_string()))? + as usize; + let public_key: RSAPublicKey = + RSAPrivateKey::from_pkcs8(&*args.key.data)?.to_public_key(); + + let rng = OsRng; + let (padding, hashed) = match args + .hash + .ok_or_else(|| type_error("Missing argument hash".to_string()))? + { + CryptoHash::Sha1 => { + let mut hasher = Sha1::new(); + hasher.update(&data); + ( + PaddingScheme::new_pss_with_salt::(rng, salt_len), + hasher.finalize()[..].to_vec(), + ) + } + CryptoHash::Sha256 => { + let mut hasher = Sha256::new(); + hasher.update(&data); + ( + PaddingScheme::new_pss_with_salt::(rng, salt_len), + hasher.finalize()[..].to_vec(), + ) + } + CryptoHash::Sha384 => { + let mut hasher = Sha384::new(); + hasher.update(&data); + ( + PaddingScheme::new_pss_with_salt::(rng, salt_len), + hasher.finalize()[..].to_vec(), + ) + } + CryptoHash::Sha512 => { + let mut hasher = Sha512::new(); + hasher.update(&data); + ( + PaddingScheme::new_pss_with_salt::(rng, salt_len), + hasher.finalize()[..].to_vec(), + ) + } + }; + + public_key + .verify(padding, &hashed, &*args.signature) + .is_ok() + } + _ => return Err(type_error("Unsupported algorithm".to_string())), + }; + + Ok(verification) +} + pub fn op_crypto_random_uuid( state: &mut OpState, _: (), diff --git a/tools/wpt/expectation.json b/tools/wpt/expectation.json index cf03ced803623f..fbab59d6893406 100644 --- a/tools/wpt/expectation.json +++ b/tools/wpt/expectation.json @@ -1932,8 +1932,6 @@ "SubtleCrypto interface: calling encrypt(AlgorithmIdentifier, CryptoKey, BufferSource) on crypto.subtle with too few arguments must throw TypeError", "SubtleCrypto interface: crypto.subtle must inherit property \"decrypt(AlgorithmIdentifier, CryptoKey, BufferSource)\" with the proper type", "SubtleCrypto interface: calling decrypt(AlgorithmIdentifier, CryptoKey, BufferSource) on crypto.subtle with too few arguments must throw TypeError", - "SubtleCrypto interface: crypto.subtle must inherit property \"verify(AlgorithmIdentifier, CryptoKey, BufferSource, BufferSource)\" with the proper type", - "SubtleCrypto interface: calling verify(AlgorithmIdentifier, CryptoKey, BufferSource, BufferSource) on crypto.subtle with too few arguments must throw TypeError", "SubtleCrypto interface: crypto.subtle must inherit property \"deriveKey(AlgorithmIdentifier, CryptoKey, AlgorithmIdentifier, boolean, sequence)\" with the proper type", "SubtleCrypto interface: calling deriveKey(AlgorithmIdentifier, CryptoKey, AlgorithmIdentifier, boolean, sequence) on crypto.subtle with too few arguments must throw TypeError", "SubtleCrypto interface: crypto.subtle must inherit property \"deriveBits(AlgorithmIdentifier, CryptoKey, unsigned long)\" with the proper type",