package tls
import (
"crypto/internal/hpke"
"errors"
"strings"
"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" )
func parseECHConfigList(data []byte ) ([]echConfig , error ) {
s := cryptobyte .String (data )
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 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 {
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 :]
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 )
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
}
encryptedLen := len (encodedInner ) + 16
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 :]
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
}
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
}
type ECHRejectionError struct {
RetryConfigList []byte
}
func (e *ECHRejectionError ) Error () string {
return "tls: server rejected ECH"
}
The pages are generated with Golds v0.7.0-preview . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .