Skip to content

Commit 8e8e677

Browse files
cjpattonbwesterb
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 cf7f104 commit 8e8e677

17 files changed

+3201
-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: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ const (
102102
extensionSignatureAlgorithmsCert uint16 = 50
103103
extensionKeyShare uint16 = 51
104104
extensionRenegotiationInfo uint16 = 0xff01
105+
extensionECH uint16 = 0xfe0d // draft-ietf-tls-esni-13
106+
extensionECHOuterExtensions uint16 = 0xfd00 // draft-ietf-tls-esni-13
105107
)
106108

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

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

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

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

717767
// SessionTicketKey is used by TLS servers to provide session resumption.
@@ -775,6 +825,23 @@ type Config struct {
775825
// used for debugging.
776826
KeyLogWriter io.Writer
777827

828+
// ECHEnabled determines whether the ECH extension is enabled for this
829+
// connection.
830+
ECHEnabled bool
831+
832+
// ClientECHConfigs are the parameters used by the client when it offers the
833+
// ECH extension. If ECH is enabled, a suitable configuration is found, and
834+
// the client supports TLS 1.3, then it will offer ECH in this handshake.
835+
// Otherwise, if ECH is enabled, it will send a dummy ECH extension.
836+
ClientECHConfigs []ECHConfig
837+
838+
// ServerECHProvider is the ECH provider used by the client-facing server
839+
// for the ECH extension. If the client offers ECH and TLS 1.3 is
840+
// negotiated, then the provider is used to compute the HPKE context
841+
// (draft-irtf-cfrg-hpke-07), which in turn is used to decrypt the extension
842+
// payload.
843+
ServerECHProvider ECHProvider
844+
778845
// CFEventHandler, if set, is called by the client and server at various
779846
// points during the handshake to handle specific events. This is used
780847
// primarily for collecting metrics.
@@ -888,6 +955,9 @@ func (c *Config) Clone() *Config {
888955
Renegotiation: c.Renegotiation,
889956
KeyLogWriter: c.KeyLogWriter,
890957
SupportDelegatedCredential: c.SupportDelegatedCredential,
958+
ECHEnabled: c.ECHEnabled,
959+
ClientECHConfigs: c.ClientECHConfigs,
960+
ServerECHProvider: c.ServerECHProvider,
891961
CFEventHandler: c.CFEventHandler,
892962
CFControl: c.CFControl,
893963
sessionTicketKeys: c.sessionTicketKeys,
@@ -1077,6 +1147,27 @@ func (c *Config) supportedVersions(isClient bool) []uint16 {
10771147
return versions
10781148
}
10791149

1150+
func (c *Config) supportedVersionsFromMin(isClient bool, minVersion uint16) []uint16 {
1151+
versions := make([]uint16, 0, len(supportedVersions))
1152+
for _, v := range supportedVersions {
1153+
if (c == nil || c.MinVersion == 0) && !debugEnableTLS10 &&
1154+
isClient && v < VersionTLS12 {
1155+
continue
1156+
}
1157+
if c != nil && c.MinVersion != 0 && v < c.MinVersion {
1158+
continue
1159+
}
1160+
if c != nil && c.MaxVersion != 0 && v > c.MaxVersion {
1161+
continue
1162+
}
1163+
if v < minVersion {
1164+
continue
1165+
}
1166+
versions = append(versions, v)
1167+
}
1168+
return versions
1169+
}
1170+
10801171
func (c *Config) maxSupportedVersion(isClient bool) uint16 {
10811172
supportedVersions := c.supportedVersions(isClient)
10821173
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"
@@ -119,6 +120,20 @@ type Conn struct {
119120
activeCall int32
120121

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

124139
// Access to net.Conn methods.
@@ -703,6 +718,12 @@ func (c *Conn) readRecordOrCCS(expectChangeCipherSpec bool) error {
703718
return c.in.setErrorLocked(io.EOF)
704719
}
705720
if c.vers == VersionTLS13 {
721+
if !c.isClient && c.ech.greased && alert(data[1]) == alertECHRequired {
722+
// This condition indicates that the client intended to offer
723+
// ECH, but did not use a known ECH config.
724+
c.ech.offered = true
725+
c.ech.greased = false
726+
}
706727
return c.in.setErrorLocked(&net.OpError{Op: "remote error", Err: alert(data[1])})
707728
}
708729
switch data[0] {
@@ -1347,6 +1368,29 @@ func (c *Conn) Close() error {
13471368
if err := c.conn.Close(); err != nil {
13481369
return err
13491370
}
1371+
1372+
// Resolve ECH status.
1373+
if !c.isClient && c.config.MaxVersion < VersionTLS13 {
1374+
c.handleCFEvent(CFEventECHServerStatus(echStatusBypassed))
1375+
} else if !c.ech.offered {
1376+
if !c.ech.greased {
1377+
c.handleCFEvent(CFEventECHClientStatus(echStatusBypassed))
1378+
} else {
1379+
c.handleCFEvent(CFEventECHClientStatus(echStatusOuter))
1380+
}
1381+
} else {
1382+
c.handleCFEvent(CFEventECHClientStatus(echStatusInner))
1383+
if !c.ech.accepted {
1384+
if len(c.ech.retryConfigs) > 0 {
1385+
c.handleCFEvent(CFEventECHServerStatus(echStatusOuter))
1386+
} else {
1387+
c.handleCFEvent(CFEventECHServerStatus(echStatusBypassed))
1388+
}
1389+
} else {
1390+
c.handleCFEvent(CFEventECHServerStatus(echStatusInner))
1391+
}
1392+
}
1393+
13501394
return alertErr
13511395
}
13521396

@@ -1502,6 +1546,8 @@ func (c *Conn) connectionStateLocked() ConnectionState {
15021546
}
15031547
state.SignedCertificateTimestamps = c.scts
15041548
state.OCSPResponse = c.ocspResponse
1549+
state.ECHAccepted = c.ech.accepted
1550+
state.ECHOffered = c.ech.offered || c.ech.greased
15051551
state.CFControl = c.config.CFControl
15061552
if !c.didResume && c.vers != VersionTLS13 {
15071553
if c.clientFinishedIsFirst {

0 commit comments

Comments
 (0)