diff --git a/Cargo.lock b/Cargo.lock index f67a8e0989..b164192a78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,6 +24,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +dependencies = [ + "generic-array", +] + [[package]] name = "aes" version = "0.7.5" @@ -36,6 +45,20 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "aes-gcm" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "ahash" version = "0.7.6" @@ -791,6 +814,7 @@ name = "deno_crypto" version = "0.44.0" dependencies = [ "aes", + "aes-gcm", "base64 0.13.0", "block-modes", "ctr", @@ -1614,6 +1638,16 @@ dependencies = [ "wasi 0.10.0+wasi-snapshot-preview1", ] +[[package]] +name = "ghash" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "glow" version = "0.11.0" @@ -2691,6 +2725,18 @@ dependencies = [ "syn 1.0.65", ] +[[package]] +name = "polyval" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.15" @@ -4559,6 +4605,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[package]] +name = "universal-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "unreachable" version = "0.1.1" diff --git a/cli/tests/unit/webcrypto_test.ts b/cli/tests/unit/webcrypto_test.ts index 8b5ce55e86..926ed6b6c1 100644 --- a/cli/tests/unit/webcrypto_test.ts +++ b/cli/tests/unit/webcrypto_test.ts @@ -1418,6 +1418,34 @@ Deno.test(async function testImportEcSpkiPkcs8() { } }); +Deno.test(async function testAesGcmEncrypt() { + const key = await crypto.subtle.importKey( + "raw", + new Uint8Array(16), + { name: "AES-GCM", length: 256 }, + true, + ["encrypt", "decrypt"], + ); + + // deno-fmt-ignore + const iv = new Uint8Array([0,1,2,3,4,5,6,7,8,9,10,11]); + const data = new Uint8Array([1, 2, 3]); + + const cipherText = await crypto.subtle.encrypt( + { name: "AES-GCM", iv, additionalData: new Uint8Array() }, + key, + data, + ); + + assert(cipherText instanceof ArrayBuffer); + assertEquals(cipherText.byteLength, 19); + assertEquals( + new Uint8Array(cipherText), + // deno-fmt-ignore + new Uint8Array([50,223,112,178,166,156,255,110,125,138,95,141,82,47,14,164,134,247,22]), + ); +}); + async function roundTripSecretJwk( jwk: JsonWebKey, algId: AlgorithmIdentifier | HmacImportParams, diff --git a/ext/crypto/00_crypto.js b/ext/crypto/00_crypto.js index 95eb18daa0..f76232136b 100644 --- a/ext/crypto/00_crypto.js +++ b/ext/crypto/00_crypto.js @@ -54,6 +54,7 @@ ]; const simpleAlgorithmDictionaries = { + AesGcmParams: { iv: "BufferSource", additionalData: "BufferSource" }, RsaHashedKeyGenParams: { hash: "HashAlgorithmIdentifier" }, EcKeyGenParams: {}, HmacKeyGenParams: { hash: "HashAlgorithmIdentifier" }, @@ -123,6 +124,7 @@ "encrypt": { "RSA-OAEP": "RsaOaepParams", "AES-CBC": "AesCbcParams", + "AES-GCM": "AesGcmParams", "AES-CTR": "AesCtrParams", }, "decrypt": { @@ -3502,6 +3504,69 @@ // 4. return cipherText.buffer; } + case "AES-GCM": { + normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv); + + // 1. + if (data.byteLength > (2 ** 39) - 256) { + throw new DOMException( + "Plaintext too large", + "OperationError", + ); + } + + // 2. + // We only support 96-bit nonce for now. + if (normalizedAlgorithm.iv.byteLength !== 12) { + throw new DOMException( + "Initialization vector length not supported", + "NotSupportedError", + ); + } + + // 3. + if (normalizedAlgorithm.additionalData !== undefined) { + if (normalizedAlgorithm.additionalData.byteLength > (2 ** 64) - 1) { + throw new DOMException( + "Additional data too large", + "OperationError", + ); + } + } + + // 4. + if (normalizedAlgorithm.tagLength == undefined) { + normalizedAlgorithm.tagLength = 128; + } else if ( + !ArrayPrototypeIncludes( + [32, 64, 96, 104, 112, 120, 128], + normalizedAlgorithm.tagLength, + ) + ) { + throw new DOMException( + "Invalid tag length", + "OperationError", + ); + } + // 5. + if (normalizedAlgorithm.additionalData) { + normalizedAlgorithm.additionalData = copyBuffer( + normalizedAlgorithm.additionalData, + ); + } + // 6-7. + const cipherText = await core.opAsync("op_crypto_encrypt", { + key: keyData, + algorithm: "AES-GCM", + length: key[_algorithm].length, + iv: normalizedAlgorithm.iv, + additionalData: normalizedAlgorithm.additionalData, + tagLength: normalizedAlgorithm.tagLength, + }, data); + + // 8. + return cipherText.buffer; + } default: throw new DOMException("Not implemented", "NotSupportedError"); } diff --git a/ext/crypto/01_webidl.js b/ext/crypto/01_webidl.js index 04315204f3..52f2124611 100644 --- a/ext/crypto/01_webidl.js +++ b/ext/crypto/01_webidl.js @@ -398,11 +398,23 @@ }, ]; - webidl.converters.AesDerivedKeyParams = webidl - .createDictionaryConverter("AesDerivedKeyParams", dictAesDerivedKeyParams); - - webidl.converters.AesCbcParams = webidl - .createDictionaryConverter("AesCbcParams", dictAesCbcParams); + const dictAesGcmParams = [ + ...dictAlgorithm, + { + key: "iv", + converter: webidl.converters["BufferSource"], + required: true, + }, + { + key: "tagLength", + converter: (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }), + }, + { + key: "additionalData", + converter: webidl.converters["BufferSource"], + }, + ]; const dictAesCtrParams = [ ...dictAlgorithm, @@ -419,6 +431,15 @@ }, ]; + webidl.converters.AesDerivedKeyParams = webidl + .createDictionaryConverter("AesDerivedKeyParams", dictAesDerivedKeyParams); + + webidl.converters.AesCbcParams = webidl + .createDictionaryConverter("AesCbcParams", dictAesCbcParams); + + webidl.converters.AesGcmParams = webidl + .createDictionaryConverter("AesGcmParams", dictAesGcmParams); + webidl.converters.AesCtrParams = webidl .createDictionaryConverter("AesCtrParams", dictAesCtrParams); diff --git a/ext/crypto/Cargo.toml b/ext/crypto/Cargo.toml index 2ab3ff1713..26448c1a09 100644 --- a/ext/crypto/Cargo.toml +++ b/ext/crypto/Cargo.toml @@ -15,6 +15,7 @@ path = "lib.rs" [dependencies] aes = "0.7.5" +aes-gcm = "0.9.4" base64 = "0.13.0" block-modes = "0.8.1" ctr = "0.8.0" diff --git a/ext/crypto/encrypt.rs b/ext/crypto/encrypt.rs index 99f4762d09..f1d88438b5 100644 --- a/ext/crypto/encrypt.rs +++ b/ext/crypto/encrypt.rs @@ -6,6 +6,13 @@ use crate::shared::*; use aes::cipher::NewCipher; use aes::BlockEncrypt; use aes::NewBlockCipher; +use aes_gcm::aead::generic_array::typenum::U12; +use aes_gcm::aes::Aes192; +use aes_gcm::AeadInPlace; +use aes_gcm::Aes128Gcm; +use aes_gcm::Aes256Gcm; +use aes_gcm::NewAead; +use aes_gcm::Nonce; use ctr::Ctr; use block_modes::BlockMode; @@ -53,6 +60,15 @@ pub enum EncryptAlgorithm { iv: Vec, length: usize, }, + #[serde(rename = "AES-GCM", rename_all = "camelCase")] + AesGcm { + #[serde(with = "serde_bytes")] + iv: Vec, + #[serde(with = "serde_bytes")] + additional_data: Option>, + length: usize, + tag_length: usize, + }, #[serde(rename = "AES-CTR", rename_all = "camelCase")] AesCtr { #[serde(with = "serde_bytes")] @@ -74,6 +90,12 @@ pub async fn op_crypto_encrypt( EncryptAlgorithm::AesCbc { iv, length } => { encrypt_aes_cbc(key, length, iv, &data) } + EncryptAlgorithm::AesGcm { + iv, + additional_data, + length, + tag_length, + } => encrypt_aes_gcm(key, length, tag_length, iv, additional_data, &data), EncryptAlgorithm::AesCtr { counter, ctr_length, @@ -89,7 +111,7 @@ fn encrypt_rsa_oaep( hash: ShaHash, label: Vec, data: &[u8], -) -> Result, deno_core::anyhow::Error> { +) -> Result, AnyError> { let label = String::from_utf8_lossy(&label).to_string(); let public_key = key.as_rsa_public_key()?; @@ -129,7 +151,7 @@ fn encrypt_aes_cbc( length: usize, iv: Vec, data: &[u8], -) -> Result, deno_core::anyhow::Error> { +) -> Result, AnyError> { let key = key.as_secret_key()?; let ciphertext = match length { 128 => { @@ -161,6 +183,62 @@ fn encrypt_aes_cbc( Ok(ciphertext) } +fn encrypt_aes_gcm( + key: RawKeyData, + length: usize, + tag_length: usize, + iv: Vec, + additional_data: Option>, + data: &[u8], +) -> Result, AnyError> { + let key = key.as_secret_key()?; + let additional_data = additional_data.unwrap_or_default(); + + // Fixed 96-bit nonce + if iv.len() != 12 { + return Err(type_error("iv length not equal to 12")); + } + + let nonce = Nonce::from_slice(&iv); + + let mut ciphertext = data.to_vec(); + let tag = match length { + 128 => { + let cipher = Aes128Gcm::new_from_slice(key) + .map_err(|_| operation_error("Encryption failed"))?; + cipher + .encrypt_in_place_detached(nonce, &additional_data, &mut ciphertext) + .map_err(|_| operation_error("Encryption failed"))? + } + 192 => { + type Aes192Gcm = aes_gcm::AesGcm; + + let cipher = Aes192Gcm::new_from_slice(key) + .map_err(|_| operation_error("Encryption failed"))?; + cipher + .encrypt_in_place_detached(nonce, &additional_data, &mut ciphertext) + .map_err(|_| operation_error("Encryption failed"))? + } + 256 => { + let cipher = Aes256Gcm::new_from_slice(key) + .map_err(|_| operation_error("Encryption failed"))?; + cipher + .encrypt_in_place_detached(nonce, &additional_data, &mut ciphertext) + .map_err(|_| operation_error("Encryption failed"))? + } + _ => return Err(type_error("invalid length")), + }; + + // Truncated tag to the specified tag length. + // `tag` is fixed to be 16 bytes long and (tag_length / 8) is always <= 16 + let tag = &tag[..(tag_length / 8)]; + + // C | T + ciphertext.extend_from_slice(tag); + + Ok(ciphertext) +} + fn encrypt_aes_ctr_gen( key: &[u8], counter: &[u8], diff --git a/ext/crypto/lib.deno_crypto.d.ts b/ext/crypto/lib.deno_crypto.d.ts index f7d735721c..9c7386dc97 100644 --- a/ext/crypto/lib.deno_crypto.d.ts +++ b/ext/crypto/lib.deno_crypto.d.ts @@ -62,6 +62,12 @@ interface AesCbcParams extends Algorithm { iv: BufferSource; } +interface AesGcmParams extends Algorithm { + iv: BufferSource; + additionalData?: BufferSource; + tagLength?: number; +} + interface AesCtrParams extends Algorithm { counter: BufferSource; length: number; @@ -248,6 +254,7 @@ interface SubtleCrypto { | AlgorithmIdentifier | RsaOaepParams | AesCbcParams + | AesGcmParams | AesCtrParams, key: CryptoKey, data: BufferSource, diff --git a/tools/wpt/expectation.json b/tools/wpt/expectation.json index 5d6128cb38..96944aff46 100644 --- a/tools/wpt/expectation.json +++ b/tools/wpt/expectation.json @@ -212,48 +212,6 @@ "AES-GCM 256-bit key, no additional data, 120-bit tag decryption with altered ciphertext", "AES-GCM 256-bit key, 128-bit tag decryption with altered ciphertext", "AES-GCM 256-bit key, no additional data, 128-bit tag decryption with altered ciphertext", - "AES-GCM 128-bit key, 32-bit tag without encrypt usage", - "AES-GCM 128-bit key, no additional data, 32-bit tag without encrypt usage", - "AES-GCM 128-bit key, 64-bit tag without encrypt usage", - "AES-GCM 128-bit key, no additional data, 64-bit tag without encrypt usage", - "AES-GCM 128-bit key, 96-bit tag without encrypt usage", - "AES-GCM 128-bit key, no additional data, 96-bit tag without encrypt usage", - "AES-GCM 128-bit key, 104-bit tag without encrypt usage", - "AES-GCM 128-bit key, no additional data, 104-bit tag without encrypt usage", - "AES-GCM 128-bit key, 112-bit tag without encrypt usage", - "AES-GCM 128-bit key, no additional data, 112-bit tag without encrypt usage", - "AES-GCM 128-bit key, 120-bit tag without encrypt usage", - "AES-GCM 128-bit key, no additional data, 120-bit tag without encrypt usage", - "AES-GCM 128-bit key, 128-bit tag without encrypt usage", - "AES-GCM 128-bit key, no additional data, 128-bit tag without encrypt usage", - "AES-GCM 192-bit key, 32-bit tag without encrypt usage", - "AES-GCM 192-bit key, no additional data, 32-bit tag without encrypt usage", - "AES-GCM 192-bit key, 64-bit tag without encrypt usage", - "AES-GCM 192-bit key, no additional data, 64-bit tag without encrypt usage", - "AES-GCM 192-bit key, 96-bit tag without encrypt usage", - "AES-GCM 192-bit key, no additional data, 96-bit tag without encrypt usage", - "AES-GCM 192-bit key, 104-bit tag without encrypt usage", - "AES-GCM 192-bit key, no additional data, 104-bit tag without encrypt usage", - "AES-GCM 192-bit key, 112-bit tag without encrypt usage", - "AES-GCM 192-bit key, no additional data, 112-bit tag without encrypt usage", - "AES-GCM 192-bit key, 120-bit tag without encrypt usage", - "AES-GCM 192-bit key, no additional data, 120-bit tag without encrypt usage", - "AES-GCM 192-bit key, 128-bit tag without encrypt usage", - "AES-GCM 192-bit key, no additional data, 128-bit tag without encrypt usage", - "AES-GCM 256-bit key, 32-bit tag without encrypt usage", - "AES-GCM 256-bit key, no additional data, 32-bit tag without encrypt usage", - "AES-GCM 256-bit key, 64-bit tag without encrypt usage", - "AES-GCM 256-bit key, no additional data, 64-bit tag without encrypt usage", - "AES-GCM 256-bit key, 96-bit tag without encrypt usage", - "AES-GCM 256-bit key, no additional data, 96-bit tag without encrypt usage", - "AES-GCM 256-bit key, 104-bit tag without encrypt usage", - "AES-GCM 256-bit key, no additional data, 104-bit tag without encrypt usage", - "AES-GCM 256-bit key, 112-bit tag without encrypt usage", - "AES-GCM 256-bit key, no additional data, 112-bit tag without encrypt usage", - "AES-GCM 256-bit key, 120-bit tag without encrypt usage", - "AES-GCM 256-bit key, no additional data, 120-bit tag without encrypt usage", - "AES-GCM 256-bit key, 128-bit tag without encrypt usage", - "AES-GCM 256-bit key, no additional data, 128-bit tag without encrypt usage", "AES-GCM 128-bit key, 32-bit tag without decrypt usage", "AES-GCM 128-bit key, no additional data, 32-bit tag without decrypt usage", "AES-GCM 128-bit key, 64-bit tag without decrypt usage", @@ -502,48 +460,6 @@ "AES-GCM 256-bit key, no additional data, 120-bit tag decryption with altered ciphertext", "AES-GCM 256-bit key, 128-bit tag decryption with altered ciphertext", "AES-GCM 256-bit key, no additional data, 128-bit tag decryption with altered ciphertext", - "AES-GCM 128-bit key, 32-bit tag without encrypt usage", - "AES-GCM 128-bit key, no additional data, 32-bit tag without encrypt usage", - "AES-GCM 128-bit key, 64-bit tag without encrypt usage", - "AES-GCM 128-bit key, no additional data, 64-bit tag without encrypt usage", - "AES-GCM 128-bit key, 96-bit tag without encrypt usage", - "AES-GCM 128-bit key, no additional data, 96-bit tag without encrypt usage", - "AES-GCM 128-bit key, 104-bit tag without encrypt usage", - "AES-GCM 128-bit key, no additional data, 104-bit tag without encrypt usage", - "AES-GCM 128-bit key, 112-bit tag without encrypt usage", - "AES-GCM 128-bit key, no additional data, 112-bit tag without encrypt usage", - "AES-GCM 128-bit key, 120-bit tag without encrypt usage", - "AES-GCM 128-bit key, no additional data, 120-bit tag without encrypt usage", - "AES-GCM 128-bit key, 128-bit tag without encrypt usage", - "AES-GCM 128-bit key, no additional data, 128-bit tag without encrypt usage", - "AES-GCM 192-bit key, 32-bit tag without encrypt usage", - "AES-GCM 192-bit key, no additional data, 32-bit tag without encrypt usage", - "AES-GCM 192-bit key, 64-bit tag without encrypt usage", - "AES-GCM 192-bit key, no additional data, 64-bit tag without encrypt usage", - "AES-GCM 192-bit key, 96-bit tag without encrypt usage", - "AES-GCM 192-bit key, no additional data, 96-bit tag without encrypt usage", - "AES-GCM 192-bit key, 104-bit tag without encrypt usage", - "AES-GCM 192-bit key, no additional data, 104-bit tag without encrypt usage", - "AES-GCM 192-bit key, 112-bit tag without encrypt usage", - "AES-GCM 192-bit key, no additional data, 112-bit tag without encrypt usage", - "AES-GCM 192-bit key, 120-bit tag without encrypt usage", - "AES-GCM 192-bit key, no additional data, 120-bit tag without encrypt usage", - "AES-GCM 192-bit key, 128-bit tag without encrypt usage", - "AES-GCM 192-bit key, no additional data, 128-bit tag without encrypt usage", - "AES-GCM 256-bit key, 32-bit tag without encrypt usage", - "AES-GCM 256-bit key, no additional data, 32-bit tag without encrypt usage", - "AES-GCM 256-bit key, 64-bit tag without encrypt usage", - "AES-GCM 256-bit key, no additional data, 64-bit tag without encrypt usage", - "AES-GCM 256-bit key, 96-bit tag without encrypt usage", - "AES-GCM 256-bit key, no additional data, 96-bit tag without encrypt usage", - "AES-GCM 256-bit key, 104-bit tag without encrypt usage", - "AES-GCM 256-bit key, no additional data, 104-bit tag without encrypt usage", - "AES-GCM 256-bit key, 112-bit tag without encrypt usage", - "AES-GCM 256-bit key, no additional data, 112-bit tag without encrypt usage", - "AES-GCM 256-bit key, 120-bit tag without encrypt usage", - "AES-GCM 256-bit key, no additional data, 120-bit tag without encrypt usage", - "AES-GCM 256-bit key, 128-bit tag without encrypt usage", - "AES-GCM 256-bit key, no additional data, 128-bit tag without encrypt usage", "AES-GCM 128-bit key, 32-bit tag without decrypt usage", "AES-GCM 128-bit key, no additional data, 32-bit tag without decrypt usage", "AES-GCM 128-bit key, 64-bit tag without decrypt usage",