Skip to content

Commit

Permalink
crypto/tls: Move draft-ietf-tls-esni-09 to 10
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Christopher Wood authored and cjpatton committed Mar 19, 2021
1 parent 76b537c commit 7c96cb6
Show file tree
Hide file tree
Showing 9 changed files with 84 additions and 123 deletions.
6 changes: 3 additions & 3 deletions src/crypto/tls/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,9 @@ const (
extensionSignatureAlgorithmsCert uint16 = 50
extensionKeyShare uint16 = 51
extensionRenegotiationInfo uint16 = 0xff01
extensionECH uint16 = 0xfe09 // draft-ietf-tls-esni-09
extensionECHIsInner uint16 = 0xda09 // draft-ietf-tls-esni-09
extensionECHOuterExtensions uint16 = 0xfd00 // draft-ietf-tls-esni-09
extensionECH uint16 = 0xfe0a // draft-ietf-tls-esni-10
extensionECHIsInner uint16 = 0xda09 // draft-ietf-tls-esni-10
extensionECHOuterExtensions uint16 = 0xfd00 // draft-ietf-tls-esni-10
)

// TLS signaling cipher suite values
Expand Down
9 changes: 5 additions & 4 deletions src/crypto/tls/conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,11 @@ type Conn struct {
retryConfigs []byte // The retry configurations

// The client's state, used in case of HRR.
innerRandom []byte // ClientHelloInner.random
publicName string // ECHConfig.contents.public_name
suite echCipherSuite // ClientECH.cipher_suite
dummy []byte // serialized ClientECH when greasing ECH
innerRandom []byte // ClientHelloInner.random
publicName string // ECHConfig.contents.public_name
configId uint8 // ECHConfig.contents.key_config.config_id
suite hpkeSymmetricCipherSuite // ClientECH.cipher_suite
dummy []byte // serialized ClientECH when greasing ECH
}

// Set by the client and server when an HRR message was sent in this
Expand Down
45 changes: 19 additions & 26 deletions src/crypto/tls/ech.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ const (
echAcceptConfirmationLabel = "ech accept confirmation"

// Constants for HPKE operations
echHpkeInfoConfigId = "tls ech config id"
echHpkeInfoSetup = "tls ech"
echHpkeInfoSetup = "tls ech"
)

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

