Skip to content

Commit 7c96cb6

Browse files
Christopher Woodcjpatton
authored andcommitted
crypto/tls: Move draft-ietf-tls-esni-09 to 10
This change adds support for ECH-10 and removes support for ECH-09. The primary changes are moving to HPKE-08 and changing the ECHConfig identifier from a client-computed value to a server-chosen value. ECHProviders MUST use rejection sampling in choosing the configuration identifier so as to not introduce conflicts.
1 parent 76b537c commit 7c96cb6

File tree

9 files changed

+84
-123
lines changed

9 files changed

+84
-123
lines changed

src/crypto/tls/common.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,9 @@ const (
103103
extensionSignatureAlgorithmsCert uint16 = 50
104104
extensionKeyShare uint16 = 51
105105
extensionRenegotiationInfo uint16 = 0xff01
106-
extensionECH uint16 = 0xfe09 // draft-ietf-tls-esni-09
107-
extensionECHIsInner uint16 = 0xda09 // draft-ietf-tls-esni-09
108-
extensionECHOuterExtensions uint16 = 0xfd00 // draft-ietf-tls-esni-09
106+
extensionECH uint16 = 0xfe0a // draft-ietf-tls-esni-10
107+
extensionECHIsInner uint16 = 0xda09 // draft-ietf-tls-esni-10
108+
extensionECHOuterExtensions uint16 = 0xfd00 // draft-ietf-tls-esni-10
109109
)
110110

111111
// TLS signaling cipher suite values

src/crypto/tls/conn.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,11 @@ type Conn struct {
131131
retryConfigs []byte // The retry configurations
132132

133133
// The client's state, used in case of HRR.
134-
innerRandom []byte // ClientHelloInner.random
135-
publicName string // ECHConfig.contents.public_name
136-
suite echCipherSuite // ClientECH.cipher_suite
137-
dummy []byte // serialized ClientECH when greasing ECH
134+
innerRandom []byte // ClientHelloInner.random
135+
publicName string // ECHConfig.contents.public_name
136+
configId uint8 // ECHConfig.contents.key_config.config_id
137+
suite hpkeSymmetricCipherSuite // ClientECH.cipher_suite
138+
dummy []byte // serialized ClientECH when greasing ECH
138139
}
139140

140141
// Set by the client and server when an HRR message was sent in this

src/crypto/tls/ech.go

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ const (
1717
echAcceptConfirmationLabel = "ech accept confirmation"
1818

1919
// Constants for HPKE operations
20-
echHpkeInfoConfigId = "tls ech config id"
21-
echHpkeInfoSetup = "tls ech"
20+
echHpkeInfoSetup = "tls ech"
2221
)
2322

2423
// TODO(cjpatton): "[When offering ECH, the client] MUST NOT offer to resume any
@@ -60,24 +59,20 @@ func (c *Conn) echOfferOrGrease(helloBase *clientHelloMsg) (hello, helloInner *c
6059
}
6160

6261
// Compute artifacts that are reused across HRR.
63-
var enc, configId []byte
62+
var enc []byte
6463
if c.ech.sealer == nil {
6564
// HPKE context.
6665
enc, c.ech.sealer, err = echConfig.setupSealer(config.rand())
6766
if err != nil {
6867
return nil, nil, fmt.Errorf("tls: ech: %s", err)
6968
}
7069

71-
// ClientECH.config_id.
72-
_, kdfId, _ := c.ech.sealer.Suite().Params()
73-
configId, err = echConfig.id(kdfId)
74-
if err != nil {
75-
return nil, nil, fmt.Errorf("tls: ech: %s", err)
76-
}
77-
7870
// ECHConfig.contents.public_name.
7971
c.ech.publicName = string(echConfig.rawPublicName)
8072

73+
// ECHConfig.contents.key_config.config_id.
74+
c.ech.configId = echConfig.configId
75+
8176
// ClientHelloInner.random.
8277
c.ech.innerRandom = make([]byte, 32)
8378
if _, err = io.ReadFull(config.rand(), c.ech.innerRandom); err != nil {
@@ -148,8 +143,8 @@ func (c *Conn) echOfferOrGrease(helloBase *clientHelloMsg) (hello, helloInner *c
148143
_, kdfId, aeadId := c.ech.sealer.Suite().Params()
149144
ech.handle.suite.kdfId = uint16(kdfId)
150145
ech.handle.suite.aeadId = uint16(aeadId)
151-
ech.handle.configId = configId // Empty after HRR
152-
ech.handle.enc = enc // Empty after HRR
146+
ech.handle.configId = echConfig.configId
147+
ech.handle.enc = enc // Empty after HRR
153148
helloOuterAad := echEncodeClientHelloOuterAAD(hello.marshal(), ech.handle.marshal())
154149
if helloOuterAad == nil {
155150
return nil, nil, errors.New("tls: ech: encoding of ClientHelloOuterAAD failed")
@@ -221,7 +216,7 @@ func (c *Conn) echAcceptOrReject(hello *clientHelloMsg) (*clientHelloMsg, error)
221216
}
222217
if c.hrrTriggered && c.ech.offered &&
223218
(ech.handle.suite != c.ech.suite ||
224-
len(ech.handle.configId) > 0 ||
219+
ech.handle.configId != c.ech.configId ||
225220
len(ech.handle.enc) > 0) {
226221
// The context handle shouldn't change across HRR.
227222
c.sendAlert(alertIllegalParameter)
@@ -252,7 +247,7 @@ func (c *Conn) echAcceptOrReject(hello *clientHelloMsg) (*clientHelloMsg, error)
252247

253248
// Check if the outer SNI matches the public name of any ECH config
254249
// advertised by the client-facing server. As of
255-
// draft-ietf-tls-esni-09, the client is required to use the ECH
250+
// draft-ietf-tls-esni-10, the client is required to use the ECH
256251
// config's public name as the outer SNI. Although there's no real
257252
// reason for the server to enforce this, it worth noting it when it
258253
// happens.
@@ -377,14 +372,14 @@ func (ech *echClient) marshal() []byte {
377372
return b.BytesOrPanic()
378373
}
379374

380-
// echContexttHandle represents the prefix of a ClientECH structure used by
375+
// echContextHandle represents the prefix of a ClientECH structure used by
381376
// the server to compute the HPKE context.
382377
type echContextHandle struct {
383378
raw []byte
384379

385380
// Parsed from raw
386-
suite echCipherSuite
387-
configId []byte
381+
suite hpkeSymmetricCipherSuite
382+
configId uint8
388383
enc []byte
389384
}
390385

@@ -395,9 +390,7 @@ func (handle *echContextHandle) marshal() []byte {
395390
var b cryptobyte.Builder
396391
b.AddUint16(handle.suite.kdfId)
397392
b.AddUint16(handle.suite.aeadId)
398-
b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
399-
b.AddBytes(handle.configId)
400-
})
393+
b.AddUint8(handle.configId)
401394
b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
402395
b.AddBytes(handle.enc)
403396
})
@@ -408,19 +401,18 @@ func echReadContextHandle(s *cryptobyte.String, handle *echContextHandle) bool {
408401
var t cryptobyte.String
409402
if !s.ReadUint16(&handle.suite.kdfId) || // cipher_suite.kdf_id
410403
!s.ReadUint16(&handle.suite.aeadId) || // cipher_suite.aead_id
411-
!s.ReadUint8LengthPrefixed(&t) || // config_id
412-
!t.ReadBytes(&handle.configId, len(t)) ||
404+
!s.ReadUint8(&handle.configId) || // config_id
413405
!s.ReadUint16LengthPrefixed(&t) || // enc
414406
!t.ReadBytes(&handle.enc, len(t)) {
415407
return false
416408
}
417409
return true
418410
}
419411

420-
// echCipherSuite represents an ECH ciphersuite, a KDF/AEAD algorithm pair. This
412+
// hpkeSymmetricCipherSuite represents an ECH ciphersuite, a KDF/AEAD algorithm pair. This
421413
// is different from an HPKE ciphersuite, which represents a KEM/KDF/AEAD
422414
// triple.
423-
type echCipherSuite struct {
415+
type hpkeSymmetricCipherSuite struct {
424416
kdfId, aeadId uint16
425417
}
426418

@@ -446,11 +438,12 @@ func echGenerateDummyExt(rand io.Reader) ([]byte, error) {
446438
var ech echClient
447439
ech.handle.suite.kdfId = uint16(kdf)
448440
ech.handle.suite.aeadId = uint16(aead)
449-
ech.handle.configId = make([]byte, 8)
450-
_, err = io.ReadFull(rand, ech.handle.configId)
441+
randomByte := make([]byte, 1)
442+
_, err = io.ReadFull(rand, randomByte)
451443
if err != nil {
452444
return nil, fmt.Errorf("tls: grease ech:: %s", err)
453445
}
446+
ech.handle.configId = randomByte[0]
454447
ech.handle.enc, _, err = sender.Setup(rand)
455448
if err != nil {
456449
return nil, fmt.Errorf("tls: grease ech:: %s", err)

src/crypto/tls/ech_config.go

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@ type ECHConfig struct {
2020

2121
// Parsed from raw
2222
version uint16
23+
configId uint8
2324
rawPublicName []byte
2425
rawPublicKey []byte
2526
kemId uint16
26-
suites []echCipherSuite
27+
suites []hpkeSymmetricCipherSuite
2728
maxNameLen uint16
2829
ignoredExtensions []byte
2930
}
@@ -87,11 +88,10 @@ func echMarshalConfigs(configs []ECHConfig) ([]byte, error) {
8788

8889
func readConfigContents(contents *cryptobyte.String, config *ECHConfig) bool {
8990
var t cryptobyte.String
90-
if !contents.ReadUint16LengthPrefixed(&t) ||
91-
!t.ReadBytes(&config.rawPublicName, len(t)) ||
91+
if !contents.ReadUint8(&config.configId) ||
92+
!contents.ReadUint16(&config.kemId) ||
9293
!contents.ReadUint16LengthPrefixed(&t) ||
9394
!t.ReadBytes(&config.rawPublicKey, len(t)) ||
94-
!contents.ReadUint16(&config.kemId) ||
9595
!contents.ReadUint16LengthPrefixed(&t) ||
9696
len(t)%4 != 0 {
9797
return false
@@ -104,10 +104,12 @@ func readConfigContents(contents *cryptobyte.String, config *ECHConfig) bool {
104104
// This indicates an internal bug.
105105
panic("internal error while parsing contents.cipher_suites")
106106
}
107-
config.suites = append(config.suites, echCipherSuite{kdfId, aeadId})
107+
config.suites = append(config.suites, hpkeSymmetricCipherSuite{kdfId, aeadId})
108108
}
109109

110110
if !contents.ReadUint16(&config.maxNameLen) ||
111+
!contents.ReadUint16LengthPrefixed(&t) ||
112+
!t.ReadBytes(&config.rawPublicName, len(t)) ||
111113
!contents.ReadUint16LengthPrefixed(&t) ||
112114
!t.ReadBytes(&config.ignoredExtensions, len(t)) ||
113115
!contents.Empty() {
@@ -136,7 +138,7 @@ func (config *ECHConfig) setupSealer(rand io.Reader) (enc []byte, sealer hpke.Se
136138

137139
// isPeerCipherSuiteSupported returns true if this configuration indicates
138140
// support for the given ciphersuite.
139-
func (config *ECHConfig) isPeerCipherSuiteSupported(suite echCipherSuite) bool {
141+
func (config *ECHConfig) isPeerCipherSuiteSupported(suite hpkeSymmetricCipherSuite) bool {
140142
for _, configSuite := range config.suites {
141143
if suite == configSuite {
142144
return true
@@ -160,16 +162,3 @@ func (config *ECHConfig) selectSuite() (hpke.Suite, error) {
160162
}
161163
return hpke.Suite{}, errors.New("could not negotiate a ciphersuite")
162164
}
163-
164-
// id returns the configuration identifier for the given KDF.
165-
func (config *ECHConfig) id(kdf hpke.KDF) ([]byte, error) {
166-
if config.raw == nil {
167-
panic("config.raw not set")
168-
}
169-
if !kdf.IsValid() {
170-
return nil, errors.New("KDF algorithm not supported")
171-
}
172-
id := kdf.Expand(kdf.Extract(config.raw, nil),
173-
[]byte(echHpkeInfoConfigId), 8)
174-
return id, nil
175-
}

src/crypto/tls/ech_provider.go

Lines changed: 14 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@ import (
1212
"golang.org/x/crypto/cryptobyte"
1313
)
1414

15-
const (
16-
maxConfigIdLen = 255
17-
)
18-
1915
// ECHProvider specifies the interface of an ECH service provider that decrypts
2016
// the ECH payload on behalf of the client-facing server. It also defines the
2117
// set of acceptable ECH configurations.
@@ -26,7 +22,7 @@ type ECHProvider interface {
2622
//
2723
// handle encodes the parameters of the client's "encrypted_client_hello"
2824
// extension that are needed to construct the context. In
29-
// draft-ietf-tls-esni-09, these are the ECH cipher suite, the identity of
25+
// draft-ietf-tls-esni-10, these are the ECH cipher suite, the identity of
3026
// the ECH configuration, and the encapsulated key.
3127
//
3228
// version is the version of ECH indicated by the client.
@@ -94,37 +90,24 @@ type EXP_ECHKeySet struct {
9490
configs []byte
9591

9692
// Maps a configuration identifier to its secret key.
97-
sk map[[maxConfigIdLen + 1]byte]EXP_ECHKey
93+
sk map[uint8]EXP_ECHKey
9894
}
9995

10096
// EXP_NewECHKeySet constructs an EXP_ECHKeySet.
10197
func EXP_NewECHKeySet(keys []EXP_ECHKey) (*EXP_ECHKeySet, error) {
98+
if len(keys) > 255 {
99+
return nil, fmt.Errorf("tls: ech provider: unable to support more than 255 ECH configurations at once")
100+
}
101+
102102
keySet := new(EXP_ECHKeySet)
103-
keySet.sk = make(map[[maxConfigIdLen + 1]byte]EXP_ECHKey)
103+
keySet.sk = make(map[uint8]EXP_ECHKey)
104104
configs := make([]byte, 0)
105105
for _, key := range keys {
106-
// Compute the set of KDF algorithms supported by this configuration.
107-
kdfIds := make(map[uint16]bool)
108-
for _, suite := range key.config.suites {
109-
kdfIds[suite.kdfId] = true
110-
}
111-
112-
// Compute the configuration identifier for each KDF.
113-
for kdfId, _ := range kdfIds {
114-
configId, err := key.config.id(hpke.KDF(kdfId))
115-
if err != nil {
116-
return nil, err
117-
}
118-
119-
var b cryptobyte.Builder
120-
b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
121-
b.AddBytes(configId)
122-
})
123-
var id [maxConfigIdLen + 1]byte // Initialized to zero
124-
copy(id[:], b.BytesOrPanic())
125-
keySet.sk[id] = key
106+
if _, ok := keySet.sk[key.config.configId]; ok {
107+
return nil, fmt.Errorf("tls: ech provider: ECH config conflict for configId %d", key.config.configId)
126108
}
127109

110+
keySet.sk[key.config.configId] = key
128111
configs = append(configs, key.config.raw...)
129112
}
130113

@@ -144,7 +127,7 @@ func (keySet *EXP_ECHKeySet) GetDecryptionContext(rawHandle []byte, version uint
144127
res.RetryConfigs = keySet.configs
145128

146129
// Ensure we know how to proceed, i.e., the caller has indicated a supported
147-
// version of ECH. Currently only draft-ietf-tls-esni-09 is supported.
130+
// version of ECH. Currently only draft-ietf-tls-esni-10 is supported.
148131
if version != extensionECH {
149132
res.Status = ECHProviderAbort
150133
res.Alert = uint8(alertInternalError)
@@ -165,13 +148,7 @@ func (keySet *EXP_ECHKeySet) GetDecryptionContext(rawHandle []byte, version uint
165148
handle.raw = rawHandle
166149

167150
// Look up the secret key for the configuration indicated by the client.
168-
var id [maxConfigIdLen + 1]byte // Initialized to zero
169-
var b cryptobyte.Builder
170-
b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
171-
b.AddBytes(handle.configId)
172-
})
173-
copy(id[:], b.BytesOrPanic())
174-
key, ok := keySet.sk[id]
151+
key, ok := keySet.sk[handle.configId]
175152
if !ok {
176153
res.Status = ECHProviderReject
177154
res.RetryConfigs = keySet.configs
@@ -227,7 +204,7 @@ func (keySet *EXP_ECHKeySet) GetDecryptionContext(rawHandle []byte, version uint
227204
//
228205
// struct {
229206
// opaque sk<0..2^16-1>;
230-
// ECHConfig config<0..2^16>; // draft-ietf-tls-esni-09
207+
// ECHConfig config<0..2^16>; // draft-ietf-tls-esni-10
231208
// } ECHKey;
232209
type EXP_ECHKey struct {
233210
sk kem.PrivateKey
@@ -291,7 +268,7 @@ KeysLoop:
291268

292269
// setupOpener computes the HPKE context used by the server in the ECH
293270
// extension.i
294-
func (key *EXP_ECHKey) setupOpener(enc []byte, suite echCipherSuite) (hpke.Opener, error) {
271+
func (key *EXP_ECHKey) setupOpener(enc []byte, suite hpkeSymmetricCipherSuite) (hpke.Opener, error) {
295272
if key.config.raw == nil {
296273
panic("raw config not set")
297274
}

0 commit comments

Comments
 (0)