Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ module github.com/xtls/xray-core
go 1.24

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/golang/mock v1.7.0-rc.1
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -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.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
Expand Down
12 changes: 12 additions & 0 deletions infra/conf/transport_internet.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,8 @@ type TLSConfig struct {
MasterKeyLog string `json:"masterKeyLog"`
ServerNameToVerify string `json:"serverNameToVerify"`
VerifyPeerCertInNames []string `json:"verifyPeerCertInNames"`
ECHConfigList string `json:"echConfigList"`
ECHServerKeys string `json:"echServerKeys"`
}

// Build implements Buildable.
Expand Down Expand Up @@ -483,6 +485,16 @@ func (c *TLSConfig) Build() (proto.Message, error) {
}
config.VerifyPeerCertInNames = c.VerifyPeerCertInNames

config.EchConfigList = c.ECHConfigList

if c.ECHServerKeys != "" {
EchPrivateKey, err := base64.StdEncoding.DecodeString(c.ECHServerKeys)
if err != nil {
return nil, errors.New("invalid ECH Config", c.ECHServerKeys)
}
config.EchServerKeys = EchPrivateKey
}

return config, nil
}

Expand Down
88 changes: 56 additions & 32 deletions main/commands/all/tls/ech.go
Original file line number Diff line number Diff line change
@@ -1,69 +1,93 @@
package tls

import (
"encoding/json"
"encoding/base64"
"encoding/pem"
"os"
"strings"

"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{
UsageLine: `{{.Exec}} tls ech [--serverName (string)] [--json]`,
UsageLine: `{{.Exec}} tls ech [--serverName (string)] [--pem] [-i "ECHServerKeys (base64.StdEncoding)"]`,
Short: `Generate TLS-ECH certificates`,
Long: `
Generate TLS-ECH certificates.

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
Restore ECHConfigs from ECHServerKeys: {{.Exec}} tls ech -i "ECHServerKeys (base64.StdEncoding)"
`, // Enable PQ signature schemes: {{.Exec}} tls ech --pq-signature-schemes-enabled
}

func init() {
cmdECH.Run = executeECH
}

var input_pqSignatureSchemesEnabled = cmdECH.Flag.Bool("pqSignatureSchemesEnabled", false, "")
var input_echServerKeys = cmdECH.Flag.String("i", "", "ECHServerKeys (base64.StdEncoding)")

// var input_pqSignatureSchemesEnabled = cmdECH.Flag.Bool("pqSignatureSchemesEnabled", false, "")
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) {
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)
echConfig, priv, err := tls.GenerateECHKeySet(0, *input_serverName, kem)
common.Must(err)

configBuffer, _ := echKeySet.ECHConfig.MarshalBinary()
keyBuffer, _ := echKeySet.MarshalBinary()

configPEM := string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBuffer}))
keyPEM := string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBuffer}))
if *input_json {
jECHConfigs := map[string]interface{}{
"configs": strings.Split(strings.TrimSpace(string(configPEM)), "\n"),
var configBuffer, keyBuffer []byte
if *input_echServerKeys == "" {
configBytes, _ := tls.MarshalBinary(echConfig)
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()
} else {
keySetsByte, err := base64.StdEncoding.DecodeString(*input_echServerKeys)
if err != nil {
os.Stdout.WriteString("Failed to decode ECHServerKeys: " + err.Error() + "\n")
return
}
jECHKey := map[string]interface{}{
"key": strings.Split(strings.TrimSpace(string(keyPEM)), "\n"),
keyBuffer = keySetsByte
KeySets, err := tls.ConvertToGoECHKeys(keySetsByte)
if err != nil {
os.Stdout.WriteString("Failed to decode ECHServerKeys: " + err.Error() + "\n")
return
}

for _, i := range []map[string]interface{}{jECHConfigs, jECHKey} {
content, err := json.MarshalIndent(i, "", " ")
common.Must(err)
os.Stdout.Write(content)
os.Stdout.WriteString("\n")
var b cryptobyte.Builder
for _, keySet := range KeySets {
b.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {
child.AddBytes(keySet.Config)
})
}
} else {
configBuffer, _ = b.Bytes()
}

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" + base64.StdEncoding.EncodeToString(configBuffer) + "\n")
os.Stdout.WriteString("ECH server keys: \n" + base64.StdEncoding.EncodeToString(keyBuffer) + "\n")
}
}
6 changes: 6 additions & 0 deletions transport/internet/tls/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,12 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config {
config.KeyLogWriter = writer
}
}
if len(c.EchConfigList) > 0 || len(c.EchServerKeys) > 0 {
err := ApplyECH(c, config)
if err != nil {
errors.LogError(context.Background(), err)
}
}

return config
}
Expand Down
25 changes: 23 additions & 2 deletions transport/internet/tls/config.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions transport/internet/tls/config.proto
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,8 @@ message Config {
@Critical
*/
repeated string verify_peer_cert_in_names = 17;

string ech_config_list = 18;

bytes ech_server_keys = 19;
}
Loading
Loading