-
Notifications
You must be signed in to change notification settings - Fork 29.6k
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
crypto: pass all WebCryptoAPI WPTs #43656
Conversation
Review requested:
|
716caf7
to
5b47b67
Compare
This comment was marked as outdated.
This comment was marked as outdated.
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 drive-by comments. I'm surprised WPT expects zero-sized keys to do something meaningful.
This comment was marked as outdated.
This comment was marked as outdated.
@nodejs/testing how can I increase the test timeout for |
This comment was marked as outdated.
This comment was marked as outdated.
@nodejs/testing please take a look at my previous question #43656 (comment) |
@panva Is the issue with the timeout set in Lines 945 to 949 in 3aec7da
If that's the timeout you are referring to, you can perhaps do something similar to what was done for |
Thank you @Trott, I've just pushed an inclusion of |
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
@Trott the timeout change has definitely helped on most configurations, and github CI, but node-test-commit-freebsd failed the same way two times in a row, the webcrypto job is not even logged |
@Trott Similar to FreeBSD before, the |
@panva I've increased the timeout to 12 minutes. nodejs/build#3001 I didn't do it before resuming the build 5 minutes ago, though, so if that build fails, use Resume Build on it and it should run again with the 12-minute limit instead. |
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 really great work.
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.
❗ support for zero-length secret KeyObject
I am not happy about this, but I also don't see a way around it.
❗ workaround for openssl EVP_PKEY_derive HKDF not playing nice with zero-length IKM
It might be worth considering allowing this only through Web Crypto, not through node crypto. On the other hand, it seems to go through createSecretKey
in both cases, so that might not be pretty.
I really don't like supporting things that OpenSSL does not support, but at least it's somewhat simple here.
@@ -871,7 +871,6 @@ void KeyObjectData::MemoryInfo(MemoryTracker* tracker) const { | |||
} | |||
|
|||
std::shared_ptr<KeyObjectData> KeyObjectData::CreateSecret(ByteSource key) { | |||
CHECK(key); |
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.
We might need to verify that no other uses of KeyObjectData
(and related structures) assume that the stored pointer is not nullptr
. Otherwise, we might end up with aborts/crashes if someone constructs 0-length keys and uses them with other APIs.
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.
Why is a zero length buffer key data treated as a nullptr? I would like to keep the check here but I don't understand the C++ keyobject implementation enough.
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.
It might be because malloc(len)
for len = 0
is allowed to return a nullptr
. The standard only requires the returned pointer to be valid for len
bytes, which nullptr
fulfills when len = 0
.
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 you have a suggestion that would allow us to keep this check?
if (!EVP_PKEY_CTX_hkdf_mode(ctx.get(), | ||
EVP_PKEY_HKDEF_MODE_EXTRACT_AND_EXPAND) || | ||
!EVP_PKEY_CTX_set1_hkdf_salt( | ||
ctx.get(), params.salt.data<unsigned char>(), params.salt.size()) || |
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.
Does a non-zero-length key work with a 0-length salt?
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.
Yes.
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.
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 know it's allowed, I am just wondering if OpenSSL implements it that way. The RFC's statement if not provided
could be interpreted as "if the user does not call EVP_PKEY_CTX_set1_hkdf_salt
".
Now I am confused as to whether an empty salt is the same as passing no salt in our current implementation.
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.
Our implementation does not allow to omit passing salt, we require the argument, albeit we allow it to be zero-length.
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.
Does a non-zero-length key work with a 0-length salt?
that's also covered by a passing wpt btw
My thought process was exactly the same. I know we're missing 0-length tests for every single operation we accept a SecretKeyObject in.
OpenSSL does support it, but this fix did not get extended to the particular API we use. I would happily use the new API but it's only available in 3 and we still have to support 1. |
params.key->GetSymmetricKeySize())) { | ||
return false; | ||
} | ||
} else { |
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.
Could you add a brief comment to the beginning of the else
branch to explain why it exists for future readers?
If we accept that this branch must exist (until we fully drop OpenSSL 1.1.1), then we should probably either
- remove the other branch that uses
EVP_PKEY_HKDEF_MODE_EXTRACT_AND_EXPAND
and pass thekey
toHMAC
in this new branch (that should be equivalent as far as I can tell), or - add a
TODO
comment saying to remove this branch once we have dropped OpenSSL 1.1.1.
The first option would give us improved coverage because all HMAC operations would go through 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.
Could you add a brief comment to the beginning of the
else
branch to explain why it exists for future readers?
Sure.
If we accept that this branch must exist (until we fully drop OpenSSL 1.1.1), then we should probably either
- remove the other branch that uses
EVP_PKEY_HKDEF_MODE_EXTRACT_AND_EXPAND
and pass thekey
toHMAC
in this new branch (that should be equivalent as far as I can tell)
I think that can be done as a follow-up, I am not up for such challenge myself.
- add a
TODO
comment saying to remove this branch once we have dropped OpenSSL 1.1.1.
You mean to change the implementation to use EVP_KDF-HKDF
when 1.1.1 is dropped?
@@ -459,6 +459,12 @@ class WPTRunner { | |||
); | |||
this.inProgress.delete(testFileName); | |||
}); | |||
|
|||
await new Promise((resolve) => { |
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 want to reject
when the worker
emits an error
event, you could also use
await events.once(worker, 'exit')
.
That being said, it looks like the caller never awaits the returned Promise
(e.g., in test-webcrypto.js
). What is the point in creating the Promise
here if the caller does not await 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.
The point is to process the queue one by one. This has helped stabilize CI, especially the keygen tests that easily bogged the hosts down minutes at a time.
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 want to
reject
when theworker
emits anerror
event, you could also useawait events.once(worker, 'exit')
.
Rejecting would skip running the rest of the queue. So maybe this?
await Promise.allSettled([events.once(worker, 'exit')])
or
await events.once(worker, 'exit').catch(() => {})
Now on the matter of eventually landing this. I propose to
commit-queue-rebase
WDYT? |
I will pick this up again and break down to individual PRs after #43455 when every new WPT fixing PR will visibly show how many tests it fixes. |
I'm not expecting to land this PR as-is but i'm seeking feedback from @nodejs/crypto namely @tniessen and @jasnell on the end state which this currently reflects.
Overall the question is - is passing all relevant WebCryptoAPI WPT worth the ❗ items?
The C++ linter fails due to variable-length array use and I'd like suggestions for the C++ changes anyway.
I would still break this down into more individual commits. Or PRs if that's what the ask will be.