mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-05-08 14:13:22 +00:00
180 lines
5.5 KiB
Go
180 lines
5.5 KiB
Go
package tls
|
|
|
|
import (
|
|
"crypto/ecdh"
|
|
"crypto/hpke"
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"encoding/pem"
|
|
"io"
|
|
"os"
|
|
|
|
"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)] [--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 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_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_pem = cmdECH.Flag.Bool("pem", false, "True == turn on pem output")
|
|
|
|
func executeECH(cmd *base.Command, args []string) {
|
|
var kem uint16
|
|
|
|
// if *input_pqSignatureSchemesEnabled {
|
|
// kem = 0x30 // hpke.KEM_X25519_KYBER768_DRAFT00
|
|
// } else {
|
|
kem = hpke.DHKEM(ecdh.X25519()).ID()
|
|
// }
|
|
|
|
echConfig, priv, err := generateECHKeySet(0, *input_serverName, kem)
|
|
common.Must(err)
|
|
|
|
var configBuffer, keyBuffer []byte
|
|
if *input_echServerKeys == "" {
|
|
configBytes, _ := 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
|
|
}
|
|
keyBuffer = keySetsByte
|
|
KeySets, err := tls.ConvertToGoECHKeys(keySetsByte)
|
|
if err != nil {
|
|
os.Stdout.WriteString("Failed to decode ECHServerKeys: " + err.Error() + "\n")
|
|
return
|
|
}
|
|
var b cryptobyte.Builder
|
|
for _, keySet := range KeySets {
|
|
b.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {
|
|
child.AddBytes(keySet.Config)
|
|
})
|
|
}
|
|
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")
|
|
}
|
|
}
|
|
|
|
type EchConfig struct {
|
|
Version uint16
|
|
ConfigID uint8
|
|
KemID uint16
|
|
PublicKey []byte
|
|
SymmetricCipherSuite []EchCipher
|
|
MaxNameLength uint8
|
|
PublicName []byte
|
|
Extensions []Extension
|
|
}
|
|
|
|
type EchCipher struct {
|
|
KDFID uint16
|
|
AEADID uint16
|
|
}
|
|
|
|
type Extension struct {
|
|
Type uint16
|
|
Data []byte
|
|
}
|
|
|
|
// reference github.com/OmarTariq612/goech
|
|
func marshalBinary(ech 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()
|
|
}
|
|
|
|
const ExtensionEncryptedClientHello = 0xfe0d
|
|
|
|
func generateECHKeySet(configID uint8, domain string, kem uint16) (EchConfig, []byte, error) {
|
|
config := EchConfig{
|
|
Version: ExtensionEncryptedClientHello,
|
|
ConfigID: configID,
|
|
PublicName: []byte(domain),
|
|
KemID: kem,
|
|
SymmetricCipherSuite: []EchCipher{
|
|
{KDFID: hpke.HKDFSHA256().ID(), AEADID: hpke.AES128GCM().ID()},
|
|
{KDFID: hpke.HKDFSHA256().ID(), AEADID: hpke.AES256GCM().ID()},
|
|
{KDFID: hpke.HKDFSHA256().ID(), AEADID: hpke.ChaCha20Poly1305().ID()},
|
|
{KDFID: hpke.HKDFSHA384().ID(), AEADID: hpke.AES128GCM().ID()},
|
|
{KDFID: hpke.HKDFSHA384().ID(), AEADID: hpke.AES256GCM().ID()},
|
|
{KDFID: hpke.HKDFSHA384().ID(), AEADID: hpke.ChaCha20Poly1305().ID()},
|
|
{KDFID: hpke.HKDFSHA512().ID(), AEADID: hpke.AES128GCM().ID()},
|
|
{KDFID: hpke.HKDFSHA512().ID(), AEADID: hpke.AES256GCM().ID()},
|
|
{KDFID: hpke.HKDFSHA512().ID(), AEADID: hpke.ChaCha20Poly1305().ID()},
|
|
},
|
|
MaxNameLength: 0,
|
|
Extensions: nil,
|
|
}
|
|
// if kem == hpke.DHKEM_X25519_HKDF_SHA256 {
|
|
curve := ecdh.X25519()
|
|
priv := make([]byte, 32)
|
|
_, 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
|
|
}
|