package tls
import (
"crypto/rand"
"errors"
"fmt"
"io"
"math/big"
"sync"
"github.com/refraction-networking/utls/dicttls"
"github.com/refraction-networking/utls/internal/hpke"
"golang.org/x/crypto/cryptobyte"
)
const (
OuterClientHello byte = 0x00
InnerClientHello byte = 0x01
)
type EncryptedClientHelloExtension interface {
TLSExtension
MarshalClientHello (*UConn ) error
mustEmbedUnimplementedECHExtension ()
}
type ECHExtension = EncryptedClientHelloExtension
var (
_ EncryptedClientHelloExtension = (*GREASEEncryptedClientHelloExtension )(nil )
_ EncryptedClientHelloExtension = (*UnimplementedECHExtension )(nil )
)
type GREASEEncryptedClientHelloExtension struct {
CandidateCipherSuites []HPKESymmetricCipherSuite
cipherSuite HPKESymmetricCipherSuite
CandidateConfigIds []uint8
configId uint8
EncapsulatedKey []byte
CandidatePayloadLens []uint16
payload []byte
initOnce sync .Once
UnimplementedECHExtension
}
type GREASEECHExtension = GREASEEncryptedClientHelloExtension
func (g *GREASEEncryptedClientHelloExtension ) init () error {
var initErr error
g .initOnce .Do (func () {
if len (g .CandidateConfigIds ) == 0 {
var b []byte = make ([]byte , 1 )
_ , err := rand .Read (b [:])
if err != nil {
initErr = fmt .Errorf ("error generating random byte for config_id: %w" , err )
return
}
g .configId = b [0 ]
} else {
rndIndex , err := rand .Int (rand .Reader , big .NewInt (int64 (len (g .CandidateConfigIds ))))
if err != nil {
initErr = fmt .Errorf ("error generating random index for config_id: %w" , err )
return
}
g .configId = g .CandidateConfigIds [rndIndex .Int64 ()]
}
if len (g .CandidateCipherSuites ) == 0 {
g .cipherSuite = HPKESymmetricCipherSuite {uint16 (defaultHpkeKdf ), uint16 (defaultHpkeAead )}
} else {
rndIndex , err := rand .Int (rand .Reader , big .NewInt (int64 (len (g .CandidateCipherSuites ))))
if err != nil {
initErr = fmt .Errorf ("error generating random index for cipher_suite: %w" , err )
return
}
g .cipherSuite = HPKESymmetricCipherSuite {
g .CandidateCipherSuites [rndIndex .Int64 ()].KdfId ,
g .CandidateCipherSuites [rndIndex .Int64 ()].AeadId ,
}
}
if len (g .EncapsulatedKey ) == 0 {
kem := uint16 (defaultHpkeKem )
echPK , err := hpke .ParseHPKEPublicKey (uint16 (kem ), dummyX25519PublicKey )
if err != nil {
initErr = fmt .Errorf ("tls: grease ech: failed to parse dummy public key: %w" , err )
return
}
suite := echCipher {
KDFID : defaultHpkeKdf ,
AEADID : defaultHpkeAead ,
}
g .EncapsulatedKey , _, err = hpke .SetupSender (kem , suite .KDFID , suite .AEADID , echPK , []byte {})
if err != nil {
initErr = fmt .Errorf ("tls: grease ech: failed to setup encapsulated key: %w" , err )
return
}
}
if len (g .payload ) == 0 {
if len (g .CandidatePayloadLens ) == 0 {
g .CandidatePayloadLens = []uint16 {128 }
}
rndIndex , err := rand .Int (rand .Reader , big .NewInt (int64 (len (g .CandidatePayloadLens ))))
if err != nil {
initErr = fmt .Errorf ("error generating random index for payload length: %w" , err )
return
}
initErr = g .randomizePayload (g .CandidatePayloadLens [rndIndex .Int64 ()])
}
})
return initErr
}
func (g *GREASEEncryptedClientHelloExtension ) randomizePayload (encodedHelloInnerLen uint16 ) error {
if len (g .payload ) != 0 {
return errors .New ("tls: grease ech: regenerating payload is forbidden" )
}
g .payload = make ([]byte , cipherLen (g .cipherSuite .AeadId , int (encodedHelloInnerLen )))
_ , err := rand .Read (g .payload )
if err != nil {
return fmt .Errorf ("tls: generating grease ech payload: %w" , err )
}
return nil
}
func (g *GREASEEncryptedClientHelloExtension ) writeToUConn (uconn *UConn ) error {
uconn .ech = g
return uconn .MarshalClientHelloNoECH ()
}
func (g *GREASEEncryptedClientHelloExtension ) Len () int {
g .init ()
return 2 + 2 + 1 + 4 + 1 + 2 + len (g .EncapsulatedKey ) + 2 + len (g .payload )
}
func (g *GREASEEncryptedClientHelloExtension ) Read (b []byte ) (int , error ) {
if len (b ) < g .Len () {
return 0 , io .ErrShortBuffer
}
b [0 ] = byte (utlsExtensionECH >> 8 )
b [1 ] = byte (utlsExtensionECH & 0xFF )
b [2 ] = byte ((g .Len () - 4 ) >> 8 )
b [3 ] = byte ((g .Len () - 4 ) & 0xFF )
b [4 ] = OuterClientHello
b [5 ] = byte (g .cipherSuite .KdfId >> 8 )
b [6 ] = byte (g .cipherSuite .KdfId & 0xFF )
b [7 ] = byte (g .cipherSuite .AeadId >> 8 )
b [8 ] = byte (g .cipherSuite .AeadId & 0xFF )
b [9 ] = g .configId
b [10 ] = byte (len (g .EncapsulatedKey ) >> 8 )
b [11 ] = byte (len (g .EncapsulatedKey ) & 0xFF )
copy (b [12 :], g .EncapsulatedKey )
b [12 +len (g .EncapsulatedKey )] = byte (len (g .payload ) >> 8 )
b [12 +len (g .EncapsulatedKey )+1 ] = byte (len (g .payload ) & 0xFF )
copy (b [12 +len (g .EncapsulatedKey )+2 :], g .payload )
return g .Len (), io .EOF
}
func (*GREASEEncryptedClientHelloExtension ) MarshalClientHello (*UConn ) error {
return errors .New ("tls: grease ech: MarshalClientHello() is not implemented, use (*UConn).MarshalClientHello() instead" )
}
func (g *GREASEEncryptedClientHelloExtension ) Write (b []byte ) (int , error ) {
fullLen := len (b )
extData := cryptobyte .String (b )
var chType uint8
var ignored cryptobyte .String
if !extData .ReadUint8 (&chType ) || chType != 0 {
return fullLen , errors .New ("bad Client Hello type, expected 0, got " + fmt .Sprintf ("%d" , chType ))
}
if !extData .ReadUint16 (&g .cipherSuite .KdfId ) || !extData .ReadUint16 (&g .cipherSuite .AeadId ) {
return fullLen , errors .New ("bad cipher suite" )
}
if g .cipherSuite .KdfId != dicttls .HKDF_SHA256 &&
g .cipherSuite .KdfId != dicttls .HKDF_SHA384 &&
g .cipherSuite .KdfId != dicttls .HKDF_SHA512 {
return fullLen , errors .New ("bad KDF ID: " + fmt .Sprintf ("%d" , g .cipherSuite .KdfId ))
}
if g .cipherSuite .AeadId != dicttls .AEAD_AES_128_GCM &&
g .cipherSuite .AeadId != dicttls .AEAD_AES_256_GCM &&
g .cipherSuite .AeadId != dicttls .AEAD_CHACHA20_POLY1305 {
return fullLen , errors .New ("bad AEAD ID: " + fmt .Sprintf ("%d" , g .cipherSuite .AeadId ))
}
g .CandidateCipherSuites = []HPKESymmetricCipherSuite {g .cipherSuite }
if !extData .ReadUint8 (&g .configId ) {
return fullLen , errors .New ("bad config ID" )
}
if !extData .ReadUint16LengthPrefixed (&ignored ) {
return fullLen , errors .New ("bad encapsulated key" )
}
g .EncapsulatedKey = make ([]byte , len (ignored ))
n , err := rand .Read (g .EncapsulatedKey )
if err != nil {
return fullLen , fmt .Errorf ("tls: generating grease ech encapsulated key: %w" , err )
}
if n != len (g .EncapsulatedKey ) {
return fullLen , fmt .Errorf ("tls: generating grease ech encapsulated key: short read for %d bytes" , len (ignored )-n )
}
if !extData .ReadUint16LengthPrefixed (&ignored ) {
return fullLen , errors .New ("bad payload" )
}
g .CandidatePayloadLens = []uint16 {uint16 (len (ignored ) - cipherLen (g .cipherSuite .AeadId , 0 ))}
return fullLen , nil
}
type UnimplementedECHExtension struct {}
func (*UnimplementedECHExtension ) writeToUConn (_ *UConn ) error {
return errors .New ("tls: unimplemented ECHExtension" )
}
func (*UnimplementedECHExtension ) Len () int {
return 0
}
func (*UnimplementedECHExtension ) Read (_ []byte ) (int , error ) {
return 0 , errors .New ("tls: unimplemented ECHExtension" )
}
func (*UnimplementedECHExtension ) MarshalClientHello (*UConn ) error {
return errors .New ("tls: unimplemented ECHExtension" )
}
func (*UnimplementedECHExtension ) mustEmbedUnimplementedECHExtension () {
panic ("mustEmbedUnimplementedECHExtension() is not implemented" )
}
func BoringGREASEECH () *GREASEEncryptedClientHelloExtension {
return &GREASEEncryptedClientHelloExtension {
CandidateCipherSuites : []HPKESymmetricCipherSuite {
{
KdfId : dicttls .HKDF_SHA256 ,
AeadId : dicttls .AEAD_AES_128_GCM ,
},
},
CandidatePayloadLens : []uint16 {128 , 160 , 192 , 224 },
}
}
The pages are generated with Golds v0.8.4 . (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 .