// Compute artifacts that are reused across HRR.
var enc, configId []byte
var enc []byte
if c.ech.sealer == nil {
// HPKE context.
enc, c.ech.sealer, err = echConfig.setupSealer(config.rand())
if err != nil {
return nil, nil, fmt.Errorf("tls: ech: %s", err)
}

// ClientECH.config_id.
_, kdfId, _ := c.ech.sealer.Suite().Params()
configId, err = echConfig.id(kdfId)
if err != nil {
return nil, nil, fmt.Errorf("tls: ech: %s", err)
}

// ECHConfig.contents.public_name.
c.ech.publicName = string(echConfig.rawPublicName)

// ECHConfig.contents.key_config.config_id.
c.ech.configId = echConfig.configId

// ClientHelloInner.random.
c.ech.innerRandom = make([]byte, 32)
if _, err = io.ReadFull(config.rand(), c.ech.innerRandom); err != nil {
Expand Down Expand Up @@ -148,8 +143,8 @@ func (c *Conn) echOfferOrGrease(helloBase *clientHelloMsg) (hello, helloInner *c
_, kdfId, aeadId := c.ech.sealer.Suite().Params()
ech.handle.suite.kdfId = uint16(kdfId)
ech.handle.suite.aeadId = uint16(aeadId)
ech.handle.configId = configId // Empty after HRR
ech.handle.enc = enc // Empty after HRR
ech.handle.configId = echConfig.configId
ech.handle.enc = enc // Empty after HRR
helloOuterAad := echEncodeClientHelloOuterAAD(hello.marshal(), ech.handle.marshal())
if helloOuterAad == nil {
return nil, nil, errors.New("tls: ech: encoding of ClientHelloOuterAAD failed")
Expand Down Expand Up @@ -221,7 +216,7 @@ func (c *Conn) echAcceptOrReject(hello *clientHelloMsg) (*clientHelloMsg, error)
}
if c.hrrTriggered && c.ech.offered &&
(ech.handle.suite != c.ech.suite ||
len(ech.handle.configId) > 0 ||
ech.handle.configId != c.ech.configId ||
len(ech.handle.enc) > 0) {
// The context handle shouldn't change across HRR.
c.sendAlert(alertIllegalParameter)
Expand Down Expand Up @@ -252,7 +247,7 @@ func (c *Conn) echAcceptOrReject(hello *clientHelloMsg) (*clientHelloMsg, error)

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

// echContexttHandle represents the prefix of a ClientECH structure used by
// echContextHandle represents the prefix of a ClientECH structure used by
// the server to compute the HPKE context.
type echContextHandle struct {
raw []byte

// Parsed from raw
suite echCipherSuite
configId []byte
suite hpkeSymmetricCipherSuite
configId uint8
enc []byte
}

Expand All @@ -395,9 +390,7 @@ func (handle *echContextHandle) marshal() []byte {
var b cryptobyte.Builder
b.AddUint16(handle.suite.kdfId)
b.AddUint16(handle.suite.aeadId)
b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
b.AddBytes(handle.configId)
})
b.AddUint8(handle.configId)
b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
b.AddBytes(handle.enc)
})
Expand All @@ -408,19 +401,18 @@ func echReadContextHandle(s *cryptobyte.String, handle *echContextHandle) bool {
var t cryptobyte.String
if !s.ReadUint16(&handle.suite.kdfId) || // cipher_suite.kdf_id
!s.ReadUint16(&handle.suite.aeadId) || // cipher_suite.aead_id
!s.ReadUint8LengthPrefixed(&t) || // config_id
!t.ReadBytes(&handle.configId, len(t)) ||
!s.ReadUint8(&handle.configId) || // config_id
!s.ReadUint16LengthPrefixed(&t) || // enc
!t.ReadBytes(&handle.enc, len(t)) {
return false
}
return true
}

// echCipherSuite represents an ECH ciphersuite, a KDF/AEAD algorithm pair. This
// hpkeSymmetricCipherSuite represents an ECH ciphersuite, a KDF/AEAD algorithm pair. This
// is different from an HPKE ciphersuite, which represents a KEM/KDF/AEAD
// triple.
type echCipherSuite struct {
type hpkeSymmetricCipherSuite struct {
kdfId, aeadId uint16
}

Expand All @@ -446,11 +438,12 @@ func echGenerateDummyExt(rand io.Reader) ([]byte, error) {
var ech echClient
ech.handle.suite.kdfId = uint16(kdf)
ech.handle.suite.aeadId = uint16(aead)
ech.handle.configId = make([]byte, 8)
_, err = io.ReadFull(rand, ech.handle.configId)
randomByte := make([]byte, 1)
_, err = io.ReadFull(rand, randomByte)
if err != nil {
return nil, fmt.Errorf("tls: grease ech:: %s", err)
}
ech.handle.configId = randomByte[0]
ech.handle.enc, _, err = sender.Setup(rand)
if err != nil {
return nil, fmt.Errorf("tls: grease ech:: %s", err)
Expand Down
27 changes: 8 additions & 19 deletions src/crypto/tls/ech_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ type ECHConfig struct {

// Parsed from raw
version uint16
configId uint8
rawPublicName []byte
rawPublicKey []byte
kemId uint16
suites []echCipherSuite
suites []hpkeSymmetricCipherSuite
maxNameLen uint16
ignoredExtensions []byte
}
Expand Down Expand Up @@ -87,11 +88,10 @@ func echMarshalConfigs(configs []ECHConfig) ([]byte, error) {

func readConfigContents(contents *cryptobyte.String, config *ECHConfig) bool {
var t cryptobyte.String
if !contents.ReadUint16LengthPrefixed(&t) ||
!t.ReadBytes(&config.rawPublicName, len(t)) ||
if !contents.ReadUint8(&config.configId) ||
!contents.ReadUint16(&config.kemId) ||
!contents.ReadUint16LengthPrefixed(&t) ||
!t.ReadBytes(&config.rawPublicKey, len(t)) ||
!contents.ReadUint16(&config.kemId) ||
!contents.ReadUint16LengthPrefixed(&t) ||
len(t)%4 != 0 {
return false
Expand All @@ -104,10 +104,12 @@ func readConfigContents(contents *cryptobyte.String, config *ECHConfig) bool {
// This indicates an internal bug.
panic("internal error while parsing contents.cipher_suites")
}
config.suites = append(config.suites, echCipherSuite{kdfId, aeadId})
config.suites = append(config.suites, hpkeSymmetricCipherSuite{kdfId, aeadId})
}

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

// isPeerCipherSuiteSupported returns true if this configuration indicates
// support for the given ciphersuite.
func (config *ECHConfig) isPeerCipherSuiteSupported(suite echCipherSuite) bool {
func (config *ECHConfig) isPeerCipherSuiteSupported(suite hpkeSymmetricCipherSuite) bool {
for _, configSuite := range config.suites {
if suite == configSuite {
return true
Expand All @@ -160,16 +162,3 @@ func (config *ECHConfig) selectSuite() (hpke.Suite, error) {
}
return hpke.Suite{}, errors.New("could not negotiate a ciphersuite")
}

// id returns the configuration identifier for the given KDF.
func (config *ECHConfig) id(kdf hpke.KDF) ([]byte, error) {
if config.raw == nil {
panic("config.raw not set")
}
if !kdf.IsValid() {
return nil, errors.New("KDF algorithm not supported")
}
id := kdf.Expand(kdf.Extract(config.raw, nil),
[]byte(echHpkeInfoConfigId), 8)
return id, nil
}
51 changes: 14 additions & 37 deletions src/crypto/tls/ech_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ import (
"golang.org/x/crypto/cryptobyte"
)

const (
maxConfigIdLen = 255
)

// ECHProvider specifies the interface of an ECH service provider that decrypts
// the ECH payload on behalf of the client-facing server. It also defines the
// set of acceptable ECH configurations.
Expand All @@ -26,7 +22,7 @@ type ECHProvider interface {
//
// handle encodes the parameters of the client's "encrypted_client_hello"
// extension that are needed to construct the context. In
// draft-ietf-tls-esni-09, these are the ECH cipher suite, the identity of
// draft-ietf-tls-esni-10, these are the ECH cipher suite, the identity of
// the ECH configuration, and the encapsulated key.
//
// version is the version of ECH indicated by the client.
Expand Down Expand Up @@ -94,37 +90,24 @@ type EXP_ECHKeySet struct {
configs []byte

// Maps a configuration identifier to its secret key.
sk map[[maxConfigIdLen + 1]byte]EXP_ECHKey
sk map[uint8]EXP_ECHKey
}

// EXP_NewECHKeySet constructs an EXP_ECHKeySet.
func EXP_NewECHKeySet(keys []EXP_ECHKey) (*EXP_ECHKeySet, error) {
if len(keys) > 255 {
return nil, fmt.Errorf("tls: ech provider: unable to support more than 255 ECH configurations at once")
}

keySet := new(EXP_ECHKeySet)
keySet.sk = make(map[[maxConfigIdLen + 1]byte]EXP_ECHKey)
keySet.sk = make(map[uint8]EXP_ECHKey)
configs := make([]byte, 0)
for _, key := range keys {
// Compute the set of KDF algorithms supported by this configuration.
kdfIds := make(map[uint16]bool)
for _, suite := range key.config.suites {
kdfIds[suite.kdfId] = true
}

// Compute the configuration identifier for each KDF.
for kdfId, _ := range kdfIds {
configId, err := key.config.id(hpke.KDF(kdfId))
if err != nil {
return nil, err
}

var b cryptobyte.Builder
b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
b.AddBytes(configId)
})
var id [maxConfigIdLen + 1]byte // Initialized to zero
copy(id[:], b.BytesOrPanic())
keySet.sk[id] = key
if _, ok := keySet.sk[key.config.configId]; ok {
return nil, fmt.Errorf("tls: ech provider: ECH config conflict for configId %d", key.config.configId)
}

keySet.sk[key.config.configId] = key
configs = append(configs, key.config.raw...)
}

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

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

// Look up the secret key for the configuration indicated by the client.
var id [maxConfigIdLen + 1]byte // Initialized to zero
var b cryptobyte.Builder
b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
b.AddBytes(handle.configId)
})
copy(id[:], b.BytesOrPanic())
key, ok := keySet.sk[id]
key, ok := keySet.sk[handle.configId]
if !ok {
res.Status = ECHProviderReject
res.RetryConfigs = keySet.configs
Expand Down Expand Up @@ -227,7 +204,7 @@ func (keySet *EXP_ECHKeySet) GetDecryptionContext(rawHandle []byte, version uint
//
// struct {
// opaque sk<0..2^16-1>;
// ECHConfig config<0..2^16>; // draft-ietf-tls-esni-09
// ECHConfig config<0..2^16>; // draft-ietf-tls-esni-10
// } ECHKey;
type EXP_ECHKey struct {
sk kem.PrivateKey
Expand Down Expand Up @@ -291,7 +268,7 @@ KeysLoop:

// setupOpener computes the HPKE context used by the server in the ECH
// extension.i
func (key *EXP_ECHKey) setupOpener(enc []byte, suite echCipherSuite) (hpke.Opener, error) {
func (key *EXP_ECHKey) setupOpener(enc []byte, suite hpkeSymmetricCipherSuite) (hpke.Opener, error) {
if key.config.raw == nil {
panic("raw config not set")
}
Expand Down
Loading

0 comments on commit 7c96cb6

Please sign in to comment.