summaryrefslogtreecommitdiff
path: root/ext/node
diff options
context:
space:
mode:
Diffstat (limited to 'ext/node')
-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
5 files changed, 216 insertions, 52 deletions
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,