mirror of
https://github.com/denoland/deno.git
synced 2024-12-01 16:51:13 -05:00
fix(ext/node): add crypto.createCipheriv (#18091)
This commit is contained in:
parent
ab2200afd8
commit
9d0161356b
9 changed files with 247 additions and 55 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1244,6 +1244,8 @@ dependencies = [
|
|||
name = "deno_node"
|
||||
version = "0.29.0"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"cbc",
|
||||
"deno_core",
|
||||
"digest 0.10.6",
|
||||
"hex",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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");
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
108
ext/node/crypto/cipher.rs
Normal file
108
ext/node/crypto/cipher.rs
Normal file
|
@ -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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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 {
|
||||
constructor(
|
||||
_cipher: string,
|
||||
_key: CipherKey,
|
||||
_iv: BinaryLike | null,
|
||||
_options?: TransformOptions,
|
||||
) {
|
||||
super();
|
||||
/** CipherContext resource id */
|
||||
#context: number;
|
||||
|
||||
notImplemented("crypto.Cipheriv");
|
||||
/** plaintext data cache */
|
||||
#cache: BlockModeCache;
|
||||
|
||||
constructor(
|
||||
cipher: string,
|
||||
key: CipherKey,
|
||||
iv: BinaryLike | null,
|
||||
options?: TransformOptions,
|
||||
) {
|
||||
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,
|
||||
|
|
Loading…
Reference in a new issue