| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283 |
- // Copyright 2024 The Go Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- package tls
- import (
- "errors"
- "strings"
- "github.com/Psiphon-Labs/utls/internal/hpke"
- "golang.org/x/crypto/cryptobyte"
- )
- type echCipher struct {
- KDFID uint16
- AEADID uint16
- }
- type echExtension struct {
- Type uint16
- Data []byte
- }
- type echConfig struct {
- raw []byte
- Version uint16
- Length uint16
- ConfigID uint8
- KemID uint16
- PublicKey []byte
- SymmetricCipherSuite []echCipher
- MaxNameLength uint8
- PublicName []byte
- Extensions []echExtension
- }
- var errMalformedECHConfig = errors.New("tls: malformed ECHConfigList")
- // parseECHConfigList parses a draft-ietf-tls-esni-18 ECHConfigList, returning a
- // slice of parsed ECHConfigs, in the same order they were parsed, or an error
- // if the list is malformed.
- func parseECHConfigList(data []byte) ([]echConfig, error) {
- s := cryptobyte.String(data)
- // Skip the length prefix
- var length uint16
- if !s.ReadUint16(&length) {
- return nil, errMalformedECHConfig
- }
- if length != uint16(len(data)-2) {
- return nil, errMalformedECHConfig
- }
- var configs []echConfig
- for len(s) > 0 {
- var ec echConfig
- ec.raw = []byte(s)
- if !s.ReadUint16(&ec.Version) {
- return nil, errMalformedECHConfig
- }
- if !s.ReadUint16(&ec.Length) {
- return nil, errMalformedECHConfig
- }
- if len(ec.raw) < int(ec.Length)+4 {
- return nil, errMalformedECHConfig
- }
- ec.raw = ec.raw[:ec.Length+4]
- if ec.Version != extensionEncryptedClientHello {
- s.Skip(int(ec.Length))
- continue
- }
- if !s.ReadUint8(&ec.ConfigID) {
- return nil, errMalformedECHConfig
- }
- if !s.ReadUint16(&ec.KemID) {
- return nil, errMalformedECHConfig
- }
- if !s.ReadUint16LengthPrefixed((*cryptobyte.String)(&ec.PublicKey)) {
- return nil, errMalformedECHConfig
- }
- var cipherSuites cryptobyte.String
- if !s.ReadUint16LengthPrefixed(&cipherSuites) {
- return nil, errMalformedECHConfig
- }
- for !cipherSuites.Empty() {
- var c echCipher
- if !cipherSuites.ReadUint16(&c.KDFID) {
- return nil, errMalformedECHConfig
- }
- if !cipherSuites.ReadUint16(&c.AEADID) {
- return nil, errMalformedECHConfig
- }
- ec.SymmetricCipherSuite = append(ec.SymmetricCipherSuite, c)
- }
- if !s.ReadUint8(&ec.MaxNameLength) {
- return nil, errMalformedECHConfig
- }
- var publicName cryptobyte.String
- if !s.ReadUint8LengthPrefixed(&publicName) {
- return nil, errMalformedECHConfig
- }
- ec.PublicName = publicName
- var extensions cryptobyte.String
- if !s.ReadUint16LengthPrefixed(&extensions) {
- return nil, errMalformedECHConfig
- }
- for !extensions.Empty() {
- var e echExtension
- if !extensions.ReadUint16(&e.Type) {
- return nil, errMalformedECHConfig
- }
- if !extensions.ReadUint16LengthPrefixed((*cryptobyte.String)(&e.Data)) {
- return nil, errMalformedECHConfig
- }
- ec.Extensions = append(ec.Extensions, e)
- }
- configs = append(configs, ec)
- }
- return configs, nil
- }
- func pickECHConfig(list []echConfig) *echConfig {
- for _, ec := range list {
- if _, ok := hpke.SupportedKEMs[ec.KemID]; !ok {
- continue
- }
- var validSCS bool
- for _, cs := range ec.SymmetricCipherSuite {
- if _, ok := hpke.SupportedAEADs[cs.AEADID]; !ok {
- continue
- }
- if _, ok := hpke.SupportedKDFs[cs.KDFID]; !ok {
- continue
- }
- validSCS = true
- break
- }
- if !validSCS {
- continue
- }
- if !validDNSName(string(ec.PublicName)) {
- continue
- }
- var unsupportedExt bool
- for _, ext := range ec.Extensions {
- // If high order bit is set to 1 the extension is mandatory.
- // Since we don't support any extensions, if we see a mandatory
- // bit, we skip the config.
- if ext.Type&uint16(1<<15) != 0 {
- unsupportedExt = true
- }
- }
- if unsupportedExt {
- continue
- }
- return &ec
- }
- return nil
- }
- func pickECHCipherSuite(suites []echCipher) (echCipher, error) {
- for _, s := range suites {
- // NOTE: all of the supported AEADs and KDFs are fine, rather than
- // imposing some sort of preference here, we just pick the first valid
- // suite.
- if _, ok := hpke.SupportedAEADs[s.AEADID]; !ok {
- continue
- }
- if _, ok := hpke.SupportedKDFs[s.KDFID]; !ok {
- continue
- }
- return s, nil
- }
- return echCipher{}, errors.New("tls: no supported symmetric ciphersuites for ECH")
- }
- func encodeInnerClientHello(inner *clientHelloMsg, maxNameLength int) ([]byte, error) {
- h, err := inner.marshalMsg(true)
- if err != nil {
- return nil, err
- }
- h = h[4:] // strip four byte prefix
- var paddingLen int
- if inner.serverName != "" {
- paddingLen = max(0, maxNameLength-len(inner.serverName))
- } else {
- paddingLen = maxNameLength + 9
- }
- paddingLen = 31 - ((len(h) + paddingLen - 1) % 32)
- return append(h, make([]byte, paddingLen)...), nil
- }
- func generateOuterECHExt(id uint8, kdfID, aeadID uint16, encodedKey []byte, payload []byte) ([]byte, error) {
- var b cryptobyte.Builder
- b.AddUint8(0) // outer
- b.AddUint16(kdfID)
- b.AddUint16(aeadID)
- b.AddUint8(id)
- b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { b.AddBytes(encodedKey) })
- b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { b.AddBytes(payload) })
- return b.Bytes()
- }
- func computeAndUpdateOuterECHExtension(outer, inner *clientHelloMsg, ech *echContext, useKey bool) error {
- var encapKey []byte
- if useKey {
- encapKey = ech.encapsulatedKey
- }
- encodedInner, err := encodeInnerClientHello(inner, int(ech.config.MaxNameLength))
- if err != nil {
- return err
- }
- // NOTE: the tag lengths for all of the supported AEADs are the same (16
- // bytes), so we have hardcoded it here. If we add support for another AEAD
- // with a different tag length, we will need to change this.
- encryptedLen := len(encodedInner) + 16 // AEAD tag length
- outer.encryptedClientHello, err = generateOuterECHExt(ech.config.ConfigID, ech.kdfID, ech.aeadID, encapKey, make([]byte, encryptedLen))
- if err != nil {
- return err
- }
- serializedOuter, err := outer.marshal()
- if err != nil {
- return err
- }
- serializedOuter = serializedOuter[4:] // strip the four byte prefix
- encryptedInner, err := ech.hpkeContext.Seal(serializedOuter, encodedInner)
- if err != nil {
- return err
- }
- outer.encryptedClientHello, err = generateOuterECHExt(ech.config.ConfigID, ech.kdfID, ech.aeadID, encapKey, encryptedInner)
- if err != nil {
- return err
- }
- return nil
- }
- // validDNSName is a rather rudimentary check for the validity of a DNS name.
- // This is used to check if the public_name in a ECHConfig is valid when we are
- // picking a config. This can be somewhat lax because even if we pick a
- // valid-looking name, the DNS layer will later reject it anyway.
- func validDNSName(name string) bool {
- if len(name) > 253 {
- return false
- }
- labels := strings.Split(name, ".")
- if len(labels) <= 1 {
- return false
- }
- for _, l := range labels {
- labelLen := len(l)
- if labelLen == 0 {
- return false
- }
- for i, r := range l {
- if r == '-' && (i == 0 || i == labelLen-1) {
- return false
- }
- if (r < '0' || r > '9') && (r < 'a' || r > 'z') && (r < 'A' || r > 'Z') && r != '-' {
- return false
- }
- }
- }
- return true
- }
- // ECHRejectionError is the error type returned when ECH is rejected by a remote
- // server. If the server offered a ECHConfigList to use for retries, the
- // RetryConfigList field will contain this list.
- //
- // The client may treat an ECHRejectionError with an empty set of RetryConfigs
- // as a secure signal from the server.
- type ECHRejectionError struct {
- RetryConfigList []byte
- }
- func (e *ECHRejectionError) Error() string {
- return "tls: server rejected ECH"
- }
|