Source file src/crypto/tls/ech.go

     1  // Copyright 2024 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package tls
     6  
     7  import (
     8  	"bytes"
     9  	"crypto/internal/hpke"
    10  	"errors"
    11  	"fmt"
    12  	"slices"
    13  	"strings"
    14  
    15  	"golang.org/x/crypto/cryptobyte"
    16  )
    17  
    18  // sortedSupportedAEADs is just a sorted version of hpke.SupportedAEADS.
    19  // We need this so that when we insert them into ECHConfigs the ordering
    20  // is stable.
    21  var sortedSupportedAEADs []uint16
    22  
    23  func init() {
    24  	for aeadID := range hpke.SupportedAEADs {
    25  		sortedSupportedAEADs = append(sortedSupportedAEADs, aeadID)
    26  	}
    27  	slices.Sort(sortedSupportedAEADs)
    28  }
    29  
    30  type echCipher struct {
    31  	KDFID  uint16
    32  	AEADID uint16
    33  }
    34  
    35  type echExtension struct {
    36  	Type uint16
    37  	Data []byte
    38  }
    39  
    40  type echConfig struct {
    41  	raw []byte
    42  
    43  	Version uint16
    44  	Length  uint16
    45  
    46  	ConfigID             uint8
    47  	KemID                uint16
    48  	PublicKey            []byte
    49  	SymmetricCipherSuite []echCipher
    50  
    51  	MaxNameLength uint8
    52  	PublicName    []byte
    53  	Extensions    []echExtension
    54  }
    55  
    56  var errMalformedECHConfig = errors.New("tls: malformed ECHConfigList")
    57  
    58  func parseECHConfig(enc []byte) (skip bool, ec echConfig, err error) {
    59  	s := cryptobyte.String(enc)
    60  	ec.raw = []byte(enc)
    61  	if !s.ReadUint16(&ec.Version) {
    62  		return false, echConfig{}, errMalformedECHConfig
    63  	}
    64  	if !s.ReadUint16(&ec.Length) {
    65  		return false, echConfig{}, errMalformedECHConfig
    66  	}
    67  	if len(ec.raw) < int(ec.Length)+4 {
    68  		return false, echConfig{}, errMalformedECHConfig
    69  	}
    70  	ec.raw = ec.raw[:ec.Length+4]
    71  	if ec.Version != extensionEncryptedClientHello {
    72  		s.Skip(int(ec.Length))
    73  		return true, echConfig{}, nil
    74  	}
    75  	if !s.ReadUint8(&ec.ConfigID) {
    76  		return false, echConfig{}, errMalformedECHConfig
    77  	}
    78  	if !s.ReadUint16(&ec.KemID) {
    79  		return false, echConfig{}, errMalformedECHConfig
    80  	}
    81  	if !readUint16LengthPrefixed(&s, &ec.PublicKey) {
    82  		return false, echConfig{}, errMalformedECHConfig
    83  	}
    84  	var cipherSuites cryptobyte.String
    85  	if !s.ReadUint16LengthPrefixed(&cipherSuites) {
    86  		return false, echConfig{}, errMalformedECHConfig
    87  	}
    88  	for !cipherSuites.Empty() {
    89  		var c echCipher
    90  		if !cipherSuites.ReadUint16(&c.KDFID) {
    91  			return false, echConfig{}, errMalformedECHConfig
    92  		}
    93  		if !cipherSuites.ReadUint16(&c.AEADID) {
    94  			return false, echConfig{}, errMalformedECHConfig
    95  		}
    96  		ec.SymmetricCipherSuite = append(ec.SymmetricCipherSuite, c)
    97  	}
    98  	if !s.ReadUint8(&ec.MaxNameLength) {
    99  		return false, echConfig{}, errMalformedECHConfig
   100  	}
   101  	var publicName cryptobyte.String
   102  	if !s.ReadUint8LengthPrefixed(&publicName) {
   103  		return false, echConfig{}, errMalformedECHConfig
   104  	}
   105  	ec.PublicName = publicName
   106  	var extensions cryptobyte.String
   107  	if !s.ReadUint16LengthPrefixed(&extensions) {
   108  		return false, echConfig{}, errMalformedECHConfig
   109  	}
   110  	for !extensions.Empty() {
   111  		var e echExtension
   112  		if !extensions.ReadUint16(&e.Type) {
   113  			return false, echConfig{}, errMalformedECHConfig
   114  		}
   115  		if !extensions.ReadUint16LengthPrefixed((*cryptobyte.String)(&e.Data)) {
   116  			return false, echConfig{}, errMalformedECHConfig
   117  		}
   118  		ec.Extensions = append(ec.Extensions, e)
   119  	}
   120  
   121  	return false, ec, nil
   122  }
   123  
   124  // parseECHConfigList parses a draft-ietf-tls-esni-18 ECHConfigList, returning a
   125  // slice of parsed ECHConfigs, in the same order they were parsed, or an error
   126  // if the list is malformed.
   127  func parseECHConfigList(data []byte) ([]echConfig, error) {
   128  	s := cryptobyte.String(data)
   129  	var length uint16
   130  	if !s.ReadUint16(&length) {
   131  		return nil, errMalformedECHConfig
   132  	}
   133  	if length != uint16(len(data)-2) {
   134  		return nil, errMalformedECHConfig
   135  	}
   136  	var configs []echConfig
   137  	for len(s) > 0 {
   138  		if len(s) < 4 {
   139  			return nil, errors.New("tls: malformed ECHConfig")
   140  		}
   141  		configLen := uint16(s[2])<<8 | uint16(s[3])
   142  		skip, ec, err := parseECHConfig(s)
   143  		if err != nil {
   144  			return nil, err
   145  		}
   146  		s = s[configLen+4:]
   147  		if !skip {
   148  			configs = append(configs, ec)
   149  		}
   150  	}
   151  	return configs, nil
   152  }
   153  
   154  func pickECHConfig(list []echConfig) *echConfig {
   155  	for _, ec := range list {
   156  		if _, ok := hpke.SupportedKEMs[ec.KemID]; !ok {
   157  			continue
   158  		}
   159  		var validSCS bool
   160  		for _, cs := range ec.SymmetricCipherSuite {
   161  			if _, ok := hpke.SupportedAEADs[cs.AEADID]; !ok {
   162  				continue
   163  			}
   164  			if _, ok := hpke.SupportedKDFs[cs.KDFID]; !ok {
   165  				continue
   166  			}
   167  			validSCS = true
   168  			break
   169  		}
   170  		if !validSCS {
   171  			continue
   172  		}
   173  		if !validDNSName(string(ec.PublicName)) {
   174  			continue
   175  		}
   176  		var unsupportedExt bool
   177  		for _, ext := range ec.Extensions {
   178  			// If high order bit is set to 1 the extension is mandatory.
   179  			// Since we don't support any extensions, if we see a mandatory
   180  			// bit, we skip the config.
   181  			if ext.Type&uint16(1<<15) != 0 {
   182  				unsupportedExt = true
   183  			}
   184  		}
   185  		if unsupportedExt {
   186  			continue
   187  		}
   188  		return &ec
   189  	}
   190  	return nil
   191  }
   192  
   193  func pickECHCipherSuite(suites []echCipher) (echCipher, error) {
   194  	for _, s := range suites {
   195  		// NOTE: all of the supported AEADs and KDFs are fine, rather than
   196  		// imposing some sort of preference here, we just pick the first valid
   197  		// suite.
   198  		if _, ok := hpke.SupportedAEADs[s.AEADID]; !ok {
   199  			continue
   200  		}
   201  		if _, ok := hpke.SupportedKDFs[s.KDFID]; !ok {
   202  			continue
   203  		}
   204  		return s, nil
   205  	}
   206  	return echCipher{}, errors.New("tls: no supported symmetric ciphersuites for ECH")
   207  }
   208  
   209  func encodeInnerClientHello(inner *clientHelloMsg, maxNameLength int) ([]byte, error) {
   210  	h, err := inner.marshalMsg(true)
   211  	if err != nil {
   212  		return nil, err
   213  	}
   214  	h = h[4:] // strip four byte prefix
   215  
   216  	var paddingLen int
   217  	if inner.serverName != "" {
   218  		paddingLen = max(0, maxNameLength-len(inner.serverName))
   219  	} else {
   220  		paddingLen = maxNameLength + 9
   221  	}
   222  	paddingLen = 31 - ((len(h) + paddingLen - 1) % 32)
   223  
   224  	return append(h, make([]byte, paddingLen)...), nil
   225  }
   226  
   227  func skipUint8LengthPrefixed(s *cryptobyte.String) bool {
   228  	var skip uint8
   229  	if !s.ReadUint8(&skip) {
   230  		return false
   231  	}
   232  	return s.Skip(int(skip))
   233  }
   234  
   235  func skipUint16LengthPrefixed(s *cryptobyte.String) bool {
   236  	var skip uint16
   237  	if !s.ReadUint16(&skip) {
   238  		return false
   239  	}
   240  	return s.Skip(int(skip))
   241  }
   242  
   243  type rawExtension struct {
   244  	extType uint16
   245  	data    []byte
   246  }
   247  
   248  func extractRawExtensions(hello *clientHelloMsg) ([]rawExtension, error) {
   249  	s := cryptobyte.String(hello.original)
   250  	if !s.Skip(4+2+32) || // header, version, random
   251  		!skipUint8LengthPrefixed(&s) || // session ID
   252  		!skipUint16LengthPrefixed(&s) || // cipher suites
   253  		!skipUint8LengthPrefixed(&s) { // compression methods
   254  		return nil, errors.New("tls: malformed outer client hello")
   255  	}
   256  	var rawExtensions []rawExtension
   257  	var extensions cryptobyte.String
   258  	if !s.ReadUint16LengthPrefixed(&extensions) {
   259  		return nil, errors.New("tls: malformed outer client hello")
   260  	}
   261  
   262  	for !extensions.Empty() {
   263  		var extension uint16
   264  		var extData cryptobyte.String
   265  		if !extensions.ReadUint16(&extension) ||
   266  			!extensions.ReadUint16LengthPrefixed(&extData) {
   267  			return nil, errors.New("tls: invalid inner client hello")
   268  		}
   269  		rawExtensions = append(rawExtensions, rawExtension{extension, extData})
   270  	}
   271  	return rawExtensions, nil
   272  }
   273  
   274  func decodeInnerClientHello(outer *clientHelloMsg, encoded []byte) (*clientHelloMsg, error) {
   275  	// Reconstructing the inner client hello from its encoded form is somewhat
   276  	// complicated. It is missing its header (message type and length), session
   277  	// ID, and the extensions may be compressed. Since we need to put the
   278  	// extensions back in the same order as they were in the raw outer hello,
   279  	// and since we don't store the raw extensions, or the order we parsed them
   280  	// in, we need to reparse the raw extensions from the outer hello in order
   281  	// to properly insert them into the inner hello. This _should_ result in raw
   282  	// bytes which match the hello as it was generated by the client.
   283  	innerReader := cryptobyte.String(encoded)
   284  	var versionAndRandom, sessionID, cipherSuites, compressionMethods []byte
   285  	var extensions cryptobyte.String
   286  	if !innerReader.ReadBytes(&versionAndRandom, 2+32) ||
   287  		!readUint8LengthPrefixed(&innerReader, &sessionID) ||
   288  		len(sessionID) != 0 ||
   289  		!readUint16LengthPrefixed(&innerReader, &cipherSuites) ||
   290  		!readUint8LengthPrefixed(&innerReader, &compressionMethods) ||
   291  		!innerReader.ReadUint16LengthPrefixed(&extensions) {
   292  		return nil, errors.New("tls: invalid inner client hello")
   293  	}
   294  
   295  	// The specification says we must verify that the trailing padding is all
   296  	// zeros. This is kind of weird for TLS messages, where we generally just
   297  	// throw away any trailing garbage.
   298  	for _, p := range innerReader {
   299  		if p != 0 {
   300  			return nil, errors.New("tls: invalid inner client hello")
   301  		}
   302  	}
   303  
   304  	rawOuterExts, err := extractRawExtensions(outer)
   305  	if err != nil {
   306  		return nil, err
   307  	}
   308  
   309  	recon := cryptobyte.NewBuilder(nil)
   310  	recon.AddUint8(typeClientHello)
   311  	recon.AddUint24LengthPrefixed(func(recon *cryptobyte.Builder) {
   312  		recon.AddBytes(versionAndRandom)
   313  		recon.AddUint8LengthPrefixed(func(recon *cryptobyte.Builder) {
   314  			recon.AddBytes(outer.sessionId)
   315  		})
   316  		recon.AddUint16LengthPrefixed(func(recon *cryptobyte.Builder) {
   317  			recon.AddBytes(cipherSuites)
   318  		})
   319  		recon.AddUint8LengthPrefixed(func(recon *cryptobyte.Builder) {
   320  			recon.AddBytes(compressionMethods)
   321  		})
   322  		recon.AddUint16LengthPrefixed(func(recon *cryptobyte.Builder) {
   323  			for !extensions.Empty() {
   324  				var extension uint16
   325  				var extData cryptobyte.String
   326  				if !extensions.ReadUint16(&extension) ||
   327  					!extensions.ReadUint16LengthPrefixed(&extData) {
   328  					recon.SetError(errors.New("tls: invalid inner client hello"))
   329  					return
   330  				}
   331  				if extension == extensionECHOuterExtensions {
   332  					if !extData.ReadUint8LengthPrefixed(&extData) {
   333  						recon.SetError(errors.New("tls: invalid inner client hello"))
   334  						return
   335  					}
   336  					var i int
   337  					for !extData.Empty() {
   338  						var extType uint16
   339  						if !extData.ReadUint16(&extType) {
   340  							recon.SetError(errors.New("tls: invalid inner client hello"))
   341  							return
   342  						}
   343  						if extType == extensionEncryptedClientHello {
   344  							recon.SetError(errors.New("tls: invalid outer extensions"))
   345  							return
   346  						}
   347  						for ; i <= len(rawOuterExts); i++ {
   348  							if i == len(rawOuterExts) {
   349  								recon.SetError(errors.New("tls: invalid outer extensions"))
   350  								return
   351  							}
   352  							if rawOuterExts[i].extType == extType {
   353  								break
   354  							}
   355  						}
   356  						recon.AddUint16(rawOuterExts[i].extType)
   357  						recon.AddUint16LengthPrefixed(func(recon *cryptobyte.Builder) {
   358  							recon.AddBytes(rawOuterExts[i].data)
   359  						})
   360  					}
   361  				} else {
   362  					recon.AddUint16(extension)
   363  					recon.AddUint16LengthPrefixed(func(recon *cryptobyte.Builder) {
   364  						recon.AddBytes(extData)
   365  					})
   366  				}
   367  			}
   368  		})
   369  	})
   370  
   371  	reconBytes, err := recon.Bytes()
   372  	if err != nil {
   373  		return nil, err
   374  	}
   375  	inner := &clientHelloMsg{}
   376  	if !inner.unmarshal(reconBytes) {
   377  		return nil, errors.New("tls: invalid reconstructed inner client hello")
   378  	}
   379  
   380  	if !bytes.Equal(inner.encryptedClientHello, []byte{uint8(innerECHExt)}) {
   381  		return nil, errors.New("tls: client sent invalid encrypted_client_hello extension")
   382  	}
   383  
   384  	if len(inner.supportedVersions) != 1 || (len(inner.supportedVersions) >= 1 && inner.supportedVersions[0] != VersionTLS13) {
   385  		return nil, errors.New("tls: client sent encrypted_client_hello extension and offered incompatible versions")
   386  	}
   387  
   388  	return inner, nil
   389  }
   390  
   391  func decryptECHPayload(context *hpke.Receipient, hello, payload []byte) ([]byte, error) {
   392  	outerAAD := bytes.Replace(hello[4:], payload, make([]byte, len(payload)), 1)
   393  	return context.Open(outerAAD, payload)
   394  }
   395  
   396  func generateOuterECHExt(id uint8, kdfID, aeadID uint16, encodedKey []byte, payload []byte) ([]byte, error) {
   397  	var b cryptobyte.Builder
   398  	b.AddUint8(0) // outer
   399  	b.AddUint16(kdfID)
   400  	b.AddUint16(aeadID)
   401  	b.AddUint8(id)
   402  	b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { b.AddBytes(encodedKey) })
   403  	b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { b.AddBytes(payload) })
   404  	return b.Bytes()
   405  }
   406  
   407  func computeAndUpdateOuterECHExtension(outer, inner *clientHelloMsg, ech *echClientContext, useKey bool) error {
   408  	var encapKey []byte
   409  	if useKey {
   410  		encapKey = ech.encapsulatedKey
   411  	}
   412  	encodedInner, err := encodeInnerClientHello(inner, int(ech.config.MaxNameLength))
   413  	if err != nil {
   414  		return err
   415  	}
   416  	// NOTE: the tag lengths for all of the supported AEADs are the same (16
   417  	// bytes), so we have hardcoded it here. If we add support for another AEAD
   418  	// with a different tag length, we will need to change this.
   419  	encryptedLen := len(encodedInner) + 16 // AEAD tag length
   420  	outer.encryptedClientHello, err = generateOuterECHExt(ech.config.ConfigID, ech.kdfID, ech.aeadID, encapKey, make([]byte, encryptedLen))
   421  	if err != nil {
   422  		return err
   423  	}
   424  	serializedOuter, err := outer.marshal()
   425  	if err != nil {
   426  		return err
   427  	}
   428  	serializedOuter = serializedOuter[4:] // strip the four byte prefix
   429  	encryptedInner, err := ech.hpkeContext.Seal(serializedOuter, encodedInner)
   430  	if err != nil {
   431  		return err
   432  	}
   433  	outer.encryptedClientHello, err = generateOuterECHExt(ech.config.ConfigID, ech.kdfID, ech.aeadID, encapKey, encryptedInner)
   434  	if err != nil {
   435  		return err
   436  	}
   437  	return nil
   438  }
   439  
   440  // validDNSName is a rather rudimentary check for the validity of a DNS name.
   441  // This is used to check if the public_name in a ECHConfig is valid when we are
   442  // picking a config. This can be somewhat lax because even if we pick a
   443  // valid-looking name, the DNS layer will later reject it anyway.
   444  func validDNSName(name string) bool {
   445  	if len(name) > 253 {
   446  		return false
   447  	}
   448  	labels := strings.Split(name, ".")
   449  	if len(labels) <= 1 {
   450  		return false
   451  	}
   452  	for _, l := range labels {
   453  		labelLen := len(l)
   454  		if labelLen == 0 {
   455  			return false
   456  		}
   457  		for i, r := range l {
   458  			if r == '-' && (i == 0 || i == labelLen-1) {
   459  				return false
   460  			}
   461  			if (r < '0' || r > '9') && (r < 'a' || r > 'z') && (r < 'A' || r > 'Z') && r != '-' {
   462  				return false
   463  			}
   464  		}
   465  	}
   466  	return true
   467  }
   468  
   469  // ECHRejectionError is the error type returned when ECH is rejected by a remote
   470  // server. If the server offered a ECHConfigList to use for retries, the
   471  // RetryConfigList field will contain this list.
   472  //
   473  // The client may treat an ECHRejectionError with an empty set of RetryConfigs
   474  // as a secure signal from the server.
   475  type ECHRejectionError struct {
   476  	RetryConfigList []byte
   477  }
   478  
   479  func (e *ECHRejectionError) Error() string {
   480  	return "tls: server rejected ECH"
   481  }
   482  
   483  var errMalformedECHExt = errors.New("tls: malformed encrypted_client_hello extension")
   484  
   485  type echExtType uint8
   486  
   487  const (
   488  	innerECHExt echExtType = 1
   489  	outerECHExt echExtType = 0
   490  )
   491  
   492  func parseECHExt(ext []byte) (echType echExtType, cs echCipher, configID uint8, encap []byte, payload []byte, err error) {
   493  	data := make([]byte, len(ext))
   494  	copy(data, ext)
   495  	s := cryptobyte.String(data)
   496  	var echInt uint8
   497  	if !s.ReadUint8(&echInt) {
   498  		err = errMalformedECHExt
   499  		return
   500  	}
   501  	echType = echExtType(echInt)
   502  	if echType == innerECHExt {
   503  		if !s.Empty() {
   504  			err = errMalformedECHExt
   505  			return
   506  		}
   507  		return echType, cs, 0, nil, nil, nil
   508  	}
   509  	if echType != outerECHExt {
   510  		err = errMalformedECHExt
   511  		return
   512  	}
   513  	if !s.ReadUint16(&cs.KDFID) {
   514  		err = errMalformedECHExt
   515  		return
   516  	}
   517  	if !s.ReadUint16(&cs.AEADID) {
   518  		err = errMalformedECHExt
   519  		return
   520  	}
   521  	if !s.ReadUint8(&configID) {
   522  		err = errMalformedECHExt
   523  		return
   524  	}
   525  	if !readUint16LengthPrefixed(&s, &encap) {
   526  		err = errMalformedECHExt
   527  		return
   528  	}
   529  	if !readUint16LengthPrefixed(&s, &payload) {
   530  		err = errMalformedECHExt
   531  		return
   532  	}
   533  
   534  	// NOTE: clone encap and payload so that mutating them does not mutate the
   535  	// raw extension bytes.
   536  	return echType, cs, configID, bytes.Clone(encap), bytes.Clone(payload), nil
   537  }
   538  
   539  func marshalEncryptedClientHelloConfigList(configs []EncryptedClientHelloKey) ([]byte, error) {
   540  	builder := cryptobyte.NewBuilder(nil)
   541  	builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
   542  		for _, c := range configs {
   543  			builder.AddBytes(c.Config)
   544  		}
   545  	})
   546  	return builder.Bytes()
   547  }
   548  
   549  func (c *Conn) processECHClientHello(outer *clientHelloMsg) (*clientHelloMsg, *echServerContext, error) {
   550  	echType, echCiphersuite, configID, encap, payload, err := parseECHExt(outer.encryptedClientHello)
   551  	if err != nil {
   552  		c.sendAlert(alertDecodeError)
   553  		return nil, nil, errors.New("tls: client sent invalid encrypted_client_hello extension")
   554  	}
   555  
   556  	if echType == innerECHExt {
   557  		return outer, &echServerContext{inner: true}, nil
   558  	}
   559  
   560  	if len(c.config.EncryptedClientHelloKeys) == 0 {
   561  		return outer, nil, nil
   562  	}
   563  
   564  	for _, echKey := range c.config.EncryptedClientHelloKeys {
   565  		skip, config, err := parseECHConfig(echKey.Config)
   566  		if err != nil || skip {
   567  			c.sendAlert(alertInternalError)
   568  			return nil, nil, fmt.Errorf("tls: invalid EncryptedClientHelloKeys Config: %s", err)
   569  		}
   570  		if skip {
   571  			continue
   572  		}
   573  		echPriv, err := hpke.ParseHPKEPrivateKey(config.KemID, echKey.PrivateKey)
   574  		if err != nil {
   575  			c.sendAlert(alertInternalError)
   576  			return nil, nil, fmt.Errorf("tls: invalid EncryptedClientHelloKeys PrivateKey: %s", err)
   577  		}
   578  		info := append([]byte("tls ech\x00"), echKey.Config...)
   579  		hpkeContext, err := hpke.SetupReceipient(hpke.DHKEM_X25519_HKDF_SHA256, echCiphersuite.KDFID, echCiphersuite.AEADID, echPriv, info, encap)
   580  		if err != nil {
   581  			// attempt next trial decryption
   582  			continue
   583  		}
   584  
   585  		encodedInner, err := decryptECHPayload(hpkeContext, outer.original, payload)
   586  		if err != nil {
   587  			// attempt next trial decryption
   588  			continue
   589  		}
   590  
   591  		// NOTE: we do not enforce that the sent server_name matches the ECH
   592  		// configs PublicName, since this is not particularly important, and
   593  		// the client already had to know what it was in order to properly
   594  		// encrypt the payload. This is only a MAY in the spec, so we're not
   595  		// doing anything revolutionary.
   596  
   597  		echInner, err := decodeInnerClientHello(outer, encodedInner)
   598  		if err != nil {
   599  			c.sendAlert(alertIllegalParameter)
   600  			return nil, nil, errors.New("tls: client sent invalid encrypted_client_hello extension")
   601  		}
   602  
   603  		c.echAccepted = true
   604  
   605  		return echInner, &echServerContext{
   606  			hpkeContext: hpkeContext,
   607  			configID:    configID,
   608  			ciphersuite: echCiphersuite,
   609  		}, nil
   610  	}
   611  
   612  	return outer, nil, nil
   613  }
   614  
   615  func buildRetryConfigList(keys []EncryptedClientHelloKey) ([]byte, error) {
   616  	var atLeastOneRetryConfig bool
   617  	var retryBuilder cryptobyte.Builder
   618  	retryBuilder.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
   619  		for _, c := range keys {
   620  			if !c.SendAsRetry {
   621  				continue
   622  			}
   623  			atLeastOneRetryConfig = true
   624  			b.AddBytes(c.Config)
   625  		}
   626  	})
   627  	if !atLeastOneRetryConfig {
   628  		return nil, nil
   629  	}
   630  	return retryBuilder.Bytes()
   631  }
   632  

View as plain text