Skip to content

Comments

PSK and key_share compliance fixes for RFC 8446#14749

Closed
kaduk wants to merge 4 commits intoopenssl:masterfrom
kaduk:psk-compliance
Closed

PSK and key_share compliance fixes for RFC 8446#14749
kaduk wants to merge 4 commits intoopenssl:masterfrom
kaduk:psk-compliance

Conversation

@kaduk
Copy link
Contributor

@kaduk kaduk commented Mar 30, 2021

While testing some other work I ran across a couple places where we were needlessly out of compliance with RFC 8446.

At the end of https://tools.ietf.org/html/rfc8446#section-4.2.8 :

If using (EC)DHE key establishment, servers offer exactly one
KeyShareEntry in the ServerHello. This value MUST be in the same
group as the KeyShareEntry value offered by the client that the
server has selected for the negotiated key exchange. Servers
MUST NOT send a KeyShareEntry for any group not indicated in the
client's "supported_groups" extension and MUST NOT send a
KeyShareEntry when using the "psk_ke" PskKeyExchangeMode. [...]

Prior to these changes, the server would send a KeyShareEntry when the
"psk_ke" mode was used, if the client had actually sent an "key_share".
OpenSSL speaking to OpenSSL will never negotiate the "psk_ke" mode,
since we prefer to have the ephemeral key exchange.

In https://tools.ietf.org/html/rfc8446#section-4.2.9 :

A client MUST provide a "psk_key_exchange_modes" extension if it
offers a "pre_shared_key" extension. If clients offer
"pre_shared_key" without a "psk_key_exchange_modes" extension,
servers MUST abort the handshake. [...]

We were not detecting this condition. In practice it seems that the server
would proceed without sending a key share; an openssl client would typically
fail at this point, though any client sending such a malformed ClientHello
might well behave unexpectedly in other ways.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's a little unfortunate that we can currently only use checkhandshake() for successful handshakes, as it does a lot of in-depth inspection of the transcript that would be useful to have even for failure cases. In particular, with just the new ok(TLSProxy::Message->fail() test we don't actually get good coverage of the "missing psk kex modes extension" case, since a handshake failure is a failure whether it's detected by the client or the server.

The issue seems to come into play because we pass as the second argument the DEFAULT_HANDSHAKE value, which is part of a bitmask. We'd have to gain a way to be able to exclude specific messages that are not expected and not process those, which the current classification/bitmask scheme doesn't seem a good fit for.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, I guess technically with only about 20 handshake message types having ever been defined we could just give each one a bit in the bitmask and make the "DEFAULT_HANDSHAKE" be more than one bit ... that would be a bunch of churn in the @handmessages array in each test that uses checkhandshake(), though. Not an exercise to go through speculatively, probably...

@kaduk kaduk added approval: otc review pending branch: 1.1.1 Applies to OpenSSL_1_1_1-stable branch (EOL) branch: master Applies to master branch triaged: bug The issue/pr is/fixes a bug labels Mar 30, 2021
@kaduk
Copy link
Contributor Author

kaduk commented Mar 30, 2021

Tentatively marking this for 1.1.1, since I believe it is a bugfix.

Copy link
Contributor

Choose a reason for hiding this comment

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

Is this right? I'm not familiar with this code, but it looks like this gets filled in by tls_parse_ctos_psk_kex_modes, which is the client's PSK capabilities. But the server might not be using a PSK, in which case the server should still send key_share.

I imagine somewhere in the parameter selection process, there's some logic that decides:

  • Is the server using a PSK?
  • If so, is the server doing so with psk_dhe_ke or psk_ke?

If the answers are (yes, psk_ke), the server should omit key_share. Otherwise, the server should send key_share.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think you are correct and we need another check here; thanks for catching it.
This is obscured in the tests because openssl as a client always sends "psk_key_exchange_modes" even if there is no PSK (and the extra testing I did with a patched version that doesn't send psk_ke_dhe was targeted at the behavior during resumption of a different API, and so didn't test full handshakes).
Unfortunately, it looks like there isn't a great way to check for "did we use a PSK" already, so we might have to add a new field for that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is possible that we can use s->hit to stand in for "server used PSK", but I will want to carefully check the various ways we can get a PSK before pushing that patch to the PR. (It is set somewhat far away from the decision on whether or not to use a PSK, which makes me somewhat uneasy about relying on it.)

Copy link
Member

Choose a reason for hiding this comment

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

Could you please fix this so it can be considered for merge into 3.0?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, this is near the top of my list again (I feel like I have been staggering from one crisis to the next...)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is possible that we can use s->hit to stand in for "server used PSK"

