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

util: add util.types.isKeyObject and util.types.isCryptoKey #38619

Closed
wants to merge 1 commit into from

Conversation

panva
Copy link
Member

@panva panva commented May 10, 2021

closes #38611

cc @tniessen @mscdex @nodejs/crypto @nodejs/util

@panva panva requested review from mscdex, jasnell and tniessen May 10, 2021 10:38
@github-actions github-actions bot added crypto Issues and PRs related to the crypto subsystem. needs-ci PRs that need a full CI run. labels May 10, 2021
@panva panva added webcrypto semver-minor PRs that contain new features and should be released in the next minor version. labels May 10, 2021
@tniessen
Copy link
Member

tniessen commented May 10, 2021

This would be the first visible deviation from the WebCrypto standard, wouldn't it? It might not be a violation of the spec since we are still implementing the required interface members, but it would still add a non-standard function to a standard class.

One of the very few benefits of WebCrypto is portability, and this change appears to reduce that further. (But we are already doing that with Node.js specific algorithms, so...)

@panva
Copy link
Member Author

panva commented May 10, 2021

This would be the first visible deviation from the WebCrypto standard, wouldn't it? It might not be a violation of the spec since we are still implementing the required interface members, but it would still add a non-standard function to a standard class.

One of the very few benefits of WebCrypto is portability, and this change appears to reduce that further. (But we are already doing that with Node.js specific algorithms, so...)

Plus i made sure to mention so in the method's documentation.

lib/internal/crypto/keys.js Outdated Show resolved Hide resolved
lib/internal/crypto/keys.js Outdated Show resolved Hide resolved
@panva panva force-pushed the crypto-key-checks branch 2 times, most recently from 96301e1 to 856c82a Compare May 10, 2021 12:37
@panva panva removed the needs-ci PRs that need a full CI run. label May 10, 2021
@nodejs-github-bot

This comment has been minimized.

@tniessen
Copy link
Member

Plus i made sure to mention so in the method's documentation.

Sure, but that doesn't prevent code that uses crypto.subtle in Node.js from breaking once it is used in a browser or in Deno.

Why is instanceof not enough? And if it isn't, why doesn't the standard provide an API? And what do other runtimes do to solve this?

@jasnell
Copy link
Member

jasnell commented May 10, 2021

@tniessen

Why is instanceof not enough? And if it isn't, why doesn't the standard provide an API? And what do other runtimes do to solve this?

instanceof is both slow and does not work across contexts. Both of which may be acceptable limitations.

None of the JavaScript or Web Platform standards that I'm aware of include APIs for brand checks like this. Node.js has always been unique in doing so (e.g. util/types).

Other platforms don't solve this. Most rely on instanceof ... but then again, Node.js is unique in that it allows creating multiple contexts via the vm module.

@jasnell
Copy link
Member

jasnell commented May 10, 2021

@panva ... I don't think Crypto.isCryptoKey() is a good idea. We've been criticized in the past for attempting to add non-standard things to standard APIs and I'd rather not go down that road. Adding is as a utility method off crypto or even a static on KeyObject would work tho... e.g. KeyObject.isCryptoKey() and KeyObject.isKeyObject() ... given that CryptoKey is just a thin wrapper around KeyObject I don't think that's too much of a stretch.

@panva
Copy link
Member Author

panva commented May 10, 2021

@panva ... I don't think Crypto.isCryptoKey() is a good idea. We've been criticized in the past for attempting to add non-standard things to standard APIs and I'd rather not go down that road. Adding is as a utility method off crypto or even a static on KeyObject would work tho... e.g. KeyObject.isCryptoKey() and KeyObject.isKeyObject() ... given that CryptoKey is just a thin wrapper around KeyObject I don't think that's too much of a stretch.

I don't see a problem per se, but i'm fine with going either way. Depends on what the (hopefully) coming consensus here is going to be.

@tniessen
Copy link
Member

As I said before, here and in various other places, from my perspective, the main benefit of having WebCrypto in Node.js is compatibility with other systems that implement the same standard. instanceof might be slow, but WebCrypto really isn't great for scalable crypto performance anyway. Contexts are a good point, but even then, extending a standard class for a Node.js-specific problem doesn't seem like a good idea to me.

@panva
Copy link
Member Author

panva commented May 10, 2021

Would this be less controversial then?

KeyObject.isKeyObject(any)
KeyObject.isCryptoKey(any)

@panva panva force-pushed the crypto-key-checks branch from 856c82a to cafd19c Compare May 10, 2021 14:27
@panva panva changed the title crypto: expose KeyObject.isKeyObject(obj) and CryptoKey.isCryptoKey(obj) crypto: expose KeyObject.isKeyObject(obj) and KeyObject.isCryptoKey(obj) May 10, 2021
doc/api/crypto.md Outdated Show resolved Hide resolved
doc/api/crypto.md Outdated Show resolved Hide resolved
@panva panva force-pushed the crypto-key-checks branch from cafd19c to 56e2976 Compare May 10, 2021 16:37
@nodejs-github-bot

