ECDSA verification fails when it should succeed
Categories
(NSS :: Libraries, defect, P1)
Tracking
(Not tracked)
People
(Reporter: guidovranken, Assigned: bbb)
Details
(Keywords: sec-other, Whiteboard: [nss-fx])
Attachments
(6 files)
User Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:84.0) Gecko/20100101 Firefox/84.0
Steps to reproduce:
ECDSA verification using the following parameters
public key X: 3287081257432259362393812976079081421412151647138720924241920297746134087425717291680098733199669585693238116089896462389392776287267107954818562468740630350
public key Y: 5047884165615205077529306077209628022504281088340332331957752940735437106855961661784277419343640566825146183053470636970974876726952117391717027289712479371
cleartext: {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} (59 bytes)
signature R: 2398833381252220480870377713423558965855096706378426427899888394173467690495466969487925690736977872006638457011575098146569145494858882127318415314133391193
signature S: 2318419597317490008949351307731018419258702345790149047845632642305600089435507511726637998566288744337568482741407664437071146659987012556995409299000152793
digest: NULL
using the "ECDSA_VerifyDigest" function (where 'cleartext' is the data that is passed as-is to this function, not hashed) fails, but it succeeds in OpenSSL, Botan and wolfCrypt, which suggests that NSS is wrong.
Probably not a security issue, unless whatever bug is causing this can be exploited into doing the reverse (verifying signatures when it should not), so I'm marking it as security bug just in case.
Actual results:
Print:
Verify: 0
Expected results:
Print:
Verify: 1
Comment 1•4 years ago
|
||
Thanks for the report and sorry I didn't get to this before the holidays.
Ben, could you please take a look into this?
Comment 2•4 years ago
•
|
||
Could you please confirm the public key as you're passing it into NSS (that is, the raw bytes of ecpub.publicValue
) and the other libraries? I get:
0x04, 0x00, 0xF5, 0x29, 0x66, 0x80, 0x3F, 0xBB, 0xE4, 0x37, 0x2C, 0x12, 0x0D, 0xE6, 0x00, 0x22, 0x9A, 0xF2, 0x35, 0xD9, 0x50, 0x2C, 0xC8, 0x95, 0x20, 0x79, 0x95, 0x31, 0xD1, 0x6B, 0x53, 0x9F, 0x4C, 0xC9, 0xC9, 0x11, 0x68, 0x77, 0xC9, 0xB8, 0x0C, 0x73, 0x79, 0x40, 0xF9, 0xD7, 0x94, 0x65, 0xE6, 0xE8, 0x35, 0xEC, 0xC4, 0x0C, 0xE1, 0x55, 0x56, 0x7C, 0xA3, 0x30, 0xDB, 0x1D, 0x3D, 0x52, 0x48, 0x87, 0x4E, 0x17, 0x87, 0xD0, 0x7D, 0x58, 0x34, 0xE0, 0x35, 0xD8, 0x5C, 0xC7, 0xCD, 0x4F, 0x53, 0x4F, 0x71, 0x6D, 0xD5, 0x81, 0xF6, 0x65, 0x7A, 0x16, 0x5F, 0xF8, 0xD9, 0xB3, 0x75, 0x7E, 0xA0, 0x14, 0xF0, 0xB8, 0x47, 0xBD, 0xA3, 0x87, 0x3B, 0x44, 0x92, 0x83, 0x80, 0xF5, 0x7F, 0x30, 0xB7, 0xAF, 0xFA, 0x0F, 0x58, 0x67, 0x48, 0x28, 0x07, 0x52, 0x39, 0x7D, 0x2D, 0x9F, 0x96, 0xB0, 0x07, 0x26, 0xEC, 0xC8, 0x0B
and with that value, EC_ValidatePublicKey
fails at [1] because the y-coordinate is outside of [0, p−1].
If that's the case, it's interesting that the other libraries consider the signature correct... But maybe I did a conversion wrong.
[1] https://searchfox.org/mozilla-central/source/security/nss/lib/freebl/ecl/ecp_aff.c#266
Reporter | ||
Comment 3•4 years ago
|
||
Your X/Y:
X: 00F52966803FBBE4372C120DE600229AF235D9502CC89520799531D16B539F4CC9C9116877C9B80C737940F9D79465E6E835ECC40CE155567CA330DB1D3D5248874E
Y: 1787D07D5834E035D85CC7CD4F534F716DD581F6657A165FF8D9B3757EA014F0B847BDA3873B44928380F57F30B7AFFA0F586748280752397D2D9F96B00726ECC80B
My X/Y:
X: 00F52966803FBBE4372C120DE600229AF235D9502CC89520799531D16B539F4CC9C9116877C9B80C737940F9D79465E6E835ECC40CE155567CA330DB1D3D5248874E
Y: 01787D07D5834E035D85CC7CD4F534F716DD581F6657A165FF8D9B3757EA014F0B847BDA3873B44928380F57F30B7AFFA0F586748280752397D2D9F96B00726ECC8B
Maybe you're using an old version of Boost that does the conversion from string to binary wrong?
Whatever the cause, to be testing the same parameters as me, put this in the reproducer:
pub = std::vector<uint8_t>({0x04, 0x00, 0xF5, 0x29, 0x66, 0x80, 0x3F, 0xBB, 0xE4, 0x37, 0x2C, 0x12, 0x0D, 0xE6, 0x00, 0x22, 0x9A, 0xF2, 0x35, 0xD9, 0x50, 0x2C, 0xC8, 0x95, 0x20, 0x79, 0x95, 0x31, 0xD1, 0x6B, 0x53, 0x9F, 0x4C, 0xC9, 0xC9, 0x11, 0x68, 0x77, 0xC9, 0xB8, 0x0C, 0x73, 0x79, 0x40, 0xF9, 0xD7, 0x94, 0x65, 0xE6, 0xE8, 0x35, 0xEC, 0xC4, 0x0C, 0xE1, 0x55, 0x56, 0x7C, 0xA3, 0x30, 0xDB, 0x1D, 0x3D, 0x52, 0x48, 0x87, 0x4E, 0x01, 0x78, 0x7D, 0x07, 0xD5, 0x83, 0x4E, 0x03, 0x5D, 0x85, 0xCC, 0x7C, 0xD4, 0xF5, 0x34, 0xF7, 0x16, 0xDD, 0x58, 0x1F, 0x66, 0x57, 0xA1, 0x65, 0xFF, 0x8D, 0x9B, 0x37, 0x57, 0xEA, 0x01, 0x4F, 0x0B, 0x84, 0x7B, 0xDA, 0x38, 0x73, 0xB4, 0x49, 0x28, 0x38, 0x0F, 0x57, 0xF3, 0x0B, 0x7A, 0xFF, 0xA0, 0xF5, 0x86, 0x74, 0x82, 0x80, 0x75, 0x23, 0x97, 0xD2, 0xD9, 0xF9, 0x6B, 0x00, 0x72, 0x6E, 0xCC, 0x8B});
Reporter | ||
Comment 4•4 years ago
|
||
Comment 5•4 years ago
•
|
||
Ack, sorry about that. I had no internet access (to download boost) when I was working on this and I apparently screwed up the conversion.
The problem is this: In point_mul_two_secp521r1
(and point_mul_two_secp384r1
, from inspection). It's considered out of range for n1 or n2 to be zero [1] . This isn't always the check we want:
- ECDSA Sign: n1==k and n2 is NULL. The check is correct, as k must be in [1, n-1].
- ECDSA Verify: n1 and n2 are the "u1" and "u2" values, either of which may be zero (at least, their values are not checked per the spec). In this case, it's u1.
- ECDH: n1 is NULL and n2==k. Again this is right, k must be in [1, n-1].
If we skip the != 0
check, verification succeeds.
Now, this code is actually from ECCKiila [2], so we should report to them before deciding whether to fix this ourselves vs. updating the autogenerated code. FWIW: apparently, OpenSSL used to have a similar bug [3].
Nice find :)
[1] https://searchfox.org/mozilla-central/source/security/nss/lib/freebl/ecl/ecp_secp521r1.c#4597
[2] https://gitlab.com/nisec/ecckiila/
[3] http://openssl.6102.n7.nabble.com/ECDSA-verify-fails-when-digest-is-all-zeros-in-0-9-8e-td34477.html
Reporter | ||
Comment 6•4 years ago
|
||
Great. Will you report it to ECCKiila? Do you consider this a security issue? If not, I will add it to the public Cryptofuzz bug list.
Comment 7•4 years ago
|
||
Yes, I'll reach out to them now.
I don't see any security impact here but if you don't mind, let's check with ECCKiila before we open it.
Comment 8•4 years ago
•
|
||
Hi bbrumley,
This issue (see comment 5) comes from code that is marked "Autogenerated: ECCKiila".
It seems one minimally-invasive fix would be to disable the zero checks on n1 and possibly n2 when are both specified (i.e. only in the verify case).
- Do you have a preference of whether we fix this in NSS, or import new autogenerated code?
- Any concern with Guido adding this bug to a public list?
Thanks.
Assignee | ||
Comment 9•4 years ago
|
||
u2 is not allowed to be zero.
You have u2 = r/s, so u2 = 0 only holds if r is zero. Which is not allowed in ECDSA.
For u1 = z/s, you get u1=0 iff z=0. Which yea I guess is possible -- with probability roughly the same as guessing a private key.
But yea it should be fixed. We have a few non-critical updates to ECCKiila code in NSS anyway, so I will roll it in when we have time.
Assignee | ||
Comment 10•4 years ago
|
||
And thanks for the report!
Reporter | ||
Comment 11•4 years ago
|
||
You're welcome bbrumley. Check out my project https://github.com/guidovranken/cryptofuzz which has found a lot of ECC/ECDSA and other bugs :).
Guido
Assignee | ||
Comment 12•4 years ago
|
||
Assignee | ||
Comment 13•4 years ago
|
||
At first glance the diff looks non-trivial but realize it's mostly fiat changes, plus some code doc.
The relevant part is here
@@ -6423,20 +6500,20 @@ point_mul_two_secp384r1(const mp_int *n1, const mp_int *n2,
unsigned char b_n2[48];
mp_err res;
- /* If n2 == NULL, this is just a base-point multiplication. */
- if (n2 == NULL)
+ /* If n2 == NULL or 0, this is just a base-point multiplication. */
+ if (n2 == NULL || mp_cmp_z(n2) == MP_EQ)
return point_mul_g_secp384r1(n1, out_x, out_y, group);
- /* If n1 == NULL, this is just an arbitary-point multiplication. */
- if (n1 == NULL)
+ /* If n1 == NULL or 0, this is just an arbitary-point multiplication. */
+ if (n1 == NULL || mp_cmp_z(n1) == MP_EQ)
return point_mul_secp384r1(n2, in_x, in_y, out_x, out_y, group);
ARGCHK(in_x != NULL && in_y != NULL && out_x != NULL && out_y != NULL,
MP_BADARG);
I really did not test this much at all internal to NSS besides running fbectest
and ecperf
. Guido could you try your reproducer? (You might not have access in Phabricator -- maybe one of the maintainers can help out there? I'm such a Phabricator newb.)
Assignee | ||
Comment 14•4 years ago
|
||
Assignee | ||
Comment 15•4 years ago
|
||
Assignee | ||
Comment 16•4 years ago
|
||
(I apologize -- I had stale source in D102406. It should be fixed in Phabricator now.)
Reporter | ||
Comment 17•4 years ago
|
||
I'd like to test your patch but I don't have access to Phabricator so I can't download the diff. Can you post the diff (for NSS, not ECCKilla) here, or can someone give me access?
Comment 18•4 years ago
|
||
Thanks for the patch. I'm happy to test and review this, but I have a number of competing priorities this week (sorry). I'll set a reminder for myself to come back to this next week.
(In reply to Guido Vranken from comment #17)
I'd like to test your patch but I don't have access to Phabricator so I can't download the diff. Can you post the diff (for NSS, not ECCKilla) here, or can someone give me access?
If you create a Phabricator account, you should have access to the patch.
Assignee | ||
Comment 19•4 years ago
|
||
Thank you, Kevin!
I'm using the .clang-format
in the root of the repo. Is that the correct one? (It appears to be, in that I see only a single formatting nit in the diff.)
Comment 20•4 years ago
|
||
(In reply to bbrumley from comment #19)
Thank you, Kevin!
I'm using the
.clang-format
in the root of the repo. Is that the correct one? (It appears to be, in that I see only a single formatting nit in the diff.)
Yes, that should be it. On my machine it changes all of these lines:
diff --git a/lib/freebl/ecl/ecp_secp384r1.c b/lib/freebl/ecl/ecp_secp384r1.c
--- a/lib/freebl/ecl/ecp_secp384r1.c
+++ b/lib/freebl/ecl/ecp_secp384r1.c
@@ -6150,7 +6150,7 @@ var_smul_rwnaf(pt_aff_t *out, const unsi
scalar_rwnaf(rnaf, scalar);
#if defined(_MSC_VER)
- /* result still unsigned: yes we know */
+/* result still unsigned: yes we know */
#pragma warning(push)
#pragma warning(disable : 4146)
... (repeating)...
But we can also format the patch if you're getting different results.
I've confirmed that this patch fixes the issue.
Ben, can you please take this from here?
Comment 21•4 years ago
|
||
@Kevin
Sure, thanks all!
@Billy
FYI, if you have Docker installed, you should be able to use ./mach clang-format
at the root of the NSS directory.
This will run a correctly configured clang-format from a Docker VM. I can assist if this doesn't work well for you... : )
@Guido
If you can't/don't want to use Phabricator I can send you the raw diff
Reporter | ||
Comment 22•4 years ago
|
||
I have Phabricator account now. When I click on bbrumley's patch, I get:
Access Denied: Restricted Differential Revision
You do not have permission to view this object.
Users with the "Can View" capability:
This object has a custom policy controlling who can take this action.
The owner of a revision can always view and edit it.If a revision belongs to a repository, other users must be able to view the repository in order to view the revision.
Reporter | ||
Comment 23•4 years ago
|
||
Nevermind, it works now.
Assignee | ||
Comment 25•4 years ago
|
||
moved |
Hey Guido,
Very interesting find!
Indeed, secp256r1 (P-256) has a different code path than the curves under consideration here, P-384 and P-521. So I think you should file that under a distinct issue.
(My 2c: it's unclear to me if your new issue is related to curve-specific code or generically at the EC layer.)
Reporter | ||
Comment 26•4 years ago
|
||
moved |
bbrumley: Can't reproduce it for other curves so far, only secp256r1. However this is not a guarantee that the other curves are not affected.
Created a new issue: https://bugzilla.mozilla.org/show_bug.cgi?id=1689574
Updated•4 years ago
|
Comment 27•4 years ago
|
||
Depends on D102389
Comment 28•4 years ago
|
||
Depends on D102406
Comment 29•4 years ago
|
||
Hi Billy,
As discussed, I pushed a minor change for nested struct initializations to {{ 0 }}
as separate commits on top of your two patches for P384 and P521 so that our CI doesn't complain. These are on separate phabricator patches and have a link to their respective CI runs, you should have been notified by email.
If you think that it works for you, please accept the two patches and I'll push the changes to NSS.
Thanks a lot !
B.
(note that clang-format is failing but it is not blocking, so I'll fix formatting separately so that it is easier for us both ;)
Updated•4 years ago
|
Assignee | ||
Comment 30•4 years ago
|
||
Hey Benjamin,
Thanks for the update.
I tried the proposed fix but now ECCKiila CI is nit barfing with clang 11
[ 60%] Building C object CMakeFiles/ecp_test.dir/ecp_secp128r2.c.o
/home/bbrumley/svnrepos/ecckiila/ecp/secp128r2/ecp_secp128r2.c:2664:22: error: missing field 'Y' initializer [-Werror,-Wmissing-field-initializers]
pt_prj_t Q = {{0}};
^
It is fun, battling the CI for 3 projects, to work around false positives :) The = { 0 }
initialize was due to a coverity nit for the gost-engine project -- ECCKiila provides eight standardized curves there.
Ofc I can turn off -Wmissing-field-initializers
but that seems kind of arbitrary compared to the -Wmissing-braces
NSS is apparently using.
I'll have to think about it.
Comment 31•4 years ago
|
||
Fun indeed : )
I think the correct way to do this was the original {0}
, and at this point, I don't think it is necessary for you to do unnecessary work on ECCKiila for NSS due to a broken GCC in our CI. So, I am perfectly happy for you to keep {0}
as a default, and for me to do a similar manual change to {{0}}
next time we want to update this code. Hopefully, I can update that compiler in our CI by then so that it doesn't cause trouble : )
Let me know what works best for you.
Best, B.
Updated•4 years ago
|
Updated•4 years ago
|
Comment 32•4 years ago
|
||
Update of ECCKiila P521:
https://hg.mozilla.org/projects/nss/rev/a8f4918cd5461aaf0981580650f69a430e176155
https://hg.mozilla.org/projects/nss/rev/5e7affa3ce43b331c9783ec92b402438039066b0
Update of ECCKiila P384:
https://hg.mozilla.org/projects/nss/rev/76aca2d944ae572cff942729ca08599452bb33d1
https://hg.mozilla.org/projects/nss/rev/150cbb169f1e707a07fa1911928f489e38dcb1d6
Clang-format:
https://hg.mozilla.org/projects/nss/rev/8a9174a7820777399507e255e927d2654ab33cf7
Comment 33•4 years ago
|
||
Thanks a lot Guido and Billy for reporting and updating the code, this is highly appreciated : )
Best, B.
Description
•