(we can't.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops, spoke too soon. Actually we can, since the psk_server_callback and psk_find_session_callback are only called from tls_parse_ctos_psk(), and the bit I forgot was that we specifically process that extension inside ssl_get_prev_session(), and if the latter returns a session it counts as a 'hit'. (If the callbacks find a session but it's not acceptable in a way that's not a fatal error, the session is not used, not associated with the SSL on return, ssl_get_prev_session() returns 0, and it is not a 'hit'.
I should probably tidy up my notes and put them in the tree somewhere...

@openssl-machine
Copy link
Collaborator

This PR is in a state where it requires action by @openssl/otc but the last update was 30 days ago

It's a MUST-level requirement that if the client sends a pre_shared_key
extension not accompanied by a psk_key_exchange_modes extension, the
server must abort the handshake.  Prior to this commit the server
would continue on.
@kaduk
Copy link
Contributor Author

kaduk commented May 10, 2021

I'm about to push an update, and will put some commentary here first.

The main diff from the original version of the PR is:

diff --git a/ssl/statem/extensions_srvr.c b/ssl/statem/extensions_srvr.c
index 9cddb35a84..6b3b33e239 100644
--- a/ssl/statem/extensions_srvr.c
+++ b/ssl/statem/extensions_srvr.c
@@ -1616,8 +1616,11 @@ EXT_RETURN tls_construct_stoc_key_share(SSL *s, WPACKET *pkt,
         }
         return EXT_RETURN_NOT_SENT;
     }
