Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 16 commits into from
Mar 14, 2023

Conversation

kt3k
Copy link
Member

@kt3k kt3k commented Mar 9, 2023

This PR implements very basic feature of crypto.createCipheriv with only 'aes-128-cbc' algorithm support.

The behaviors/values of cipheriv.update and cipheriv.final are aligned to Node.js.

(The below works in the same way in this branch and Node for example)

import crypto from "node:crypto";
const cipher = crypto.createCipheriv("aes-128-cbc", new Uint8Array(16), new Uint8Array(16));
console.log(cipher.update(new Uint8Array(55)).toString("hex"));
console.log(cipher.final().toString("hex"));

The following features will be added in the follow up PRs:

  • Decipheriv
  • stream.Transform interface support
  • More algorithms (aes-128-gcm, etc)

closes #17848

@kt3k kt3k force-pushed the node-crypto-create-cipheriv branch from 3a92185 to 28fcb0a Compare March 10, 2023 15:14
@kt3k kt3k force-pushed the node-crypto-create-cipheriv branch from 28fcb0a to a964d5d Compare March 13, 2023 12:08
@kt3k kt3k force-pushed the node-crypto-create-cipheriv branch from a964d5d to a8fabf6 Compare March 13, 2023 12:26
ext/node/crypto/cipher.rs Outdated Show resolved Hide resolved
ext/node/crypto/cipher.rs Outdated Show resolved Hide resolved
ext/node/crypto/cipher.rs Outdated Show resolved Hide resolved
@kt3k kt3k marked this pull request as ready for review March 13, 2023 12:52
Comment on lines 17 to 18
aes = "0.8.2"
cbc = "0.1.2"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider moving these two to root Cargo.toml and using aes.workspace = true here (make sure to update other references in various Cargo.toml files)

Comment on lines +165 to +170
state.resource_table.add(
match cipher::CipherContext::new(algorithm, key, iv) {
Ok(context) => context,
Err(_) => return 0,
},
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Appears that this resource will never be dropped which will cause the same issue as here: #18140

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure this is the correct way to implement it, but Cipheriv.prototype.final method calls Deno.close(this.context) which closes the cipher resource, and the test cases pass without disabling the sanitizer

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah! Makes sense, thanks for clarifying.

use std::cell::RefCell;
use std::rc::Rc;

pub enum Cipher {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe no need to make it pub?

// TODO(kt3k): add more algorithms Aes192Cbc, Aes256Cbc, Aes128ECB, Aes128GCM, etc.
}

pub enum Decipher {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

}

pub struct CipherContext {
pub cipher: Rc<RefCell<Cipher>>,
Copy link
Member

@magurotuna magurotuna Mar 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto (I'm pointing to L24 only)

}

pub struct DecipherContext {
pub decipher: Rc<RefCell<Decipher>>,
Copy link
Member

@magurotuna magurotuna Mar 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto (I'm pointing to L28 only)

}

impl Cipher {
pub fn new(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

})
}

pub fn encrypt(&mut self, input: &[u8], output: &mut [u8]) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Member

@bartlomieju bartlomieju left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment on lines 172 to 181
for (let i = 0; i < input.length; i += 16) {
ops.op_node_cipheriv_encrypt(
this.context,
input.subarray(i, i + 16),
output.subarray(i, i + 16),
);
}
return outputEncoding === "buffer"
? output
: output.toString(outputEncoding);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this could done in Rust, subarrays are much cheaper there.

if (outputEncoding === "buffer") ops.op_node_encrypt_block(...);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the iteration over chunks to the rust side

ext/node/polyfills/internal/crypto/cipher.ts Outdated Show resolved Hide resolved
ext/node/polyfills/internal/crypto/cipher.ts Outdated Show resolved Hide resolved
Comment on lines +180 to +183
let context = match state.resource_table.get::<cipher::CipherContext>(rid) {
Ok(context) => context,
Err(_) => return false,
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should just consume the resource i.e resource_table.take. We don't need to call core.close from JS

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added op_node_cipheriv_final which is called at the end of encryption and consumes the resource. (op_node_cipheriv_encrypt was used both for the last and middle encryptions, but that was probably confusing)

@kt3k kt3k force-pushed the node-crypto-create-cipheriv branch from 298abd1 to 76ed25d Compare March 14, 2023 03:04
Copy link
Member

@littledivy littledivy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@@ -14,7 +14,7 @@ description = "Web Cryptography API implementation for Deno"
path = "lib.rs"

[dependencies]
aes = "0.8.1"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cbc crate is now in workspace scope too. cbc.workspace = true

@kt3k kt3k merged commit e80cc17 into denoland:main Mar 14, 2023
@kt3k
Copy link
Member Author

kt3k commented Mar 14, 2023

Tagged the commit as fix(ext/node): as any missing node.js APIs can be considered as a bug of node compatibility.

@kt3k kt3k deleted the node-crypto-create-cipheriv branch March 14, 2023 07:00
kt3k added a commit that referenced this pull request Mar 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Node compatibility: "Uncaught Error: Not implemented: crypto.Cipheriv"
4 participants