This comment has been minimized.

@tniessen
Copy link
Member

Would this be less controversial then?

KeyObject.isKeyObject(any)
KeyObject.isCryptoKey(any)

KeyObject.isKeyObject appears to be in line with Buffer.isBuffer, Array.isArray etc. ✔️

KeyObject.isCryptoKey, introduces a strong reference to the CryptoKey class in the KeyObject API, which, so far, could as well exist in an environment that doesn't support WebCrypto.

@nodejs-github-bot

This comment has been minimized.

@panva panva force-pushed the crypto-key-checks branch from 4c60e55 to dec5d19 Compare May 14, 2021 11:41
@nodejs-github-bot
Copy link
Collaborator

@tniessen
Copy link
Member

@panva Your code LGTM :) But, to be honest, I'm confused. I'd like to ask a few more things for my own understanding.


From #38611 (comment):

as with Buffer.isBuffer() (where the detection method did change over time).

I traced the current behavior back to at least 22c68fd, which was eight years ago:

node/lib/util.js

Lines 508 to 510 in 22c68fd

function isBuffer(arg) {
return arg instanceof Buffer;
}

It appears to be the same logic that's still there today, so while the detection method probably changed in the past, it's been stable for at least eight years:

node/lib/buffer.js

Lines 507 to 509 in 70157b9

Buffer.isBuffer = function isBuffer(b) {
return b instanceof Buffer;
};


instanceof is both slow and does not work across contexts. Both of which may be acceptable limitations.

@jasnell Is there a fundamental difference between Buffer and KeyObject/CryptoKey that would justify using instanceof for Buffer but not for KeyObject/CryptoKey? Or should Buffer really not be using instanceof but it's there for backward compatibility / lack of alternatives?

does not work across contexts

@jasnell I am trying to come up with a scenario in which obj instanceof KeyObject would return false. I assume this means that KeyObjectClassInContextA !== KeyObjectClassInContextB. But wouldn't that usually imply kHandleInContextA !== kHandleInContextB?

I don't usually use the vm module, so I'm not sure how this would work. I was under the impression that Node.js builtins would be shared either entirely or not at all.


Recapping #26200 and #26438:

  • I originally intentionally did not expose KeyObject to make it more difficult to misuse the API, but this made it difficult to check whether any given value was a KeyObject.
  • Hence crypto: expose crypto.isKeyObject() helper #26200, which suggested exposing isKeyObject and was based on a discussion between @panva and myself. However, others pointed out that isKeyObject was merely doing instanceof, and that it would be more consistent with the rest of the crypto module to expose KeyObject than to add isKeyObject, since we also don't have isCipher, isHash, isHmac, etc.
  • Hence crypto: expose KeyObject class #26438, which exposed the KeyObject class to allow using instanceof, and which landed with no controversial discussion.

@tniessen tniessen dismissed their stale review May 14, 2021 12:16

Code fixed.

@panva
Copy link
Member Author

panva commented May 14, 2021

Recapping #26200 and #26438:

  • I originally intentionally did not expose KeyObject to make it more difficult to misuse the API, but this made it difficult to check whether any given value was a KeyObject.
  • Hence crypto: expose crypto.isKeyObject() helper #26200, which suggested exposing isKeyObject and was based on a discussion between @panva and myself. However, others pointed out that isKeyObject was merely doing instanceof, and that it would be more consistent with the rest of the crypto module to expose KeyObject than to add isKeyObject, since we also don't have isCipher, isHash, isHmac, etc.
  • Hence crypto: expose KeyObject class #26438, which exposed the KeyObject class to allow using instanceof, and which landed with no controversial discussion.

since we also don't have isCipher, isHash, isHmac, etc.

I have yet to come across the need to verify user input is Cipher, Hash, or Hmac. KeyObject/CryptoKey OTOH is much more likely to be a function input, specifically one that needs to be distinguished from Buffers, strings, objects that are otherwise accepted in the same argument.

Hence #26200, which suggested exposing isKeyObject and was based on a discussion between @panva and myself. However, others pointed out that isKeyObject was merely doing instanceof

I didn't know better at the time 🤷 as shown above doing instanceof is leaving performance on the table.

@tniessen
Copy link
Member

@panva Neither of us should have to justify the design choices back then, I was just recapping for context. I think all your contributions to the crypto APIs were well-designed :)

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.

LGTM but I'd love to see an example where this helps across contexts, maybe even an added test.

panva added a commit that referenced this pull request May 17, 2021
closes #38611

PR-URL: #38619
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
@panva
Copy link
Member Author

panva commented May 17, 2021

Landed in 3ee1f9a

@panva panva closed this May 17, 2021
@tniessen
Copy link
Member

I realize that this was landed, but there are still some unanswered questions...

