-
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
Add crypto.timingSafeEqual() #8040
Conversation
For the record, this still has the issue that the binary distinction between a successful and an unsuccessful comparison is leaked as timing information, and the choices seem to be either saying that that doesn’t matter or that the extra |
b4d7f6f
to
4d5339b
Compare
I don’t think agreement has been reached there, either. |
/cc @nodejs/crypto |
4d5339b
to
f079149
Compare
@@ -488,22 +488,21 @@ class Hmac : public BaseObject { | |||
|
|||
static void Initialize(Environment* env, v8::Local<v8::Object> target); | |||
|
|||
protected: |
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'm not sure about this. Not only would it create an inconsistency with the other crypto C++ APIs (since we'd now be exposing the 3 below methods to everyone), but I think doing so could even constitute a semver-major
change?
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 just seems to turn a couple of protected
properties into public
ones, which would be a semver-minor
change (if this is a public API). But this header is one of those with a NODE_WANT_INTERNALS
, so I don’t think we even consider this public.
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.
Fixed; this PR no longer modifies these properties.
It would definitely be good to have this in but we do need to get a better sense of just how much of a timing issue exists with the second |
|
||
Returns true if `a` is equal to `b`, without leaking timing information that would allow an attacker to guess one of the values. This is suitable for comparing HMAC digests or secret values like authentication cookies or [capability urls](https://www.w3.org/TR/capability-urls/). | ||
|
||
A TypeError will be thrown if either `a` or `b` is not a Buffer instance, or if `a` and `b` have different lengths. |
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.
Long lines, please wrap at 80 columns.
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.
Fixed.
Agreed. As is, I don't think this pull request is acceptable. |
432750c
to
d4f6385
Compare
From what I can tell, here are the main issues that have discussed regarding the second
Based on this, I think we should remove the second |
Dropping the second
Again, it's not foolproof because there's likely some timing information that can be extracted from the hmac generation but it avoids the issue above with the second |
d4f6385
to
053f844
Compare
If we care about the distinction between a 1/2256 chance of failure and a 1/2512 chance of failure, it might be better to use sha512 rather than using sha256 twice. That said, I think a 1/2256 error rate is low enough that we shouldn't be too concerned about it. |
Wouldn't this be an issue even without the second |
Yes, I believe that is correct. I do believe |
It seems like that would have the same issue (unpredictable compiler optimization) as simply comparing the input buffers directly using iteration that doesn't exit early. |
yep... we'll have to explore the options there more. The only thing that's certain is that |
Good point, that should really be CRYPTO_memcmp(). A sufficiently smart whole program optimizer could still thwart it but it's better than plain memcmp(). |
Last I checked (and it's been a while) but CRYPTO_memcmp() support was rather sparse (update: as in not extensively used). Has that improved? (mainly concerned here about whether there is enough usage analysis to support the claim that it's fundamentally less of an issue that memcmp) |
Assuming there are no usage/support issues, Otherwise, we could probably just keep the double HMAC solution and note in the docs that it will leak the success/failure status of the comparison. In the majority of use-cases, the success/failure status is observable to an attacker anyway (e.g. a request with a signed token will fail if the HMAC signature is invalid). |
@Trott I think for the most part, the flakiness that we saw wasn't from statistical noise; it was from external factors (such as gc pauses) that caused some benchmarks to take longer than they otherwise would have. The 1/1000 chance of failure noted in the test file only accounts for statistical noise. The question is whether these external factors are a timing problem as far as security is concerned. I don't think they are a problem, because a gc pause is no more likely to affect the case where two buffers are equal than it is to affect the case where two buffers are different. |
@not-an-aardvark and anyone else: Any opinion on whether these ideas are great, terrible, or just kinda meh?
|
Seems like a good idea to me.
I have no strong opinions either way. Of course, the ideal solution would be to investigate the flakiness and get rid of it (perhaps by doing manual gc in the test). I'd be willing to help with this, but it might be difficult for me to do so since I can't seem to reproduce the issue locally. |
By the way, why is the test for |
@not-an-aardvark Apparently, there's a bug in the flaky status stuff. nodejs/build#467 |
PR to split test: #8202 |
Does it make sense to skip the memory-intensive stats-based currently-flaky part of the test on machines that are memory constrained? We currently skip a number of tests on devices that have 1Gb or less of RAM. If it makes sense that the test would fail under those circumstances, we can certainly add it. |
We likely should consider a separate bucket for inherently flaky tests that are evaluated more along the lines that @bnoordhuis described. That is, monitoring the rate of flakiness over time. I don't think skipping this test entirely on more constrained devices is necessarily a good idea but running less frequently and not as part of the on demand CI we use for PR review would be just fine.
|
I don't think this test should be using a lot of memory (it creates two arrays of 10000 integers as it gathers benchmarks, but not much aside from that). That said, at least part of the problem should be fixable. While the test will always fail 1/1000 test runs due to statistical noise, failures like this one have very high Would it be worth creating a separate PR to see if this is fixed by using manual garbage collection with the |
EDIT: Never mind, I'm probably wrong about much of the below.
|
Yes, definitely. |
@jasnell (And never mind. I'm wrong about much of what I wrote above. Ugh. Sorry for my confusion.) |
@not-an-aardvark PTAL at #8203 to confirm that is more or less what you had in mind. |
This reverts commit 0fc5e0d. Additional testing indicates that there may still be timing issues with this implementation. Revert in order to give more time for testing before this goes out into a release... Refs: nodejs#8040 Refs: nodejs#8203
See: #8225 |
This reverts commit 0fc5e0d. Additional testing indicates that there may still be timing issues with this implementation. Revert in order to give more time for testing before this goes out into a release... Refs: #8040 Refs: #8203 PR-URL: #8225 Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Fixes: #3043 |
Checklist
make -j4 test
(UNIX), orvcbuild test nosign
(Windows) passesAffected core subsystem(s)
crypto
Description of change
Relates to #3073. This is an implementation of a timing-safe key comparison function, implemented on the C++ side as suggested in #3073 (comment) and #3073 (comment).