diff --git a/cli/tests/unit/webcrypto_test.ts b/cli/tests/unit/webcrypto_test.ts index c942e36c6b31e2..1082cbb571886e 100644 --- a/cli/tests/unit/webcrypto_test.ts +++ b/cli/tests/unit/webcrypto_test.ts @@ -1438,32 +1438,78 @@ Deno.test(async function testAesGcmEncrypt() { ["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 nonces = [{ + iv: new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]), + ciphertext: new Uint8Array([ + 50, + 223, + 112, + 178, + 166, + 156, + 255, + 110, + 125, + 138, + 95, + 141, + 82, + 47, + 14, + 164, + 134, + 247, + 22, + ]), + }, { + iv: new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]), + ciphertext: new Uint8Array([ + 210, + 101, + 81, + 216, + 151, + 9, + 192, + 197, + 62, + 254, + 28, + 132, + 89, + 106, + 40, + 29, + 175, + 232, + 201, + ]), + }]; + for (const { iv, ciphertext: fixture } of nonces) { + const data = new Uint8Array([1, 2, 3]); - const cipherText = await crypto.subtle.encrypt( - { name: "AES-GCM", iv }, - key, - data, - ); + const cipherText = await crypto.subtle.encrypt( + { name: "AES-GCM", iv }, + 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]), - ); + assert(cipherText instanceof ArrayBuffer); + assertEquals(cipherText.byteLength, 19); + assertEquals( + new Uint8Array(cipherText), + fixture, + ); - const plainText = await crypto.subtle.decrypt( - { name: "AES-GCM", iv }, - key, - cipherText, - ); - assert(plainText instanceof ArrayBuffer); - assertEquals(plainText.byteLength, 3); - assertEquals(new Uint8Array(plainText), data); + const plainText = await crypto.subtle.decrypt( + { name: "AES-GCM", iv }, + 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 8203a530702e52..ed179a31356b4e 100644 --- a/ext/crypto/00_crypto.js +++ b/ext/crypto/00_crypto.js @@ -664,8 +664,13 @@ ); } - // 3. We only support 96-bit nonce for now. - if (normalizedAlgorithm.iv.byteLength !== 12) { + // 3. We only support 96-bit and 128-bit nonce. + if ( + ArrayPrototypeIncludes( + [12, 16], + normalizedAlgorithm.iv.byteLength, + ) === undefined + ) { throw new DOMException( "Initialization vector length not supported", "NotSupportedError", @@ -3782,8 +3787,13 @@ } // 2. - // We only support 96-bit nonce for now. - if (normalizedAlgorithm.iv.byteLength !== 12) { + // We only support 96-bit and 128-bit nonce. + if ( + ArrayPrototypeIncludes( + [12, 16], + normalizedAlgorithm.iv.byteLength, + ) === undefined + ) { throw new DOMException( "Initialization vector length not supported", "NotSupportedError", diff --git a/ext/crypto/decrypt.rs b/ext/crypto/decrypt.rs index 40dd3a5b52fa8f..801e72ea077aef 100644 --- a/ext/crypto/decrypt.rs +++ b/ext/crypto/decrypt.rs @@ -2,14 +2,15 @@ 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::aead::generic_array::typenum::U12; +use aes_gcm::aead::generic_array::typenum::U16; +use aes_gcm::aead::generic_array::ArrayLength; +use aes_gcm::aes::Aes128; +use aes_gcm::aes::Aes192; +use aes_gcm::aes::Aes256; use aes_gcm::AeadInPlace; -use aes_gcm::Aes128Gcm; -use aes_gcm::Aes256Gcm; use aes_gcm::NewAead; use aes_gcm::Nonce; use block_modes::BlockMode; @@ -25,7 +26,6 @@ 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; @@ -76,8 +76,6 @@ pub enum DecryptAlgorithm { }, } -type Aes192Gcm = aes_gcm::AesGcm; - pub async fn op_crypto_decrypt( _state: Rc>, opts: DecryptOptions, @@ -221,26 +219,54 @@ where Ok(plaintext) } -fn decrypt_aes_gcm_gen( +fn decrypt_aes_gcm_gen>( key: &[u8], - tag: &GenericArray::TagSize>, - nonce: &GenericArray::NonceSize>, + tag: &aes_gcm::Tag, + nonce: &[u8], + length: usize, 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"))?; +) -> Result<(), AnyError> { + let nonce = Nonce::from_slice(nonce); + match length { + 128 => { + let cipher = aes_gcm::AesGcm::::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"))? + } + 192 => { + let cipher = aes_gcm::AesGcm::::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"))? + } + 256 => { + let cipher = aes_gcm::AesGcm::::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"))? + } + _ => return Err(type_error("invalid length")), + }; Ok(()) } @@ -290,11 +316,6 @@ fn decrypt_aes_gcm( 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")); - } - // The `aes_gcm` crate only supports 128 bits tag length. // // Note that encryption won't fail, it instead truncates the tag @@ -303,37 +324,32 @@ fn decrypt_aes_gcm( return Err(type_error("tag length not equal to 128")); } - 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::( + + // Fixed 96-bit or 128-bit nonce + match iv.len() { + 12 => decrypt_aes_gcm_gen::( key, tag.into(), - nonce, + &iv, + length, additional_data, &mut plaintext, )?, - 256 => decrypt_aes_gcm_gen::( + 16 => decrypt_aes_gcm_gen::( key, tag.into(), - nonce, + &iv, + length, additional_data, &mut plaintext, )?, - _ => return Err(type_error("invalid length")), - }; + _ => return Err(type_error("iv length not equal to 12 or 16")), + } Ok(plaintext) } diff --git a/ext/crypto/encrypt.rs b/ext/crypto/encrypt.rs index f1d88438b5f5c8..63f8af889c88b7 100644 --- a/ext/crypto/encrypt.rs +++ b/ext/crypto/encrypt.rs @@ -7,10 +7,12 @@ use aes::cipher::NewCipher; use aes::BlockEncrypt; use aes::NewBlockCipher; use aes_gcm::aead::generic_array::typenum::U12; +use aes_gcm::aead::generic_array::typenum::U16; +use aes_gcm::aead::generic_array::ArrayLength; +use aes_gcm::aes::Aes128; use aes_gcm::aes::Aes192; +use aes_gcm::aes::Aes256; use aes_gcm::AeadInPlace; -use aes_gcm::Aes128Gcm; -use aes_gcm::Aes256Gcm; use aes_gcm::NewAead; use aes_gcm::Nonce; use ctr::Ctr; @@ -183,52 +185,73 @@ fn encrypt_aes_cbc( Ok(ciphertext) } -fn encrypt_aes_gcm( - key: RawKeyData, - length: usize, - tag_length: usize, +fn encrypt_aes_gcm_general>( + key: &[u8], 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(); + length: usize, + ciphertext: &mut [u8], + additional_data: Vec, +) -> Result { + let nonce = Nonce::::from_slice(&iv); let tag = match length { 128 => { - let cipher = Aes128Gcm::new_from_slice(key) + let cipher = aes_gcm::AesGcm::::new_from_slice(key) .map_err(|_| operation_error("Encryption failed"))?; cipher - .encrypt_in_place_detached(nonce, &additional_data, &mut ciphertext) + .encrypt_in_place_detached(nonce, &additional_data, ciphertext) .map_err(|_| operation_error("Encryption failed"))? } 192 => { - type Aes192Gcm = aes_gcm::AesGcm; - - let cipher = Aes192Gcm::new_from_slice(key) + let cipher = aes_gcm::AesGcm::::new_from_slice(key) .map_err(|_| operation_error("Encryption failed"))?; cipher - .encrypt_in_place_detached(nonce, &additional_data, &mut ciphertext) + .encrypt_in_place_detached(nonce, &additional_data, ciphertext) .map_err(|_| operation_error("Encryption failed"))? } 256 => { - let cipher = Aes256Gcm::new_from_slice(key) + let cipher = aes_gcm::AesGcm::::new_from_slice(key) .map_err(|_| operation_error("Encryption failed"))?; cipher - .encrypt_in_place_detached(nonce, &additional_data, &mut ciphertext) + .encrypt_in_place_detached(nonce, &additional_data, ciphertext) .map_err(|_| operation_error("Encryption failed"))? } _ => return Err(type_error("invalid length")), }; + Ok(tag) +} + +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(); + + let mut ciphertext = data.to_vec(); + // Fixed 96-bit OR 128-bit nonce + let tag = match iv.len() { + 12 => encrypt_aes_gcm_general::( + key, + iv, + length, + &mut ciphertext, + additional_data, + )?, + 16 => encrypt_aes_gcm_general::( + key, + iv, + length, + &mut ciphertext, + additional_data, + )?, + _ => return Err(type_error("iv length not equal to 12 or 16")), + }; + // 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)]; diff --git a/tools/wpt/expectation.json b/tools/wpt/expectation.json index b2a2ec86d057c5..3f614bc2e56d02 100644 --- a/tools/wpt/expectation.json +++ b/tools/wpt/expectation.json @@ -211,25 +211,7 @@ "AES-GCM 256-bit key, 120-bit tag decryption with altered ciphertext", "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, 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", - "AES-GCM 128-bit key, illegal tag length 95-bits", - "AES-GCM 128-bit key, illegal tag length 129-bits", - "AES-GCM 128-bit key, illegal tag length 256-bits", - "AES-GCM 192-bit key, illegal tag length 24-bits", - "AES-GCM 192-bit key, illegal tag length 48-bits", - "AES-GCM 192-bit key, illegal tag length 72-bits", - "AES-GCM 192-bit key, illegal tag length 95-bits", - "AES-GCM 192-bit key, illegal tag length 129-bits", - "AES-GCM 192-bit key, illegal tag length 256-bits", - "AES-GCM 256-bit key, illegal tag length 24-bits", - "AES-GCM 256-bit key, illegal tag length 48-bits", - "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 256-bit key, no additional data, 128-bit tag decryption with altered ciphertext" ], "aes_gcm.https.any.worker.html": [ "AES-GCM 128-bit key, 32-bit tag", @@ -399,25 +381,7 @@ "AES-GCM 256-bit key, 120-bit tag decryption with altered ciphertext", "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, 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", - "AES-GCM 128-bit key, illegal tag length 95-bits", - "AES-GCM 128-bit key, illegal tag length 129-bits", - "AES-GCM 128-bit key, illegal tag length 256-bits", - "AES-GCM 192-bit key, illegal tag length 24-bits", - "AES-GCM 192-bit key, illegal tag length 48-bits", - "AES-GCM 192-bit key, illegal tag length 72-bits", - "AES-GCM 192-bit key, illegal tag length 95-bits", - "AES-GCM 192-bit key, illegal tag length 129-bits", - "AES-GCM 192-bit key, illegal tag length 256-bits", - "AES-GCM 256-bit key, illegal tag length 24-bits", - "AES-GCM 256-bit key, illegal tag length 48-bits", - "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 256-bit key, no additional data, 128-bit tag decryption with altered ciphertext" ], "rsa_oaep.https.any.html": true, "rsa_oaep.https.any.worker.html": true @@ -853,20 +817,7 @@ "wrapKey_unwrapKey": { "wrapKey_unwrapKey.https.any.html": [ "Can wrap and unwrap ECDH public key keys using spki and AES-CTR", - "Can wrap and unwrap AES-GCM keys as non-extractable using raw and AES-CTR", - "Can wrap and unwrap AES-GCM keys as non-extractable using jwk and AES-CTR", - "Can unwrap AES-GCM non-extractable keys using jwk and AES-CTR", "Can wrap and unwrap ECDH public key keys using spki and AES-CBC", - "Can wrap and unwrap AES-GCM keys as non-extractable using raw and AES-CBC", - "Can wrap and unwrap AES-GCM keys as non-extractable using jwk and AES-CBC", - "Can unwrap AES-GCM non-extractable keys using jwk and AES-CBC", - "Can wrap and unwrap ECDSA public key keys using spki and AES-GCM", - "Can wrap and unwrap ECDSA public key keys using jwk and AES-GCM", - "Can wrap and unwrap ECDSA private key keys using pkcs8 and AES-GCM", - "Can wrap and unwrap ECDSA private key keys as non-extractable using pkcs8 and AES-GCM", - "Can wrap and unwrap ECDSA private key keys using jwk and AES-GCM", - "Can wrap and unwrap ECDSA private key keys as non-extractable using jwk and AES-GCM", - "Can unwrap ECDSA private key non-extractable keys using jwk and AES-GCM", "Can wrap and unwrap ECDH public key keys using spki and AES-GCM", "Can wrap and unwrap ECDH public key keys using jwk and AES-GCM", "Can wrap and unwrap ECDH private key keys using pkcs8 and AES-GCM", @@ -874,6 +825,11 @@ "Can wrap and unwrap ECDH private key keys using jwk and AES-GCM", "Can wrap and unwrap ECDH private key keys as non-extractable using jwk and AES-GCM", "Can unwrap ECDH private key non-extractable keys using jwk and AES-GCM", + "Can wrap and unwrap HMAC keys using raw and AES-GCM", + "Can wrap and unwrap HMAC keys as non-extractable using raw and AES-GCM", + "Can wrap and unwrap HMAC keys using jwk and AES-GCM", + "Can wrap and unwrap HMAC keys as non-extractable using jwk and AES-GCM", + "Can unwrap HMAC non-extractable keys using jwk and AES-GCM", "Can wrap and unwrap AES-CTR keys using raw and AES-GCM", "Can wrap and unwrap AES-CTR keys as non-extractable using raw and AES-GCM", "Can wrap and unwrap AES-CTR keys using jwk and AES-GCM", @@ -894,11 +850,20 @@ "Can wrap and unwrap AES-KW keys using jwk and AES-GCM", "Can wrap and unwrap AES-KW keys as non-extractable using jwk and AES-GCM", "Can unwrap AES-KW non-extractable keys using jwk and AES-GCM", - "Can wrap and unwrap HMAC keys using raw and AES-GCM", - "Can wrap and unwrap HMAC keys as non-extractable using raw and AES-GCM", - "Can wrap and unwrap HMAC keys using jwk and AES-GCM", - "Can wrap and unwrap HMAC keys as non-extractable using jwk and AES-GCM", - "Can unwrap HMAC non-extractable keys using jwk and AES-GCM", + "Can wrap and unwrap ECDSA public key keys using spki and AES-GCM", + "Can wrap and unwrap ECDSA public key keys using jwk and AES-GCM", + "Can wrap and unwrap ECDSA private key keys using pkcs8 and AES-GCM", + "Can wrap and unwrap ECDSA private key keys as non-extractable using pkcs8 and AES-GCM", + "Can wrap and unwrap ECDSA private key keys using jwk and AES-GCM", + "Can wrap and unwrap ECDSA private key keys as non-extractable using jwk and AES-GCM", + "Can unwrap ECDSA private key non-extractable keys using jwk and AES-GCM", + "Can wrap and unwrap RSASSA-PKCS1-v1_5 public key keys using spki and AES-GCM", + "Can wrap and unwrap RSASSA-PKCS1-v1_5 public key keys using jwk and AES-GCM", + "Can wrap and unwrap RSASSA-PKCS1-v1_5 private key keys using pkcs8 and AES-GCM", + "Can wrap and unwrap RSASSA-PKCS1-v1_5 private key keys as non-extractable using pkcs8 and AES-GCM", + "Can wrap and unwrap RSASSA-PKCS1-v1_5 private key keys using jwk and AES-GCM", + "Can wrap and unwrap RSASSA-PKCS1-v1_5 private key keys as non-extractable using jwk and AES-GCM", + "Can unwrap RSASSA-PKCS1-v1_5 private key non-extractable keys using jwk and AES-GCM", "Can wrap and unwrap RSA-PSS public key keys using spki and AES-GCM", "Can wrap and unwrap RSA-PSS public key keys using jwk and AES-GCM", "Can wrap and unwrap RSA-PSS private key keys using pkcs8 and AES-GCM", @@ -913,28 +878,11 @@ "Can wrap and unwrap RSA-OAEP private key keys using jwk and AES-GCM", "Can wrap and unwrap RSA-OAEP private key keys as non-extractable using jwk and AES-GCM", "Can unwrap RSA-OAEP private key non-extractable keys using jwk and AES-GCM", - "Can wrap and unwrap RSASSA-PKCS1-v1_5 public key keys using spki and AES-GCM", - "Can wrap and unwrap RSASSA-PKCS1-v1_5 public key keys using jwk and AES-GCM", - "Can wrap and unwrap RSASSA-PKCS1-v1_5 private key keys using pkcs8 and AES-GCM", - "Can wrap and unwrap RSASSA-PKCS1-v1_5 private key keys as non-extractable using pkcs8 and AES-GCM", - "Can wrap and unwrap RSASSA-PKCS1-v1_5 private key keys using jwk and AES-GCM", - "Can wrap and unwrap RSASSA-PKCS1-v1_5 private key keys as non-extractable using jwk and AES-GCM", - "Can unwrap RSASSA-PKCS1-v1_5 private key non-extractable keys using jwk and AES-GCM", - "Can wrap and unwrap AES-GCM keys as non-extractable using raw and AES-KW", - "Can wrap and unwrap ECDH public key keys using spki and RSA-OAEP", - "Can wrap and unwrap AES-GCM keys as non-extractable using raw and RSA-OAEP", - "Can wrap and unwrap AES-GCM keys as non-extractable using jwk and RSA-OAEP", - "Can unwrap AES-GCM non-extractable keys using jwk and RSA-OAEP" + "Can wrap and unwrap ECDH public key keys using spki and RSA-OAEP" ], "wrapKey_unwrapKey.https.any.worker.html": [ "Can wrap and unwrap ECDH public key keys using spki and AES-CTR", - "Can wrap and unwrap AES-GCM keys as non-extractable using raw and AES-CTR", - "Can wrap and unwrap AES-GCM keys as non-extractable using jwk and AES-CTR", - "Can unwrap AES-GCM non-extractable keys using jwk and AES-CTR", "Can wrap and unwrap ECDH public key keys using spki and AES-CBC", - "Can wrap and unwrap AES-GCM keys as non-extractable using raw and AES-CBC", - "Can wrap and unwrap AES-GCM keys as non-extractable using jwk and AES-CBC", - "Can unwrap AES-GCM non-extractable keys using jwk and AES-CBC", "Can wrap and unwrap ECDSA public key keys using spki and AES-GCM", "Can wrap and unwrap ECDSA public key keys using jwk and AES-GCM", "Can wrap and unwrap ECDSA private key keys using pkcs8 and AES-GCM", @@ -995,11 +943,7 @@ "Can wrap and unwrap RSA-PSS private key keys using jwk and AES-GCM", "Can wrap and unwrap RSA-PSS private key keys as non-extractable using jwk and AES-GCM", "Can unwrap RSA-PSS private key non-extractable keys using jwk and AES-GCM", - "Can wrap and unwrap AES-GCM keys as non-extractable using raw and AES-KW", - "Can wrap and unwrap ECDH public key keys using spki and RSA-OAEP", - "Can wrap and unwrap AES-GCM keys as non-extractable using raw and RSA-OAEP", - "Can wrap and unwrap AES-GCM keys as non-extractable using jwk and RSA-OAEP", - "Can unwrap AES-GCM non-extractable keys using jwk and RSA-OAEP" + "Can wrap and unwrap ECDH public key keys using spki and RSA-OAEP" ] } },