Clear direct dependencies on other ECH package

This commit is contained in:
yuhan6665 2025-05-18 17:13:16 -04:00
parent cd3fabe9bd
commit ef7852c030
4 changed files with 139 additions and 44 deletions

3
go.mod
View File

@ -3,8 +3,6 @@ module github.com/xtls/xray-core
go 1.24
require (
github.com/OmarTariq612/goech v0.0.1
github.com/cloudflare/circl v1.6.1
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344
github.com/golang/mock v1.7.0-rc.1
github.com/google/go-cmp v0.7.0
@ -36,6 +34,7 @@ require (
require (
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect

4
go.sum
View File

@ -1,5 +1,3 @@
github.com/OmarTariq612/goech v0.0.1 h1:/0c918Bk1ik65GXDj2k7SOK78DyZr30Jmq9euy1/HXg=
github.com/OmarTariq612/goech v0.0.1/go.mod h1:FVGavL/QEBQDcBpr3fAojoK17xX5k9bicBphrOpP7uM=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
@ -76,8 +74,6 @@ github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/xtls/reality v0.0.0-20250516070713-4df2ec9a5b47 h1:9aJWkgWBwZ83l3j7+hBh3SurvRKuNfCgsSner5n6BcM=
github.com/xtls/reality v0.0.0-20250516070713-4df2ec9a5b47/go.mod h1:bJdU3ExzfUlY40Xxfibq3THW9IHiE8mHu/tEzud5JWM=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=

View File

@ -1,13 +1,15 @@
package tls
import (
"encoding/base64"
"encoding/pem"
"os"
"github.com/OmarTariq612/goech"
"github.com/cloudflare/circl/hpke"
"github.com/xtls/reality/hpke"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/main/commands/base"
"github.com/xtls/xray-core/transport/internet/tls"
"golang.org/x/crypto/cryptobyte"
)
var cmdECH = &base.Command{
@ -30,34 +32,37 @@ var input_serverName = cmdECH.Flag.String("serverName", "cloudflare-ech.com", ""
var input_pem = cmdECH.Flag.Bool("pem", false, "True == turn on pem output")
func executeECH(cmd *base.Command, args []string) {
var kem hpke.KEM
var kem uint16
if *input_pqSignatureSchemesEnabled {
kem = hpke.KEM_X25519_KYBER768_DRAFT00
} else {
kem = hpke.KEM_X25519_HKDF_SHA256
}
// if *input_pqSignatureSchemesEnabled {
// kem = 0x30 // hpke.KEM_X25519_KYBER768_DRAFT00
// } else {
kem = hpke.DHKEM_X25519_HKDF_SHA256
// }
echKeySet, err := goech.GenerateECHKeySet(0, *input_serverName, kem, nil)
echKeySet, priv, err := tls.GenerateECHKeySet(0, *input_serverName, kem)
common.Must(err)
// Make single key set to a list with only one element
ECHConfigList := make(goech.ECHConfigList, 1)
ECHConfigList[0] = echKeySet.ECHConfig
ECHKeySetList := make(goech.ECHKeySetList, 1)
ECHKeySetList[0] = echKeySet
configBuffer, _ := ECHConfigList.MarshalBinary()
keyBuffer, _ := ECHKeySetList.MarshalBinary()
configStr, _ := ECHConfigList.ToBase64()
keySetStr, _ := ECHKeySetList.ToBase64()
configBytes, _ := tls.MarshalBinary(echKeySet)
var b cryptobyte.Builder
b.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {
child.AddBytes(configBytes)
})
configBuffer, _ := b.Bytes()
var b2 cryptobyte.Builder
b2.AddUint16(uint16(len(priv)))
b2.AddBytes(priv)
b2.AddUint16(uint16(len(configBytes)))
b2.AddBytes(configBytes)
keyBuffer, _ := b2.Bytes()
configPEM := string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBuffer}))
keyPEM := string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBuffer}))
if *input_pem {
configPEM := string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBuffer}))
keyPEM := string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBuffer}))
os.Stdout.WriteString(configPEM)
os.Stdout.WriteString(keyPEM)
} else {
os.Stdout.WriteString("ECH config list: \n" + configStr + "\n")
os.Stdout.WriteString("ECH Key sets: \n" + keySetStr + "\n")
os.Stdout.WriteString("ECH config list: \n" + base64.StdEncoding.EncodeToString(configBuffer) + "\n")
os.Stdout.WriteString("ECH Key sets: \n" + base64.StdEncoding.EncodeToString(keyBuffer) + "\n")
}
}

View File

@ -3,18 +3,24 @@ package tls
import (
"bytes"
"context"
"crypto/ecdh"
"crypto/rand"
"crypto/tls"
"encoding/base64"
"encoding/binary"
"io"
"net/http"
"strings"
"sync"
"time"
"github.com/OmarTariq612/goech"
"github.com/miekg/dns"
"github.com/xtls/reality"
"github.com/xtls/reality/hpke"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet"
"golang.org/x/crypto/cryptobyte"
)
func ApplyECH(c *Config, config *tls.Config) error {
@ -48,11 +54,10 @@ func ApplyECH(c *Config, config *tls.Config) error {
return err
}
} else {
ECHConfigList, err := goech.ECHConfigListFromBase64(c.EchConfigList)
ECHConfig, err = base64.StdEncoding.DecodeString(c.EchConfigList)
if err != nil {
return errors.New("Failed to unmarshal ECHConfigList: ", err)
}
ECHConfig, _ = ECHConfigList.MarshalBinary()
}
config.EncryptedClientHelloConfigList = ECHConfig
@ -60,22 +65,11 @@ func ApplyECH(c *Config, config *tls.Config) error {
// for server
if len(c.EchKeySets) != 0 {
var keys []tls.EncryptedClientHelloKey
KeySets, err := goech.UnmarshalECHKeySetList(c.EchKeySets)
KeySets, err := ConvertToGoECHKeys(c.EchKeySets)
if err != nil {
return errors.New("Failed to unmarshal ECHKeySetList: ", err)
}
for idx, keySet := range KeySets {
ECHConfig, err := keySet.ECHConfig.MarshalBinary()
ECHPrivateKey, err := keySet.PrivateKey.MarshalBinary()
if err != nil {
return errors.New("Failed to marshal ECHKey in index: ", idx, "with err: ", err)
}
keys = append(keys, tls.EncryptedClientHelloKey{
Config: ECHConfig,
PrivateKey: ECHPrivateKey})
}
config.EncryptedClientHelloKeys = keys
config.EncryptedClientHelloKeys = KeySets
}
return nil
@ -237,3 +231,104 @@ func dnsQuery(server string, domain string) ([]byte, uint32, error) {
}
return []byte{}, 0, errors.New("no ech record found")
}
// reference github.com/OmarTariq612/goech
func MarshalBinary(ech reality.EchConfig) ([]byte, error) {
var b cryptobyte.Builder
b.AddUint16(ech.Version)
b.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {
child.AddUint8(ech.ConfigID)
child.AddUint16(ech.KemID)
child.AddUint16(uint16(len(ech.PublicKey)))
child.AddBytes(ech.PublicKey)
child.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {
for _, cipherSuite := range ech.SymmetricCipherSuite {
child.AddUint16(cipherSuite.KDFID)
child.AddUint16(cipherSuite.AEADID)
}
})
child.AddUint8(ech.MaxNameLength)
child.AddUint8(uint8(len(ech.PublicName)))
child.AddBytes(ech.PublicName)
child.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {
for _, extention := range ech.Extensions {
child.AddUint16(extention.Type)
child.AddBytes(extention.Data)
}
})
})
return b.Bytes()
}
var ErrInvalidLen = errors.New("goech: invalid length")
func ConvertToGoECHKeys(data []byte) ([]tls.EncryptedClientHelloKey, error) {
var keys []tls.EncryptedClientHelloKey
s := cryptobyte.String(data)
for !s.Empty() {
if len(s) < 2 {
return keys, ErrInvalidLen
}
keyLength := int(binary.BigEndian.Uint16(s[:2]))
if len(s) < keyLength+4 {
return keys, ErrInvalidLen
}
configLength := int(binary.BigEndian.Uint16(s[keyLength+2 : keyLength+4]))
if len(s) < 2+keyLength+2+configLength {
return keys, ErrInvalidLen
}
child := cryptobyte.String(s[:2+keyLength+2+configLength])
var (
sk, config cryptobyte.String
)
if !child.ReadUint16LengthPrefixed(&sk) || !child.ReadUint16LengthPrefixed(&config) || !child.Empty() {
return keys, ErrInvalidLen
}
if !s.Skip(2 + keyLength + 2 + configLength) {
return keys, ErrInvalidLen
}
keys = append(keys, tls.EncryptedClientHelloKey{
Config: config,
PrivateKey: sk,
})
}
return keys, nil
}
const ExtensionEncryptedClientHello = 0xfe0d
const KDF_HKDF_SHA384 = 0x0002
const KDF_HKDF_SHA512 = 0x0003
func GenerateECHKeySet(configID uint8, domain string, kem uint16) (reality.EchConfig, []byte, error) {
config := reality.EchConfig{
Version: ExtensionEncryptedClientHello,
ConfigID: configID,
PublicName: []byte(domain),
KemID: kem,
SymmetricCipherSuite: []reality.EchCipher{
{KDFID: hpke.KDF_HKDF_SHA256, AEADID: hpke.AEAD_AES_128_GCM},
{KDFID: hpke.KDF_HKDF_SHA256, AEADID: hpke.AEAD_AES_256_GCM},
{KDFID: hpke.KDF_HKDF_SHA256, AEADID: hpke.AEAD_ChaCha20Poly1305},
{KDFID: KDF_HKDF_SHA384, AEADID: hpke.AEAD_AES_128_GCM},
{KDFID: KDF_HKDF_SHA384, AEADID: hpke.AEAD_AES_256_GCM},
{KDFID: KDF_HKDF_SHA384, AEADID: hpke.AEAD_ChaCha20Poly1305},
{KDFID: KDF_HKDF_SHA512, AEADID: hpke.AEAD_AES_128_GCM},
{KDFID: KDF_HKDF_SHA512, AEADID: hpke.AEAD_AES_256_GCM},
{KDFID: KDF_HKDF_SHA512, AEADID: hpke.AEAD_ChaCha20Poly1305},
},
MaxNameLength: 0,
Extensions: nil,
}
// if kem == hpke.DHKEM_X25519_HKDF_SHA256 {
curve := ecdh.X25519()
priv := make([]byte, 32) //x25519
_, err := io.ReadFull(rand.Reader, priv)
if err != nil {
return config, nil, err
}
privKey, _ := curve.NewPrivateKey(priv)
config.PublicKey = privKey.PublicKey().Bytes();
return config, priv, nil
// }
// TODO: add mlkem768 (former kyber768 draft00). The golang mlkem private key is 64 bytes seed?
}