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

crypto: pass all WebCryptoAPI WPTs #43656

Closed
wants to merge 17 commits into from
Closed

Conversation

panva
Copy link
Member

@panva panva commented Jul 2, 2022

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?

  • update WebCryptoAPI WPT
  • OperationError for generateKey() AES key length validation
  • NotSupportedError for generateKey() and importKey() EC key namedCurve validation
  • OperationError for HKDF deriveBits() length being null
  • ❗ support for zero-length secret KeyObject
  • OperationError for PBKDF2 deriveBits() iterations being 0
  • OperationError for PBKDF2 deriveBits() length being null
  • OperationError for when async crypto jobs fail for operation specific reasons
  • ❗ workaround for openssl EVP_PKEY_derive HKDF not playing nice with zero-length IKM
  • WPT global.location polyfill so that wpt/common/subset-tests.js execute

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.

@panva panva added crypto Issues and PRs related to the crypto subsystem. experimental Issues and PRs related to experimental features. webcrypto labels Jul 2, 2022
@panva panva requested review from jasnell and tniessen July 2, 2022 16:57
@nodejs-github-bot
Copy link
Collaborator

Review requested:

  • @nodejs/crypto

@nodejs-github-bot nodejs-github-bot added lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. labels Jul 2, 2022
@panva panva force-pushed the webcrypto-wpt branch 3 times, most recently from 716caf7 to 5b47b67 Compare July 2, 2022 17:40
@panva panva added request-ci Add this label to start a Jenkins CI on a PR. labels Jul 2, 2022
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Jul 2, 2022
@nodejs-github-bot

This comment was marked as outdated.

Copy link
Member

@bnoordhuis bnoordhuis left a 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.

src/crypto/crypto_hkdf.cc Outdated Show resolved Hide resolved
src/crypto/crypto_hkdf.cc Outdated Show resolved Hide resolved
@nodejs-github-bot

This comment was marked as outdated.

@panva
Copy link
Member Author

panva commented Jul 3, 2022

@nodejs/testing how can I increase the test timeout for test.wpt/test-webcrypto? The new vectors and the fact we execute them all seems to have pushed it over the 2m edge on some configurations as well as github CI.

@nodejs-github-bot

This comment was marked as outdated.

src/crypto/crypto_hkdf.cc Outdated Show resolved Hide resolved
src/crypto/crypto_hkdf.cc Outdated Show resolved Hide resolved
@panva panva added the review wanted PRs that need reviews. label Jul 12, 2022
@panva
Copy link
Member Author

panva commented Jul 12, 2022

@nodejs/testing please take a look at my previous question #43656 (comment)

@Trott
Copy link
Member

Trott commented Jul 12, 2022

@panva Is the issue with the timeout set in tools/test.py or is this some other timeout?

node/tools/test.py

Lines 945 to 949 in 3aec7da

def GetTimeout(self, mode, section=''):
timeout = self.timeout * TIMEOUT_SCALEFACTOR[ARCH_GUESS or 'ia32'][mode]
if section == 'pummel' or section == 'benchmark':
timeout = timeout * 6
return timeout

If that's the timeout you are referring to, you can perhaps do something similar to what was done for pummel and benchmark tests.

@panva
Copy link
Member Author

panva commented Jul 12, 2022

@panva Is the issue with the timeout set in tools/test.py or is this some other timeout?

node/tools/test.py

Lines 945 to 949 in 3aec7da

def GetTimeout(self, mode, section=''):
timeout = self.timeout * TIMEOUT_SCALEFACTOR[ARCH_GUESS or 'ia32'][mode]
if section == 'pummel' or section == 'benchmark':
timeout = timeout * 6
return timeout

If that's the timeout you are referring to, you can perhaps do something similar to what was done for pummel and benchmark tests.

Thank you @Trott, I've just pushed an inclusion of wpt in the same condition to see if it'll help or if it times out for some other reason.

@nodejs-github-bot

This comment was marked as outdated.

@nodejs-github-bot

This comment was marked as outdated.

@panva
Copy link
Member Author

panva commented Jul 13, 2022

@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

@panva
Copy link
Member Author

panva commented Jul 20, 2022

@Trott Similar to FreeBSD before, the node-test-commit-arm-debug build times out after 5 minutes.

@nodejs-github-bot
Copy link
Collaborator

@Trott
Copy link
Member

Trott commented Jul 22, 2022

@Trott Similar to FreeBSD before, the node-test-commit-arm-debug build times out after 5 minutes.

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

@nodejs-github-bot
Copy link
Collaborator

Copy link
Member

@tniessen tniessen left a 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.

test/parallel/test-crypto-key-objects.js Show resolved Hide resolved
Copy link
Member

@tniessen tniessen left a 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);
Copy link
Member

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.

Copy link
Member Author

@panva panva Jul 23, 2022

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.

Copy link
Member

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.

Copy link
Member Author

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?

src/crypto/crypto_hkdf.cc Outdated Show resolved Hide resolved
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()) ||
Copy link
Member

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?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes.

Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member Author

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

src/crypto/crypto_hkdf.cc Outdated Show resolved Hide resolved
@panva
Copy link
Member Author

panva commented Jul 23, 2022

❗ 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.

My thought process was exactly the same. I know we're missing 0-length tests for every single operation we accept a SecretKeyObject in.

I really don't like supporting things that OpenSSL does not support, but at least it's somewhat simple here.

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.

@panva panva added the request-ci Add this label to start a Jenkins CI on a PR. label Jul 23, 2022
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Jul 23, 2022
@nodejs-github-bot
Copy link
Collaborator

doc/api/crypto.md Show resolved Hide resolved
lib/internal/crypto/keys.js Show resolved Hide resolved
params.key->GetSymmetricKeySize())) {
return false;
}
} else {
Copy link
Member

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 the key to HMAC 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.

Copy link
Member Author

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 the key to HMAC 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) => {
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member Author

@panva panva Jul 23, 2022

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').

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(() => {})

@panva
Copy link
Member Author

panva commented Jul 23, 2022

Now on the matter of eventually landing this. I propose to commit-queue-rebase Add this label to allow the Commit Queue to land a PR in several commits. to land the PR in several commits. Also semver-minor PRs that contain new features and should be released in the next minor version. and leave a note to releasers that only some of the commits are minor, the rest are patch.

WDYT?

@panva
Copy link
Member Author

panva commented Aug 7, 2022

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.

@panva
Copy link
Member Author

panva commented Aug 11, 2022

Split up into #44170, #44171, #44172, #44173, and #44201. Pulling updated WPT will be done after they land to avoid unnecessary conflicts.

@panva panva deleted the webcrypto-wpt branch October 13, 2022 09:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
crypto Issues and PRs related to the crypto subsystem. experimental Issues and PRs related to experimental features. lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. review wanted PRs that need reviews. webcrypto
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants