mirror of
https://github.com/XTLS/Xray-core.git
synced 2025-07-23 18:34:14 +00:00
Merge 5760b373b7
into 1976d02ec9
This commit is contained in:
commit
4aad2076a9
10 changed files with 489 additions and 53 deletions
3
go.mod
3
go.mod
|
@ -3,8 +3,6 @@ module github.com/xtls/xray-core
|
||||||
go 1.24
|
go 1.24
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0
|
|
||||||
github.com/cloudflare/circl v1.6.1
|
|
||||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344
|
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344
|
||||||
github.com/golang/mock v1.7.0-rc.1
|
github.com/golang/mock v1.7.0-rc.1
|
||||||
github.com/google/go-cmp v0.7.0
|
github.com/google/go-cmp v0.7.0
|
||||||
|
@ -36,6 +34,7 @@ require (
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
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/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
|
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||||
|
|
8
go.sum
8
go.sum
|
@ -1,5 +1,3 @@
|
||||||
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0 h1:Wo41lDOevRJSGpevP+8Pk5bANX7fJacO2w04aqLiC5I=
|
|
||||||
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0/go.mod h1:FVGavL/QEBQDcBpr3fAojoK17xX5k9bicBphrOpP7uM=
|
|
||||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||||
|
@ -78,6 +76,8 @@ 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/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 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
||||||
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||||
|
github.com/xtls/reality v0.0.0-20250527000105-e679ef7bb130 h1:v/TVypWnLferyoaNHh6a8oyggj9APBUzfl1OOgXNbpw=
|
||||||
|
github.com/xtls/reality v0.0.0-20250527000105-e679ef7bb130/go.mod h1:bJdU3ExzfUlY40Xxfibq3THW9IHiE8mHu/tEzud5JWM=
|
||||||
github.com/xtls/reality v0.0.0-20250608132114-50752aec6bfb h1:X6ziJCMsFF8Ac/0F3W7+UbFdHZTu+r5nZ/smksHVxNQ=
|
github.com/xtls/reality v0.0.0-20250608132114-50752aec6bfb h1:X6ziJCMsFF8Ac/0F3W7+UbFdHZTu+r5nZ/smksHVxNQ=
|
||||||
github.com/xtls/reality v0.0.0-20250608132114-50752aec6bfb/go.mod h1:yD47RN65bDLZgyHWMfFDiqlzrq4usDMt/Xzsk6tMbhw=
|
github.com/xtls/reality v0.0.0-20250608132114-50752aec6bfb/go.mod h1:yD47RN65bDLZgyHWMfFDiqlzrq4usDMt/Xzsk6tMbhw=
|
||||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
|
@ -143,8 +143,12 @@ golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeu
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||||
|
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
|
||||||
|
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
||||||
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
|
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
|
||||||
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
|
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
|
||||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||||
|
|
|
@ -412,6 +412,8 @@ type TLSConfig struct {
|
||||||
MasterKeyLog string `json:"masterKeyLog"`
|
MasterKeyLog string `json:"masterKeyLog"`
|
||||||
ServerNameToVerify string `json:"serverNameToVerify"`
|
ServerNameToVerify string `json:"serverNameToVerify"`
|
||||||
VerifyPeerCertInNames []string `json:"verifyPeerCertInNames"`
|
VerifyPeerCertInNames []string `json:"verifyPeerCertInNames"`
|
||||||
|
ECHConfigList string `json:"echConfigList"`
|
||||||
|
EchKeySets string `json:"echKeySets"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build implements Buildable.
|
// Build implements Buildable.
|
||||||
|
@ -483,6 +485,16 @@ func (c *TLSConfig) Build() (proto.Message, error) {
|
||||||
}
|
}
|
||||||
config.VerifyPeerCertInNames = c.VerifyPeerCertInNames
|
config.VerifyPeerCertInNames = c.VerifyPeerCertInNames
|
||||||
|
|
||||||
|
config.EchConfigList = c.ECHConfigList
|
||||||
|
|
||||||
|
if c.EchKeySets != "" {
|
||||||
|
EchPrivateKey, err := base64.StdEncoding.DecodeString(c.EchKeySets)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("invalid ECH Config", c.EchKeySets)
|
||||||
|
}
|
||||||
|
config.EchKeySets = EchPrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
package tls
|
package tls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/base64"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/OmarTariq612/goech"
|
"github.com/xtls/reality/hpke"
|
||||||
"github.com/cloudflare/circl/hpke"
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/main/commands/base"
|
"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{
|
var cmdECH = &base.Command{
|
||||||
UsageLine: `{{.Exec}} tls ech [--serverName (string)] [--json]`,
|
UsageLine: `{{.Exec}} tls ech [--serverName (string)] [--pem]`,
|
||||||
Short: `Generate TLS-ECH certificates`,
|
Short: `Generate TLS-ECH certificates`,
|
||||||
Long: `
|
Long: `
|
||||||
Generate TLS-ECH certificates.
|
Generate TLS-ECH certificates.
|
||||||
|
|
||||||
Set serverName to your custom string: {{.Exec}} tls ech --serverName (string)
|
Set serverName to your custom string: {{.Exec}} tls ech --serverName (string)
|
||||||
Generate into json format: {{.Exec}} tls ech --json
|
Generate into pem format: {{.Exec}} tls ech --pem
|
||||||
`, // Enable PQ signature schemes: {{.Exec}} tls ech --pq-signature-schemes-enabled
|
`, // Enable PQ signature schemes: {{.Exec}} tls ech --pq-signature-schemes-enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,41 +29,40 @@ func init() {
|
||||||
|
|
||||||
var input_pqSignatureSchemesEnabled = cmdECH.Flag.Bool("pqSignatureSchemesEnabled", false, "")
|
var input_pqSignatureSchemesEnabled = cmdECH.Flag.Bool("pqSignatureSchemesEnabled", false, "")
|
||||||
var input_serverName = cmdECH.Flag.String("serverName", "cloudflare-ech.com", "")
|
var input_serverName = cmdECH.Flag.String("serverName", "cloudflare-ech.com", "")
|
||||||
var input_json = cmdECH.Flag.Bool("json", false, "True == turn on json output")
|
var input_pem = cmdECH.Flag.Bool("pem", false, "True == turn on pem output")
|
||||||
|
|
||||||
func executeECH(cmd *base.Command, args []string) {
|
func executeECH(cmd *base.Command, args []string) {
|
||||||
var kem hpke.KEM
|
var kem uint16
|
||||||
|
|
||||||
if *input_pqSignatureSchemesEnabled {
|
// if *input_pqSignatureSchemesEnabled {
|
||||||
kem = hpke.KEM_X25519_KYBER768_DRAFT00
|
// kem = 0x30 // hpke.KEM_X25519_KYBER768_DRAFT00
|
||||||
} else {
|
// } else {
|
||||||
kem = hpke.KEM_X25519_HKDF_SHA256
|
kem = hpke.DHKEM_X25519_HKDF_SHA256
|
||||||
}
|
// }
|
||||||
|
|
||||||
echKeySet, err := goech.GenerateECHKeySet(0, *input_serverName, kem)
|
echKeySet, priv, err := tls.GenerateECHKeySet(0, *input_serverName, kem)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
configBuffer, _ := echKeySet.ECHConfig.MarshalBinary()
|
configBytes, _ := tls.MarshalBinary(echKeySet)
|
||||||
keyBuffer, _ := echKeySet.MarshalBinary()
|
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}))
|
if *input_pem {
|
||||||
keyPEM := string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBuffer}))
|
configPEM := string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBuffer}))
|
||||||
if *input_json {
|
keyPEM := string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBuffer}))
|
||||||
jECHConfigs := map[string]interface{}{
|
|
||||||
"configs": strings.Split(strings.TrimSpace(string(configPEM)), "\n"),
|
|
||||||
}
|
|
||||||
jECHKey := map[string]interface{}{
|
|
||||||
"key": strings.Split(strings.TrimSpace(string(keyPEM)), "\n"),
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, i := range []map[string]interface{}{jECHConfigs, jECHKey} {
|
|
||||||
content, err := json.MarshalIndent(i, "", " ")
|
|
||||||
common.Must(err)
|
|
||||||
os.Stdout.Write(content)
|
|
||||||
os.Stdout.WriteString("\n")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
os.Stdout.WriteString(configPEM)
|
os.Stdout.WriteString(configPEM)
|
||||||
os.Stdout.WriteString(keyPEM)
|
os.Stdout.WriteString(keyPEM)
|
||||||
|
} else {
|
||||||
|
os.Stdout.WriteString("ECH config list: \n" + base64.StdEncoding.EncodeToString(configBuffer) + "\n")
|
||||||
|
os.Stdout.WriteString("ECH Key sets: \n" + base64.StdEncoding.EncodeToString(keyBuffer) + "\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -444,6 +444,12 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config {
|
||||||
config.KeyLogWriter = writer
|
config.KeyLogWriter = writer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(c.EchConfigList) > 0 || len(c.EchKeySets) > 0 {
|
||||||
|
err := ApplyECH(c, config)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogError(context.Background(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
|
@ -217,6 +217,8 @@ type Config struct {
|
||||||
// @Document After allow_insecure (automatically), if the server's cert can't be verified by any of these names, pinned_peer_certificate_chain_sha256 will be tried.
|
// @Document After allow_insecure (automatically), if the server's cert can't be verified by any of these names, pinned_peer_certificate_chain_sha256 will be tried.
|
||||||
// @Critical
|
// @Critical
|
||||||
VerifyPeerCertInNames []string `protobuf:"bytes,17,rep,name=verify_peer_cert_in_names,json=verifyPeerCertInNames,proto3" json:"verify_peer_cert_in_names,omitempty"`
|
VerifyPeerCertInNames []string `protobuf:"bytes,17,rep,name=verify_peer_cert_in_names,json=verifyPeerCertInNames,proto3" json:"verify_peer_cert_in_names,omitempty"`
|
||||||
|
EchConfigList string `protobuf:"bytes,18,opt,name=ech_config_list,json=echConfigList,proto3" json:"ech_config_list,omitempty"`
|
||||||
|
EchKeySets []byte `protobuf:"bytes,19,opt,name=ech_key_sets,json=echKeySets,proto3" json:"ech_key_sets,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) Reset() {
|
func (x *Config) Reset() {
|
||||||
|
@ -361,6 +363,20 @@ func (x *Config) GetVerifyPeerCertInNames() []string {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetEchConfigList() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.EchConfigList
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetEchKeySets() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.EchKeySets
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var File_transport_internet_tls_config_proto protoreflect.FileDescriptor
|
var File_transport_internet_tls_config_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_transport_internet_tls_config_proto_rawDesc = []byte{
|
var file_transport_internet_tls_config_proto_rawDesc = []byte{
|
||||||
|
@ -392,7 +408,7 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{
|
||||||
0x4e, 0x43, 0x49, 0x50, 0x48, 0x45, 0x52, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x00, 0x12, 0x14, 0x0a,
|
0x4e, 0x43, 0x49, 0x50, 0x48, 0x45, 0x52, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x00, 0x12, 0x14, 0x0a,
|
||||||
0x10, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x56, 0x45, 0x52, 0x49, 0x46,
|
0x10, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x56, 0x45, 0x52, 0x49, 0x46,
|
||||||
0x59, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59,
|
0x59, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59,
|
||||||
0x5f, 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0x9a, 0x06, 0x0a, 0x06, 0x43, 0x6f, 0x6e,
|
0x5f, 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0xe4, 0x06, 0x0a, 0x06, 0x43, 0x6f, 0x6e,
|
||||||
0x66, 0x69, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x6e, 0x73,
|
0x66, 0x69, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x6e, 0x73,
|
||||||
0x65, 0x63, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, 0x6c, 0x6c,
|
0x65, 0x63, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, 0x6c, 0x6c,
|
||||||
0x6f, 0x77, 0x49, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x12, 0x4a, 0x0a, 0x0b, 0x63, 0x65,
|
0x6f, 0x77, 0x49, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x12, 0x4a, 0x0a, 0x0b, 0x63, 0x65,
|
||||||
|
@ -442,15 +458,19 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{
|
||||||
0x65, 0x72, 0x69, 0x66, 0x79, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x5f,
|
0x65, 0x72, 0x69, 0x66, 0x79, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x5f,
|
||||||
0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x11, 0x20, 0x03, 0x28, 0x09, 0x52, 0x15,
|
0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x11, 0x20, 0x03, 0x28, 0x09, 0x52, 0x15,
|
||||||
0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x65, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x49, 0x6e,
|
0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x65, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x49, 0x6e,
|
||||||
0x4e, 0x61, 0x6d, 0x65, 0x73, 0x42, 0x73, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61,
|
0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x65, 0x63, 0x68, 0x5f, 0x63, 0x6f, 0x6e,
|
||||||
0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65,
|
0x66, 0x69, 0x67, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d,
|
||||||
0x72, 0x6e, 0x65, 0x74, 0x2e, 0x74, 0x6c, 0x73, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68,
|
0x65, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x20, 0x0a,
|
||||||
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79,
|
0x0c, 0x65, 0x63, 0x68, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x73, 0x18, 0x13, 0x20,
|
||||||
0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f,
|
0x01, 0x28, 0x0c, 0x52, 0x0a, 0x65, 0x63, 0x68, 0x4b, 0x65, 0x79, 0x53, 0x65, 0x74, 0x73, 0x42,
|
||||||
0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x74, 0x6c, 0x73, 0xaa, 0x02, 0x1b, 0x58,
|
0x73, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e,
|
||||||
0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e,
|
0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x74,
|
||||||
0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x54, 0x6c, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
|
0x6c, 0x73, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
|
||||||
0x6f, 0x33,
|
0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f,
|
||||||
|
0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
|
||||||
|
0x65, 0x74, 0x2f, 0x74, 0x6c, 0x73, 0xaa, 0x02, 0x1b, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72,
|
||||||
|
0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74,
|
||||||
|
0x2e, 0x54, 0x6c, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -91,4 +91,8 @@ message Config {
|
||||||
@Critical
|
@Critical
|
||||||
*/
|
*/
|
||||||
repeated string verify_peer_cert_in_names = 17;
|
repeated string verify_peer_cert_in_names = 17;
|
||||||
|
|
||||||
|
string ech_config_list = 18;
|
||||||
|
|
||||||
|
bytes ech_key_sets = 19;
|
||||||
}
|
}
|
||||||
|
|
348
transport/internet/tls/ech.go
Normal file
348
transport/internet/tls/ech.go
Normal file
|
@ -0,0 +1,348 @@
|
||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/ecdh"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"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 {
|
||||||
|
var ECHConfig []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
nameToQuery := c.ServerName
|
||||||
|
var DNSServer string
|
||||||
|
|
||||||
|
// for client
|
||||||
|
if len(c.EchConfigList) != 0 {
|
||||||
|
// direct base64 config
|
||||||
|
if strings.Contains(c.EchConfigList, "://") {
|
||||||
|
// query config from dns
|
||||||
|
parts := strings.Split(c.EchConfigList, "+")
|
||||||
|
if len(parts) == 2 {
|
||||||
|
// parse ECH DNS server in format of "example.com+https://1.1.1.1/dns-query"
|
||||||
|
nameToQuery = parts[0]
|
||||||
|
DNSServer = parts[1]
|
||||||
|
} else if len(parts) == 1 {
|
||||||
|
// normal format
|
||||||
|
DNSServer = parts[0]
|
||||||
|
} else {
|
||||||
|
return errors.New("Invalid ECH DNS server format: ", c.EchConfigList)
|
||||||
|
}
|
||||||
|
if nameToQuery == "" {
|
||||||
|
return errors.New("Using DNS for ECH Config needs serverName or use Server format example.com+https://1.1.1.1/dns-query")
|
||||||
|
}
|
||||||
|
ECHConfig, err = QueryRecord(nameToQuery, DNSServer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ECHConfig, err = base64.StdEncoding.DecodeString(c.EchConfigList)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("Failed to unmarshal ECHConfigList: ", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config.EncryptedClientHelloConfigList = ECHConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// for server
|
||||||
|
if len(c.EchKeySets) != 0 {
|
||||||
|
KeySets, err := ConvertToGoECHKeys(c.EchKeySets)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("Failed to unmarshal ECHKeySetList: ", err)
|
||||||
|
}
|
||||||
|
config.EncryptedClientHelloKeys = KeySets
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ECHConfigCache struct {
|
||||||
|
echConfig atomic.Pointer[[]byte]
|
||||||
|
expire atomic.Pointer[time.Time]
|
||||||
|
// updateLock is not for preventing concurrent read/write, but for preventing concurrent update
|
||||||
|
updateLock sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ECHConfigCache) update(domain string, server string) ([]byte, error) {
|
||||||
|
c.updateLock.Lock()
|
||||||
|
defer c.updateLock.Unlock()
|
||||||
|
// Double check cache after acquiring lock
|
||||||
|
if c.expire.Load().After(time.Now()) {
|
||||||
|
errors.LogDebug(context.Background(), "Cache hit for domain after double check: ", domain)
|
||||||
|
return *c.echConfig.Load(), nil
|
||||||
|
}
|
||||||
|
// Query ECH config from DNS server
|
||||||
|
errors.LogDebug(context.Background(), "Trying to query ECH config for domain: ", domain, " with ECH server: ", server)
|
||||||
|
echConfig, ttl, err := dnsQuery(server, domain)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.echConfig.Store(&echConfig)
|
||||||
|
expire := time.Now().Add(time.Duration(ttl) * time.Second)
|
||||||
|
c.expire.Store(&expire)
|
||||||
|
return *c.echConfig.Load(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
GlobalECHConfigCache map[string]*ECHConfigCache
|
||||||
|
GlobalECHConfigCacheAccess sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
|
// QueryRecord returns the ECH config for given domain.
|
||||||
|
// If the record is not in cache or expired, it will query the DNS server and update the cache.
|
||||||
|
func QueryRecord(domain string, server string) ([]byte, error) {
|
||||||
|
// Global cache init
|
||||||
|
GlobalECHConfigCacheAccess.Lock()
|
||||||
|
if GlobalECHConfigCache == nil {
|
||||||
|
GlobalECHConfigCache = make(map[string]*ECHConfigCache)
|
||||||
|
}
|
||||||
|
|
||||||
|
echConfigCache := GlobalECHConfigCache[domain]
|
||||||
|
if echConfigCache == nil {
|
||||||
|
echConfigCache = &ECHConfigCache{}
|
||||||
|
echConfigCache.expire.Store(&time.Time{}) // zero value means initial state
|
||||||
|
GlobalECHConfigCache[domain] = echConfigCache
|
||||||
|
}
|
||||||
|
if echConfigCache != nil && echConfigCache.expire.Load().After(time.Now()) {
|
||||||
|
errors.LogDebug(context.Background(), "Cache hit for domain: ", domain)
|
||||||
|
GlobalECHConfigCacheAccess.Unlock()
|
||||||
|
return *echConfigCache.echConfig.Load(), nil
|
||||||
|
}
|
||||||
|
GlobalECHConfigCacheAccess.Unlock()
|
||||||
|
|
||||||
|
// If expire is zero value, it means we are in initial state, wait for the query to finish
|
||||||
|
// otherwise return old value immediately and update in a goroutine
|
||||||
|
if *echConfigCache.expire.Load() == (time.Time{}) {
|
||||||
|
return echConfigCache.update(domain, server)
|
||||||
|
} else {
|
||||||
|
// If someone already acquired the lock, it means it is updating, do not start another update goroutine
|
||||||
|
if echConfigCache.updateLock.TryLock() {
|
||||||
|
go echConfigCache.update(domain, server)
|
||||||
|
}
|
||||||
|
return *echConfigCache.echConfig.Load(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dnsQuery is the real func for sending type65 query for given domain to given DNS server.
|
||||||
|
// return ECH config, TTL and error
|
||||||
|
func dnsQuery(server string, domain string) ([]byte, uint32, error) {
|
||||||
|
m := new(dns.Msg)
|
||||||
|
var dnsResolve []byte
|
||||||
|
m.SetQuestion(dns.Fqdn(domain), dns.TypeHTTPS)
|
||||||
|
// for DOH server
|
||||||
|
if strings.HasPrefix(server, "https://") {
|
||||||
|
// always 0 in DOH
|
||||||
|
m.Id = 0
|
||||||
|
msg, err := m.Pack()
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, 0, err
|
||||||
|
}
|
||||||
|
// All traffic sent by core should via xray's internet.DialSystem
|
||||||
|
// This involves the behavior of some Android VPN GUI clients
|
||||||
|
tr := &http.Transport{
|
||||||
|
IdleConnTimeout: 90 * time.Second,
|
||||||
|
ForceAttemptHTTP2: true,
|
||||||
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
dest, err := net.ParseDestination(network + ":" + addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conn, err := internet.DialSystem(ctx, dest, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
client := &http.Client{
|
||||||
|
Timeout: 5 * time.Second,
|
||||||
|
Transport: tr,
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest("POST", server, bytes.NewReader(msg))
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, 0, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/dns-message")
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, 0, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
respBody, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, 0, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return []byte{}, 0, errors.New("query failed with response code:", resp.StatusCode)
|
||||||
|
}
|
||||||
|
dnsResolve = respBody
|
||||||
|
} else if strings.HasPrefix(server, "udp://") { // for classic udp dns server
|
||||||
|
udpServerAddr := server[len("udp://"):]
|
||||||
|
// default port 53 if not specified
|
||||||
|
if !strings.Contains(udpServerAddr, ":") {
|
||||||
|
udpServerAddr = udpServerAddr + ":53"
|
||||||
|
}
|
||||||
|
dest, err := net.ParseDestination("udp" + ":" + udpServerAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, errors.New("failed to parse udp dns server ", udpServerAddr, " for ECH: ", err)
|
||||||
|
}
|
||||||
|
dnsTimeoutCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
// use xray's internet.DialSystem as mentioned above
|
||||||
|
conn, err := internet.DialSystem(dnsTimeoutCtx, dest, nil)
|
||||||
|
defer conn.Close()
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, 0, err
|
||||||
|
}
|
||||||
|
msg, err := m.Pack()
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, 0, err
|
||||||
|
}
|
||||||
|
conn.Write(msg)
|
||||||
|
udpResponse := make([]byte, 512)
|
||||||
|
_, err = conn.Read(udpResponse)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, 0, err
|
||||||
|
}
|
||||||
|
dnsResolve = udpResponse
|
||||||
|
}
|
||||||
|
respMsg := new(dns.Msg)
|
||||||
|
err := respMsg.Unpack(dnsResolve)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, 0, errors.New("failed to unpack dns response for ECH: ", err)
|
||||||
|
}
|
||||||
|
if len(respMsg.Answer) > 0 {
|
||||||
|
for _, answer := range respMsg.Answer {
|
||||||
|
if https, ok := answer.(*dns.HTTPS); ok && https.Hdr.Name == dns.Fqdn(domain) {
|
||||||
|
for _, v := range https.Value {
|
||||||
|
if echConfig, ok := v.(*dns.SVCBECHConfig); ok {
|
||||||
|
errors.LogDebug(context.Background(), "Get ECH config:", echConfig.String(), " TTL:", respMsg.Answer[0].Header().Ttl)
|
||||||
|
return echConfig.ECH, answer.Header().Ttl, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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?
|
||||||
|
}
|
43
transport/internet/tls/ech_test.go
Normal file
43
transport/internet/tls/ech_test.go
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
package tls_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
. "github.com/xtls/xray-core/transport/internet/tls"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestECHDial(t *testing.T) {
|
||||||
|
config := &Config{
|
||||||
|
ServerName: "encryptedsni.com",
|
||||||
|
EchConfigList: "udp://1.1.1.1",
|
||||||
|
}
|
||||||
|
// test concurrent Dial(to test cache problem)
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
for range 10 {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
TLSConfig := config.GetTLSConfig()
|
||||||
|
TLSConfig.NextProtos = []string{"http/1.1"}
|
||||||
|
client := &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
TLSClientConfig: TLSConfig,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
resp, err := client.Get("https://encryptedsni.com/cdn-cgi/trace")
|
||||||
|
common.Must(err)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
common.Must(err)
|
||||||
|
if !strings.Contains(string(body), "sni=encrypted") {
|
||||||
|
t.Error("ECH Dial success but SNI is not encrypted")
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
|
@ -128,12 +128,13 @@ func UClient(c net.Conn, config *tls.Config, fingerprint *utls.ClientHelloID) ne
|
||||||
|
|
||||||
func copyConfig(c *tls.Config) *utls.Config {
|
func copyConfig(c *tls.Config) *utls.Config {
|
||||||
return &utls.Config{
|
return &utls.Config{
|
||||||
Rand: c.Rand,
|
Rand: c.Rand,
|
||||||
RootCAs: c.RootCAs,
|
RootCAs: c.RootCAs,
|
||||||
ServerName: c.ServerName,
|
ServerName: c.ServerName,
|
||||||
InsecureSkipVerify: c.InsecureSkipVerify,
|
InsecureSkipVerify: c.InsecureSkipVerify,
|
||||||
VerifyPeerCertificate: c.VerifyPeerCertificate,
|
VerifyPeerCertificate: c.VerifyPeerCertificate,
|
||||||
KeyLogWriter: c.KeyLogWriter,
|
KeyLogWriter: c.KeyLogWriter,
|
||||||
|
EncryptedClientHelloConfigList: c.EncryptedClientHelloConfigList,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue