1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-21 15:04:11 -05:00

fix(ext/node): add crypto.createCipheriv (#18091)

This commit is contained in:
Yoshiya Hinosawa 2023-03-14 15:59:23 +09:00 committed by GitHub
parent 9aa20b3ba7
commit e80cc17dc4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 247 additions and 55 deletions

2
Cargo.lock generated
View file

@ -1244,6 +1244,8 @@ dependencies = [
name = "deno_node"
version = "0.29.0"
dependencies = [
"aes",
"cbc",
"deno_core",
"digest 0.10.6",
"hex",

View file

@ -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"

View file

@ -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");
},
});

View file

@ -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

View file

@ -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
View 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(())
}
}
}
}

View file

@ -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)
}

View file

@ -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(),

View file

@ -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,