-
Notifications
You must be signed in to change notification settings - Fork 121
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
PQ: fix timing sidechannels and add IPDWing #243
Conversation
Fix three potential timing sidechannels. These don't affect ephemeral usage of Kyber as in TLS, but it's good practice to get rid of them anyway. Also adds IPDWing, a preliminary version of X-Wing using the initial public draft (IPD) of ML-KEM. Don't use 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.
I'm working my way down from the TLS bits. Everything looks good so far. My next step is to look at what's been modified in fips202.c, then ipdwing.c itself. Shouldn't be too bad.
A couple of questions:
- Can you provide a source for the side channel mitigations? I want to make sure I understand what they're doing and that they've been implemented properly.
- I think it would be worth pulling in the test vectors from the xwing draft. Would you mind adding these to the patch, as well as code to exercise them in tests?
+}; | ||
+struct IPDWING_public_key { | ||
+ struct KYBER768_public_key m; | ||
+ uint8_t x[32]; |
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.
nit: I find it odd that the curve point is named x
here but named xpub
as part of the private key. In the private key, x
is the name of the secret scalar. Could we go with xpub
here instead so that we have one name for each thing?
+// IPDWING_generate_key is a deterministic function that outputs a public and | ||
+// private key based on the given entropy. |
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.
Following boringSSL comment conventions, I think you'd want to say here what values are written to which variables.
+// session key from the given entropy, writing those values to |out_shared_key| | ||
+// and |out_ciphertext|, respectively. | ||
+OPENSSL_EXPORT void IPDWING_encap(uint8_t out_ciphertext[IPDWING_CIPHERTEXT_BYTES], | ||
+ uint8_t out_shared_key[IPDWING_KEY_BYTES], |
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.
Unless boringSSL's variable naming conventions dictate otehrwise, I'd go with "shared_secret" here in order to align with the draft.
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've reviewed up to IPDWing and everything looks good. Again, I would strongly reocommend pulling in the test vectors from the draft.
What remains to check:
- It looks like this there is a new abosrb-then-squeeze API for SHAKE25.
- The side channel mitigations. Can you please provide a reference for this? I don't really know what I'm looking at. An in-line comment with a pointer would be most helpful.
+void encap(uint8_t out_ciphertext[KYBER_CIPHERTEXTBYTES], | ||
+// Internal version that allows us to select between initial public draft | ||
+// (when ipd=1) and round3 kyber (ipd=0). | ||
+void encap2(uint8_t out_ciphertext[KYBER_CIPHERTEXTBYTES], |
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.
"encap2" is confusing. Did you name this way because "encap" was already taken?
Likewise for "decap2" below.
+void encap2(uint8_t out_ciphertext[KYBER_CIPHERTEXTBYTES], | |
+void encap_for_version(uint8_t out_ciphertext[KYBER_CIPHERTEXTBYTES], |
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.
+ uint8_t ss[KYBER_KEY_BYTES], | ||
+ const struct public_key *in_pub, | ||
+ const uint8_t seed[KYBER_ENCAP_BYTES]) | ||
+ const uint8_t seed[KYBER_ENCAP_BYTES], | ||
+ int ipd) | ||
+{ | ||
+ const uint8_t *pk = &in_pub->opaque[0]; | ||
+ uint8_t *ct = out_ciphertext; |
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.
nit: There are spurious white spaces on the blankline below here. This is not clear in this PR, but is evident after applying the patch (kyber.c:1593).
+ &out_pub->m, | ||
+ &out_priv->m, | ||
+ seed); | ||
+ memcpy(out_priv->x, seed+64, 32); |
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.
+ memcpy(out_priv->x, seed+64, 32); | |
+ memcpy(out_priv->x, seed+KYBER_GENERATE_KEY_BYTES, 32); |
+ memcpy(out_priv->x, seed+64, 32); | ||
+ X25519_public_from_private(out_pub->x, out_priv->x); | ||
+ memcpy(out_priv->xpub, out_pub->x, 32); |
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.
pedantic: as far as I've seen, boringSSL's convention is to use named constants in these types of situations. I think in this case it makes the code more readable to non-domain experts and is therfore worth doing.
Likewise elsewhere.
+ memcpy(out_priv->x, seed+64, 32); | |
+ X25519_public_from_private(out_pub->x, out_priv->x); | |
+ memcpy(out_priv->xpub, out_pub->x, 32); | |
+ memcpy(out_priv->x, seed+64, X25519_PRIVATE_KEY_LEN); | |
+ X25519_public_from_private(out_pub->x, out_priv->x); | |
+ memcpy(out_priv->xpub, out_pub->x, X25519_PUBLIC_KEY_LEN); |
+ uint8_t ss[IPDWING_KEY_BYTES], | ||
+ const struct IPDWING_public_key *in_pub, | ||
+ const uint8_t seed[IPDWING_ENCAP_BYTES]) { | ||
+ |
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.
Spurious newline?
+ |
+void IPDWING_decap( | ||
+ uint8_t out_shared_key[IPDWING_KEY_BYTES], | ||
+ const struct IPDWING_private_key *in_priv, | ||
+ const uint8_t *ct) { |
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.
nit: consistency with out_ciphertext
for encapsulation
+ const uint8_t *ct) { | |
+ const uint8_t *ciphertext) { |
+ uint32_t t,d; | ||
+ int16_t a,b; | ||
+void IPDWING_decap( | ||
+ uint8_t out_shared_key[IPDWING_KEY_BYTES], |
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.
Consistency with the same parameter for encapsulation
+ uint8_t out_shared_key[IPDWING_KEY_BYTES], | |
+ uint8_t ss[IPDWING_KEY_BYTES], |
+ d += (t>>1) & 0x55555555; | ||
+void IPDWING_marshal_public_key( | ||
+ uint8_t out[IPDWING_PUBLIC_KEY_BYTES], | ||
+ const struct IPDWING_public_key *in) { |
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.
Consistency
+ const struct IPDWING_public_key *in) { | |
+ const struct IPDWING_public_key *in_pub) { |
+ KYBER768_parse_public_key(&out->m, in); | ||
+ memcpy(out->x, in + KYBER768_PUBLIC_KEY_BYTES, 32); | ||
+} | ||
+ |
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.
Spurious newline
+ |
src/crypto/kyber/keccak.c | 204 -- | ||
src/crypto/kyber/kyber.c | 2865 ++++++++++++++++++++------- | ||
src/crypto/kyber/kyber.c | 2319 +++++++++++++++------- |
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.
Reviewer note: Most of the changes here are moving SHA-3 out of this file into fips202.c. Also, some functions have been added for SHAKE256 (needed for IPD); and some unused functions have been pruned.
I cross tested connections with the Go implementation, which tests against the test vectors.
It's re-introduced. Upstream always contained the functions, but didn't use it, so I pruned it for this patch.
For 2/3 division is replaced by Barrett reduction, which is the exact same fix as upstream (1, 2). For the third, we use BoringSSL's internal value barrier to prevent the compiler from using the fact that an intermediate is {0,1}-valued. Upstream fixes that in a less robust way that presumes that one does not use LTO. |
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.
Everything looks correct to me. Readability could be improved (see inline comments), but if you want you can ignore those.
I've reviewed up to IPDWing and everything looks good. Again, I would strongly reocommend pulling in the test vectors from the draft.
I cross tested connections with the Go implementation, which tests against the test vectors.
Fair enough, but implementations can drift. In my opinion, you should either add that test to CI here or add consume the test vectors here as well.
What remains to check:
- It looks like this there is a new abosrb-then-squeeze API for SHAKE25.
It's re-introduced. Upstream always contained the functions, but didn't use it, so I pruned it for this patch.
Thanks! I confirmed the code is the same.
- The side channel mitigations. Can you please provide a reference for this? I don't really know what I'm looking at. An in-line comment with a pointer would be most helpful.
For 2/3 division is replaced by Barrett reduction, which is the exact same fix as upstream (1, 2).
For the third, we use BoringSSL's internal value barrier to prevent the compiler from using the fact that an intermediate is {0,1}-valued. Upstream fixes that in a less robust way that presumes that one does not use LTO.
I'm able to verify most of the changes, except those to polyvec_compress()
, as there is no corresponding change in pq-crystals as far as I can tell. I'll just have to trust you that you got the magic constants right. (And one day I'll learn Kyber!)
+ | ||
+static void polyvec_tobytes(uint8_t r[KYBER_POLYVECBYTES], const polyvec *a); | ||
+static void polyvec_frombytes(polyvec *r, const uint8_t a[KYBER_POLYVECBYTES]); | ||
+/************************************************* |
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 PR adds keccak_init, keccak_absorb, keccak_finalize, and keccak_squeeze from https://github.com/pq-crystals/kyber/blob/main/ref/fips202.c#L637. The code is the same.
nit: keccak_squeeze now appears out of order in this file: in the source it follows keccak_finalize.
Likewise for shake256 below.
+} | ||
+ unsigned int i,j; | ||
+ int16_t u; | ||
+ uint32_t d0; |
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.
You use uint64_t
for the same variable below in polyvec_compress()
. Is uint32_t
large enough here?
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.
From the name (taken from upstream) you would think polyvec_compress
is just a component-wise version of poly_vec
, which it's not. For Kyber768 the former leaves 10 bits per coefficient (the d_u
parameter), whereas the latter leaves 4 (the d_v
parameter). Because the numbers are bigger, we also need a more accurate Barrett approximant for the division by Q. The intermediate values in the latter can grow as large as 2^42, whereas they stay below 2^32 in the former.
Thanks for the review!
The patch is here Here is an explanation of the magic constants. That implementation chooses different ones, but the idea is the same. |
@@ -20,43 +20,55 @@ This patch adds: | |||
key agreement should only be used for testing: to see if the smaller | |||
keyshare makes a difference. | |||
|
|||
4. Supportfor IPDWing under codepoint 0xfe41. This key agreement |
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.
4. Supportfor IPDWing under codepoint 0xfe41. This key agreement | |
4. Support for IPDWing under codepoint 0xfe41. This key agreement |
This is the successor of X25519Kyber768Draft00. Spec: https://datatracker.ietf.org/doc/draft-kwiatkowski-tls-ecdhe-mlkem/02/ IANA has assigned the codepoint. https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8 Upstream BoringSSL support landed in. google/boringssl@7fb4d3d The version of BoringSSL we patch does not include it, so we add it manually. Chrome and Firefox are planning to enable in October. This PR is based on the IPD-Wing patch reviewed here: #243 There are two changes. First we simplify the patch a bit as we do not need IPD-Wing. Secondly, we perform the encapsulation key check, which was a last minute addition of NIST. We perform this check also for Kyber.
This is the successor of X25519Kyber768Draft00. Spec: https://datatracker.ietf.org/doc/draft-kwiatkowski-tls-ecdhe-mlkem/02/ IANA has assigned the codepoint. https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8 Upstream BoringSSL support landed in. google/boringssl@7fb4d3d The version of BoringSSL we patch does not include it, so we add it manually. Chrome and Firefox are planning to enable in October. This PR is based on the IPD-Wing patch reviewed here: #243 There are two changes. First we simplify the patch a bit as we do not need IPD-Wing. Secondly, we perform the encapsulation key check, which was a last minute addition of NIST. We perform this check also for Kyber.
Fix three potential timing sidechannels. These don't affect ephemeral usage of Kyber as in TLS, but it's good practice to get rid of them anyway.
Also adds IPDWing, a preliminary version of X-Wing using the initial public draft (IPD) of ML-KEM. Don't use it.