mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 17:34:47 -05:00
fix(ext/node): add crypto.createCipheriv (#18091)
This commit is contained in:
parent
9aa20b3ba7
commit
e80cc17dc4
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"
|
name = "deno_node"
|
||||||
version = "0.29.0"
|
version = "0.29.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"aes",
|
||||||
|
"cbc",
|
||||||
"deno_core",
|
"deno_core",
|
||||||
"digest 0.10.6",
|
"digest 0.10.6",
|
||||||
"hex",
|
"hex",
|
||||||
|
|
|
@ -77,6 +77,7 @@ deno_websocket = { version = "0.97.0", path = "./ext/websocket" }
|
||||||
deno_webstorage = { version = "0.87.0", path = "./ext/webstorage" }
|
deno_webstorage = { version = "0.87.0", path = "./ext/webstorage" }
|
||||||
deno_napi = { version = "0.22.0", path = "./ext/napi" }
|
deno_napi = { version = "0.22.0", path = "./ext/napi" }
|
||||||
|
|
||||||
|
aes = "=0.8.2"
|
||||||
anyhow = "1.0.57"
|
anyhow = "1.0.57"
|
||||||
async-trait = "0.1.51"
|
async-trait = "0.1.51"
|
||||||
atty = "=0.2.14"
|
atty = "=0.2.14"
|
||||||
|
@ -84,6 +85,7 @@ base64 = "=0.13.1"
|
||||||
bencher = "0.1"
|
bencher = "0.1"
|
||||||
bytes = "1.4.0"
|
bytes = "1.4.0"
|
||||||
cache_control = "=0.2.0"
|
cache_control = "=0.2.0"
|
||||||
|
cbc = { version = "=0.1.2", features = ["alloc"] }
|
||||||
console_static_text = "=0.7.1"
|
console_static_text = "=0.7.1"
|
||||||
data-url = "=0.2.0"
|
data-url = "=0.2.0"
|
||||||
dlopen = "0.1.8"
|
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"
|
path = "lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
aes = "0.8.1"
|
aes.workspace = true
|
||||||
aes-gcm = "0.10"
|
aes-gcm = "0.10"
|
||||||
aes-kw = { version = "0.2.1", features = ["alloc"] }
|
aes-kw = { version = "0.2.1", features = ["alloc"] }
|
||||||
base64.workspace = true
|
base64.workspace = true
|
||||||
block-modes = "0.9.1"
|
block-modes = "0.9.1"
|
||||||
cbc = { version = "0.1.2", features = ["alloc"] }
|
cbc.workspace = true
|
||||||
const-oid = "0.9.0"
|
const-oid = "0.9.0"
|
||||||
ctr = "0.9.1"
|
ctr = "0.9.1"
|
||||||
# https://github.com/dalek-cryptography/curve25519-dalek/pull/397
|
# https://github.com/dalek-cryptography/curve25519-dalek/pull/397
|
||||||
|
|
|
@ -14,6 +14,8 @@ description = "Node compatibility for Deno"
|
||||||
path = "lib.rs"
|
path = "lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
aes.workspace = true
|
||||||
|
cbc.workspace = true
|
||||||
deno_core.workspace = true
|
deno_core.workspace = true
|
||||||
digest = { version = "0.10.5", features = ["core-api", "std"] }
|
digest = { version = "0.10.5", features = ["core-api", "std"] }
|
||||||
hex = "0.4.3"
|
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::RsaPrivateKey;
|
||||||
use rsa::RsaPublicKey;
|
use rsa::RsaPublicKey;
|
||||||
|
|
||||||
|
mod cipher;
|
||||||
mod digest;
|
mod digest;
|
||||||
|
|
||||||
#[op(fast)]
|
#[op(fast)]
|
||||||
|
@ -153,3 +154,46 @@ pub fn op_node_public_encrypt(
|
||||||
_ => Err(type_error("Unknown padding")),
|
_ => 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 {
|
fn ops_polyfill(ext: &mut ExtensionBuilder) -> &mut ExtensionBuilder {
|
||||||
ext.ops(vec![
|
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_create_hash::decl(),
|
||||||
crypto::op_node_hash_update::decl(),
|
crypto::op_node_hash_update::decl(),
|
||||||
crypto::op_node_hash_update_str::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 { notImplemented } from "ext:deno_node/_utils.ts";
|
||||||
import type { TransformOptions } from "ext:deno_node/_stream.d.ts";
|
import type { TransformOptions } from "ext:deno_node/_stream.d.ts";
|
||||||
import { Transform } from "ext:deno_node/_stream.mjs";
|
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 { BufferEncoding } from "ext:deno_node/_global.d.ts";
|
||||||
import type {
|
import type {
|
||||||
BinaryLike,
|
BinaryLike,
|
||||||
Encoding,
|
Encoding,
|
||||||
} from "ext:deno_node/internal/crypto/types.ts";
|
} from "ext:deno_node/internal/crypto/types.ts";
|
||||||
|
import { getDefaultEncoding } from "ext:deno_node/internal/crypto/util.ts";
|
||||||
|
|
||||||
const { ops } = globalThis.__bootstrap.core;
|
const { ops } = globalThis.__bootstrap.core;
|
||||||
|
|
||||||
|
@ -42,21 +43,13 @@ export interface CipherOCBOptions extends TransformOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Cipher extends ReturnType<typeof Transform> {
|
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(
|
update(
|
||||||
data: string,
|
data: string,
|
||||||
inputEncoding: Encoding | undefined,
|
inputEncoding?: Encoding,
|
||||||
outputEncoding: Encoding,
|
outputEncoding?: Encoding,
|
||||||
): string;
|
): string;
|
||||||
|
|
||||||
final(): Buffer;
|
final(outputEncoding?: BufferEncoding): string;
|
||||||
final(outputEncoding: BufferEncoding): string;
|
|
||||||
|
|
||||||
setAutoPadding(autoPadding?: boolean): this;
|
setAutoPadding(autoPadding?: boolean): this;
|
||||||
}
|
}
|
||||||
|
@ -124,21 +117,27 @@ export interface DecipherOCB extends Decipher {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Cipheriv extends Transform implements Cipher {
|
export class Cipheriv extends Transform implements Cipher {
|
||||||
constructor(
|
/** CipherContext resource id */
|
||||||
_cipher: string,
|
#context: number;
|
||||||
_key: CipherKey,
|
|
||||||
_iv: BinaryLike | null,
|
|
||||||
_options?: TransformOptions,
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
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(encoding: string = getDefaultEncoding()): Buffer | string {
|
||||||
final(outputEncoding: BufferEncoding): string;
|
const buf = new Buffer(16);
|
||||||
final(_outputEncoding?: string): Buffer | string {
|
ops.op_node_cipheriv_final(this.#context, this.#cache.cache, buf);
|
||||||
notImplemented("crypto.Cipheriv.prototype.final");
|
return encoding === "buffer" ? buf : buf.toString(encoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
getAuthTag(): Buffer {
|
getAuthTag(): Buffer {
|
||||||
|
@ -152,30 +151,52 @@ export class Cipheriv extends Transform implements Cipher {
|
||||||
},
|
},
|
||||||
): this {
|
): this {
|
||||||
notImplemented("crypto.Cipheriv.prototype.setAAD");
|
notImplemented("crypto.Cipheriv.prototype.setAAD");
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setAutoPadding(_autoPadding?: boolean): this {
|
setAutoPadding(_autoPadding?: boolean): this {
|
||||||
notImplemented("crypto.Cipheriv.prototype.setAutoPadding");
|
notImplemented("crypto.Cipheriv.prototype.setAutoPadding");
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
update(data: BinaryLike): Buffer;
|
|
||||||
update(data: string, inputEncoding: Encoding): Buffer;
|
|
||||||
update(
|
update(
|
||||||
data: ArrayBufferView,
|
data: string | Buffer | ArrayBufferView,
|
||||||
inputEncoding: undefined,
|
// TODO(kt3k): Handle inputEncoding
|
||||||
outputEncoding: Encoding,
|
|
||||||
): string;
|
|
||||||
update(
|
|
||||||
data: string,
|
|
||||||
inputEncoding: Encoding | undefined,
|
|
||||||
outputEncoding: Encoding,
|
|
||||||
): string;
|
|
||||||
update(
|
|
||||||
_data: string | BinaryLike | ArrayBufferView,
|
|
||||||
_inputEncoding?: Encoding,
|
_inputEncoding?: Encoding,
|
||||||
_outputEncoding?: Encoding,
|
outputEncoding: Encoding = getDefaultEncoding(),
|
||||||
): Buffer | string {
|
): 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");
|
notImplemented("crypto.Decipheriv");
|
||||||
}
|
}
|
||||||
|
|
||||||
final(): Buffer;
|
|
||||||
final(outputEncoding: BufferEncoding): string;
|
|
||||||
final(_outputEncoding?: string): Buffer | string {
|
final(_outputEncoding?: string): Buffer | string {
|
||||||
notImplemented("crypto.Decipheriv.prototype.final");
|
notImplemented("crypto.Decipheriv.prototype.final");
|
||||||
}
|
}
|
||||||
|
@ -214,18 +233,6 @@ export class Decipheriv extends Transform implements Cipher {
|
||||||
notImplemented("crypto.Decipheriv.prototype.setAutoPadding");
|
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(
|
update(
|
||||||
_data: string | BinaryLike | ArrayBufferView,
|
_data: string | BinaryLike | ArrayBufferView,
|
||||||
_inputEncoding?: Encoding,
|
_inputEncoding?: Encoding,
|
||||||
|
|
Loading…
Add table
Reference in a new issue