Skip to content

Commit

Permalink
feat(ext/crypto): AES-GCM support for 128bit IVs (#13805)
Browse files Browse the repository at this point in the history
  • Loading branch information
littledivy committed Mar 2, 2022
1 parent b751e97 commit 8b2989c
Show file tree
Hide file tree
Showing 5 changed files with 219 additions and 180 deletions.
92 changes: 69 additions & 23 deletions cli/tests/unit/webcrypto_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
18 changes: 14 additions & 4 deletions ext/crypto/00_crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
108 changes: 62 additions & 46 deletions ext/crypto/decrypt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -76,8 +76,6 @@ pub enum DecryptAlgorithm {
},
}

type Aes192Gcm = aes_gcm::AesGcm<Aes192, U12>;

pub async fn op_crypto_decrypt(
_state: Rc<RefCell<OpState>>,
opts: DecryptOptions,
Expand Down Expand Up @@ -221,26 +219,54 @@ where
Ok(plaintext)
}

fn decrypt_aes_gcm_gen<B>(
fn decrypt_aes_gcm_gen<N: ArrayLength<u8>>(
key: &[u8],
tag: &GenericArray<u8, <B as AeadCore>::TagSize>,
nonce: &GenericArray<u8, <B as AeadCore>::NonceSize>,
tag: &aes_gcm::Tag,
nonce: &[u8],
length: usize,
additional_data: Vec<u8>,
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::<Aes128, N>::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::<Aes192, N>::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::<Aes256, N>::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(())
}
Expand Down Expand Up @@ -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
Expand All @@ -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::<Aes128Gcm>(
key,
tag.into(),
nonce,
additional_data,
&mut plaintext,
)?,
192 => decrypt_aes_gcm_gen::<Aes192Gcm>(

// Fixed 96-bit or 128-bit nonce
match iv.len() {
12 => decrypt_aes_gcm_gen::<U12>(
key,
tag.into(),
nonce,
&iv,
length,
additional_data,
&mut plaintext,
)?,
256 => decrypt_aes_gcm_gen::<Aes256Gcm>(
16 => decrypt_aes_gcm_gen::<U16>(
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)
}

0 comments on commit 8b2989c

Please sign in to comment.