summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDivy Srivastava <dj.srivastava23@gmail.com>2021-10-11 20:07:51 +0530
committerGitHub <noreply@github.com>2021-10-11 16:37:51 +0200
commit3b2cb8e7113b19344209eddc8bc1bd447fcec4ea (patch)
tree616169290b703184047ad7beb4b9c79011a2a6ef
parent426ebf854a82c63cdaa2413fbd1b005025dba95b (diff)
feat(ext/crypto): implement AES-CBC encryption & decryption (#12123)
* initial stuff * stuff * merge stuff * cleanup * fmt * length * update lockfile * decrypt * fixy * clippy hello? * hmm * fixs * fix lint * add AesCbcParams * fixes * fixy * lockfile fixy * fix dumb assertions * re run CI * rerun CI * rerun CI
-rw-r--r--Cargo.lock47
-rw-r--r--cli/tests/unit/webcrypto_test.ts34
-rw-r--r--ext/crypto/00_crypto.js71
-rw-r--r--ext/crypto/01_webidl.js12
-rw-r--r--ext/crypto/Cargo.toml2
-rw-r--r--ext/crypto/lib.deno_crypto.d.ts8
-rw-r--r--ext/crypto/lib.rs90
7 files changed, 258 insertions, 6 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 7b2076cee..fa521076e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -25,6 +25,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
+name = "aes"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8"
+dependencies = [
+ "cfg-if 1.0.0",
+ "cipher",
+ "cpufeatures",
+ "opaque-debug",
+]
+
+[[package]]
name = "ahash"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -263,6 +275,22 @@ dependencies = [
]
[[package]]
+name = "block-modes"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e"
+dependencies = [
+ "block-padding",
+ "cipher",
+]
+
+[[package]]
+name = "block-padding"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
+
+[[package]]
name = "brotli"
version = "3.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -345,6 +373,15 @@ dependencies = [
]
[[package]]
+name = "cipher"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
name = "clap"
version = "2.33.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -734,6 +771,8 @@ dependencies = [
name = "deno_crypto"
version = "0.34.0"
dependencies = [
+ "aes",
+ "block-modes",
"deno_core",
"deno_web",
"lazy_static",
@@ -1996,9 +2035,9 @@ checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6"
[[package]]
name = "libloading"
-version = "0.7.0"
+version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a"
+checksum = "c0cf036d15402bea3c5d4de17b3fce76b3e4a56ebc1f577be0e7a72f7c607cf0"
dependencies = [
"cfg-if 1.0.0",
"winapi 0.3.9",
@@ -3899,9 +3938,9 @@ dependencies = [
[[package]]
name = "synstructure"
-version = "0.12.5"
+version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "474aaa926faa1603c40b7885a9eaea29b444d1cb2850cb7c0e37bb1a4182f4fa"
+checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
dependencies = [
"proc-macro2 1.0.29",
"quote 1.0.10",
diff --git a/cli/tests/unit/webcrypto_test.ts b/cli/tests/unit/webcrypto_test.ts
index 80275b002..420fa2a7f 100644
--- a/cli/tests/unit/webcrypto_test.ts
+++ b/cli/tests/unit/webcrypto_test.ts
@@ -513,6 +513,40 @@ unitTest(async function testHkdfDeriveBits() {
assertEquals(result.byteLength, 128 / 8);
});
+unitTest(async function testAesCbcEncryptDecrypt() {
+ const key = await crypto.subtle.generateKey(
+ { name: "AES-CBC", length: 128 },
+ true,
+ ["encrypt", "decrypt"],
+ );
+
+ const iv = await crypto.getRandomValues(new Uint8Array(16));
+ const encrypted = await crypto.subtle.encrypt(
+ {
+ name: "AES-CBC",
+ iv,
+ },
+ key as CryptoKey,
+ new Uint8Array([1, 2, 3, 4, 5, 6]),
+ );
+
+ assert(encrypted instanceof ArrayBuffer);
+ assertEquals(encrypted.byteLength, 16);
+
+ const decrypted = await crypto.subtle.decrypt(
+ {
+ name: "AES-CBC",
+ iv,
+ },
+ key as CryptoKey,
+ encrypted,
+ );
+
+ assert(decrypted instanceof ArrayBuffer);
+ assertEquals(decrypted.byteLength, 6);
+ assertEquals(new Uint8Array(decrypted), new Uint8Array([1, 2, 3, 4, 5, 6]));
+});
+
// TODO(@littledivy): Enable WPT when we have importKey support
unitTest(async function testECDH() {
const namedCurve = "P-256";
diff --git a/ext/crypto/00_crypto.js b/ext/crypto/00_crypto.js
index 4b4770e13..85849153d 100644
--- a/ext/crypto/00_crypto.js
+++ b/ext/crypto/00_crypto.js
@@ -117,9 +117,11 @@
},
"encrypt": {
"RSA-OAEP": "RsaOaepParams",
+ "AES-CBC": "AesCbcParams",
},
"decrypt": {
"RSA-OAEP": "RsaOaepParams",
+ "AES-CBC": "AesCbcParams",
},
"wrapKey": {
// TODO(@littledivy): Enable this once implemented.
@@ -440,6 +442,41 @@
// 6.
return cipherText.buffer;
}
+ case "AES-CBC": {
+ if (ArrayBufferIsView(normalizedAlgorithm.iv)) {
+ normalizedAlgorithm.iv = new Uint8Array(
+ normalizedAlgorithm.iv.buffer,
+ normalizedAlgorithm.iv.byteOffset,
+ normalizedAlgorithm.iv.byteLength,
+ );
+ } else {
+ normalizedAlgorithm.iv = new Uint8Array(
+ normalizedAlgorithm.iv,
+ );
+ }
+ normalizedAlgorithm.iv = TypedArrayPrototypeSlice(
+ normalizedAlgorithm.iv,
+ );
+
+ // 1.
+ if (normalizedAlgorithm.iv.byteLength !== 16) {
+ throw new DOMException(
+ "Initialization vector must be 16 bytes",
+ "OperationError",
+ );
+ }
+
+ // 2.
+ const cipherText = await core.opAsync("op_crypto_encrypt_key", {
+ key: keyData,
+ algorithm: "AES-CBC",
+ length: key[_algorithm].length,
+ iv: normalizedAlgorithm.iv,
+ }, data);
+
+ // 4.
+ return cipherText.buffer;
+ }
default:
throw new DOMException("Not implemented", "NotSupportedError");
}
@@ -524,6 +561,40 @@
// 6.
return plainText.buffer;
}
+ case "AES-CBC": {
+ if (ArrayBufferIsView(normalizedAlgorithm.iv)) {
+ normalizedAlgorithm.iv = new Uint8Array(
+ normalizedAlgorithm.iv.buffer,
+ normalizedAlgorithm.iv.byteOffset,
+ normalizedAlgorithm.iv.byteLength,
+ );
+ } else {
+ normalizedAlgorithm.iv = new Uint8Array(
+ normalizedAlgorithm.iv,
+ );
+ }
+ normalizedAlgorithm.iv = TypedArrayPrototypeSlice(
+ normalizedAlgorithm.iv,
+ );
+
+ // 1.
+ if (normalizedAlgorithm.iv.byteLength !== 16) {
+ throw new DOMException(
+ "Counter must be 16 bytes",
+ "OperationError",
+ );
+ }
+
+ const plainText = await core.opAsync("op_crypto_decrypt_key", {
+ key: keyData,
+ algorithm: "AES-CBC",
+ iv: normalizedAlgorithm.iv,
+ length: key[_algorithm].length,
+ }, data);
+
+ // 6.
+ return plainText.buffer;
+ }
default:
throw new DOMException("Not implemented", "NotSupportedError");
}
diff --git a/ext/crypto/01_webidl.js b/ext/crypto/01_webidl.js
index 90bee464c..c14e4ff22 100644
--- a/ext/crypto/01_webidl.js
+++ b/ext/crypto/01_webidl.js
@@ -367,6 +367,18 @@
webidl.converters.Pbkdf2Params = webidl
.createDictionaryConverter("Pbkdf2Params", dictPbkdf2Params);
+ const dictAesCbcParams = [
+ ...dictAlgorithm,
+ {
+ key: "iv",
+ converter: webidl.converters["BufferSource"],
+ required: true,
+ },
+ ];
+
+ webidl.converters.AesCbcParams = webidl
+ .createDictionaryConverter("AesCbcParams", dictAesCbcParams);
+
webidl.converters.CryptoKey = webidl.createInterfaceConverter(
"CryptoKey",
CryptoKey,
diff --git a/ext/crypto/Cargo.toml b/ext/crypto/Cargo.toml
index 060845b70..16839d8d1 100644
--- a/ext/crypto/Cargo.toml
+++ b/ext/crypto/Cargo.toml
@@ -14,6 +14,8 @@ description = "Web Cryptography API implementation for Deno"
path = "lib.rs"
[dependencies]
+aes = "0.7.5"
+block-modes = "0.8.1"
deno_core = { version = "0.102.0", path = "../../core" }
deno_web = { version = "0.51.0", path = "../web" }
lazy_static = "1.4.0"
diff --git a/ext/crypto/lib.deno_crypto.d.ts b/ext/crypto/lib.deno_crypto.d.ts
index e5592d5bf..4ed7c51f8 100644
--- a/ext/crypto/lib.deno_crypto.d.ts
+++ b/ext/crypto/lib.deno_crypto.d.ts
@@ -56,6 +56,10 @@ interface JsonWebKey {
y?: string;
}
+interface AesCbcParams extends Algorithm {
+ iv: BufferSource;
+}
+
interface HmacKeyGenParams extends Algorithm {
hash: HashAlgorithmIdentifier;
length?: number;
@@ -213,12 +217,12 @@ interface SubtleCrypto {
data: BufferSource,
): Promise<ArrayBuffer>;
encrypt(
- algorithm: AlgorithmIdentifier | RsaOaepParams,
+ algorithm: AlgorithmIdentifier | RsaOaepParams | AesCbcParams,
key: CryptoKey,
data: BufferSource,
): Promise<ArrayBuffer>;
decrypt(
- algorithm: AlgorithmIdentifier | RsaOaepParams,
+ algorithm: AlgorithmIdentifier | RsaOaepParams | AesCbcParams,
key: CryptoKey,
data: BufferSource,
): Promise<ArrayBuffer>;
diff --git a/ext/crypto/lib.rs b/ext/crypto/lib.rs
index 6376aedbb..6b67185dd 100644
--- a/ext/crypto/lib.rs
+++ b/ext/crypto/lib.rs
@@ -19,6 +19,7 @@ use std::convert::TryInto;
use std::num::NonZeroU32;
use std::rc::Rc;
+use block_modes::BlockMode;
use lazy_static::lazy_static;
use num_traits::cast::FromPrimitive;
use rand::rngs::OsRng;
@@ -892,8 +893,12 @@ pub async fn op_crypto_derive_bits(
pub struct EncryptArg {
key: KeyData,
algorithm: Algorithm,
+ // RSA-OAEP
hash: Option<CryptoHash>,
label: Option<ZeroCopyBuf>,
+ // AES-CBC
+ iv: Option<ZeroCopyBuf>,
+ length: Option<usize>,
}
pub async fn op_crypto_encrypt_key(
@@ -945,6 +950,46 @@ pub async fn op_crypto_encrypt_key(
.into(),
)
}
+ Algorithm::AesCbc => {
+ let key = &*args.key.data;
+ let length = args
+ .length
+ .ok_or_else(|| type_error("Missing argument length".to_string()))?;
+ let iv = args
+ .iv
+ .ok_or_else(|| type_error("Missing argument iv".to_string()))?;
+
+ // 2-3.
+ let ciphertext = match length {
+ 128 => {
+ // Section 10.3 Step 2 of RFC 2315 https://www.rfc-editor.org/rfc/rfc2315
+ type Aes128Cbc =
+ block_modes::Cbc<aes::Aes128, block_modes::block_padding::Pkcs7>;
+
+ let cipher = Aes128Cbc::new_from_slices(key, &iv)?;
+ cipher.encrypt_vec(data)
+ }
+ 192 => {
+ // Section 10.3 Step 2 of RFC 2315 https://www.rfc-editor.org/rfc/rfc2315
+ type Aes192Cbc =
+ block_modes::Cbc<aes::Aes192, block_modes::block_padding::Pkcs7>;
+
+ let cipher = Aes192Cbc::new_from_slices(key, &iv)?;
+ cipher.encrypt_vec(data)
+ }
+ 256 => {
+ // Section 10.3 Step 2 of RFC 2315 https://www.rfc-editor.org/rfc/rfc2315
+ type Aes256Cbc =
+ block_modes::Cbc<aes::Aes256, block_modes::block_padding::Pkcs7>;
+
+ let cipher = Aes256Cbc::new_from_slices(key, &iv)?;
+ cipher.encrypt_vec(data)
+ }
+ _ => unreachable!(),
+ };
+
+ Ok(ciphertext.into())
+ }
_ => Err(type_error("Unsupported algorithm".to_string())),
}
}
@@ -1451,8 +1496,12 @@ pub async fn op_crypto_import_key(
pub struct DecryptArg {
key: KeyData,
algorithm: Algorithm,
+ // RSA-OAEP
hash: Option<CryptoHash>,
label: Option<ZeroCopyBuf>,
+ // AES-CBC
+ iv: Option<ZeroCopyBuf>,
+ length: Option<usize>,
}
pub async fn op_crypto_decrypt_key(
@@ -1503,6 +1552,47 @@ pub async fn op_crypto_decrypt_key(
.into(),
)
}
+ Algorithm::AesCbc => {
+ let key = &*args.key.data;
+ let length = args
+ .length
+ .ok_or_else(|| type_error("Missing argument length".to_string()))?;
+ let iv = args
+ .iv
+ .ok_or_else(|| type_error("Missing argument iv".to_string()))?;
+
+ // 2.
+ let plaintext = match length {
+ 128 => {
+ // Section 10.3 Step 2 of RFC 2315 https://www.rfc-editor.org/rfc/rfc2315
+ type Aes128Cbc =
+ block_modes::Cbc<aes::Aes128, block_modes::block_padding::Pkcs7>;
+ let cipher = Aes128Cbc::new_from_slices(key, &iv)?;
+
+ cipher.decrypt_vec(data)?
+ }
+ 192 => {
+ // Section 10.3 Step 2 of RFC 2315 https://www.rfc-editor.org/rfc/rfc2315
+ type Aes192Cbc =
+ block_modes::Cbc<aes::Aes192, block_modes::block_padding::Pkcs7>;
+ let cipher = Aes192Cbc::new_from_slices(key, &iv)?;
+
+ cipher.decrypt_vec(data)?
+ }
+ 256 => {
+ // Section 10.3 Step 2 of RFC 2315 https://www.rfc-editor.org/rfc/rfc2315
+ type Aes256Cbc =
+ block_modes::Cbc<aes::Aes256, block_modes::block_padding::Pkcs7>;
+ let cipher = Aes256Cbc::new_from_slices(key, &iv)?;
+
+ cipher.decrypt_vec(data)?
+ }
+ _ => unreachable!(),
+ };
+
+ // 6.
+ Ok(plaintext.into())
+ }
_ => Err(type_error("Unsupported algorithm".to_string())),
}
}