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

PQ: fix timing sidechannels and add IPDWing #243

Merged
merged 1 commit into from
Jul 8, 2024
Merged

Conversation

bwesterb
Copy link
Member

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.

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.
Copy link
Collaborator

@cjpatton cjpatton left a 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:

  1. 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.
  2. 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?

boring-sys/patches/boring-pq.patch Show resolved Hide resolved
boring-sys/patches/boring-pq.patch Show resolved Hide resolved
+};
+struct IPDWING_public_key {
+ struct KYBER768_public_key m;
+ uint8_t x[32];
Copy link
Collaborator

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?

Comment on lines +4391 to +4392
+// IPDWING_generate_key is a deterministic function that outputs a public and
+// private key based on the given entropy.
Copy link
Collaborator

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],
Copy link
Collaborator

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.

boring-sys/patches/boring-pq.patch Show resolved Hide resolved
boring-sys/patches/boring-pq.patch Show resolved Hide resolved
Copy link
Collaborator

@cjpatton cjpatton left a 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:

  1. It looks like this there is a new abosrb-then-squeeze API for SHAKE25.
  2. 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],
Copy link
Collaborator

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.

Suggested change
+void encap2(uint8_t out_ciphertext[KYBER_CIPHERTEXTBYTES],
+void encap_for_version(uint8_t out_ciphertext[KYBER_CIPHERTEXTBYTES],

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.

+ 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;
Copy link
Collaborator

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);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
+ memcpy(out_priv->x, seed+64, 32);
+ memcpy(out_priv->x, seed+KYBER_GENERATE_KEY_BYTES, 32);

Comment on lines +1086 to +1088
+ 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);
Copy link
Collaborator

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.

Suggested change
+ 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]) {
+
Copy link
Collaborator

Choose a reason for hiding this comment

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

Spurious newline?

Suggested change
+

+void IPDWING_decap(
+ uint8_t out_shared_key[IPDWING_KEY_BYTES],
+ const struct IPDWING_private_key *in_priv,
+ const uint8_t *ct) {
Copy link
Collaborator

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

Suggested change
+ 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],
Copy link
Collaborator

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

Suggested change
+ 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) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Consistency

Suggested change
+ 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);
+}
+
Copy link
Collaborator

Choose a reason for hiding this comment

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

Spurious newline

Suggested change
+

src/crypto/kyber/keccak.c | 204 --
src/crypto/kyber/kyber.c | 2865 ++++++++++++++++++++-------
src/crypto/kyber/kyber.c | 2319 +++++++++++++++-------
Copy link
Collaborator

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.

@bwesterb
Copy link
Member Author

bwesterb commented Jun 28, 2024

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.

What remains to check:

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

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

Copy link
Collaborator

@cjpatton cjpatton left a 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:

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

  1. 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]);
+/*************************************************
Copy link
Collaborator

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;
Copy link
Collaborator

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?

Copy link
Member Author

@bwesterb bwesterb Jun 30, 2024

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.

@bwesterb
Copy link
Member Author

bwesterb commented Jun 30, 2024

Thanks for the review!

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!)

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
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
4. Supportfor IPDWing under codepoint 0xfe41. This key agreement
4. Support for IPDWing under codepoint 0xfe41. This key agreement

@inikulin inikulin merged commit 4725a93 into master Jul 8, 2024
23 checks passed
bwesterb added a commit that referenced this pull request Sep 17, 2024
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.
bwesterb added a commit that referenced this pull request Sep 18, 2024
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants