-
Notifications
You must be signed in to change notification settings - Fork 359
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(crypto): Initial support for SubtleCrypto #698
base: main
Are you sure you want to change the base?
Conversation
Nice!!!
|
Super nice! Yeah, async needs to use |
@richarddavison Not in this case I think, spawn_exit is only for running background tasks. Here we want to asyncify a blocking task, so you have to use the tokio thread pool for that. Same as when we do file operations. |
There is still debate as to which parts of the implementation should be asynchronous, but for the time being, I think it will be meaningful to simply add "async" to a function in order to conform to the specifications. For example, the following BLOB functions appear to be marked as "async" to comply with the specification, even though no asynchronous operations are actually being invoked: llrt/modules/llrt_http/src/blob.rs Lines 91 to 102 in aa742eb
|
I know it's necessary, but I'm not sure how to implement it yet. For example, when you run the following code on bun, generateKey is returned as a CryptoKey type, but when you look at it in console.log, it is an ordinary object. // subtleGenerateKey.js
async function exportCryptoKey(key) {
const exported = await crypto.subtle.exportKey("raw", key);
return new Uint8Array(exported);
}
const key = await crypto.subtle
.generateKey(
{
name: "AES-GCM",
length: 256,
},
true,
["encrypt", "decrypt"],
);
console.log(key);
console.log(await exportCryptoKey(key));
console.log(await exportCryptoKey(key));
Nevertheless, since the same value is returned even if you run exportKey multiple times, this object must have hidden information (seed). https://w3c.github.io/webcrypto/#cryptokey-interface-internal-slots |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some comments
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really need all these deps? I know SHA1, HMAC is available in ring and used already in crypto module
use sha1::Sha1; | ||
use sha2::{Digest, Sha256, Sha384, Sha512}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use from ring? ring::digest
hmac = "0.12" | ||
sha1 = "0.10" | ||
sha2 = "0.10" | ||
aes = "0.8" | ||
aes-gcm = "0.10" | ||
cbc = { version = "0.1", features = ["std"] } | ||
num-traits = "0.2" | ||
rsa = { version = "0.9", features = ["std", "sha2"], default-features = false } | ||
p256 = { version = "0.13", features = ["ecdh"] } | ||
p384 = "0.13" | ||
ctr = "0.9" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A lot of these are already in the ring crate. Use them instead of pulling in separate deps for each signature, hash etc. The only thing that ring doesn't have (yet) is AES encryption AND decryption so we need to keep ctr
and cbc
and cgm
crates until its supported:
briansmith/ring#588 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm... I'm thinking of replacing some of the ring parts, but even though the names are the same, the methods are different, so it's not going to work.
It looks like I'll need to spend some time on this...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should basically be equivalent:
hmac = ring::hmac
sha1 = ring::digest
sha2 = ring::digest
aes = keep as ring only has encryption and not decryption
aes-gcm = keep as ring only has encryption and not decryption
cbc = keep as ring only has encryption and not decryption
rsa = ring::rsa
p256 = ring:.agreement
p384 = ring:.agreement
ctr = keep as ring only has encryption and not decryption
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Take RSA for example.
// Cargo.toml
...
#rsa = { version = "0.9", features = ["std", "sha2"], default-features = false }
Then, if you replace use rsa::...
with use ring::rsa::...
, you will get an error saying that the import cannot be resolved.
If the command doesn't exist, do we need to look for an alias? If it still doesn't exist, do we need to implement it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For RSA I don't think ring can decrypt. So we probably need to keep it as well. Rings API is totally different then what you already have so you have to change it when switching to ring (where applicable)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As for the other components, they are completely different from those built into the ring, so it would be difficult to replace them as is.
Even if it were possible, I would like to consider it in a separate PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure but I'll rather not merge duplicate dependencies. We already use ring for sha hash and hmac. And these can be used as well:
p256 = ring:.agreement
p384 = ring:.agreement
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you don't use the p256/p384 crates I don't understand how the implementation below replaces it. Are these features implemented in ring?
modules/llrt_crypto/src/subtle/derive_bits.rs:
- p256::SecretKey::from_pkcs8_der()
- p256::elliptic_curve::ecdh::diffie_hellman()
- p384::SecretKey::from_pkcs8_der()
- p384::elliptic_curve::ecdh::diffie_hellman()
@richarddavison and @Sytten , Any good ideas on how to implement this? Is it possible to retain values in LLRT that are not displayed in console.log? |
I would create a class to wrap the vec at the very least so the user cant assume it is an array. Then we can start implementing the props the spec requires. |
Yes, create a Class for CryptoKey and CryptoKeyPair and keep hidden fields in rust only. Then expose only getters to what you need to provide as read only: #[rquickjs::class]
#[derive(rquickjs::JsLifetime)]
struct CryptoKey<'js>{
algorithm: Object<'js>,
usages: Array<'js>,
...
}
impl<'js> CryptoKey<'js>{
#[qjs(get)]
pub fn algorithm(&self) -> Object<'js> {
self.algorithm.clone()
}
} |
hmac = "0.12" | ||
sha1 = "0.10" | ||
sha2 = "0.10" | ||
aes = "0.8" | ||
aes-gcm = "0.10" | ||
cbc = { version = "0.1", features = ["std"] } | ||
num-traits = "0.2" | ||
rsa = { version = "0.9", features = ["std", "sha2"], default-features = false } | ||
p256 = { version = "0.13", features = ["ecdh"] } | ||
p384 = "0.13" | ||
ctr = "0.9" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure but I'll rather not merge duplicate dependencies. We already use ring for sha hash and hmac. And these can be used as well:
p256 = ring:.agreement
p384 = ring:.agreement
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fantastic work so far! 🥇
algorithm | ||
.get_optional::<_, String>("name")? | ||
.ok_or_else(|| { | ||
Exception::throw_message(&ctx, "Missing algorithm name should cause TypeError") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is sounds like a comment. I think we can return simply: algorithm 'name' property required
. Also if its a type error, it should be Exception::throw_type
for TypeErrors
let obj = Object::new(ctx)?; | ||
obj.set("privateKey", private)?; | ||
obj.set("publicKey", public)?; | ||
Ok(obj.into()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be a class? CryptoKeyPair? Or is it fine with an object?
Issue # (if available)
Closed #184
Description of changes
With this PR, we're making a small step forward with SubtleCrypto.
We're not yet fully web standards compliant, but we wanted to share our progress so far.
Checklist
tests/unit
and/or in Rust for my feature if neededmake fix
to format JS and apply Clippy auto fixesmake check
types/
directoryBy submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.