Skip to content

Commit 7a24ba3

Browse files
cjpattonLekensteyn
authored andcommitted
crypto/tls: implement draft-ietf-tls-esni-13
Adds support for draft 13 of the Encrypted ClientHello (ECH) extension for TLS. This requires CIRCL to implement draft 08 or later of the HPKE specification (draft-irtf-cfrg-hpke-08). Adds a CFEvent for reporting when ECH is offered or greased by the client, when ECH is accepted or rejected by the server, and when the outer SNI doesn't match the public name of the ECH config. Missing ECH features: * Record-level padding. * Proper validation of the public name by the client. * Retry after rejection. * PSKs are disabled when ECH is accepted.
1 parent e3dc99b commit 7a24ba3

17 files changed

+3197
-34
lines changed

src/crypto/tls/alert.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ const (
4848
alertUnknownPSKIdentity alert = 115
4949
alertCertificateRequired alert = 116
5050
alertNoApplicationProtocol alert = 120
51+
alertECHRequired alert = 121
5152
)
5253

5354
var alertText = map[alert]string{
@@ -84,6 +85,7 @@ var alertText = map[alert]string{
8485
alertUnknownPSKIdentity: "unknown PSK identity",
8586
alertCertificateRequired: "certificate required",
8687
alertNoApplicationProtocol: "no application protocol",
88+
alertECHRequired: "ECH required",
8789
}
8890

8991
func (e alert) String() string {

src/crypto/tls/common.go

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ const (
101101
extensionSignatureAlgorithmsCert uint16 = 50
102102
extensionKeyShare uint16 = 51
103103
extensionRenegotiationInfo uint16 = 0xff01
104+
extensionECH uint16 = 0xfe0d // draft-ietf-tls-esni-13
105+
extensionECHOuterExtensions uint16 = 0xfd00 // draft-ietf-tls-esni-13
104106
)
105107

106108
// TLS signaling cipher suite values
@@ -223,6 +225,45 @@ const (
223225
// include downgrade canaries even if it's using its highers supported version.
224226
var testingOnlyForceDowngradeCanary bool
225227

228+
// testingTriggerHRR causes the server to intentionally trigger a
229+
// HelloRetryRequest (HRR). This is useful for testing new TLS features that
230+
// change the HRR codepath.
231+
var testingTriggerHRR bool
232+
233+
// testingECHTriggerBypassAfterHRR causes the client to bypass ECH after HRR.
234+
// If available, the client will offer ECH in the first CH only.
235+
var testingECHTriggerBypassAfterHRR bool
236+
237+
// testingECHTriggerBypassBeforeHRR causes the client to bypass ECH before HRR.
238+
// The client will offer ECH in the second CH only.
239+
var testingECHTriggerBypassBeforeHRR bool
240+
241+
// testingECHIllegalHandleAfterHRR causes the client to illegally change the ECH
242+
// extension after HRR.
243+
var testingECHIllegalHandleAfterHRR bool
244+
245+
// testingECHTriggerPayloadDecryptError causes the client to to send an
246+
// inauthentic payload.
247+
var testingECHTriggerPayloadDecryptError bool
248+
249+
// testingECHOuterExtMany causes a client to incorporate a sequence of
250+
// outer extensions into the ClientHelloInner when it offers the ECH extension.
251+
// The "key_share" extension is the only incorporated extension by default.
252+
var testingECHOuterExtMany bool
253+
254+
// testingECHOuterExtNone causes a client to not use the "outer_extension"
255+
// mechanism for ECH. The "key_shares" extension is incorporated by default.
256+
var testingECHOuterExtNone bool
257+
258+
// testingECHOuterExtIncorrectOrder causes the client to send the
259+
// "outer_extension" extension in the wrong order when offering the ECH
260+
// extension.
261+
var testingECHOuterExtIncorrectOrder bool
262+
263+
// testingECHOuterExtIllegal causes the client to send in its
264+
// "outer_extension" extension the codepoint for the ECH extension.
265+
var testingECHOuterExtIllegal bool
266+
226267
// ConnectionState records basic TLS details about the connection.
227268
type ConnectionState struct {
228269
// Version is the TLS version used by the connection (e.g. VersionTLS12).
@@ -291,6 +332,14 @@ type ConnectionState struct {
291332
// RFC 7627, and https://mitls.org/pages/attacks/3SHAKE#channelbindings.
292333
TLSUnique []byte
293334

335+
// ECHAccepted is set if the ECH extension was offered by the client and
336+
// accepted by the server.
337+
ECHAccepted bool
338+
339+
// ECHOffered is set if the ECH extension is present in the ClientHello.
340+
// This means the client has offered ECH or sent GREASE ECH.
341+
ECHOffered bool
342+
294343
// CFControl is used to pass additional TLS configuration information to
295344
// HTTP requests.
296345
//
@@ -710,7 +759,8 @@ type Config struct {
710759

711760
// SessionTicketsDisabled may be set to true to disable session ticket and
712761
// PSK (resumption) support. Note that on clients, session ticket support is
713-
// also disabled if ClientSessionCache is nil.
762+
// also disabled if ClientSessionCache is nil. On clients or servers,
763+
// support is disabled if the ECH extension is enabled.
714764
SessionTicketsDisabled bool
715765

716766
// SessionTicketKey is used by TLS servers to provide session resumption.
@@ -765,6 +815,23 @@ type Config struct {
765815
// used for debugging.
766816
KeyLogWriter io.Writer
767817

818+
// ECHEnabled determines whether the ECH extension is enabled for this
819+
// connection.
820+
ECHEnabled bool
821+
822+
// ClientECHConfigs are the parameters used by the client when it offers the
823+
// ECH extension. If ECH is enabled, a suitable configuration is found, and
824+
// the client supports TLS 1.3, then it will offer ECH in this handshake.
825+
// Otherwise, if ECH is enabled, it will send a dummy ECH extension.
826+
ClientECHConfigs []ECHConfig
827+
828+
// ServerECHProvider is the ECH provider used by the client-facing server
829+
// for the ECH extension. If the client offers ECH and TLS 1.3 is
830+
// negotiated, then the provider is used to compute the HPKE context
831+
// (draft-irtf-cfrg-hpke-07), which in turn is used to decrypt the extension
832+
// payload.
833+
ServerECHProvider ECHProvider
834+
768835
// CFEventHandler, if set, is called by the client and server at various
769836
// points during the handshake to handle specific events. This is used
770837
// primarily for collecting metrics.
@@ -878,6 +945,9 @@ func (c *Config) Clone() *Config {
878945
Renegotiation: c.Renegotiation,
879946
KeyLogWriter: c.KeyLogWriter,
880947
SupportDelegatedCredential: c.SupportDelegatedCredential,
948+
ECHEnabled: c.ECHEnabled,
949+
ClientECHConfigs: c.ClientECHConfigs,
950+
ServerECHProvider: c.ServerECHProvider,
881951
CFEventHandler: c.CFEventHandler,
882952
CFControl: c.CFControl,
883953
sessionTicketKeys: c.sessionTicketKeys,
@@ -1055,6 +1125,23 @@ func (c *Config) supportedVersions() []uint16 {
10551125
return versions
10561126
}
10571127

1128+
func (c *Config) supportedVersionsFromMin(minVersion uint16) []uint16 {
1129+
versions := make([]uint16, 0, len(supportedVersions))
1130+
for _, v := range supportedVersions {
1131+
if c != nil && c.MinVersion != 0 && v < c.MinVersion {
1132+
continue
1133+
}
1134+
if c != nil && c.MaxVersion != 0 && v > c.MaxVersion {
1135+
continue
1136+
}
1137+
if v < minVersion {
1138+
continue
1139+
}
1140+
versions = append(versions, v)
1141+
}
1142+
return versions
1143+
}
1144+
10581145
func (c *Config) maxSupportedVersion() uint16 {
10591146
supportedVersions := c.supportedVersions()
10601147
if len(supportedVersions) == 0 {

src/crypto/tls/conn.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package tls
88

99
import (
1010
"bytes"
11+
"circl/hpke"
1112
"context"
1213
"crypto/cipher"
1314
"crypto/subtle"
@@ -118,6 +119,20 @@ type Conn struct {
118119
activeCall int32
119120

120121
tmp [16]byte
122+
123+
// State used for the ECH extension.
124+
ech struct {
125+
sealer hpke.Sealer // The client's HPKE context
126+
opener hpke.Opener // The server's HPKE context
127+
128+
// The state shared by the client and server.
129+
offered bool // Client offered ECH
130+
greased bool // Client greased ECH
131+
accepted bool // Server accepted ECH
132+
retryConfigs []byte // The retry configurations
133+
configId uint8 // The ECH config id
134+
maxNameLen int // maximum_name_len indicated by the ECH config
135+
}
121136
}
122137

123138
// Access to net.Conn methods.
@@ -695,6 +710,12 @@ func (c *Conn) readRecordOrCCS(expectChangeCipherSpec bool) error {
695710
return c.in.setErrorLocked(io.EOF)
696711
}
697712
if c.vers == VersionTLS13 {
713+
if !c.isClient && c.ech.greased && alert(data[1]) == alertECHRequired {
714+
// This condition indicates that the client intended to offer
715+
// ECH, but did not use a known ECH config.
716+
c.ech.offered = true
717+
c.ech.greased = false
718+
}
698719
return c.in.setErrorLocked(&net.OpError{Op: "remote error", Err: alert(data[1])})
699720
}
700721
switch data[0] {
@@ -1339,6 +1360,29 @@ func (c *Conn) Close() error {
13391360
if err := c.conn.Close(); err != nil {
13401361
return err
13411362
}
1363+
1364+
// Resolve ECH status.
1365+
if !c.isClient && c.config.MaxVersion < VersionTLS13 {
1366+
c.handleCFEvent(CFEventECHServerStatus(echStatusBypassed))
1367+
} else if !c.ech.offered {
1368+
if !c.ech.greased {
1369+
c.handleCFEvent(CFEventECHClientStatus(echStatusBypassed))
1370+
} else {
1371+
c.handleCFEvent(CFEventECHClientStatus(echStatusOuter))
1372+
}
1373+
} else {
1374+
c.handleCFEvent(CFEventECHClientStatus(echStatusInner))
1375+
if !c.ech.accepted {
1376+
if len(c.ech.retryConfigs) > 0 {
1377+
c.handleCFEvent(CFEventECHServerStatus(echStatusOuter))
1378+
} else {
1379+
c.handleCFEvent(CFEventECHServerStatus(echStatusBypassed))
1380+
}
1381+
} else {
1382+
c.handleCFEvent(CFEventECHServerStatus(echStatusInner))
1383+
}
1384+
}
1385+
13421386
return alertErr
13431387
}
13441388

@@ -1484,6 +1528,8 @@ func (c *Conn) connectionStateLocked() ConnectionState {
14841528
}
14851529
state.SignedCertificateTimestamps = c.scts
14861530
state.OCSPResponse = c.ocspResponse
1531+
state.ECHAccepted = c.ech.accepted
1532+
state.ECHOffered = c.ech.offered || c.ech.greased
14871533
state.CFControl = c.config.CFControl
14881534
if !c.didResume && c.vers != VersionTLS13 {
14891535
if c.clientFinishedIsFirst {

0 commit comments

Comments
 (0)