Draft: draft-ietf-tls-esni

Commentary: The first commit shaves 33 bytes ( or 49 with SHA-384 ) from the ciphertext by replacing inner_digest with ClientHelloOuterAAD . This also allows for more compact ClientHelloInner encodings when non - extension fields are expected to match . The second commit shaves a further 32 bytes . We could probably further optimize the encoding but I 've left it alone here . In particular , if we merge , all non - extension fields may as well be implicit . We could then replace the encryption payload with a plain extensions block . 
 I guess that does n't quite work , but we could replace it with the new random value , and an extensions block . Something like : Anyway , a topic for a later PR , depending on how strongly we want to match cipher suites for . The size savings are a bit more marginal beyond that . 
 I like this change a lot . At some point while implementing ECH , I noted that it would be nice to have an explicit binding of CHOuter to the handshake in case of ECH acceptance . This provides that . 
 Rebased and review comments addressed . The PR should also no longer be in a confusing state if you look at the diff . 
 We previously decided not to bind the outer ClientHello to the ECH decryption , because doing so is rather a hassle . We 'd need something similar to the TLS 1.3 PSK binder mess , with the complexity of ordering relative to the PSK binders themselves . Additionally , while the binding helps with some of the do n't stick out properties , there were other attacks on distinguishability anyway . So , instead , we mitigated the inner ClientHello malleability with OuterExtensions.innerdigest and left the outer ClientHello unbound . However , we 've since decided not to allow PSKs in the outer ClientHello . Perhaps we should revisit this . Suppose the AD parameter to the ECH decryption were the entire outer ClientHello , minus the ECH extension . I believe this means we no longer need OuterExtensions.innerdigest . That would give an immediate 33 - byte savings . It would then unlock a further 32 byte savings by amending to say : Client always encodes an empty legacysessionid in ECH plaintext . Client - facing server copies outer legacysessionid to inner ClientHello as part of decompression . ( The reason we ca n't do this easily right now is that , if the inner ClientHello lacks outerextension , nothing 's authenticating legacysessionid . Since the field in TLS 1.3 is all but ignored , it 's " obvious " that the malleability does n't matter , but it 's nicer to not need to think about such things . ) From looking at Chrome 's ClientHellos and working through what we 'd send ( drop 1.2 - only extensions , compress most ) , it looks like the inner ClientHello will be maybe 130 - 140 bytes , plus a padded servername . The 65 bytes above would almost halve the overhead . In some sense what 's going on here is our 16 - byte tag on the AEAD in the ECH ciphertext is going to waste . We did n't authenticate everything that goes into reconstructing the inner ClientHello ( outerextensions ) , so instead we authenticate another hash ( OuterExtensions.innerdigest ) and check that afterwards . ( To that end , if we do this , we probably need to say the server MUST reject encryptedclienthello showing up in outer_extensions , that way all inputs to decompression are authenticated . Equivalently , decompression is done relative to the ECH - extension - less AD input rather than the original outer ClientHello . ) 
 I like this idea . How do you think the AD ought to be structured ? The ClientHelloOuter , but with an an empty " encryptedclienthello " extension ? 
 Yeah , that 's seems the simplest to me . Filling it with zeros , as pointed out to me , requires predicting the length of the encrypted inner ClientHello . This is n't strictly impossible ( ADs do n't change ciphertext length ) , but it seems needlessly fussy and ECH is already requiring we be able re - encode ClientHellos , lengths and all . Note this assumes there are no computed PSK binders in the outer ClientHello because otherwise there 's a circular dependency . ( If we wanted to allow that , the AD would need to zero / trim the binders or so . ) 
 Another benefit is it stops the attacker from probing information about compressed extensions . Currently an attacker can learn which extensions are compressed in the inner ClientHello by removing them from the outer ClientHello and seeing if the server still responds . This is n't a huge deal , as compressed extensions are assumed public , but this way we remove that vector and leave only the fuzzier signal from the ciphertext length . 
 On second thought , a lot of implementations will need to do that prediction anyway . The workaround for the old F5 bug means we 'll need to know the eventual size for the padding extension . ( On the other hand , you can probably assume anyone doing ECH is not running those things . On the other hand , turning off the workaround would stick out . ) Ah well . We did it for PSK already . ( I wonder if we can do drop that workaround now that TLS 1.0 and 1.1 are gone ... ) 
 What 's the " old F5 bug " ? What 's the value in authenticating the length of the " encryptedclienthello " extension ? This is authenticated by the handshake already , no ? 
 It 's why we have a padding extension . There is n't . It 's just a question of what 's easier to deal with . Filling it with zeros means that , when computing the AD on the receiving side , you do n't need to fully reconstruct a modified outer ClientHello with updated length prefixes . We avoided this when computing the PSK binder in RFC8446 . It 's a mild hassle but not a huge deal . More importantly , we 've thoroughly given up on that elsewhere in the spec . The F5 padding business also means computing this AD will likely take some length prediction anyway , but so it goes . 
 I agree , it 's not a big hassle . 
 Rather than make the extension empty , can we just omit the and fields ? ( The and should arguably be included , a la ) . 
 That works , though it 's even more fussy for the client to construct , since it needs to half - encode the ECH extension before fully encoding it . is already bound into HPKE itself . is less clear , but I thought our model already said all this did n't matter ? Is n't the binding here just to replace and provide malleability ? If there 's something more going on , I 'd like us to have some principled answer here , so we can answer questions like ... what about the handshake transcript , which may not be empty on  ? If we do that , what 's going on with echhrr_key ? 
 The principle I was following is : include as much as possible in the AD . Yeah , it 's more of a pain , and we 've gotten away with no binding whatsoever up to now , so maybe we do n't need it . I 'm happy to start with an empty ECH extension and then consider the other parts later , if that 'll help . 
 I do n't feel very strongly either way . 
 Let 's just do the simpler thing now , then ! :-)
Mailing list commentary: It is a little bit more calculation than that and related to some historic reasons , aka SSLv2 . For SSL records , the SSLv3 and TLS ClientHello headers are as follows : If this is interpreted as an SSLv2 header , it will be considered as a 3 byte header : The value for Client Hello message type is  which is 1 . When there is an SSLv3 / TLS client - hello of length 256 - 511 bytes , this is ambiguous as " message type " is 1 or it is the " length high bits " to be 1 . Out implementation before the patch was to prefer SSLv2 and thus the issue . As I am explaining this in detail , I would say that another work around on this would be making a client hello that exceeds 512 in length .
Paragraph (version A):
OuterExtensions consists of one or more ExtensionType values, each of
which reference an extension in ClientHelloOuter, and a digest of the
complete ClientHelloInner.
Paragraph (version A):
where "inner" is the ClientHelloInner structure and "Extract",
"Expand", and "Nh" are as defined by the KDF API in I-D.irtf-cfrg-
hpke.  Then, the client removes and replaces extensions from
EncodedClientHelloInner with a single "outer_extensions" extension.
Removed extensions MUST be ordered consecutively in ClientHelloInner.
The list of outer extensions, OuterExtensions.outer_extensions,
includes those which were removed from EncodedClientHelloInner, in
the order in which they were removed.
Paragraph (version A):
The client-facing server computes ClientHelloInner by reversing this
process.  It scans EncodedClientHelloInner for an "outer_extensions"
extension.  If there is none, the ClientHelloInner is the
EncodedClientHelloInner.  Otherwise, it replaces the extension with
the corresponding sequence of extensions in the ClientHelloOuter.  If
any referenced extensions are missing, the server MUST abort the
connection with an "illegal_parameter" alert.  The server then
computes the digest of the reconstructed ClientHelloInner.  If the
digest does not equal OuterExtensions.inner_digest, then the server
MUST abort the connection with a "decrypt_error" alert.
Paragraph (version B):
OuterExtensions consists of one or more ExtensionType values, each of
which reference an extension in ClientHelloOuter.
Paragraph (version B):
When sending ClientHello, the client first computes ClientHelloInner,
including any PSK binders.  It then computes a new value, the
EncodedClientHelloInner, by first making a copy of ClientHelloInner.
It then replaces the legacy_session_id field with an empty string.
Paragraph (version B):
The client then MAY substitute extensions which it knows will be
duplicated in ClientHelloOuter.  To do so, the client removes and
replaces extensions from EncodedClientHelloInner with a single
"outer_extensions" extension.  Removed extensions MUST be ordered
consecutively in ClientHelloInner.  The list of outer extensions,
OuterExtensions, includes those which were removed from
EncodedClientHelloInner, in the order in which they were removed.
Paragraph (version B):
The client-facing server computes ClientHelloInner by reversing this
process.  First it makes a copy of EncodedClientHelloInner and copies
the legacy_session_id field from ClientHelloOuter.  It then looks for
an "outer_extensions" extension.  If found, it replaces the extension
with the corresponding sequence of extensions in the
ClientHelloOuter.  If any referenced extensions are missing or if
"encrypted_client_hello" appears in the list, the server MUST abort
the connection with an "illegal_parameter" alert.
Paragraph (version B):
Note the decompression process in encoding-inner forbids
"encrypted_client_hello" in OuterExtensions.  This ensures the
unauthenticated portion of ClientHelloOuter is not incorporated into
ClientHelloInner.
Paragraph (version A):
If it intends to compress any extensions (see outer-extensions),
it MUST order those extensions consecutively.
Paragraph (version B):
If it intends to compress any extensions (see encoding-inner), it
MUST order those extensions consecutively.
Paragraph (version A):
If implementing TLS 1.3's compatibility mode (see Appendix D.4 of
RFC8446), it MUST copy the legacy_session_id field from
ClientHelloInner.  This allows the server to echo the correct
session ID when ECH is negotiated.
Paragraph (version B):
It MUST copy the legacy_session_id field from ClientHelloInner.
This allows the server to echo the correct session ID for TLS
negotiated.
Paragraph (version B):
To encrypt EncodedClientHelloInner, the client first computes
ClientHelloOuterAAD as described in authenticating-outer.  Note this
requires the "encrypted_client_hello" be computed after all other
extensions.  In particular, this is possible because the
"pre_shared_key" extension is forbidden in ClientHelloOuter.
Paragraph (version B):
ClientHelloOuterAAD is computed from the second ClientHelloOuter as
described in authenticating-outer.
Paragraph (version A):
If decryption fails, the server MUST abort the connection with a
"decrypt_error" alert.  Otherwise, the server reconstructs
ClientHelloInner from EncodedClientHelloInner, as described in outer-
extensions.
Paragraph (version B):
ClientHelloOuterAAD is computed from ClientHelloOuter as described in
authenticating-outer.  If decryption fails, the server MUST abort the
connection with a "decrypt_error" alert.  Otherwise, the server
reconstructs ClientHelloInner from EncodedClientHelloInner, as
described in encoding-inner.
Paragraph (version A):
Values which are independent of the true server name, or other
information the client wishes to protect, MAY be included in
ClientHelloOuter.  If they match the corresponding ClientHelloInner,
they MAY be compressed as described in outer-extensions.  However,
note the payload length reveals information about which extensions
are compressed, so inner extensions which only sometimes match the
corresponding outer extension SHOULD NOT be compressed.
Paragraph (version B):
Values which are independent of the true server name, or other
information the client wishes to protect, MAY be included in
ClientHelloOuter.  If they match the corresponding ClientHelloInner,
they MAY be compressed as described in encoding-inner.  However, note
the payload length reveals information about which extensions are
compressed, so inner extensions which only sometimes match the
corresponding outer extension SHOULD NOT be compressed.
Paragraph (version A):
This attack aims to leak information about secret parts of the
encrypted ClientHello by adding attacker-controlled parameters and
observing the server's response.  In particular, the compression
mechanism described in outer-extensions references parts of a
potentially attacker-controlled ClientHelloOuter to construct
ClientHelloInner, or a buggy server may incorrectly apply parameters
from ClientHelloOuter to the handshake.
Paragraph (version B):
This attack aims to leak information about secret parts of the
encrypted ClientHello by adding attacker-controlled parameters and
observing the server's response.  In particular, the compression
mechanism described in encoding-inner references parts of a
potentially attacker-controlled ClientHelloOuter to construct
ClientHelloInner, or a buggy server may incorrectly apply parameters
from ClientHelloOuter to the handshake.
Paragraph (version A):
ECH mitigates this attack by only using ClientHelloOuter to compute
ClientHelloInner and authenticating all of ClientHelloInner with the
HPKE AEAD.  If any extensions are compressed as in outer-extensions,
the OuterExtensions.inner_digest field authenticates the decompressed
result.  If none are compressed, the entire ClientHello is encrypted
and authenticated directly.  An earlier iteration of this
specification only encrypted and authenticated the "server_name"
extension, which left the overall ClientHello vulnerable to an
analogue of this attack.
Paragraph (version B):
ECH mitigates this attack by only negotiating TLS parameters from
ClientHelloInner and authenticating all inputs to the
ClientHelloInner (EncodedClientHelloInner and ClientHelloOuter) with
the HPKE AEAD.  See authenticating-outer.  An earlier iteration of
this specification only encrypted and authenticated the "server_name"
extension, which left the overall ClientHello vulnerable to an
analogue of this attack.