diff --git a/cli/tests/unit/webcrypto_test.ts b/cli/tests/unit/webcrypto_test.ts index 3caa927eca..82ecae4346 100644 --- a/cli/tests/unit/webcrypto_test.ts +++ b/cli/tests/unit/webcrypto_test.ts @@ -1444,6 +1444,15 @@ Deno.test(async function testAesGcmEncrypt() { // deno-fmt-ignore new Uint8Array([50,223,112,178,166,156,255,110,125,138,95,141,82,47,14,164,134,247,22]), ); + + const plainText = await crypto.subtle.decrypt( + { name: "AES-GCM", iv, additionalData: new Uint8Array() }, + key, + cipherText, + ); + assert(plainText instanceof ArrayBuffer); + assertEquals(plainText.byteLength, 3); + assertEquals(new Uint8Array(plainText), data); }); async function roundTripSecretJwk( diff --git a/ext/crypto/00_crypto.js b/ext/crypto/00_crypto.js index 1f49d1849d..81c475ad76 100644 --- a/ext/crypto/00_crypto.js +++ b/ext/crypto/00_crypto.js @@ -133,6 +133,7 @@ "decrypt": { "RSA-OAEP": "RsaOaepParams", "AES-CBC": "AesCbcParams", + "AES-GCM": "AesGcmParams", "AES-CTR": "AesCtrParams", }, "get key length": { @@ -631,6 +632,66 @@ // 4. return cipherText.buffer; } + case "AES-GCM": { + normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv); + + // 1. + 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", + ); + } + + // 2. + if (data.byteLength < normalizedAlgorithm.tagLength / 8) { + throw new DOMException( + "Tag length overflows ciphertext", + "OperationError", + ); + } + + // 3. We only support 96-bit nonce for now. + if (normalizedAlgorithm.iv.byteLength !== 12) { + throw new DOMException( + "Initialization vector length not supported", + "NotSupportedError", + ); + } + + // 4. + if (normalizedAlgorithm.additionalData !== undefined) { + if (normalizedAlgorithm.additionalData.byteLength > (2 ** 64) - 1) { + throw new DOMException( + "Additional data too large", + "OperationError", + ); + } + normalizedAlgorithm.additionalData = copyBuffer( + normalizedAlgorithm.additionalData, + ); + } + + // 5-8. + const plaintext = await core.opAsync("op_crypto_decrypt", { + key: keyData, + algorithm: "AES-GCM", + length: key[_algorithm].length, + iv: normalizedAlgorithm.iv, + additionalData: normalizedAlgorithm.additionalData, + tagLength: normalizedAlgorithm.tagLength, + }, data); + + // 9. + return plaintext.buffer; + } default: throw new DOMException("Not implemented", "NotSupportedError"); } diff --git a/ext/crypto/decrypt.rs b/ext/crypto/decrypt.rs index 90916f9c38..9f11576080 100644 --- a/ext/crypto/decrypt.rs +++ b/ext/crypto/decrypt.rs @@ -2,8 +2,16 @@ use std::cell::RefCell; use std::rc::Rc; use crate::shared::*; +use aes::cipher::generic_array::GenericArray; +use aes::Aes192; use aes::BlockEncrypt; use aes::NewBlockCipher; +use aes_gcm::AeadCore; +use aes_gcm::AeadInPlace; +use aes_gcm::Aes128Gcm; +use aes_gcm::Aes256Gcm; +use aes_gcm::NewAead; +use aes_gcm::Nonce; use block_modes::BlockMode; use ctr::cipher::NewCipher; use ctr::cipher::StreamCipher; @@ -17,6 +25,7 @@ use deno_core::error::type_error; use deno_core::error::AnyError; use deno_core::OpState; use deno_core::ZeroCopyBuf; +use elliptic_curve::consts::U12; use rsa::pkcs1::FromRsaPrivateKey; use rsa::PaddingScheme; use serde::Deserialize; @@ -56,8 +65,19 @@ pub enum DecryptAlgorithm { ctr_length: usize, key_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, + }, } +type Aes192Gcm = aes_gcm::AesGcm; + pub async fn op_crypto_decrypt( _state: Rc>, opts: DecryptOptions, @@ -76,6 +96,12 @@ pub async fn op_crypto_decrypt( ctr_length, key_length, } => decrypt_aes_ctr(key, key_length, &counter, ctr_length, &data), + DecryptAlgorithm::AesGcm { + iv, + additional_data, + length, + tag_length, + } => decrypt_aes_gcm(key, length, tag_length, iv, additional_data, &data), }; let buf = tokio::task::spawn_blocking(fun).await.unwrap()?; Ok(buf.into()) @@ -195,6 +221,30 @@ where Ok(plaintext) } +fn decrypt_aes_gcm_gen( + key: &[u8], + tag: &GenericArray::TagSize>, + nonce: &GenericArray::NonceSize>, + additional_data: Vec, + plaintext: &mut [u8], +) -> Result<(), AnyError> +where + B: AeadInPlace + NewAead, +{ + let cipher = + B::new_from_slice(key).map_err(|_| operation_error("Decryption failed"))?; + cipher + .decrypt_in_place_detached( + nonce, + additional_data.as_slice(), + plaintext, + tag, + ) + .map_err(|_| operation_error("Decryption failed"))?; + + Ok(()) +} + fn decrypt_aes_ctr( key: RawKeyData, key_length: usize, @@ -228,3 +278,53 @@ fn decrypt_aes_ctr( )), } } + +fn decrypt_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 sep = data.len() - (tag_length / 8); + let tag = &data[sep..]; + // The actual ciphertext, called plaintext because it is reused in place. + let mut plaintext = data[..sep].to_vec(); + match length { + 128 => decrypt_aes_gcm_gen::( + key, + tag.into(), + nonce, + additional_data, + &mut plaintext, + )?, + 192 => decrypt_aes_gcm_gen::( + key, + tag.into(), + nonce, + additional_data, + &mut plaintext, + )?, + 256 => decrypt_aes_gcm_gen::( + key, + tag.into(), + nonce, + additional_data, + &mut plaintext, + )?, + _ => return Err(type_error("invalid length")), + }; + + Ok(plaintext) +} diff --git a/ext/crypto/lib.deno_crypto.d.ts b/ext/crypto/lib.deno_crypto.d.ts index f17c1f582a..1f16beb044 100644 --- a/ext/crypto/lib.deno_crypto.d.ts +++ b/ext/crypto/lib.deno_crypto.d.ts @@ -264,6 +264,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 488b12d34f..36dd683076 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 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", - "AES-GCM 128-bit key, no additional data, 64-bit tag without decrypt usage", - "AES-GCM 128-bit key, 96-bit tag without decrypt usage", - "AES-GCM 128-bit key, no additional data, 96-bit tag without decrypt usage", - "AES-GCM 128-bit key, 104-bit tag without decrypt usage", - "AES-GCM 128-bit key, no additional data, 104-bit tag without decrypt usage", - "AES-GCM 128-bit key, 112-bit tag without decrypt usage", - "AES-GCM 128-bit key, no additional data, 112-bit tag without decrypt usage", - "AES-GCM 128-bit key, 120-bit tag without decrypt usage", - "AES-GCM 128-bit key, no additional data, 120-bit tag without decrypt usage", - "AES-GCM 128-bit key, 128-bit tag without decrypt usage", - "AES-GCM 128-bit key, no additional data, 128-bit tag without decrypt usage", - "AES-GCM 192-bit key, 32-bit tag without decrypt usage", - "AES-GCM 192-bit key, no additional data, 32-bit tag without decrypt usage", - "AES-GCM 192-bit key, 64-bit tag without decrypt usage", - "AES-GCM 192-bit key, no additional data, 64-bit tag without decrypt usage", - "AES-GCM 192-bit key, 96-bit tag without decrypt usage", - "AES-GCM 192-bit key, no additional data, 96-bit tag without decrypt usage", - "AES-GCM 192-bit key, 104-bit tag without decrypt usage", - "AES-GCM 192-bit key, no additional data, 104-bit tag without decrypt usage", - "AES-GCM 192-bit key, 112-bit tag without decrypt usage", - "AES-GCM 192-bit key, no additional data, 112-bit tag without decrypt usage", - "AES-GCM 192-bit key, 120-bit tag without decrypt usage", - "AES-GCM 192-bit key, no additional data, 120-bit tag without decrypt usage", - "AES-GCM 192-bit key, 128-bit tag without decrypt usage", - "AES-GCM 192-bit key, no additional data, 128-bit tag without decrypt usage", - "AES-GCM 256-bit key, 32-bit tag without decrypt usage", - "AES-GCM 256-bit key, no additional data, 32-bit tag without decrypt usage", - "AES-GCM 256-bit key, 64-bit tag without decrypt usage", - "AES-GCM 256-bit key, no additional data, 64-bit tag without decrypt usage", - "AES-GCM 256-bit key, 96-bit tag without decrypt usage", - "AES-GCM 256-bit key, no additional data, 96-bit tag without decrypt usage", - "AES-GCM 256-bit key, 104-bit tag without decrypt usage", - "AES-GCM 256-bit key, no additional data, 104-bit tag without decrypt usage", - "AES-GCM 256-bit key, 112-bit tag without decrypt usage", - "AES-GCM 256-bit key, no additional data, 112-bit tag without decrypt usage", - "AES-GCM 256-bit key, 120-bit tag without decrypt usage", - "AES-GCM 256-bit key, no additional data, 120-bit tag without decrypt usage", - "AES-GCM 256-bit key, 128-bit tag without decrypt usage", - "AES-GCM 256-bit key, no additional data, 128-bit tag without decrypt usage", "AES-GCM 128-bit key, illegal tag length 24-bits", "AES-GCM 128-bit key, illegal tag length 48-bits", "AES-GCM 128-bit key, illegal tag length 72-bits", @@ -271,25 +229,7 @@ "AES-GCM 256-bit key, illegal tag length 72-bits", "AES-GCM 256-bit key, illegal tag length 95-bits", "AES-GCM 256-bit key, illegal tag length 129-bits", - "AES-GCM 256-bit key, illegal tag length 256-bits", - "AES-GCM 128-bit key, illegal tag length 24-bits decryption", - "AES-GCM 128-bit key, illegal tag length 48-bits decryption", - "AES-GCM 128-bit key, illegal tag length 72-bits decryption", - "AES-GCM 128-bit key, illegal tag length 95-bits decryption", - "AES-GCM 128-bit key, illegal tag length 129-bits decryption", - "AES-GCM 128-bit key, illegal tag length 256-bits decryption", - "AES-GCM 192-bit key, illegal tag length 24-bits decryption", - "AES-GCM 192-bit key, illegal tag length 48-bits decryption", - "AES-GCM 192-bit key, illegal tag length 72-bits decryption", - "AES-GCM 192-bit key, illegal tag length 95-bits decryption", - "AES-GCM 192-bit key, illegal tag length 129-bits decryption", - "AES-GCM 192-bit key, illegal tag length 256-bits decryption", - "AES-GCM 256-bit key, illegal tag length 24-bits decryption", - "AES-GCM 256-bit key, illegal tag length 48-bits decryption", - "AES-GCM 256-bit key, illegal tag length 72-bits decryption", - "AES-GCM 256-bit key, illegal tag length 95-bits decryption", - "AES-GCM 256-bit key, illegal tag length 129-bits decryption", - "AES-GCM 256-bit key, illegal tag length 256-bits decryption" + "AES-GCM 256-bit key, illegal tag length 256-bits" ], "aes_gcm.https.any.worker.html": [ "AES-GCM 128-bit key, 32-bit tag", @@ -460,48 +400,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 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", - "AES-GCM 128-bit key, no additional data, 64-bit tag without decrypt usage", - "AES-GCM 128-bit key, 96-bit tag without decrypt usage", - "AES-GCM 128-bit key, no additional data, 96-bit tag without decrypt usage", - "AES-GCM 128-bit key, 104-bit tag without decrypt usage", - "AES-GCM 128-bit key, no additional data, 104-bit tag without decrypt usage", - "AES-GCM 128-bit key, 112-bit tag without decrypt usage", - "AES-GCM 128-bit key, no additional data, 112-bit tag without decrypt usage", - "AES-GCM 128-bit key, 120-bit tag without decrypt usage", - "AES-GCM 128-bit key, no additional data, 120-bit tag without decrypt usage", - "AES-GCM 128-bit key, 128-bit tag without decrypt usage", - "AES-GCM 128-bit key, no additional data, 128-bit tag without decrypt usage", - "AES-GCM 192-bit key, 32-bit tag without decrypt usage", - "AES-GCM 192-bit key, no additional data, 32-bit tag without decrypt usage", - "AES-GCM 192-bit key, 64-bit tag without decrypt usage", - "AES-GCM 192-bit key, no additional data, 64-bit tag without decrypt usage", - "AES-GCM 192-bit key, 96-bit tag without decrypt usage", - "AES-GCM 192-bit key, no additional data, 96-bit tag without decrypt usage", - "AES-GCM 192-bit key, 104-bit tag without decrypt usage", - "AES-GCM 192-bit key, no additional data, 104-bit tag without decrypt usage", - "AES-GCM 192-bit key, 112-bit tag without decrypt usage", - "AES-GCM 192-bit key, no additional data, 112-bit tag without decrypt usage", - "AES-GCM 192-bit key, 120-bit tag without decrypt usage", - "AES-GCM 192-bit key, no additional data, 120-bit tag without decrypt usage", - "AES-GCM 192-bit key, 128-bit tag without decrypt usage", - "AES-GCM 192-bit key, no additional data, 128-bit tag without decrypt usage", - "AES-GCM 256-bit key, 32-bit tag without decrypt usage", - "AES-GCM 256-bit key, no additional data, 32-bit tag without decrypt usage", - "AES-GCM 256-bit key, 64-bit tag without decrypt usage", - "AES-GCM 256-bit key, no additional data, 64-bit tag without decrypt usage", - "AES-GCM 256-bit key, 96-bit tag without decrypt usage", - "AES-GCM 256-bit key, no additional data, 96-bit tag without decrypt usage", - "AES-GCM 256-bit key, 104-bit tag without decrypt usage", - "AES-GCM 256-bit key, no additional data, 104-bit tag without decrypt usage", - "AES-GCM 256-bit key, 112-bit tag without decrypt usage", - "AES-GCM 256-bit key, no additional data, 112-bit tag without decrypt usage", - "AES-GCM 256-bit key, 120-bit tag without decrypt usage", - "AES-GCM 256-bit key, no additional data, 120-bit tag without decrypt usage", - "AES-GCM 256-bit key, 128-bit tag without decrypt usage", - "AES-GCM 256-bit key, no additional data, 128-bit tag without decrypt usage", "AES-GCM 128-bit key, illegal tag length 24-bits", "AES-GCM 128-bit key, illegal tag length 48-bits", "AES-GCM 128-bit key, illegal tag length 72-bits", @@ -519,25 +417,7 @@ "AES-GCM 256-bit key, illegal tag length 72-bits", "AES-GCM 256-bit key, illegal tag length 95-bits", "AES-GCM 256-bit key, illegal tag length 129-bits", - "AES-GCM 256-bit key, illegal tag length 256-bits", - "AES-GCM 128-bit key, illegal tag length 24-bits decryption", - "AES-GCM 128-bit key, illegal tag length 48-bits decryption", - "AES-GCM 128-bit key, illegal tag length 72-bits decryption", - "AES-GCM 128-bit key, illegal tag length 95-bits decryption", - "AES-GCM 128-bit key, illegal tag length 129-bits decryption", - "AES-GCM 128-bit key, illegal tag length 256-bits decryption", - "AES-GCM 192-bit key, illegal tag length 24-bits decryption", - "AES-GCM 192-bit key, illegal tag length 48-bits decryption", - "AES-GCM 192-bit key, illegal tag length 72-bits decryption", - "AES-GCM 192-bit key, illegal tag length 95-bits decryption", - "AES-GCM 192-bit key, illegal tag length 129-bits decryption", - "AES-GCM 192-bit key, illegal tag length 256-bits decryption", - "AES-GCM 256-bit key, illegal tag length 24-bits decryption", - "AES-GCM 256-bit key, illegal tag length 48-bits decryption", - "AES-GCM 256-bit key, illegal tag length 72-bits decryption", - "AES-GCM 256-bit key, illegal tag length 95-bits decryption", - "AES-GCM 256-bit key, illegal tag length 129-bits decryption", - "AES-GCM 256-bit key, illegal tag length 256-bits decryption" + "AES-GCM 256-bit key, illegal tag length 256-bits" ], "rsa_oaep.https.any.html": true, "rsa_oaep.https.any.worker.html": true