Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ext/crypto): implement AES-GCM decryption #13319

Merged
merged 7 commits into from
Jan 14, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions cli/tests/unit/webcrypto_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
61 changes: 61 additions & 0 deletions ext/crypto/00_crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@
"decrypt": {
"RSA-OAEP": "RsaOaepParams",
"AES-CBC": "AesCbcParams",
"AES-GCM": "AesGcmParams",
"AES-CTR": "AesCtrParams",
},
"get key length": {
Expand Down Expand Up @@ -630,6 +631,66 @@
// 4.
return cipherText.buffer;
}
case "AES-GCM": {
normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv);

// 1.
if (normalizedAlgorithm.tagLength == undefined) {
littledivy marked this conversation as resolved.
Show resolved Hide resolved
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) {
littledivy marked this conversation as resolved.
Show resolved Hide resolved
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");
}
Expand Down
89 changes: 89 additions & 0 deletions ext/crypto/decrypt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@ use std::cell::RefCell;
use std::rc::Rc;

use crate::shared::*;
use aes::Aes192;
use aes::BlockEncrypt;
use aes::NewBlockCipher;
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;
Expand All @@ -17,6 +23,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;
Expand Down Expand Up @@ -56,6 +63,15 @@ pub enum DecryptAlgorithm {
ctr_length: usize,
key_length: usize,
},
#[serde(rename = "AES-GCM", rename_all = "camelCase")]
AesGcm {
#[serde(with = "serde_bytes")]
iv: Vec<u8>,
#[serde(with = "serde_bytes")]
additional_data: Option<Vec<u8>>,
length: usize,
tag_length: usize,
},
}

pub async fn op_crypto_decrypt(
Expand All @@ -76,6 +92,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())
Expand Down Expand Up @@ -228,3 +250,70 @@ fn decrypt_aes_ctr(
)),
}
}

fn decrypt_aes_gcm(
key: RawKeyData,
length: usize,
tag_length: usize,
iv: Vec<u8>,
additional_data: Option<Vec<u8>>,
data: &[u8],
) -> Result<Vec<u8>, 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 => {
let cipher = Aes128Gcm::new_from_slice(key)
.map_err(|_| operation_error("Decryption failed"))?;
cipher
.decrypt_in_place_detached(
nonce,
&additional_data,
&mut plaintext,
tag.into(),
)
.map_err(|_| operation_error("Decryption failed"))?
}
192 => {
littledivy marked this conversation as resolved.
Show resolved Hide resolved
type Aes192Gcm = aes_gcm::AesGcm<Aes192, U12>;

let cipher = Aes192Gcm::new_from_slice(key)
.map_err(|_| operation_error("Decryption failed"))?;
cipher
.decrypt_in_place_detached(
nonce,
&additional_data,
&mut plaintext,
tag.into(),
)
.map_err(|_| operation_error("Decryption failed"))?
}
256 => {
let cipher = Aes256Gcm::new_from_slice(key)
.map_err(|_| operation_error("Decryption failed"))?;
cipher
.decrypt_in_place_detached(
nonce,
&additional_data,
&mut plaintext,
tag.into(),
)
.map_err(|_| operation_error("Decryption failed"))?
}
_ => return Err(type_error("invalid length")),
};

Ok(plaintext)
}
1 change: 1 addition & 0 deletions ext/crypto/lib.deno_crypto.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ interface SubtleCrypto {
| AlgorithmIdentifier
| RsaOaepParams
| AesCbcParams
| AesGcmParams
| AesCtrParams,
key: CryptoKey,
data: BufferSource,
Expand Down
124 changes: 2 additions & 122 deletions tools/wpt/expectation.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -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
Expand Down