Is there a fundamental difference between Buffer and KeyObject/CryptoKey that would justify using instanceof for Buffer but not for KeyObject/CryptoKey? Or should Buffer really not be using instanceof but it's there for backward compatibility / lack of alternatives?

I am trying to come up with a scenario in which obj instanceof KeyObject would return false. I assume this means that KeyObjectClassInContextA !== KeyObjectClassInContextB. But wouldn't that usually imply kHandleInContextA !== kHandleInContextB?

I'd love to see an example where this helps across contexts, maybe even an added test.

@panva panva deleted the crypto-key-checks branch May 18, 2021 09:52
targos pushed a commit that referenced this pull request May 18, 2021
closes #38611

PR-URL: #38619
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
targos added a commit that referenced this pull request May 18, 2021
Notable changes:

async_hooks:
  * (SEMVER-MINOR) use new v8::Context PromiseHook API (Stephen Belanger) #36394
lib:
  * support setting process.env.TZ on windows (James M Snell) #38642
module:
  * (SEMVER-MINOR) add support for `URL` to `import.meta.resolve` (Antoine du Hamel) #38587
process:
  * (SEMVER-MINOR) add `'worker'` event (James M Snell) #38659
util:
  * (SEMVER-MINOR) add util.types.isKeyObject and util.types.isCryptoKey (Filip Skokan) #38619

PR-URL: #38719
targos added a commit that referenced this pull request May 18, 2021
Notable changes:

async_hooks:
  * (SEMVER-MINOR) use new v8::Context PromiseHook API (Stephen Belanger) #36394
lib:
  * support setting process.env.TZ on windows (James M Snell) #38642
module:
  * (SEMVER-MINOR) add support for `URL` to `import.meta.resolve` (Antoine du Hamel) #38587
process:
  * (SEMVER-MINOR) add `'worker'` event (James M Snell) #38659
util:
  * (SEMVER-MINOR) add util.types.isKeyObject and util.types.isCryptoKey (Filip Skokan) #38619

PR-URL: #38719
@jasnell
Copy link
Member

jasnell commented May 18, 2021

@tniessen

In the most typical case the context stuff won't matter, you're right, because even with the vm module, instanceof will still work. I'm thinking about more exotic cases like electron, where someone may have both the chromium implementation of a CryptoKey and Node.js CryptoKey (similar to the fact that you can have both chromium's URL and Node.js URL ... which is the reason we have isURLInstance()). In that case, key instanceof CryptoKey could be ambiguous and could fail if you really do not know which CryptoKey class you're working with. util.types.isCryptoKey() is fast and unambiguous and tells you exactly if this thing you have is Node.js' idea of a CryptoKey.

As for KeyObject, yes, instanceof works but having a faster and consistently similar API for checking the type makes sense.

@tniessen
Copy link
Member

Thank you for the explanation, @jasnell!

targos added a commit that referenced this pull request May 19, 2021
Notable changes:

async_hooks:
  * (SEMVER-MINOR) use new v8::Context PromiseHook API (Stephen Belanger) #36394
lib:
  * support setting process.env.TZ on windows (James M Snell) #38642
module:
  * (SEMVER-MINOR) add support for `URL` to `import.meta.resolve` (Antoine du Hamel) #38587
process:
  * (SEMVER-MINOR) add `'worker'` event (James M Snell) #38659
util:
  * (SEMVER-MINOR) add util.types.isKeyObject and util.types.isCryptoKey (Filip Skokan) #38619

PR-URL: #38719
targos added a commit that referenced this pull request May 19, 2021
Notable changes:

async_hooks:
  * (SEMVER-MINOR) use new v8::Context PromiseHook API (Stephen Belanger) #36394
lib:
  * support setting process.env.TZ on windows (James M Snell) #38642
module:
  * (SEMVER-MINOR) add support for `URL` to `import.meta.resolve` (Antoine du Hamel) #38587
process:
  * (SEMVER-MINOR) add `'worker'` event (James M Snell) #38659
util:
  * (SEMVER-MINOR) add util.types.isKeyObject and util.types.isCryptoKey (Filip Skokan) #38619

PR-URL: #38719
@panva
Copy link
Member Author

panva commented May 19, 2021

@nodejs/backporters leaving a note here that if backport were to happen to do so for util.types.isKeyObject, not util.types.isCryptoKey.

(not sure i'm following the process right here, please correct me if i'm wrong to request a backport, or if i can open a backport PR myself?)

@targos
Copy link
Member

targos commented Sep 4, 2021

The WebCrypto API doesn't exist in versions <15.x

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
author ready PRs that have at least one approval, no pending requests for changes, and a CI started. crypto Issues and PRs related to the crypto subsystem. semver-minor PRs that contain new features and should be released in the next minor version. util Issues and PRs related to the built-in util module. webcrypto
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Feature Request: Safe/reliable crypto.KeyObject and webcrypto.CryptoKey detection in userland
6 participants