mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 09:31:22 -05:00
fix(ext/node): add createDecipheriv (#18245)
This commit is contained in:
parent
44553aa09e
commit
5223f3d9b6
5 changed files with 225 additions and 27 deletions
|
@ -72,3 +72,41 @@ Deno.test({
|
|||
assertEquals(cipher.final("hex"), "e11901dde4a2f99fe4efc707e48c6aed");
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "createCipheriv - input encoding",
|
||||
fn() {
|
||||
const cipher = crypto.createCipheriv(
|
||||
"aes-128-cbc",
|
||||
new Uint8Array(16),
|
||||
new Uint8Array(16),
|
||||
);
|
||||
assertEquals(
|
||||
cipher.update("hello, world! hello, world!", "utf-8", "hex"),
|
||||
"ca7df4d74f51b77a7440ead38343ab0f",
|
||||
);
|
||||
assertEquals(cipher.final("hex"), "d0da733dec1fa61125c80a6f97e6166e");
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "createDecipheriv - basic",
|
||||
fn() {
|
||||
const decipher = crypto.createDecipheriv(
|
||||
"aes-128-cbc",
|
||||
new Uint8Array(16),
|
||||
new Uint8Array(16),
|
||||
);
|
||||
assertEquals(
|
||||
decipher.update(
|
||||
"66e94bd4ef8a2c3b884cfa59ca342b2ef795bd4a52e29ed713d313fa20e98dbca10cf66d0fddf3405370b4bf8df5bfb347c78395e0d8ae2194da0a90abc9888a94ee48f6c78fcd518a941c3896102cb1e11901dde4a2f99fe4efc707e48c6aed",
|
||||
"hex",
|
||||
),
|
||||
Buffer.alloc(80),
|
||||
);
|
||||
assertEquals(
|
||||
decipher.final(),
|
||||
Buffer.alloc(10), // Checks the padding
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use aes::cipher::block_padding::Pkcs7;
|
||||
use aes::cipher::BlockDecryptMut;
|
||||
use aes::cipher::BlockEncryptMut;
|
||||
use aes::cipher::KeyIvInit;
|
||||
use deno_core::error::type_error;
|
||||
|
@ -17,8 +18,8 @@ enum Cipher {
|
|||
}
|
||||
|
||||
enum Decipher {
|
||||
// TODO(kt3k): implement Deciphers
|
||||
// Aes128Cbc(Box<cbc::Decryptor<aes::Aes128>>),
|
||||
Aes128Cbc(Box<cbc::Decryptor<aes::Aes128>>),
|
||||
// TODO(kt3k): add more algorithms Aes192Cbc, Aes256Cbc, Aes128ECB, Aes128GCM, etc.
|
||||
}
|
||||
|
||||
pub struct CipherContext {
|
||||
|
@ -26,7 +27,7 @@ pub struct CipherContext {
|
|||
}
|
||||
|
||||
pub struct DecipherContext {
|
||||
_decipher: Rc<RefCell<Decipher>>,
|
||||
decipher: Rc<RefCell<Decipher>>,
|
||||
}
|
||||
|
||||
impl CipherContext {
|
||||
|
@ -52,6 +53,29 @@ impl CipherContext {
|
|||
}
|
||||
}
|
||||
|
||||
impl DecipherContext {
|
||||
pub fn new(algorithm: &str, key: &[u8], iv: &[u8]) -> Result<Self, AnyError> {
|
||||
Ok(Self {
|
||||
decipher: Rc::new(RefCell::new(Decipher::new(algorithm, key, iv)?)),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn decrypt(&self, input: &[u8], output: &mut [u8]) {
|
||||
self.decipher.borrow_mut().decrypt(input, output);
|
||||
}
|
||||
|
||||
pub fn r#final(
|
||||
self,
|
||||
input: &[u8],
|
||||
output: &mut [u8],
|
||||
) -> Result<(), AnyError> {
|
||||
Rc::try_unwrap(self.decipher)
|
||||
.map_err(|_| type_error("Decipher context is already in use"))?
|
||||
.into_inner()
|
||||
.r#final(input, output)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resource for CipherContext {
|
||||
fn name(&self) -> Cow<str> {
|
||||
"cryptoCipher".into()
|
||||
|
@ -106,3 +130,46 @@ impl Cipher {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decipher {
|
||||
fn new(
|
||||
algorithm_name: &str,
|
||||
key: &[u8],
|
||||
iv: &[u8],
|
||||
) -> Result<Self, AnyError> {
|
||||
use Decipher::*;
|
||||
Ok(match algorithm_name {
|
||||
"aes-128-cbc" => {
|
||||
Aes128Cbc(Box::new(cbc::Decryptor::new(key.into(), iv.into())))
|
||||
}
|
||||
_ => return Err(type_error(format!("Unknown cipher {algorithm_name}"))),
|
||||
})
|
||||
}
|
||||
|
||||
/// decrypt decrypts the data in the middle of the input.
|
||||
fn decrypt(&mut self, input: &[u8], output: &mut [u8]) {
|
||||
use Decipher::*;
|
||||
match self {
|
||||
Aes128Cbc(decryptor) => {
|
||||
assert!(input.len() % 16 == 0);
|
||||
for (input, output) in input.chunks(16).zip(output.chunks_mut(16)) {
|
||||
decryptor.decrypt_block_b2b_mut(input.into(), output.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// r#final decrypts the last block of the input data.
|
||||
fn r#final(self, input: &[u8], output: &mut [u8]) -> Result<(), AnyError> {
|
||||
assert!(input.len() == 16);
|
||||
use Decipher::*;
|
||||
match self {
|
||||
Aes128Cbc(decryptor) => {
|
||||
let _ = (*decryptor)
|
||||
.decrypt_padded_b2b_mut::<Pkcs7>(input, output)
|
||||
.map_err(|_| type_error("Cannot unpad the input data"))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -197,3 +197,46 @@ pub fn op_node_cipheriv_final(
|
|||
.map_err(|_| type_error("Cipher context is already in use"))?;
|
||||
context.r#final(input, output)
|
||||
}
|
||||
|
||||
#[op(fast)]
|
||||
pub fn op_node_create_decipheriv(
|
||||
state: &mut OpState,
|
||||
algorithm: &str,
|
||||
key: &[u8],
|
||||
iv: &[u8],
|
||||
) -> u32 {
|
||||
state.resource_table.add(
|
||||
match cipher::DecipherContext::new(algorithm, key, iv) {
|
||||
Ok(context) => context,
|
||||
Err(_) => return 0,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[op(fast)]
|
||||
pub fn op_node_decipheriv_decrypt(
|
||||
state: &mut OpState,
|
||||
rid: u32,
|
||||
input: &[u8],
|
||||
output: &mut [u8],
|
||||
) -> bool {
|
||||
let context = match state.resource_table.get::<cipher::DecipherContext>(rid) {
|
||||
Ok(context) => context,
|
||||
Err(_) => return false,
|
||||
};
|
||||
context.decrypt(input, output);
|
||||
true
|
||||
}
|
||||
|
||||
#[op]
|
||||
pub fn op_node_decipheriv_final(
|
||||
state: &mut OpState,
|
||||
rid: u32,
|
||||
input: &[u8],
|
||||
output: &mut [u8],
|
||||
) -> Result<(), AnyError> {
|
||||
let context = state.resource_table.take::<cipher::DecipherContext>(rid)?;
|
||||
let context = Rc::try_unwrap(context)
|
||||
.map_err(|_| type_error("Cipher context is already in use"))?;
|
||||
context.r#final(input, output)
|
||||
}
|
||||
|
|
|
@ -96,10 +96,13 @@ fn op_node_build_os() -> String {
|
|||
deno_core::extension!(deno_node,
|
||||
deps = [ deno_io, deno_fs ],
|
||||
ops = [
|
||||
crypto::op_node_create_decipheriv,
|
||||
crypto::op_node_cipheriv_encrypt,
|
||||
crypto::op_node_cipheriv_final,
|
||||
crypto::op_node_create_cipheriv,
|
||||
crypto::op_node_create_hash,
|
||||
crypto::op_node_decipheriv_decrypt,
|
||||
crypto::op_node_decipheriv_final,
|
||||
crypto::op_node_hash_update,
|
||||
crypto::op_node_hash_update_str,
|
||||
crypto::op_node_hash_digest,
|
||||
|
|
|
@ -130,7 +130,7 @@ export class Cipheriv extends Transform implements Cipher {
|
|||
options?: TransformOptions,
|
||||
) {
|
||||
super(options);
|
||||
this.#cache = new BlockModeCache();
|
||||
this.#cache = new BlockModeCache(false);
|
||||
this.#context = ops.op_node_create_cipheriv(cipher, key, iv);
|
||||
}
|
||||
|
||||
|
@ -161,14 +161,23 @@ export class Cipheriv extends Transform implements Cipher {
|
|||
|
||||
update(
|
||||
data: string | Buffer | ArrayBufferView,
|
||||
// TODO(kt3k): Handle inputEncoding
|
||||
_inputEncoding?: Encoding,
|
||||
inputEncoding?: Encoding,
|
||||
outputEncoding: Encoding = getDefaultEncoding(),
|
||||
): Buffer | string {
|
||||
this.#cache.add(data);
|
||||
// TODO(kt3k): throw ERR_INVALID_ARG_TYPE if data is not string, Buffer, or ArrayBufferView
|
||||
if (typeof data === "string" && typeof inputEncoding === "string") {
|
||||
this.#cache.add(Buffer.from(data, inputEncoding));
|
||||
} else {
|
||||
this.#cache.add(data);
|
||||
}
|
||||
const input = this.#cache.get();
|
||||
const output = new Buffer(input.length);
|
||||
ops.op_node_cipheriv_encrypt(this.#context, input, output);
|
||||
let output;
|
||||
if (input === null) {
|
||||
output = Buffer.alloc(0);
|
||||
} else {
|
||||
output = Buffer.allocUnsafe(input.length);
|
||||
ops.op_node_cipheriv_encrypt(this.#context, input, output);
|
||||
}
|
||||
return outputEncoding === "buffer"
|
||||
? output
|
||||
: output.toString(outputEncoding);
|
||||
|
@ -178,8 +187,13 @@ export class Cipheriv extends Transform implements Cipher {
|
|||
/** Caches data and output the chunk of multiple of 16.
|
||||
* Used by CBC, ECB modes of block ciphers */
|
||||
class BlockModeCache {
|
||||
constructor() {
|
||||
cache: Uint8Array;
|
||||
// The last chunk can be padded when decrypting.
|
||||
#lastChunkIsNonZero: boolean;
|
||||
|
||||
constructor(lastChunkIsNotZero = false) {
|
||||
this.cache = new Uint8Array(0);
|
||||
this.#lastChunkIsNonZero = lastChunkIsNotZero;
|
||||
}
|
||||
|
||||
add(data: Uint8Array) {
|
||||
|
@ -189,11 +203,19 @@ class BlockModeCache {
|
|||
this.cache.set(data, cache.length);
|
||||
}
|
||||
|
||||
get(): Uint8Array {
|
||||
if (this.cache.length < 16) {
|
||||
/** Gets the chunk of the length of largest multiple of 16.
|
||||
* Used for preparing data for encryption/decryption */
|
||||
get(): Uint8Array | null {
|
||||
let len = this.cache.length;
|
||||
if (this.#lastChunkIsNonZero) {
|
||||
// Reduces the available chunk length by 1 to keep the last chunk
|
||||
len -= 1;
|
||||
}
|
||||
if (len < 16) {
|
||||
return null;
|
||||
}
|
||||
const len = Math.floor(this.cache.length / 16) * 16;
|
||||
|
||||
len = Math.floor(len / 16) * 16;
|
||||
const out = this.cache.subarray(0, len);
|
||||
this.cache = this.cache.subarray(len);
|
||||
return out;
|
||||
|
@ -201,19 +223,28 @@ class BlockModeCache {
|
|||
}
|
||||
|
||||
export class Decipheriv extends Transform implements Cipher {
|
||||
constructor(
|
||||
_cipher: string,
|
||||
_key: CipherKey,
|
||||
_iv: BinaryLike | null,
|
||||
_options?: TransformOptions,
|
||||
) {
|
||||
super();
|
||||
/** DecipherContext resource id */
|
||||
#context: number;
|
||||
|
||||
notImplemented("crypto.Decipheriv");
|
||||
/** ciphertext data cache */
|
||||
#cache: BlockModeCache;
|
||||
|
||||
constructor(
|
||||
cipher: string,
|
||||
key: CipherKey,
|
||||
iv: BinaryLike | null,
|
||||
options?: TransformOptions,
|
||||
) {
|
||||
super(options);
|
||||
this.#cache = new BlockModeCache(true);
|
||||
this.#context = ops.op_node_create_decipheriv(cipher, key, iv);
|
||||
}
|
||||
|
||||
final(_outputEncoding?: string): Buffer | string {
|
||||
notImplemented("crypto.Decipheriv.prototype.final");
|
||||
final(encoding: string = getDefaultEncoding()): Buffer | string {
|
||||
let buf = new Buffer(16);
|
||||
ops.op_node_decipheriv_final(this.#context, this.#cache.cache, buf);
|
||||
buf = buf.subarray(0, 16 - buf.at(-1)); // Padded in Pkcs7 mode
|
||||
return encoding === "buffer" ? buf : buf.toString(encoding);
|
||||
}
|
||||
|
||||
setAAD(
|
||||
|
@ -234,11 +265,27 @@ export class Decipheriv extends Transform implements Cipher {
|
|||
}
|
||||
|
||||
update(
|
||||
_data: string | BinaryLike | ArrayBufferView,
|
||||
_inputEncoding?: Encoding,
|
||||
_outputEncoding?: Encoding,
|
||||
data: string | Buffer | ArrayBufferView,
|
||||
inputEncoding?: Encoding,
|
||||
outputEncoding: Encoding = getDefaultEncoding(),
|
||||
): Buffer | string {
|
||||
notImplemented("crypto.Decipheriv.prototype.update");
|
||||
// TODO(kt3k): throw ERR_INVALID_ARG_TYPE if data is not string, Buffer, or ArrayBufferView
|
||||
if (typeof data === "string" && typeof inputEncoding === "string") {
|
||||
this.#cache.add(Buffer.from(data, inputEncoding));
|
||||
} else {
|
||||
this.#cache.add(data);
|
||||
}
|
||||
const input = this.#cache.get();
|
||||
let output;
|
||||
if (input === null) {
|
||||
output = Buffer.alloc(0);
|
||||
} else {
|
||||
output = new Buffer(input.length);
|
||||
ops.op_node_decipheriv_decrypt(this.#context, input, output);
|
||||
}
|
||||
return outputEncoding === "buffer"
|
||||
? output
|
||||
: output.toString(outputEncoding);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue