Skip to content

tris: prevent sending 0.5RTT data #34

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

Merged
merged 3 commits into from
Dec 4, 2017
Merged

Conversation

Lekensteyn
Copy link
Contributor

@Lekensteyn Lekensteyn commented Sep 21, 2017

Three patches, two of them are needed to interop as client with servers that send session tickets (#50) and are included because of contextual conflicts. The final patch is:


tris: prevent sending 0.5-RTT data

Disable 0.5-RTT as it has weaker security properties than 1-RTT. The
same security considerations from TLS False Start (RFC 7918) apply.

Currently the server Handshake method returns as soon as it has sent its
parameters, but it does not wait for the client to authenticate the
handshake via a Finished message. This broke a test that assumed that
the Handshake message performs a full handshake and also
(unintentionally?) enabled the server to send application data before
the handshake is complete ("0.5-RTT data").

Fix this by moving the implicit Finished message check in the handshake
message reader to the server handshake itself (previously readRecord
would process the Finished message as a side-effect of requesting
recordTypeApplicationData). And in the special case where 0-RTT data is
actually desired, process the Finished message in the ConfirmHandshake
and Read functions.

NOTE: 0.5-RTT is not disabled when the server enables 0-RTT. It is the
server responsibility to use ConfirmHandshake before writing anything.

Explicitly panic when ConfirmHandshake is used for client connections,
this is not the intended use of that API.

@FiloSottile
Copy link
Contributor

FiloSottile commented Sep 21, 2017 via email

@Lekensteyn
Copy link
Contributor Author

While it is technically allowed by the spec, I wanted to be sure that the Handshake method follows the state machine, eventually ending up in the CONNECTED state (and authenticating the client by default).

If an application needs to send 0.5RTT data, I think it should opt-in (via an option that can be added in the future).

@FiloSottile
Copy link
Contributor

FiloSottile commented Sep 21, 2017 via email

@Lekensteyn
Copy link
Contributor Author

One concern is related to denial of service, an attacker can flood Client Hello and if the server believes that the client handshake has been authenticated, it might unnecessarily commit resources.

Another concern is about changing the documented, expected functionality. Handshake should run the handshake protocol and error out if things went wrong. If an attacker replays the CH, then it will only be caught during the next read.

@lziest
Copy link

lziest commented Sep 22, 2017

can we have a reference of the test we want to fix?

13.go Outdated
@@ -189,7 +210,9 @@ func (hs *serverHandshakeState) readClientFinished13() error {
// Any read operation after handshakeRunning and before handshakeConfirmed
// will be holding this lock, which we release as soon as the confirmation
// happens, even if the Read call might do more work.
c.confirmMutex.Unlock()
if !hasHandshakeLock {
Copy link

Choose a reason for hiding this comment

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

call me ignorant, can you explain the logic behind this extra check? I feel this is fixing a bug. But unlock only if we have locked it, right?

Copy link
Contributor Author

@Lekensteyn Lekensteyn Sep 22, 2017

Choose a reason for hiding this comment

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

The Read and ConfirmHandshake methods can run in parallel, that's why this lock was originally needed. If ConfirmHandshake consumes data, Read must not interfere with it.

Since the Handshake method will always be executed before the main Read and ConfirmHandshake functionality gets executed (and thus before taking the confirmMutex lock), it is safe not to acquire to lock in case readClientFinished13 is executed via the handshake (hence the new hasHandshakeLock name).

I could have added a c.confirmMutex.Lock() call in the handshake function, but it would not really be required (and thus I did not do that to avoid the false impression that it is needed for something).

Copy link

Choose a reason for hiding this comment

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

I will read into it, in general check then unlock always sounds racing (pun intended)

@Lekensteyn
Copy link
Contributor Author

can we have a reference of the test we want to fix?

See c03e274 ("tris: add test for TLS 1.3 certificate validation"). The test fails because s.Close() closes the server socket before the client can write its Finished message.

@Lekensteyn
Copy link
Contributor Author

Since 0.5 RTT data has known caveats, I took the conservative approach and disabled it for non-0RTT.

Viewpoints differ, here is one from Antoine who suggests that that 0.5 RTT is used with 0-RTT in QUIC: https://www.ietf.org/mail-archive/web/tls/current/msg19337.html (this scenario will still work even after this patch).

"Any expectations of 0.5 data being directed to a specific client need to be eliminated." -- Hugo
https://www.ietf.org/mail-archive/web/tls/current/msg19317.html

Discussion about how 0.5 is strictly weaker than 1-RTT and how this could cause attacks if a client advertises weak ciphers.
https://www.ietf.org/mail-archive/web/tls/current/msg19299.html

More messages can be found here: https://mailarchive.ietf.org/arch/search/?q=%220.5+RTT%22&f_list=tls

@FiloSottile do you agree with this conservative approach, disabling 0.5RTT data unless 0RTT data is also enabled?

@Lekensteyn
Copy link
Contributor Author

This is essentially the same problem as TLS False Start (https://tools.ietf.org/html/rfc7918), but then in the opposite direction. Its security considerations also apply.

@Lekensteyn
Copy link
Contributor Author

New version pushed with comments added. I will probably change this when implementing draft 22 (which has contextual conflicts), so maybe review later.

@Lekensteyn
Copy link
Contributor Author

Lekensteyn commented Nov 27, 2017

Until this is decided, I will back out the test from commit 0f32ff2 from the client branch to allow client tests to pass.

@grittygrease
Copy link

I'm in favor of this change. When it comes to sending data in an unauthenticated context, the conservative approach should win out unless there are extraordinary gains.

Copy link

@grittygrease grittygrease 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 happy with this change. It will have to be updated for keyUpdate and other post-handshake messages, but this nicely sets the stage.

conn.go Outdated
// At this point, earlyDataReader has read all early data and received
// the end_of_early_data signal. Expect a Finished message.
// Locks held so far: c.confirmMutex, c.in
for c.phase != handshakeConfirmed { // c.phase == discardingEarlyData || c.phase == waitingClientFinished

Choose a reason for hiding this comment

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

This comment is helpful, but looks odd after the {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed this and the other comment, thanks for reviewing

// it, delay the Finished check until HandshakeConfirmed() is
// called or until all early data is Read(). Otherwise, complete
// authenticating the client now (there is no support for
// sending 0.50RTT data to a potential unauthenticated client).

Choose a reason for hiding this comment

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

0.5 RTT instead of 0.50RTT?

@Lekensteyn
Copy link
Contributor Author

Some clarifications on the last patch:

  • 0.5-RTT data is still possible if the server enables 0-RTT. Writing will not wait for the Client Finished if 0-RTT is enabled.
  • Server MUST invoke ConfirmHandshake if it wants to have replay protection (e.g. if a HTTP method is found not to be idempotent).
  • As soon as the server reads more 0-RTT data than is actually available (i.e. when it does two Read calls), it WILL will wait for Client Finished. (This behavior was present before this patch.)

I also happened to came across this boringssl documentation: https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#Early-data
They describe that 0.5-RTT ("half-RTT") is enabled with 0-RTT which seems to match with what this PR is trying to achieve.

Lekensteyn and others added 3 commits December 1, 2017 19:08
Do not assume that every post-handshake messages are HelloRequests that
try to trigger a renegotiation. This could result in a no_renegotiation
(rather than an unexpected_message) alert even for other message types
(like NewSessionTicket).

This change makes the caller of readRecord(recordTypeApplicationData)
responsible for checking for renegotiation in case of a handshake
message, but that is currently already the case. And the condition
"c.phase == waitingClientFinished" can only be hit by the server, so
that won't have break the handshake either.

Related: #50
Prevent unexpected_message alert, PSK resumption is not supported so
ignore the received session ticket as client.
Disable 0.5-RTT as it has weaker security properties than 1-RTT. The
same security considerations from TLS False Start (RFC 7918) apply.

Currently the server Handshake method returns as soon as it has sent its
parameters, but it does not wait for the client to authenticate the
handshake via a Finished message. This broke a test that assumed that
the Handshake message performs a full handshake and also
(unintentionally?) enabled the server to send application data before
the handshake is complete ("0.5-RTT data").

Fix this by moving the implicit Finished message check in the handshake
message reader to the server handshake itself (previously readRecord
would process the Finished message as a side-effect of requesting
recordTypeApplicationData). And in the special case where 0-RTT data is
actually desired, process the Finished message in the ConfirmHandshake
and Read functions.

NOTE: 0.5-RTT is not disabled when the server enables 0-RTT. It is the
server responsibility to use ConfirmHandshake before writing anything.

Explicitly panic when ConfirmHandshake is used for client connections,
this is not the intended use of that API.
@Lekensteyn
Copy link
Contributor Author

(just rebased the patches on latest master, no further changes)

@Lekensteyn Lekensteyn merged commit b1e5fea into master Dec 4, 2017
@Lekensteyn Lekensteyn deleted the pwu/finished-rework branch December 4, 2017 19:18
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