-    if ((s->ext.psk_kex_mode & TLSEXT_KEX_MODE_FLAG_KE_DHE) == 0) {
-        /* Explicitly not doing DHE; don't send key share */
+    if (s->hit && (s->ext.psk_kex_mode & TLSEXT_KEX_MODE_FLAG_KE_DHE) == 0) {
+        /*
+         * PSK ('hit') and explicitly not doing DHE (if the client sent the
+         * DHE option we always take it); don't send key share.
+         */
         return EXT_RETURN_NOT_SENT;
     }

There is also some fallout in the test suite -- the original version of the PR changed a success to an expected failure, which turns out to have been detecting exactly this bug. It can go back to its original (success) formulation, though I updated the commentary slightly.

I'm also rebasing onto a fresh master since it's been more than a month.

David had in essence asked two questions, and I'll answer them separately.

We can use s->hit to tell if a PSK is being used. For TLS 1.3, the relevant parts are inside tls_parse_ctos_psk(); it is important to note that this is not called as part of the main extension processing flow, but instead is specifically called (along with tls_parse_ctos_psk_kex_modes()) in ssl_get_prev_session(). The internal-cache stateless and stateful ticket processing is done inline here, but we give the application callbacks psk_find_session_callback (new) and psk_server_callback (legacy) [0] a chance to find a session before trying the internal processing. Regardless of where we find a session (from callback or ticket processing), either ssl_get_prev_session() succeeds and we use that session, so it's a "hit", the session is not usable and is removed from the SSL, or ssl_get_prev_session() returns a fatal error and we abort the handshake. So, if we use the PSK-derived session, it is a hit, and we can safely rely on s->hit to reflect that a PSK is being used.

In order to tell which key-exchange mode is being used, we have to jump through some contortions. We rely in a couple ways on the fact that there are only two defined modes at present (pure-psk and psk+DHE). Our server always implements psk+DHE and prefers it if possible, and our server only allows using pure-PSK if SSL_OP_ALLOW_NO_DHE_KEX is set. To reiterate, because of our server preference, if the client offered psk+DHE that is what we will use. So we can only get into this scenario if the client doesn't offer psk+DHE. If the client only offers pure-psk and the option is not set (or if the client offers only things we don't recognize, etc.), then s->ext.psk_kex_mode == 0 and we abort the handshake at the top of tls_parse_ctos_psk() and would never get this far. So if we made it this far and we are using psk-only, that means that the client offered only pure-psk (and maybe some things we don't recognize) and SSL_OP_ALLOW_NO_DHE_KEX is set. If we are using psk-only, TLSEXT_KEX_MODE_FLAG_KE_DHE is not set, since if it was set we'd be doing psk_DHE. Thus, checking that (s->ext.psk_kex_mode & TLSEXT_KEX_MODE_FLAG_KE_DHE) == 0 produces the correct result: there are only the two flags and thus four possible values. If neither flag is set, the handshake has failed already. If the psk+DHE flag is set, we're doing DHE (which is what that code checks), and the only other option is pure-PSK.

[0] The two named callbacks are the only ones relevant for session resumption on the server. Other psk-adjacent functionality exposed to the application includes: psk_client_callback (used to retrieve a session on the client to offer for potential rejection, which as client-only is not relevant for server operation), cert->psk_identity_hint (used in constructing the pre-1.3 ServerKeyExchange), session->psk_identity_hint (to hold the value received in the ServerKeyExchange), psk_identity (set when constructing the ClientKeyExchange, itself based on the output of the psk_client_callback), and psk_use_session_cb (the new way to provide a session on a client as a candidate for resumption, which again as client-only is not relevant here).

@kaduk kaduk force-pushed the psk-compliance branch from e584544 to 7d0f0f3 Compare May 10, 2021 17:52
kaduk added 3 commits May 10, 2021 11:19
TLS 1.3 allows for the "psk_ke" and "psk_dhe_ke" key-exchange modes.
Only the latter mode introduces a new ephemeral (Diffie-Hellman)
key exchange, with the PSK being the only key material used in the
former case.

It's a compliance requirement of RFC 8446 that the server MUST NOT
send a KeyShareEntry when using the "psk_ke" mode, but prior to
this commit we would send a key-share based solely on whether the
client sent one.  This bug goes unnoticed in our internal test suite
since openssl communicating with openssl can never negotiate the
PSK-only key-exchange mode.  However, we should still be compliant
with the spec, so check whether the DHE mode was offered and don't
send a key-share if it wasn't.
One of the scenarios constructed in these tests was erroneously
producing successful handshakes until the previous commits, but should
have been failing.  Update our expected behavior to match the
specification requirements, and adjust the commentary slightly for
a test case relevant for the other preceding commit.
@kaduk kaduk force-pushed the psk-compliance branch from 7d0f0f3 to f94cfcf Compare May 10, 2021 18:20
@kaduk
Copy link
Contributor Author

kaduk commented May 10, 2021

(and the CI pointed out that the make update commit had to be refreshed)

@kaduk
Copy link
Contributor Author

kaduk commented May 11, 2021

@t8m I'm not sure if you were personally interested or just relaying the results of OTC triage, but this should be ready now.

Copy link
Member

@t8m t8m left a comment

Choose a reason for hiding this comment

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

LGTM. I suppose to merge to 1.1.1 this will require a new PR due to conflicts?
It would be good if @mattcaswell could also look.

@t8m t8m added approval: done This pull request has the required number of approvals and removed approval: otc review pending labels May 11, 2021
@kaduk
Copy link
Contributor Author

kaduk commented May 11, 2021

It actually cherry-picks without code conflicts, but runs into the SSLfatal() API change around function codes (and needs a make update to allocate a code for final_psk()). Which is maybe borderline on whether it merits a new PR or not; I'm happy to go either way.

@t8m
Copy link
Member

t8m commented May 11, 2021

IMO that requires a new PR.

@openssl-machine
Copy link
Collaborator

24 hours has passed since 'approval: done' was set, but as this PR has been updated in that time the label 'approval: ready to merge' is not being automatically set. Please review the updates and set the label manually.

@t8m t8m added approval: ready to merge The 24 hour grace period has passed, ready to merge and removed approval: done This pull request has the required number of approvals branch: 1.1.1 Applies to OpenSSL_1_1_1-stable branch (EOL) labels May 12, 2021
openssl-machine pushed a commit that referenced this pull request May 12, 2021
It's a MUST-level requirement that if the client sends a pre_shared_key
extension not accompanied by a psk_key_exchange_modes extension, the
server must abort the handshake.  Prior to this commit the server
would continue on.

Reviewed-by: Tomas Mraz <[email protected]>
(Merged from #14749)
openssl-machine pushed a commit that referenced this pull request May 12, 2021
Reviewed-by: Tomas Mraz <[email protected]>
(Merged from #14749)
openssl-machine pushed a commit that referenced this pull request May 12, 2021
TLS 1.3 allows for the "psk_ke" and "psk_dhe_ke" key-exchange modes.
Only the latter mode introduces a new ephemeral (Diffie-Hellman)
key exchange, with the PSK being the only key material used in the
former case.

It's a compliance requirement of RFC 8446 that the server MUST NOT
send a KeyShareEntry when using the "psk_ke" mode, but prior to
this commit we would send a key-share based solely on whether the
client sent one.  This bug goes unnoticed in our internal test suite
since openssl communicating with openssl can never negotiate the
PSK-only key-exchange mode.  However, we should still be compliant
with the spec, so check whether the DHE mode was offered and don't
send a key-share if it wasn't.

Reviewed-by: Tomas Mraz <[email protected]>
(Merged from #14749)
openssl-machine pushed a commit that referenced this pull request May 12, 2021
One of the scenarios constructed in these tests was erroneously
producing successful handshakes until the previous commits, but should
have been failing.  Update our expected behavior to match the
specification requirements, and adjust the commentary slightly for
a test case relevant for the other preceding commit.

Reviewed-by: Tomas Mraz <[email protected]>
(Merged from #14749)
kaduk added a commit that referenced this pull request May 12, 2021
It's a MUST-level requirement that if the client sends a pre_shared_key
extension not accompanied by a psk_key_exchange_modes extension, the
server must abort the handshake.  Prior to this commit the server
would continue on.

Reviewed-by: Tomas Mraz <[email protected]>
(Merged from #14749)

(cherry picked from commit efe0f31)
kaduk added a commit that referenced this pull request May 12, 2021
TLS 1.3 allows for the "psk_ke" and "psk_dhe_ke" key-exchange modes.
Only the latter mode introduces a new ephemeral (Diffie-Hellman)
key exchange, with the PSK being the only key material used in the
former case.

It's a compliance requirement of RFC 8446 that the server MUST NOT
send a KeyShareEntry when using the "psk_ke" mode, but prior to
this commit we would send a key-share based solely on whether the
client sent one.  This bug goes unnoticed in our internal test suite
since openssl communicating with openssl can never negotiate the
PSK-only key-exchange mode.  However, we should still be compliant
with the spec, so check whether the DHE mode was offered and don't
send a key-share if it wasn't.

Reviewed-by: Tomas Mraz <[email protected]>
(Merged from #14749)

(cherry picked from commit e776858)
kaduk added a commit that referenced this pull request May 12, 2021
One of the scenarios constructed in these tests was erroneously
producing successful handshakes until the previous commits, but should
have been failing.  Update our expected behavior to match the
specification requirements, and adjust the commentary slightly for
a test case relevant for the other preceding commit.

Reviewed-by: Tomas Mraz <[email protected]>
(Merged from #14749)

(cherry picked from commit 80c2561)
@kaduk
Copy link
Contributor Author

kaduk commented May 12, 2021

Merged to master, thanks for the review.
The 1.1.1 version is in #15252 , so removing the 1.1.1 label and closing this one as complete.

@kaduk kaduk closed this May 12, 2021
devnexen pushed a commit to devnexen/openssl that referenced this pull request Jul 7, 2021
It's a MUST-level requirement that if the client sends a pre_shared_key
extension not accompanied by a psk_key_exchange_modes extension, the
server must abort the handshake.  Prior to this commit the server
would continue on.

Reviewed-by: Tomas Mraz <[email protected]>
(Merged from openssl#14749)
devnexen pushed a commit to devnexen/openssl that referenced this pull request Jul 7, 2021
Reviewed-by: Tomas Mraz <[email protected]>
(Merged from openssl#14749)
devnexen pushed a commit to devnexen/openssl that referenced this pull request Jul 7, 2021
TLS 1.3 allows for the "psk_ke" and "psk_dhe_ke" key-exchange modes.
Only the latter mode introduces a new ephemeral (Diffie-Hellman)
key exchange, with the PSK being the only key material used in the
former case.

It's a compliance requirement of RFC 8446 that the server MUST NOT
send a KeyShareEntry when using the "psk_ke" mode, but prior to
this commit we would send a key-share based solely on whether the
client sent one.  This bug goes unnoticed in our internal test suite
since openssl communicating with openssl can never negotiate the
PSK-only key-exchange mode.  However, we should still be compliant
with the spec, so check whether the DHE mode was offered and don't
send a key-share if it wasn't.

Reviewed-by: Tomas Mraz <[email protected]>
(Merged from openssl#14749)
devnexen pushed a commit to devnexen/openssl that referenced this pull request Jul 7, 2021
One of the scenarios constructed in these tests was erroneously
producing successful handshakes until the previous commits, but should
have been failing.  Update our expected behavior to match the
specification requirements, and adjust the commentary slightly for
a test case relevant for the other preceding commit.

Reviewed-by: Tomas Mraz <[email protected]>
(Merged from openssl#14749)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approval: ready to merge The 24 hour grace period has passed, ready to merge branch: master Applies to master branch triaged: bug The issue/pr is/fixes a bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants