summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock2
-rw-r--r--Cargo.toml2
-rw-r--r--cli/tests/unit_node/crypto_cipher_test.ts24
-rw-r--r--ext/crypto/Cargo.toml4
-rw-r--r--ext/node/Cargo.toml2
-rw-r--r--ext/node/crypto/cipher.rs108
-rw-r--r--ext/node/crypto/mod.rs44
-rw-r--r--ext/node/lib.rs3
-rw-r--r--ext/node/polyfills/internal/crypto/cipher.ts111
9 files changed, 246 insertions, 54 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 59cfe5611..83681a9f4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1244,6 +1244,8 @@ dependencies = [
name = "deno_node"
version = "0.29.0"
dependencies = [
+ "aes",
+ "cbc",
"deno_core",
"digest 0.10.6",
"hex",
diff --git a/Cargo.toml b/Cargo.toml
index 32823b0b9..62051cf23 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -77,6 +77,7 @@ deno_websocket = { version = "0.97.0", path = "./ext/websocket" }
deno_webstorage = { version = "0.87.0", path = "./ext/webstorage" }
deno_napi = { version = "0.22.0", path = "./ext/napi" }
+aes = "=0.8.2"
anyhow = "1.0.57"
async-trait = "0.1.51"
atty = "=0.2.14"
@@ -84,6 +85,7 @@ base64 = "=0.13.1"
bencher = "0.1"
bytes = "1.4.0"
cache_control = "=0.2.0"
+cbc = { version = "=0.1.2", features = ["alloc"] }
console_static_text = "=0.7.1"
data-url = "=0.2.0"
dlopen = "0.1.8"
diff --git a/cli/tests/unit_node/crypto_cipher_test.ts b/cli/tests/unit_node/crypto_cipher_test.ts
index e3bd7ca26..5fbaa548d 100644
--- a/cli/tests/unit_node/crypto_cipher_test.ts
+++ b/cli/tests/unit_node/crypto_cipher_test.ts
@@ -48,3 +48,27 @@ Deno.test({
);
},
});
+
+Deno.test({
+ name: "createCipheriv - basic",
+ fn() {
+ const cipher = crypto.createCipheriv(
+ "aes-128-cbc",
+ new Uint8Array(16),
+ new Uint8Array(16),
+ );
+ assertEquals(
+ cipher.update(new Uint8Array(16), undefined, "hex"),
+ "66e94bd4ef8a2c3b884cfa59ca342b2e",
+ );
+ assertEquals(
+ cipher.update(new Uint8Array(19), undefined, "hex"),
+ "f795bd4a52e29ed713d313fa20e98dbc",
+ );
+ assertEquals(
+ cipher.update(new Uint8Array(55), undefined, "hex"),
+ "a10cf66d0fddf3405370b4bf8df5bfb347c78395e0d8ae2194da0a90abc9888a94ee48f6c78fcd518a941c3896102cb1",
+ );
+ assertEquals(cipher.final("hex"), "e11901dde4a2f99fe4efc707e48c6aed");
+ },
+});
diff --git a/ext/crypto/Cargo.toml b/ext/crypto/Cargo.toml
index 45ef6c7a9..3e9dd9135 100644
--- a/ext/crypto/Cargo.toml
+++ b/ext/crypto/Cargo.toml
@@ -14,12 +14,12 @@ description = "Web Cryptography API implementation for Deno"
path = "lib.rs"
[dependencies]
-aes = "0.8.1"
+aes.workspace = true
aes-gcm = "0.10"
aes-kw = { version = "0.2.1", features = ["alloc"] }
base64.workspace = true
block-modes = "0.9.1"
-cbc = { version = "0.1.2", features = ["alloc"] }
+cbc.workspace = true
const-oid = "0.9.0"
ctr = "0.9.1"
# https://github.com/dalek-cryptography/curve25519-dalek/pull/397
diff --git a/ext/node/Cargo.toml b/ext/node/Cargo.toml
index 9afd1452c..c6ffcac48 100644
--- a/ext/node/Cargo.toml
+++ b/ext/node/Cargo.toml
@@ -14,6 +14,8 @@ description = "Node compatibility for Deno"
path = "lib.rs"
[dependencies]
+aes.workspace = true
+cbc.workspace = true
deno_core.workspace = true
digest = { version = "0.10.5", features = ["core-api", "std"] }
hex = "0.4.3"
diff --git a/ext/node/crypto/cipher.rs b/ext/node/crypto/cipher.rs
new file mode 100644
index 000000000..0833cb591
--- /dev/null
+++ b/ext/node/crypto/cipher.rs
@@ -0,0 +1,108 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+use aes::cipher::block_padding::Pkcs7;
+use aes::cipher::BlockEncryptMut;
+use aes::cipher::KeyIvInit;
+use deno_core::error::type_error;
+use deno_core::error::AnyError;
+use deno_core::Resource;
+
+use std::borrow::Cow;
+use std::cell::RefCell;
+use std::rc::Rc;
+
+enum Cipher {
+ Aes128Cbc(Box<cbc::Encryptor<aes::Aes128>>),
+ // TODO(kt3k): add more algorithms Aes192Cbc, Aes256Cbc, Aes128ECB, Aes128GCM, etc.
+}
+
+enum Decipher {
+ // TODO(kt3k): implement Deciphers
+ // Aes128Cbc(Box<cbc::Decryptor<aes::Aes128>>),
+}
+
+pub struct CipherContext {
+ cipher: Rc<RefCell<Cipher>>,
+}
+
+pub struct DecipherContext {
+ _decipher: Rc<RefCell<Decipher>>,
+}
+
+impl CipherContext {
+ pub fn new(algorithm: &str, key: &[u8], iv: &[u8]) -> Result<Self, AnyError> {
+ Ok(Self {
+ cipher: Rc::new(RefCell::new(Cipher::new(algorithm, key, iv)?)),
+ })
+ }
+
+ pub fn encrypt(&self, input: &[u8], output: &mut [u8]) {
+ self.cipher.borrow_mut().encrypt(input, output);
+ }
+
+ pub fn r#final(
+ self,
+ input: &[u8],
+ output: &mut [u8],
+ ) -> Result<(), AnyError> {
+ Rc::try_unwrap(self.cipher)
+ .map_err(|_| type_error("Cipher context is already in use"))?
+ .into_inner()
+ .r#final(input, output)
+ }
+}
+
+impl Resource for CipherContext {
+ fn name(&self) -> Cow<str> {
+ "cryptoCipher".into()
+ }
+}
+
+impl Resource for DecipherContext {
+ fn name(&self) -> Cow<str> {
+ "cryptoDecipher".into()
+ }
+}
+
+impl Cipher {
+ fn new(
+ algorithm_name: &str,
+ key: &[u8],
+ iv: &[u8],
+ ) -> Result<Self, AnyError> {
+ use Cipher::*;
+ Ok(match algorithm_name {
+ "aes-128-cbc" => {
+ Aes128Cbc(Box::new(cbc::Encryptor::new(key.into(), iv.into())))
+ }
+ _ => return Err(type_error(format!("Unknown cipher {algorithm_name}"))),
+ })
+ }
+
+ /// encrypt encrypts the data in the middle of the input.
+ fn encrypt(&mut self, input: &[u8], output: &mut [u8]) {
+ use Cipher::*;
+ match self {
+ Aes128Cbc(encryptor) => {
+ assert!(input.len() % 16 == 0);
+ for (input, output) in input.chunks(16).zip(output.chunks_mut(16)) {
+ encryptor.encrypt_block_b2b_mut(input.into(), output.into());
+ }
+ }
+ }
+ }
+
+ /// r#final encrypts the last block of the input data.
+ fn r#final(self, input: &[u8], output: &mut [u8]) -> Result<(), AnyError> {
+ assert!(input.len() < 16);
+ use Cipher::*;
+ match self {
+ Aes128Cbc(encryptor) => {
+ let _ = (*encryptor)
+ .encrypt_padded_b2b_mut::<Pkcs7>(input, output)
+ .map_err(|_| type_error("Cannot pad the input data"))?;
+ Ok(())
+ }
+ }
+ }
+}
diff --git a/ext/node/crypto/mod.rs b/ext/node/crypto/mod.rs
index f9a61b456..3e6af9b4b 100644
--- a/ext/node/crypto/mod.rs
+++ b/ext/node/crypto/mod.rs
@@ -15,6 +15,7 @@ use rsa::PublicKey;
use rsa::RsaPrivateKey;
use rsa::RsaPublicKey;
+mod cipher;
mod digest;
#[op(fast)]
@@ -153,3 +154,46 @@ pub fn op_node_public_encrypt(
_ => Err(type_error("Unknown padding")),
}
}
+
+#[op(fast)]
+pub fn op_node_create_cipheriv(
+ state: &mut OpState,
+ algorithm: &str,
+ key: &[u8],
+ iv: &[u8],
+) -> u32 {
+ state.resource_table.add(
+ match cipher::CipherContext::new(algorithm, key, iv) {
+ Ok(context) => context,
+ Err(_) => return 0,
+ },
+ )
+}
+
+#[op(fast)]
+pub fn op_node_cipheriv_encrypt(
+ state: &mut OpState,
+ rid: u32,
+ input: &[u8],
+ output: &mut [u8],
+) -> bool {
+ let context = match state.resource_table.get::<cipher::CipherContext>(rid) {
+ Ok(context) => context,
+ Err(_) => return false,
+ };
+ context.encrypt(input, output);
+ true
+}
+
+#[op]
+pub fn op_node_cipheriv_final(
+ state: &mut OpState,
+ rid: u32,
+ input: &[u8],
+ output: &mut [u8],
+) -> Result<(), AnyError> {
+ let context = state.resource_table.take::<cipher::CipherContext>(rid)?;
+ let context = Rc::try_unwrap(context)
+ .map_err(|_| type_error("Cipher context is already in use"))?;
+ context.r#final(input, output)
+}
diff --git a/ext/node/lib.rs b/ext/node/lib.rs
index 1c9d9e0aa..318667d59 100644
--- a/ext/node/lib.rs
+++ b/ext/node/lib.rs
@@ -102,6 +102,9 @@ fn ext_polyfill() -> ExtensionBuilder {
fn ops_polyfill(ext: &mut ExtensionBuilder) -> &mut ExtensionBuilder {
ext.ops(vec![
+ crypto::op_node_cipheriv_encrypt::decl(),
+ crypto::op_node_cipheriv_final::decl(),
+ crypto::op_node_create_cipheriv::decl(),
crypto::op_node_create_hash::decl(),
crypto::op_node_hash_update::decl(),
crypto::op_node_hash_update_str::decl(),
diff --git a/ext/node/polyfills/internal/crypto/cipher.ts b/ext/node/polyfills/internal/crypto/cipher.ts
index 34776e3ab..b2d24947a 100644
--- a/ext/node/polyfills/internal/crypto/cipher.ts
+++ b/ext/node/polyfills/internal/crypto/cipher.ts
@@ -10,12 +10,13 @@ import { Buffer } from "ext:deno_node/buffer.ts";
import { notImplemented } from "ext:deno_node/_utils.ts";
import type { TransformOptions } from "ext:deno_node/_stream.d.ts";
import { Transform } from "ext:deno_node/_stream.mjs";
-import { KeyObject } from "ext:deno_node/internal/crypto/keys.ts";
+import { KeyObject } from "./keys.ts";
import type { BufferEncoding } from "ext:deno_node/_global.d.ts";
import type {
BinaryLike,
Encoding,
} from "ext:deno_node/internal/crypto/types.ts";
+import { getDefaultEncoding } from "ext:deno_node/internal/crypto/util.ts";
const { ops } = globalThis.__bootstrap.core;
@@ -42,21 +43,13 @@ export interface CipherOCBOptions extends TransformOptions {
}
export interface Cipher extends ReturnType<typeof Transform> {
- update(data: BinaryLike): Buffer;
- update(data: string, inputEncoding: Encoding): Buffer;
- update(
- data: ArrayBufferView,
- inputEncoding: undefined,
- outputEncoding: Encoding,
- ): string;
update(
data: string,
- inputEncoding: Encoding | undefined,
- outputEncoding: Encoding,
+ inputEncoding?: Encoding,
+ outputEncoding?: Encoding,
): string;
- final(): Buffer;
- final(outputEncoding: BufferEncoding): string;
+ final(outputEncoding?: BufferEncoding): string;
setAutoPadding(autoPadding?: boolean): this;
}
@@ -124,21 +117,27 @@ export interface DecipherOCB extends Decipher {
}
export class Cipheriv extends Transform implements Cipher {
+ /** CipherContext resource id */
+ #context: number;
+
+ /** plaintext data cache */
+ #cache: BlockModeCache;
+
constructor(
- _cipher: string,
- _key: CipherKey,
- _iv: BinaryLike | null,
- _options?: TransformOptions,
+ cipher: string,
+ key: CipherKey,
+ iv: BinaryLike | null,
+ options?: TransformOptions,
) {
- super();
-
- notImplemented("crypto.Cipheriv");
+ super(options);
+ this.#cache = new BlockModeCache();
+ this.#context = ops.op_node_create_cipheriv(cipher, key, iv);
}
- final(): Buffer;
- final(outputEncoding: BufferEncoding): string;
- final(_outputEncoding?: string): Buffer | string {
- notImplemented("crypto.Cipheriv.prototype.final");
+ final(encoding: string = getDefaultEncoding()): Buffer | string {
+ const buf = new Buffer(16);
+ ops.op_node_cipheriv_final(this.#context, this.#cache.cache, buf);
+ return encoding === "buffer" ? buf : buf.toString(encoding);
}
getAuthTag(): Buffer {
@@ -152,30 +151,52 @@ export class Cipheriv extends Transform implements Cipher {
},
): this {
notImplemented("crypto.Cipheriv.prototype.setAAD");
+ return this;
}
setAutoPadding(_autoPadding?: boolean): this {
notImplemented("crypto.Cipheriv.prototype.setAutoPadding");
+ return this;
}
- update(data: BinaryLike): Buffer;
- update(data: string, inputEncoding: Encoding): Buffer;
- update(
- data: ArrayBufferView,
- inputEncoding: undefined,
- outputEncoding: Encoding,
- ): string;
- update(
- data: string,
- inputEncoding: Encoding | undefined,
- outputEncoding: Encoding,
- ): string;
update(
- _data: string | BinaryLike | ArrayBufferView,
+ data: string | Buffer | ArrayBufferView,
+ // TODO(kt3k): Handle inputEncoding
_inputEncoding?: Encoding,
- _outputEncoding?: Encoding,
+ outputEncoding: Encoding = getDefaultEncoding(),
): Buffer | string {
- notImplemented("crypto.Cipheriv.prototype.update");
+ this.#cache.add(data);
+ const input = this.#cache.get();
+ const output = new Buffer(input.length);
+ ops.op_node_cipheriv_encrypt(this.#context, input, output);
+ return outputEncoding === "buffer"
+ ? output
+ : output.toString(outputEncoding);
+ }
+}
+
+/** Caches data and output the chunk of multiple of 16.
+ * Used by CBC, ECB modes of block ciphers */
+class BlockModeCache {
+ constructor() {
+ this.cache = new Uint8Array(0);
+ }
+
+ add(data: Uint8Array) {
+ const cache = this.cache;
+ this.cache = new Uint8Array(cache.length + data.length);
+ this.cache.set(cache);
+ this.cache.set(data, cache.length);
+ }
+
+ get(): Uint8Array {
+ if (this.cache.length < 16) {
+ return null;
+ }
+ const len = Math.floor(this.cache.length / 16) * 16;
+ const out = this.cache.subarray(0, len);
+ this.cache = this.cache.subarray(len);
+ return out;
}
}
@@ -191,8 +212,6 @@ export class Decipheriv extends Transform implements Cipher {
notImplemented("crypto.Decipheriv");
}
- final(): Buffer;
- final(outputEncoding: BufferEncoding): string;
final(_outputEncoding?: string): Buffer | string {
notImplemented("crypto.Decipheriv.prototype.final");
}
@@ -214,18 +233,6 @@ export class Decipheriv extends Transform implements Cipher {
notImplemented("crypto.Decipheriv.prototype.setAutoPadding");
}
- update(data: BinaryLike): Buffer;
- update(data: string, inputEncoding: Encoding): Buffer;
- update(
- data: ArrayBufferView,
- inputEncoding: undefined,
- outputEncoding: Encoding,
- ): string;
- update(
- data: string,
- inputEncoding: Encoding | undefined,
- outputEncoding: Encoding,
- ): string;
update(
_data: string | BinaryLike | ArrayBufferView,
_inputEncoding?: Encoding,