Handle ECH Retry - #9611
Conversation
|
@swankjesse I don't think Conscrypt ECH PR supported this |
|
Quick search for evidence https://go.dev/src/crypto/tls/handshake_client_test.go#L2808 |
|
I'll ask at work tomorrow. |
swankjesse
left a comment
There was a problem hiding this comment.
I’d like to be particularly careful with landing this as it introduces new attack vectors and is not yet possible to test with MockWebServer + JVM Conscrypt.
I think it’s definitely good to have special recovery code for ECH handshake failures. But my first instinct is that special recovery code should mostly be about reducing the opportunity for downgrade attacks.
This is attempting to do something else, and I would like to understand the problem it solves before we land this.
|
Just chiming in here to state that the ECH recovery flow is a pretty important part of the spec, and so should be implemented if OkHttp is to say it supports ECH. The reason why is because there's no other way to recover from a mismatch in ECH config between the client and server (i.e. synchronization issues). It's a performance hit to have to fall into this codepath (and shouldn't be happening in most cases), but it is needed so the consequences of things being out of sync is latency rather than outages. I also asked BoringSSL a long time ago about this when doing the Android implementation, because I had the same question of how important the recovery flow was. Their stance was "This behavior is pretty crucial to allow servers to safely deploy ECH because DNS records can be stale and we need to make it possible for servers to recover from mismatches. The existence of any client that gets this wrong means a server now cannot deploy ECH in a rollback-safe way." |
|
Making it safer for server operators to confidently deploy ECH is good. |
swankjesse
left a comment
There was a problem hiding this comment.
This is excellent. Some feedback and I’d like another look before merge. I promise to not make you wait so long on followups
| val routePlanner = factory.newRoutePlanner(client, address) | ||
| val route = factory.newRoute(address) | ||
| val connectionSpecs = listOf(ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS) | ||
| val socket = createSocketWithEnabledProtocols(TlsVersion.TLS_1_2, TlsVersion.TLS_1_1) |
There was a problem hiding this comment.
This one’s curious 'cause it’s missing TLS 1.3
| fun staleEchConfigIsNotRetried() { | ||
| val rejection = client.echRejectionFrom("https://stale.tls-ech.dev/") | ||
| fun staleEchConfigIsRetried() { | ||
| val body = client.get("https://stale.tls-ech.dev/") |
There was a problem hiding this comment.
Just FYI stale.tls-ech.dev uses port 444 and wrong.tls-ech.dev uses port 445. Not sure if you're using the default port of 443, but if so that should probably be changed to make sure that you're not accidentally getting the default domain of plain tls-ech.dev.
Another option is adding an assertion that you're hitting the correct domain since it also appears on the page (stale.tls-ech.dev vs tls-ech.dev).
| return EchRetryConfig( | ||
| publicHostname = exception.publicHostname ?: return null, | ||
| // An absent retry config list is how a server securely disables ECH. | ||
| // TODO can Conscrypt hand us an empty list, and does it mean the same thing? |
There was a problem hiding this comment.
No, Conscrypt would either hand you null or an actual EchConfigList obj. We do some minimal validation on the Conscrypt side to check that the list isn't empty or null and that its length matches up, before creating any EchConfigList object.
The rest of the validation is done by BoringSSL when we pass it the EchConfigList (that's where all the "exciting" validation logic comes in, e.g. version checking, etc.). That was a conscious design decision because we didn't want to duplicate this logic and have it become out of sync with BoringSSL.
| import okio.ByteString | ||
|
|
||
| /** | ||
| * ECH Retry config. Generally sent by a server when there is a |
There was a problem hiding this comment.
The retry config is normally sent by a server when the ECH configurations have become out of sync (e.g. the TTL on them expired, they rotated to a new ECH config), not from mismatched A/AAAA and HTTPS records, so I would update this KDoc.
For example, Cloudflare has 1 given ECH configuration at a time and rotates it every hour. They have a grace period of 4 hours after they've rotated it, so in theory if you got this old config (e.g. if you cached it past its TTL or something) and tried to use it after the grace period expired, this is where the server should be sending you a retry config.
A few assorted changes