mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-11-01 07:57:03 +00:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbcf2c4e45 | ||
|
|
3f4a4004f9 | ||
|
|
87c08d0d7a | ||
|
|
0e6ab63b8a | ||
|
|
2f121bd6a2 | ||
|
|
fdb9b616fc |
@@ -145,6 +145,8 @@
|
||||
- [v2rayN](https://github.com/2dust/v2rayN)
|
||||
- [GenyConnect](https://github.com/genyleap/GenyConnect)
|
||||
- [OneXray](https://github.com/OneXray/OneXray)
|
||||
- HarmonyOS
|
||||
- [Hey](https://github.com/popsiclelmlm/Hey)
|
||||
|
||||
## Others that support VLESS, XTLS, REALITY, XUDP, PLUX...
|
||||
|
||||
|
||||
@@ -82,12 +82,6 @@ func KeyUsage(usage x509.KeyUsage) Option {
|
||||
}
|
||||
}
|
||||
|
||||
func ExtKeyUsage(usage []x509.ExtKeyUsage) Option {
|
||||
return func(c *x509.Certificate) {
|
||||
c.ExtKeyUsage = usage
|
||||
}
|
||||
}
|
||||
|
||||
func Organization(org string) Option {
|
||||
return func(c *x509.Certificate) {
|
||||
c.Subject.Organization = []string{org}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"runtime"
|
||||
"sync"
|
||||
"weak"
|
||||
@@ -44,16 +43,3 @@ func (c *WeakCacheMap[K, V]) Store(key K, value *V) {
|
||||
}
|
||||
}, struct{}{})
|
||||
}
|
||||
|
||||
func (c *WeakCacheMap[K, V]) Range(f func(K, *V) bool) {
|
||||
c.mu.Lock()
|
||||
snapshot := maps.Clone(c.m)
|
||||
c.mu.Unlock()
|
||||
for k, v := range snapshot {
|
||||
if value := v.Value(); value != nil {
|
||||
if !f(k, value) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"math"
|
||||
"math/big"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -219,8 +220,10 @@ type SplitHTTPConfig struct {
|
||||
XPaddingPlacement string `json:"xPaddingPlacement"`
|
||||
XPaddingMethod string `json:"xPaddingMethod"`
|
||||
UplinkHTTPMethod string `json:"uplinkHTTPMethod"`
|
||||
SessionPlacement string `json:"sessionPlacement"`
|
||||
SessionKey string `json:"sessionKey"`
|
||||
SessionIDPlacement string `json:"sessionIDPlacement"`
|
||||
SessionIDKey string `json:"sessionIDKey"`
|
||||
SessionIDTable string `json:"sessionIDTable"`
|
||||
SessionIDLength Int32Range `json:"sessionIDLength"`
|
||||
SeqPlacement string `json:"seqPlacement"`
|
||||
SeqKey string `json:"seqKey"`
|
||||
UplinkDataPlacement string `json:"uplinkDataPlacement"`
|
||||
@@ -331,12 +334,12 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
|
||||
return nil, errors.New("uplinkHTTPMethod can be GET only in packet-up mode")
|
||||
}
|
||||
|
||||
switch c.SessionPlacement {
|
||||
switch c.SessionIDPlacement {
|
||||
case "":
|
||||
c.SessionPlacement = "path"
|
||||
c.SessionIDPlacement = "path"
|
||||
case "path", "cookie", "header", "query":
|
||||
default:
|
||||
return nil, errors.New("unsupported session placement: " + c.SessionPlacement)
|
||||
return nil, errors.New("unsupported session placement: " + c.SessionIDPlacement)
|
||||
}
|
||||
|
||||
switch c.SeqPlacement {
|
||||
@@ -347,12 +350,31 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
|
||||
return nil, errors.New("unsupported seq placement: " + c.SeqPlacement)
|
||||
}
|
||||
|
||||
if c.SessionPlacement != "path" && c.SessionKey == "" {
|
||||
switch c.SessionPlacement {
|
||||
if c.SessionIDPlacement != "path" && c.SessionIDKey == "" {
|
||||
switch c.SessionIDPlacement {
|
||||
case "cookie", "query":
|
||||
c.SessionKey = "x_session"
|
||||
c.SessionIDKey = "x_session"
|
||||
case "header":
|
||||
c.SessionKey = "X-Session"
|
||||
c.SessionIDKey = "X-Session"
|
||||
}
|
||||
}
|
||||
|
||||
if c.SessionIDTable != "" {
|
||||
if predefined, ok := splithttp.PredefinedTable[c.SessionIDTable]; ok {
|
||||
c.SessionIDTable = predefined
|
||||
}
|
||||
room := roomSize(len(c.SessionIDTable), c.SessionIDLength.From, c.SessionIDLength.To)
|
||||
// 2.1B possiblities should be enough
|
||||
if room.Cmp(big.NewInt(2<<30)) < 0 {
|
||||
return nil, errors.New("sessionIDTable or sessionIDLength is too small")
|
||||
}
|
||||
if c.SessionIDLength.From <= 0 {
|
||||
return nil, errors.New("sessionIDLength.from must be greater than 0")
|
||||
}
|
||||
for i := 0; i < len(c.SessionIDTable); i++ {
|
||||
if c.SessionIDTable[i] >= 0x80 {
|
||||
return nil, errors.New("sessionIDTable must contain only ASCII characters")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,9 +424,9 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
|
||||
XPaddingPlacement: c.XPaddingPlacement,
|
||||
XPaddingMethod: c.XPaddingMethod,
|
||||
UplinkHTTPMethod: c.UplinkHTTPMethod,
|
||||
SessionPlacement: c.SessionPlacement,
|
||||
SessionIDPlacement: c.SessionIDPlacement,
|
||||
SeqPlacement: c.SeqPlacement,
|
||||
SessionKey: c.SessionKey,
|
||||
SessionIDKey: c.SessionIDKey,
|
||||
SeqKey: c.SeqKey,
|
||||
UplinkDataPlacement: c.UplinkDataPlacement,
|
||||
UplinkDataKey: c.UplinkDataKey,
|
||||
@@ -416,6 +438,8 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
|
||||
ScMaxBufferedPosts: c.ScMaxBufferedPosts,
|
||||
ScStreamUpServerSecs: newRangeConfig(c.ScStreamUpServerSecs),
|
||||
ServerMaxHeaderBytes: c.ServerMaxHeaderBytes,
|
||||
SessionIDTable: c.SessionIDTable,
|
||||
SessionIDLength: newRangeConfig(c.SessionIDLength),
|
||||
Xmux: &splithttp.XmuxConfig{
|
||||
MaxConcurrency: newRangeConfig(c.Xmux.MaxConcurrency),
|
||||
MaxConnections: newRangeConfig(c.Xmux.MaxConnections),
|
||||
@@ -439,6 +463,17 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func roomSize(tableSize int, min, max int32) *big.Int {
|
||||
base := big.NewInt(int64(tableSize))
|
||||
sum := new(big.Int)
|
||||
term := new(big.Int)
|
||||
for k := min; k <= max; k++ {
|
||||
term.Exp(base, big.NewInt(int64(k)), nil)
|
||||
sum.Add(sum, term)
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
const (
|
||||
Byte = 1
|
||||
Kilobyte = 1024 * Byte
|
||||
@@ -603,10 +638,6 @@ func (c *TLSCertConfig) Build() (*tls.Certificate, error) {
|
||||
certificate.Usage = tls.Certificate_AUTHORITY_VERIFY
|
||||
case "issue":
|
||||
certificate.Usage = tls.Certificate_AUTHORITY_ISSUE
|
||||
case "client-cert":
|
||||
certificate.Usage = tls.Certificate_MTLS_CLIENT_CERT
|
||||
case "client-ca":
|
||||
certificate.Usage = tls.Certificate_MTLS_CLIENT_CA
|
||||
default:
|
||||
certificate.Usage = tls.Certificate_ENCIPHERMENT
|
||||
}
|
||||
@@ -657,7 +688,6 @@ type TLSConfig struct {
|
||||
ECHServerKeys string `json:"echServerKeys"`
|
||||
ECHConfigList string `json:"echConfigList"`
|
||||
ECHSocketSettings *SocketConfig `json:"echSockopt"`
|
||||
ClientAuth string `json:"clientAuth"`
|
||||
}
|
||||
|
||||
// Build implements Buildable.
|
||||
@@ -746,7 +776,6 @@ func (c *TLSConfig) Build() (proto.Message, error) {
|
||||
config.EchSocketSettings = ss
|
||||
}
|
||||
|
||||
config.ClientAuth = c.ClientAuth
|
||||
return config, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -126,243 +126,6 @@ func TestSimpleTLSConnection(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMTLSConnection(t *testing.T) {
|
||||
tcpServer := tcp.Server{
|
||||
MsgProcessor: xor,
|
||||
}
|
||||
dest, err := tcpServer.Start()
|
||||
common.Must(err)
|
||||
defer tcpServer.Close()
|
||||
|
||||
serverCert, serverCertHash := cert.MustGenerate(nil, cert.CommonName("localhost"))
|
||||
|
||||
// CA that issues client certificates; the server trusts it to verify client certs.
|
||||
// ExtKeyUsage must allow ClientAuth on the CA too, otherwise the chain fails the
|
||||
// server's ClientAuth key-usage check.
|
||||
clientCA, _ := cert.MustGenerate(nil, cert.Authority(true),
|
||||
cert.KeyUsage(x509.KeyUsageCertSign|x509.KeyUsageDigitalSignature),
|
||||
cert.ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}))
|
||||
clientCACertPEM, _ := clientCA.ToPEM()
|
||||
|
||||
// Client certificate signed by the CA. It must carry ClientAuth ext key usage,
|
||||
// otherwise crypto/tls rejects it during client-cert verification.
|
||||
clientCert, _ := cert.MustGenerate(clientCA, cert.CommonName("client"),
|
||||
cert.ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}))
|
||||
clientCertPEM, clientKeyPEM := clientCert.ToPEM()
|
||||
|
||||
userID := protocol.NewID(uuid.New())
|
||||
serverPort := tcp.PickPort()
|
||||
serverConfig := &core.Config{
|
||||
Inbound: []*core.InboundHandlerConfig{
|
||||
{
|
||||
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
|
||||
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},
|
||||
Listen: net.NewIPOrDomain(net.LocalHostIP),
|
||||
StreamSettings: &internet.StreamConfig{
|
||||
SecurityType: serial.GetMessageType(&tls.Config{}),
|
||||
SecuritySettings: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&tls.Config{
|
||||
Certificate: []*tls.Certificate{
|
||||
tls.ParseCertificate(serverCert),
|
||||
{
|
||||
Certificate: clientCACertPEM,
|
||||
Usage: tls.Certificate_MTLS_CLIENT_CA,
|
||||
},
|
||||
},
|
||||
ClientAuth: "requireandverifyclientcert",
|
||||
}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
ProxySettings: serial.ToTypedMessage(&inbound.Config{
|
||||
User: []*protocol.User{
|
||||
{
|
||||
Account: serial.ToTypedMessage(&vmess.Account{
|
||||
Id: userID.String(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
clientPort := tcp.PickPort()
|
||||
clientConfig := &core.Config{
|
||||
Inbound: []*core.InboundHandlerConfig{
|
||||
{
|
||||
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
|
||||
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},
|
||||
Listen: net.NewIPOrDomain(net.LocalHostIP),
|
||||
}),
|
||||
ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
|
||||
RewriteAddress: net.NewIPOrDomain(dest.Address),
|
||||
RewritePort: uint32(dest.Port),
|
||||
AllowedNetworks: []net.Network{net.Network_TCP},
|
||||
}),
|
||||
},
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{
|
||||
ProxySettings: serial.ToTypedMessage(&outbound.Config{
|
||||
Receiver: &protocol.ServerEndpoint{
|
||||
Address: net.NewIPOrDomain(net.LocalHostIP),
|
||||
Port: uint32(serverPort),
|
||||
User: &protocol.User{
|
||||
Account: serial.ToTypedMessage(&vmess.Account{
|
||||
Id: userID.String(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
|
||||
StreamSettings: &internet.StreamConfig{
|
||||
SecurityType: serial.GetMessageType(&tls.Config{}),
|
||||
SecuritySettings: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&tls.Config{
|
||||
PinnedPeerCertSha256: [][]byte{serverCertHash[:]},
|
||||
Certificate: []*tls.Certificate{
|
||||
{
|
||||
Certificate: clientCertPEM,
|
||||
Key: clientKeyPEM,
|
||||
Usage: tls.Certificate_MTLS_CLIENT_CERT,
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
servers, err := InitializeServerConfigs(serverConfig, clientConfig)
|
||||
common.Must(err)
|
||||
defer CloseAllServers(servers)
|
||||
|
||||
if err := testTCPConn(clientPort, 1024, time.Second*20)(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMTLSConnectionMissingClientCert(t *testing.T) {
|
||||
tcpServer := tcp.Server{
|
||||
MsgProcessor: xor,
|
||||
}
|
||||
dest, err := tcpServer.Start()
|
||||
common.Must(err)
|
||||
defer tcpServer.Close()
|
||||
|
||||
serverCert, serverCertHash := cert.MustGenerate(nil, cert.CommonName("localhost"))
|
||||
|
||||
clientCA, _ := cert.MustGenerate(nil, cert.Authority(true),
|
||||
cert.KeyUsage(x509.KeyUsageCertSign|x509.KeyUsageDigitalSignature))
|
||||
clientCACertPEM, _ := clientCA.ToPEM()
|
||||
|
||||
userID := protocol.NewID(uuid.New())
|
||||
serverPort := tcp.PickPort()
|
||||
serverConfig := &core.Config{
|
||||
Inbound: []*core.InboundHandlerConfig{
|
||||
{
|
||||
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
|
||||
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},
|
||||
Listen: net.NewIPOrDomain(net.LocalHostIP),
|
||||
StreamSettings: &internet.StreamConfig{
|
||||
SecurityType: serial.GetMessageType(&tls.Config{}),
|
||||
SecuritySettings: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&tls.Config{
|
||||
Certificate: []*tls.Certificate{
|
||||
tls.ParseCertificate(serverCert),
|
||||
{
|
||||
Certificate: clientCACertPEM,
|
||||
Usage: tls.Certificate_MTLS_CLIENT_CA,
|
||||
},
|
||||
},
|
||||
ClientAuth: "requireandverifyclientcert",
|
||||
}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
ProxySettings: serial.ToTypedMessage(&inbound.Config{
|
||||
User: []*protocol.User{
|
||||
{
|
||||
Account: serial.ToTypedMessage(&vmess.Account{
|
||||
Id: userID.String(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
clientPort := tcp.PickPort()
|
||||
clientConfig := &core.Config{
|
||||
Inbound: []*core.InboundHandlerConfig{
|
||||
{
|
||||
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
|
||||
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},
|
||||
Listen: net.NewIPOrDomain(net.LocalHostIP),
|
||||
}),
|
||||
ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
|
||||
RewriteAddress: net.NewIPOrDomain(dest.Address),
|
||||
RewritePort: uint32(dest.Port),
|
||||
AllowedNetworks: []net.Network{net.Network_TCP},
|
||||
}),
|
||||
},
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{
|
||||
ProxySettings: serial.ToTypedMessage(&outbound.Config{
|
||||
Receiver: &protocol.ServerEndpoint{
|
||||
Address: net.NewIPOrDomain(net.LocalHostIP),
|
||||
Port: uint32(serverPort),
|
||||
User: &protocol.User{
|
||||
Account: serial.ToTypedMessage(&vmess.Account{
|
||||
Id: userID.String(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
|
||||
StreamSettings: &internet.StreamConfig{
|
||||
SecurityType: serial.GetMessageType(&tls.Config{}),
|
||||
SecuritySettings: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&tls.Config{
|
||||
// No MTLS_CLIENT_CERT: the client presents nothing,
|
||||
// so the server must reject the handshake.
|
||||
PinnedPeerCertSha256: [][]byte{serverCertHash[:]},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
servers, err := InitializeServerConfigs(serverConfig, clientConfig)
|
||||
common.Must(err)
|
||||
defer CloseAllServers(servers)
|
||||
|
||||
if err := testTCPConn(clientPort, 1024, time.Second*20)(); err == nil {
|
||||
t.Fatal("expected handshake failure when the client presents no certificate")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutoIssuingCertificate(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
// Not supported on Windows yet.
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand/v2"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/crypto"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
"github.com/xtls/xray-core/common/uuid"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
)
|
||||
|
||||
@@ -131,26 +133,26 @@ func (c *Config) GetNormalizedUplinkHTTPMethod() string {
|
||||
return c.UplinkHTTPMethod
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedScMaxEachPostBytes() RangeConfig {
|
||||
func (c *Config) GetNormalizedScMaxEachPostBytes() *RangeConfig {
|
||||
if c.ScMaxEachPostBytes == nil || c.ScMaxEachPostBytes.To == 0 {
|
||||
return RangeConfig{
|
||||
return &RangeConfig{
|
||||
From: 1000000,
|
||||
To: 1000000,
|
||||
}
|
||||
}
|
||||
|
||||
return *c.ScMaxEachPostBytes
|
||||
return c.ScMaxEachPostBytes
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedScMinPostsIntervalMs() RangeConfig {
|
||||
func (c *Config) GetNormalizedScMinPostsIntervalMs() *RangeConfig {
|
||||
if c.ScMinPostsIntervalMs == nil || c.ScMinPostsIntervalMs.To == 0 {
|
||||
return RangeConfig{
|
||||
return &RangeConfig{
|
||||
From: 30,
|
||||
To: 30,
|
||||
}
|
||||
}
|
||||
|
||||
return *c.ScMinPostsIntervalMs
|
||||
return c.ScMinPostsIntervalMs
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedScMaxBufferedPosts() int {
|
||||
@@ -161,27 +163,27 @@ func (c *Config) GetNormalizedScMaxBufferedPosts() int {
|
||||
return int(c.ScMaxBufferedPosts)
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedScStreamUpServerSecs() RangeConfig {
|
||||
func (c *Config) GetNormalizedScStreamUpServerSecs() *RangeConfig {
|
||||
if c.ScStreamUpServerSecs == nil || c.ScStreamUpServerSecs.To == 0 {
|
||||
return RangeConfig{
|
||||
return &RangeConfig{
|
||||
From: 20,
|
||||
To: 80,
|
||||
}
|
||||
}
|
||||
|
||||
return *c.ScStreamUpServerSecs
|
||||
return c.ScStreamUpServerSecs
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedUplinkChunkSize() RangeConfig {
|
||||
func (c *Config) GetNormalizedUplinkChunkSize() *RangeConfig {
|
||||
if c.UplinkChunkSize == nil || c.UplinkChunkSize.To == 0 {
|
||||
switch c.UplinkDataPlacement {
|
||||
case PlacementCookie:
|
||||
return RangeConfig{
|
||||
return &RangeConfig{
|
||||
From: 2 * 1024, // 2 KiB
|
||||
To: 3 * 1024, // 3 KiB
|
||||
}
|
||||
case PlacementHeader:
|
||||
return RangeConfig{
|
||||
return &RangeConfig{
|
||||
From: 3 * 1000, // 3 KB
|
||||
To: 4 * 1000, // 4 KB
|
||||
}
|
||||
@@ -189,13 +191,13 @@ func (c *Config) GetNormalizedUplinkChunkSize() RangeConfig {
|
||||
return c.GetNormalizedScMaxEachPostBytes()
|
||||
}
|
||||
} else if c.UplinkChunkSize.From < 64 {
|
||||
return RangeConfig{
|
||||
return &RangeConfig{
|
||||
From: 64,
|
||||
To: max(64, c.UplinkChunkSize.To),
|
||||
}
|
||||
}
|
||||
|
||||
return *c.UplinkChunkSize
|
||||
return c.UplinkChunkSize
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedServerMaxHeaderBytes() int {
|
||||
@@ -207,10 +209,10 @@ func (c *Config) GetNormalizedServerMaxHeaderBytes() int {
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedSessionPlacement() string {
|
||||
if c.SessionPlacement == "" {
|
||||
if c.SessionIDPlacement == "" {
|
||||
return PlacementPath
|
||||
}
|
||||
return c.SessionPlacement
|
||||
return c.SessionIDPlacement
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedSeqPlacement() string {
|
||||
@@ -228,8 +230,8 @@ func (c *Config) GetNormalizedUplinkDataPlacement() string {
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedSessionKey() string {
|
||||
if c.SessionKey != "" {
|
||||
return c.SessionKey
|
||||
if c.SessionIDKey != "" {
|
||||
return c.SessionIDKey
|
||||
}
|
||||
switch c.GetNormalizedSessionPlacement() {
|
||||
case PlacementHeader:
|
||||
@@ -417,59 +419,59 @@ func (c *Config) ExtractMetaFromRequest(req *http.Request, path string) (session
|
||||
return sessionId, seqStr
|
||||
}
|
||||
|
||||
func (m *XmuxConfig) GetNormalizedMaxConcurrency() RangeConfig {
|
||||
func (m *XmuxConfig) GetNormalizedMaxConcurrency() *RangeConfig {
|
||||
if m.MaxConcurrency == nil {
|
||||
return RangeConfig{
|
||||
return &RangeConfig{
|
||||
From: 0,
|
||||
To: 0,
|
||||
}
|
||||
}
|
||||
|
||||
return *m.MaxConcurrency
|
||||
return m.MaxConcurrency
|
||||
}
|
||||
|
||||
func (m *XmuxConfig) GetNormalizedMaxConnections() RangeConfig {
|
||||
func (m *XmuxConfig) GetNormalizedMaxConnections() *RangeConfig {
|
||||
if m.MaxConnections == nil {
|
||||
return RangeConfig{
|
||||
return &RangeConfig{
|
||||
From: 0,
|
||||
To: 0,
|
||||
}
|
||||
}
|
||||
|
||||
return *m.MaxConnections
|
||||
return m.MaxConnections
|
||||
}
|
||||
|
||||
func (m *XmuxConfig) GetNormalizedCMaxReuseTimes() RangeConfig {
|
||||
func (m *XmuxConfig) GetNormalizedCMaxReuseTimes() *RangeConfig {
|
||||
if m.CMaxReuseTimes == nil {
|
||||
return RangeConfig{
|
||||
return &RangeConfig{
|
||||
From: 0,
|
||||
To: 0,
|
||||
}
|
||||
}
|
||||
|
||||
return *m.CMaxReuseTimes
|
||||
return m.CMaxReuseTimes
|
||||
}
|
||||
|
||||
func (m *XmuxConfig) GetNormalizedHMaxRequestTimes() RangeConfig {
|
||||
func (m *XmuxConfig) GetNormalizedHMaxRequestTimes() *RangeConfig {
|
||||
if m.HMaxRequestTimes == nil {
|
||||
return RangeConfig{
|
||||
return &RangeConfig{
|
||||
From: 0,
|
||||
To: 0,
|
||||
}
|
||||
}
|
||||
|
||||
return *m.HMaxRequestTimes
|
||||
return m.HMaxRequestTimes
|
||||
}
|
||||
|
||||
func (m *XmuxConfig) GetNormalizedHMaxReusableSecs() RangeConfig {
|
||||
func (m *XmuxConfig) GetNormalizedHMaxReusableSecs() *RangeConfig {
|
||||
if m.HMaxReusableSecs == nil {
|
||||
return RangeConfig{
|
||||
return &RangeConfig{
|
||||
From: 0,
|
||||
To: 0,
|
||||
}
|
||||
}
|
||||
|
||||
return *m.HMaxReusableSecs
|
||||
return m.HMaxReusableSecs
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -478,10 +480,44 @@ func init() {
|
||||
}))
|
||||
}
|
||||
|
||||
func (c RangeConfig) rand() int32 {
|
||||
func (c *RangeConfig) rand() int32 {
|
||||
if c == nil {
|
||||
return 0
|
||||
}
|
||||
return int32(crypto.RandBetween(int64(c.From), int64(c.To)))
|
||||
}
|
||||
|
||||
// predefined
|
||||
var PredefinedTable = map[string]string{
|
||||
"ALPHABET": "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
"Alphabet": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
|
||||
"BASE36": "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
"Base62": "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
|
||||
"HEX": "0123456789ABCDEF",
|
||||
"alphabet": "abcdefghijklmnopqrstuvwxyz",
|
||||
"base36": "0123456789abcdefghijklmnopqrstuvwxyz",
|
||||
"hex": "0123456789abcdef",
|
||||
"number": "0123456789",
|
||||
}
|
||||
|
||||
func (c *Config) GenerateSessionID() string {
|
||||
length := c.SessionIDLength.rand()
|
||||
table := c.SessionIDTable
|
||||
if predefined, ok := PredefinedTable[table]; ok {
|
||||
table = predefined
|
||||
}
|
||||
if table != "" && length > 0 {
|
||||
id := make([]byte, length)
|
||||
for i := range id {
|
||||
id[i] = table[rand.N(len(table))]
|
||||
}
|
||||
return string(id)
|
||||
} else {
|
||||
uuid := uuid.New()
|
||||
return uuid.String()
|
||||
}
|
||||
}
|
||||
|
||||
func appendToPath(path, value string) string {
|
||||
if strings.HasSuffix(path, "/") {
|
||||
return path + value
|
||||
|
||||
@@ -179,14 +179,16 @@ type Config struct {
|
||||
XPaddingPlacement string `protobuf:"bytes,17,opt,name=xPaddingPlacement,proto3" json:"xPaddingPlacement,omitempty"`
|
||||
XPaddingMethod string `protobuf:"bytes,18,opt,name=xPaddingMethod,proto3" json:"xPaddingMethod,omitempty"`
|
||||
UplinkHTTPMethod string `protobuf:"bytes,19,opt,name=uplinkHTTPMethod,proto3" json:"uplinkHTTPMethod,omitempty"`
|
||||
SessionPlacement string `protobuf:"bytes,20,opt,name=sessionPlacement,proto3" json:"sessionPlacement,omitempty"`
|
||||
SessionKey string `protobuf:"bytes,21,opt,name=sessionKey,proto3" json:"sessionKey,omitempty"`
|
||||
SessionIDPlacement string `protobuf:"bytes,20,opt,name=sessionIDPlacement,proto3" json:"sessionIDPlacement,omitempty"`
|
||||
SessionIDKey string `protobuf:"bytes,21,opt,name=sessionIDKey,proto3" json:"sessionIDKey,omitempty"`
|
||||
SeqPlacement string `protobuf:"bytes,22,opt,name=seqPlacement,proto3" json:"seqPlacement,omitempty"`
|
||||
SeqKey string `protobuf:"bytes,23,opt,name=seqKey,proto3" json:"seqKey,omitempty"`
|
||||
UplinkDataPlacement string `protobuf:"bytes,24,opt,name=uplinkDataPlacement,proto3" json:"uplinkDataPlacement,omitempty"`
|
||||
UplinkDataKey string `protobuf:"bytes,25,opt,name=uplinkDataKey,proto3" json:"uplinkDataKey,omitempty"`
|
||||
UplinkChunkSize *RangeConfig `protobuf:"bytes,26,opt,name=uplinkChunkSize,proto3" json:"uplinkChunkSize,omitempty"`
|
||||
ServerMaxHeaderBytes int32 `protobuf:"varint,27,opt,name=serverMaxHeaderBytes,proto3" json:"serverMaxHeaderBytes,omitempty"`
|
||||
SessionIDTable string `protobuf:"bytes,28,opt,name=sessionIDTable,proto3" json:"sessionIDTable,omitempty"`
|
||||
SessionIDLength *RangeConfig `protobuf:"bytes,29,opt,name=sessionIDLength,proto3" json:"sessionIDLength,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -354,16 +356,16 @@ func (x *Config) GetUplinkHTTPMethod() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config) GetSessionPlacement() string {
|
||||
func (x *Config) GetSessionIDPlacement() string {
|
||||
if x != nil {
|
||||
return x.SessionPlacement
|
||||
return x.SessionIDPlacement
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config) GetSessionKey() string {
|
||||
func (x *Config) GetSessionIDKey() string {
|
||||
if x != nil {
|
||||
return x.SessionKey
|
||||
return x.SessionIDKey
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -410,6 +412,20 @@ func (x *Config) GetServerMaxHeaderBytes() int32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Config) GetSessionIDTable() string {
|
||||
if x != nil {
|
||||
return x.SessionIDTable
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config) GetSessionIDLength() *RangeConfig {
|
||||
if x != nil {
|
||||
return x.SessionIDLength
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_transport_internet_splithttp_config_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_transport_internet_splithttp_config_proto_rawDesc = "" +
|
||||
@@ -425,7 +441,7 @@ const file_transport_internet_splithttp_config_proto_rawDesc = "" +
|
||||
"\x0ecMaxReuseTimes\x18\x03 \x01(\v2..xray.transport.internet.splithttp.RangeConfigR\x0ecMaxReuseTimes\x12Z\n" +
|
||||
"\x10hMaxRequestTimes\x18\x04 \x01(\v2..xray.transport.internet.splithttp.RangeConfigR\x10hMaxRequestTimes\x12Z\n" +
|
||||
"\x10hMaxReusableSecs\x18\x05 \x01(\v2..xray.transport.internet.splithttp.RangeConfigR\x10hMaxReusableSecs\x12*\n" +
|
||||
"\x10hKeepAlivePeriod\x18\x06 \x01(\x03R\x10hKeepAlivePeriod\"\xc2\v\n" +
|
||||
"\x10hKeepAlivePeriod\x18\x06 \x01(\x03R\x10hKeepAlivePeriod\"\xcc\f\n" +
|
||||
"\x06Config\x12\x12\n" +
|
||||
"\x04host\x18\x01 \x01(\tR\x04host\x12\x12\n" +
|
||||
"\x04path\x18\x02 \x01(\tR\x04path\x12\x12\n" +
|
||||
@@ -446,17 +462,17 @@ const file_transport_internet_splithttp_config_proto_rawDesc = "" +
|
||||
"\x0exPaddingHeader\x18\x10 \x01(\tR\x0exPaddingHeader\x12,\n" +
|
||||
"\x11xPaddingPlacement\x18\x11 \x01(\tR\x11xPaddingPlacement\x12&\n" +
|
||||
"\x0exPaddingMethod\x18\x12 \x01(\tR\x0exPaddingMethod\x12*\n" +
|
||||
"\x10uplinkHTTPMethod\x18\x13 \x01(\tR\x10uplinkHTTPMethod\x12*\n" +
|
||||
"\x10sessionPlacement\x18\x14 \x01(\tR\x10sessionPlacement\x12\x1e\n" +
|
||||
"\n" +
|
||||
"sessionKey\x18\x15 \x01(\tR\n" +
|
||||
"sessionKey\x12\"\n" +
|
||||
"\x10uplinkHTTPMethod\x18\x13 \x01(\tR\x10uplinkHTTPMethod\x12.\n" +
|
||||
"\x12sessionIDPlacement\x18\x14 \x01(\tR\x12sessionIDPlacement\x12\"\n" +
|
||||
"\fsessionIDKey\x18\x15 \x01(\tR\fsessionIDKey\x12\"\n" +
|
||||
"\fseqPlacement\x18\x16 \x01(\tR\fseqPlacement\x12\x16\n" +
|
||||
"\x06seqKey\x18\x17 \x01(\tR\x06seqKey\x120\n" +
|
||||
"\x13uplinkDataPlacement\x18\x18 \x01(\tR\x13uplinkDataPlacement\x12$\n" +
|
||||
"\ruplinkDataKey\x18\x19 \x01(\tR\ruplinkDataKey\x12X\n" +
|
||||
"\x0fuplinkChunkSize\x18\x1a \x01(\v2..xray.transport.internet.splithttp.RangeConfigR\x0fuplinkChunkSize\x122\n" +
|
||||
"\x14serverMaxHeaderBytes\x18\x1b \x01(\x05R\x14serverMaxHeaderBytes\x1a:\n" +
|
||||
"\x14serverMaxHeaderBytes\x18\x1b \x01(\x05R\x14serverMaxHeaderBytes\x12&\n" +
|
||||
"\x0esessionIDTable\x18\x1c \x01(\tR\x0esessionIDTable\x12X\n" +
|
||||
"\x0fsessionIDLength\x18\x1d \x01(\v2..xray.transport.internet.splithttp.RangeConfigR\x0fsessionIDLength\x1a:\n" +
|
||||
"\fHeadersEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\x85\x01\n" +
|
||||
@@ -496,11 +512,12 @@ var file_transport_internet_splithttp_config_proto_depIdxs = []int32{
|
||||
1, // 10: xray.transport.internet.splithttp.Config.xmux:type_name -> xray.transport.internet.splithttp.XmuxConfig
|
||||
4, // 11: xray.transport.internet.splithttp.Config.downloadSettings:type_name -> xray.transport.internet.StreamConfig
|
||||
0, // 12: xray.transport.internet.splithttp.Config.uplinkChunkSize:type_name -> xray.transport.internet.splithttp.RangeConfig
|
||||
13, // [13:13] is the sub-list for method output_type
|
||||
13, // [13:13] is the sub-list for method input_type
|
||||
13, // [13:13] is the sub-list for extension type_name
|
||||
13, // [13:13] is the sub-list for extension extendee
|
||||
0, // [0:13] is the sub-list for field type_name
|
||||
0, // 13: xray.transport.internet.splithttp.Config.sessionIDLength:type_name -> xray.transport.internet.splithttp.RangeConfig
|
||||
14, // [14:14] is the sub-list for method output_type
|
||||
14, // [14:14] is the sub-list for method input_type
|
||||
14, // [14:14] is the sub-list for extension type_name
|
||||
14, // [14:14] is the sub-list for extension extendee
|
||||
0, // [0:14] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_transport_internet_splithttp_config_proto_init() }
|
||||
|
||||
@@ -42,12 +42,14 @@ message Config {
|
||||
string xPaddingPlacement = 17;
|
||||
string xPaddingMethod = 18;
|
||||
string uplinkHTTPMethod = 19;
|
||||
string sessionPlacement = 20;
|
||||
string sessionKey = 21;
|
||||
string sessionIDPlacement = 20;
|
||||
string sessionIDKey = 21;
|
||||
string seqPlacement = 22;
|
||||
string seqKey = 23;
|
||||
string uplinkDataPlacement = 24;
|
||||
string uplinkDataKey = 25;
|
||||
RangeConfig uplinkChunkSize = 26;
|
||||
int32 serverMaxHeaderBytes = 27;
|
||||
string sessionIDTable = 28;
|
||||
RangeConfig sessionIDLength = 29;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/net/cnc"
|
||||
"github.com/xtls/xray-core/common/signal/done"
|
||||
"github.com/xtls/xray-core/common/uuid"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"github.com/xtls/xray-core/transport/internet/browser_dialer"
|
||||
"github.com/xtls/xray-core/transport/internet/hysteria/congestion"
|
||||
@@ -376,8 +375,7 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
|
||||
|
||||
sessionId := ""
|
||||
if mode != "stream-one" {
|
||||
sessionIdUuid := uuid.New()
|
||||
sessionId = sessionIdUuid.String()
|
||||
sessionId = transportConfiguration.GenerateSessionID()
|
||||
}
|
||||
|
||||
errors.LogInfo(ctx, fmt.Sprintf("XHTTP is dialing to %s, mode %s, HTTP version %s, host %s", dest, mode, httpVersion, requestURL.Host))
|
||||
|
||||
@@ -176,15 +176,15 @@ func ApplyPaddingToQuery(u *url.URL, key, value string) {
|
||||
u.RawQuery = q.Encode()
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedXPaddingBytes() RangeConfig {
|
||||
func (c *Config) GetNormalizedXPaddingBytes() *RangeConfig {
|
||||
if c.XPaddingBytes == nil || c.XPaddingBytes.To == 0 {
|
||||
return RangeConfig{
|
||||
return &RangeConfig{
|
||||
From: 100,
|
||||
To: 1000,
|
||||
}
|
||||
}
|
||||
|
||||
return *c.XPaddingBytes
|
||||
return c.XPaddingBytes
|
||||
}
|
||||
|
||||
func (c *Config) ApplyXPaddingToHeader(h http.Header, config XPaddingConfig) {
|
||||
|
||||
+119
-174
@@ -11,12 +11,12 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/ocsp"
|
||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||
"github.com/xtls/xray-core/common/protocol/tls/cert"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
)
|
||||
@@ -38,9 +38,6 @@ func ParseCertificate(c *cert.Certificate) *Certificate {
|
||||
func (c *Config) loadSelfCertPool() (*x509.CertPool, error) {
|
||||
root := x509.NewCertPool()
|
||||
for _, cert := range c.Certificate {
|
||||
if cert.Usage != Certificate_AUTHORITY_VERIFY {
|
||||
continue
|
||||
}
|
||||
if !root.AppendCertsFromPEM(cert.Certificate) {
|
||||
return nil, errors.New("failed to append cert").AtWarning()
|
||||
}
|
||||
@@ -48,6 +45,91 @@ func (c *Config) loadSelfCertPool() (*x509.CertPool, error) {
|
||||
return root, nil
|
||||
}
|
||||
|
||||
// BuildCertificates builds a list of TLS certificates from proto definition.
|
||||
func (c *Config) BuildCertificates() []*tls.Certificate {
|
||||
certs := make([]*tls.Certificate, 0, len(c.Certificate))
|
||||
for _, entry := range c.Certificate {
|
||||
if entry.Usage != Certificate_ENCIPHERMENT {
|
||||
continue
|
||||
}
|
||||
getX509KeyPair := func() *tls.Certificate {
|
||||
keyPair, err := tls.X509KeyPair(entry.Certificate, entry.Key)
|
||||
if err != nil {
|
||||
errors.LogWarningInner(context.Background(), err, "ignoring invalid X509 key pair")
|
||||
return nil
|
||||
}
|
||||
keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0])
|
||||
if err != nil {
|
||||
errors.LogWarningInner(context.Background(), err, "ignoring invalid certificate")
|
||||
return nil
|
||||
}
|
||||
return &keyPair
|
||||
}
|
||||
if keyPair := getX509KeyPair(); keyPair != nil {
|
||||
certs = append(certs, keyPair)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
index := len(certs) - 1
|
||||
setupOcspTicker(entry, func(isReloaded, isOcspstapling bool) {
|
||||
cert := certs[index]
|
||||
if isReloaded {
|
||||
if newKeyPair := getX509KeyPair(); newKeyPair != nil {
|
||||
cert = newKeyPair
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
if isOcspstapling {
|
||||
if newOCSPData, err := ocsp.GetOCSPForCert(cert.Certificate); err != nil {
|
||||
errors.LogWarningInner(context.Background(), err, "ignoring invalid OCSP")
|
||||
} else if string(newOCSPData) != string(cert.OCSPStaple) {
|
||||
cert.OCSPStaple = newOCSPData
|
||||
}
|
||||
}
|
||||
certs[index] = cert
|
||||
})
|
||||
}
|
||||
return certs
|
||||
}
|
||||
|
||||
func setupOcspTicker(entry *Certificate, callback func(isReloaded, isOcspstapling bool)) {
|
||||
go func() {
|
||||
if entry.OneTimeLoading {
|
||||
return
|
||||
}
|
||||
var isOcspstapling bool
|
||||
hotReloadCertInterval := uint64(3600)
|
||||
if entry.OcspStapling != 0 {
|
||||
hotReloadCertInterval = entry.OcspStapling
|
||||
isOcspstapling = true
|
||||
}
|
||||
t := time.NewTicker(time.Duration(hotReloadCertInterval) * time.Second)
|
||||
for {
|
||||
var isReloaded bool
|
||||
if entry.CertificatePath != "" && entry.KeyPath != "" {
|
||||
newCert, err := filesystem.ReadCert(entry.CertificatePath)
|
||||
if err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "failed to parse certificate")
|
||||
return
|
||||
}
|
||||
newKey, err := filesystem.ReadCert(entry.KeyPath)
|
||||
if err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "failed to parse key")
|
||||
return
|
||||
}
|
||||
if string(newCert) != string(entry.Certificate) || string(newKey) != string(entry.Key) {
|
||||
entry.Certificate = newCert
|
||||
entry.Key = newKey
|
||||
isReloaded = true
|
||||
}
|
||||
}
|
||||
callback(isReloaded, isOcspstapling)
|
||||
<-t.C
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func isCertificateExpired(c *tls.Certificate) bool {
|
||||
if c.Leaf == nil && len(c.Certificate) > 0 {
|
||||
if pc, err := x509.ParseCertificate(c.Certificate[0]); err == nil {
|
||||
@@ -81,28 +163,7 @@ func (c *Config) getCustomCA() []*Certificate {
|
||||
for _, certificate := range c.Certificate {
|
||||
if certificate.Usage == Certificate_AUTHORITY_ISSUE {
|
||||
certs = append(certs, certificate)
|
||||
setupHotReload(certificate)
|
||||
}
|
||||
}
|
||||
return certs
|
||||
}
|
||||
|
||||
func (c *Config) getClientCert() []*Certificate {
|
||||
certs := make([]*Certificate, 0, len(c.Certificate))
|
||||
for _, certificate := range c.Certificate {
|
||||
if certificate.Usage == Certificate_MTLS_CLIENT_CERT {
|
||||
certs = append(certs, certificate)
|
||||
setupHotReload(certificate)
|
||||
}
|
||||
}
|
||||
return certs
|
||||
}
|
||||
|
||||
func (c *Config) getClientCA() []*Certificate {
|
||||
certs := make([]*Certificate, 0, len(c.Certificate))
|
||||
for _, certificate := range c.Certificate {
|
||||
if certificate.Usage == Certificate_MTLS_CLIENT_CA {
|
||||
certs = append(certs, certificate)
|
||||
setupOcspTicker(certificate, func(isReloaded, isOcspstapling bool) {})
|
||||
}
|
||||
}
|
||||
return certs
|
||||
@@ -182,109 +243,34 @@ func getGetCertificateFunc(c *tls.Config, ca []*Certificate) func(hello *tls.Cli
|
||||
}
|
||||
}
|
||||
|
||||
type extraProtoCertData struct {
|
||||
parsed atomic.Pointer[tls.Certificate]
|
||||
ocspData atomic.Pointer[[]byte]
|
||||
lastReload int64
|
||||
}
|
||||
|
||||
// atomic.Pointer must be exactly one pointer word for the ParsedCache overlay below.
|
||||
var _ [unsafe.Sizeof(unsafe.Pointer(nil))]byte = [unsafe.Sizeof(atomic.Pointer[extraProtoCertData]{})]byte{}
|
||||
|
||||
func (c *Certificate) extraData() *extraProtoCertData {
|
||||
// wtf is this
|
||||
slot := (*atomic.Pointer[extraProtoCertData])(unsafe.Pointer(&c.ExtraData))
|
||||
if s := slot.Load(); s != nil {
|
||||
return s
|
||||
}
|
||||
s := &extraProtoCertData{}
|
||||
if slot.CompareAndSwap(nil, s) {
|
||||
return s
|
||||
}
|
||||
return slot.Load()
|
||||
}
|
||||
|
||||
func (c *Certificate) parseX509KeyPair() *tls.Certificate {
|
||||
keyPair, err := tls.X509KeyPair(c.Certificate, c.Key)
|
||||
if err != nil {
|
||||
errors.LogWarningInner(context.Background(), err, "ignoring invalid X509 key pair")
|
||||
return nil
|
||||
}
|
||||
keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0])
|
||||
if err != nil {
|
||||
errors.LogWarningInner(context.Background(), err, "ignoring invalid certificate")
|
||||
return nil
|
||||
}
|
||||
st := c.extraData()
|
||||
if OCSPData := st.ocspData.Load(); OCSPData != nil {
|
||||
keyPair.OCSPStaple = *OCSPData
|
||||
}
|
||||
st.parsed.Store(&keyPair)
|
||||
return &keyPair
|
||||
}
|
||||
|
||||
func (c *Certificate) getX509KeyPair() *tls.Certificate {
|
||||
if keyPair := c.extraData().parsed.Load(); keyPair != nil {
|
||||
return keyPair
|
||||
}
|
||||
return c.parseX509KeyPair()
|
||||
}
|
||||
|
||||
func (c *Config) getCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
var defaultCert *tls.Certificate
|
||||
for _, cert := range c.Certificate {
|
||||
if cert.Usage == Certificate_ENCIPHERMENT {
|
||||
defaultCert = cert.getX509KeyPair()
|
||||
if defaultCert != nil {
|
||||
break
|
||||
}
|
||||
func getNewGetCertificateFunc(certs []*tls.Certificate, rejectUnknownSNI bool) func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
return func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
if len(certs) == 0 {
|
||||
return nil, errNoCertificates
|
||||
}
|
||||
}
|
||||
if defaultCert == nil {
|
||||
return nil, errNoCertificates
|
||||
}
|
||||
sni := strings.ToLower(hello.ServerName)
|
||||
if !c.RejectUnknownSni && (len(c.Certificate) == 1 || sni == "") {
|
||||
return defaultCert, nil
|
||||
}
|
||||
gsni := "*"
|
||||
if index := strings.IndexByte(sni, '.'); index != -1 {
|
||||
gsni += sni[index:]
|
||||
}
|
||||
for _, rawCertificate := range c.Certificate {
|
||||
if rawCertificate.Usage != Certificate_ENCIPHERMENT {
|
||||
continue
|
||||
sni := strings.ToLower(hello.ServerName)
|
||||
if !rejectUnknownSNI && (len(certs) == 1 || sni == "") {
|
||||
return certs[0], nil
|
||||
}
|
||||
keyPair := rawCertificate.getX509KeyPair()
|
||||
if keyPair == nil {
|
||||
continue
|
||||
gsni := "*"
|
||||
if index := strings.IndexByte(sni, '.'); index != -1 {
|
||||
gsni += sni[index:]
|
||||
}
|
||||
if keyPair.Leaf.Subject.CommonName == sni || keyPair.Leaf.Subject.CommonName == gsni {
|
||||
return keyPair, nil
|
||||
}
|
||||
for _, name := range keyPair.Leaf.DNSNames {
|
||||
if name == sni || name == gsni {
|
||||
for _, keyPair := range certs {
|
||||
if keyPair.Leaf.Subject.CommonName == sni || keyPair.Leaf.Subject.CommonName == gsni {
|
||||
return keyPair, nil
|
||||
}
|
||||
for _, name := range keyPair.Leaf.DNSNames {
|
||||
if name == sni || name == gsni {
|
||||
return keyPair, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if c.RejectUnknownSni {
|
||||
return nil, errNoCertificates
|
||||
}
|
||||
return defaultCert, nil
|
||||
}
|
||||
|
||||
func (c *Config) getClientCertificate(cri *tls.CertificateRequestInfo) (*tls.Certificate, error) {
|
||||
for _, cert := range c.Certificate {
|
||||
parsed := cert.getX509KeyPair()
|
||||
if cert.Usage != Certificate_MTLS_CLIENT_CERT || parsed == nil {
|
||||
continue
|
||||
}
|
||||
if err := cri.SupportsCertificate(parsed); err == nil {
|
||||
return parsed, nil
|
||||
if rejectUnknownSNI {
|
||||
return nil, errNoCertificates
|
||||
}
|
||||
return certs[0], nil
|
||||
}
|
||||
return nil, errNoCertificates
|
||||
}
|
||||
|
||||
func (c *Config) parseServerName() string {
|
||||
@@ -422,24 +408,7 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config {
|
||||
if len(caCerts) > 0 {
|
||||
config.GetCertificate = getGetCertificateFunc(config, caCerts)
|
||||
} else {
|
||||
for _, cert := range c.Certificate {
|
||||
if cert.Usage == Certificate_ENCIPHERMENT {
|
||||
setupHotReload(cert)
|
||||
}
|
||||
}
|
||||
config.GetCertificate = c.getCertificate
|
||||
}
|
||||
if len(c.getClientCert()) > 0 {
|
||||
config.GetClientCertificate = c.getClientCertificate
|
||||
}
|
||||
if clientCA := c.getClientCA(); len(clientCA) > 0 {
|
||||
clientCAPool := x509.NewCertPool()
|
||||
for _, cert := range clientCA {
|
||||
if !clientCAPool.AppendCertsFromPEM(cert.Certificate) {
|
||||
errors.LogError(context.Background(), errors.New("failed to append client CA certificate"))
|
||||
}
|
||||
}
|
||||
config.ClientCAs = clientCAPool
|
||||
config.GetCertificate = getNewGetCertificateFunc(c.BuildCertificates(), c.RejectUnknownSni)
|
||||
}
|
||||
|
||||
if sn := c.parseServerName(); len(sn) > 0 {
|
||||
@@ -503,11 +472,6 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config {
|
||||
}
|
||||
}
|
||||
|
||||
config.ClientAuth = ParseClientAuth(c.ClientAuth)
|
||||
if config.ClientAuth >= tls.VerifyClientCertIfGiven && config.ClientCAs == nil {
|
||||
errors.LogWarning(context.Background(), "clientAuth is set to ", c.ClientAuth, " but no client CA is provided")
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
@@ -553,17 +517,17 @@ func ConfigFromStreamSettings(settings *internet.MemoryStreamConfig) *Config {
|
||||
return config
|
||||
}
|
||||
|
||||
var curveMap = map[string]tls.CurveID{
|
||||
"curvep256": tls.CurveP256,
|
||||
"curvep384": tls.CurveP384,
|
||||
"curvep521": tls.CurveP521,
|
||||
"x25519": tls.X25519,
|
||||
"x25519mlkem768": tls.X25519MLKEM768,
|
||||
"secp256r1mlkem768": tls.SecP256r1MLKEM768,
|
||||
"secp384r1mlkem1024": tls.SecP384r1MLKEM1024,
|
||||
}
|
||||
|
||||
func ParseCurveName(curveNames []string) []tls.CurveID {
|
||||
curveMap := map[string]tls.CurveID{
|
||||
"curvep256": tls.CurveP256,
|
||||
"curvep384": tls.CurveP384,
|
||||
"curvep521": tls.CurveP521,
|
||||
"x25519": tls.X25519,
|
||||
"x25519mlkem768": tls.X25519MLKEM768,
|
||||
"secp256r1mlkem768": tls.SecP256r1MLKEM768,
|
||||
"secp384r1mlkem1024": tls.SecP384r1MLKEM1024,
|
||||
}
|
||||
|
||||
var curveIDs []tls.CurveID
|
||||
for _, name := range curveNames {
|
||||
if curveID, ok := curveMap[strings.ToLower(name)]; ok {
|
||||
@@ -575,25 +539,6 @@ func ParseCurveName(curveNames []string) []tls.CurveID {
|
||||
return curveIDs
|
||||
}
|
||||
|
||||
var clientAuthMap = map[string]tls.ClientAuthType{
|
||||
"noclientcert": tls.NoClientCert,
|
||||
"requestclientcert": tls.RequestClientCert,
|
||||
"requireanyclientcert": tls.RequireAnyClientCert,
|
||||
"verifyclientcertifgiven": tls.VerifyClientCertIfGiven,
|
||||
"requireandverifyclientcert": tls.RequireAndVerifyClientCert,
|
||||
}
|
||||
|
||||
func ParseClientAuth(clientAuth string) tls.ClientAuthType {
|
||||
if clientAuth == "" {
|
||||
return tls.NoClientCert
|
||||
}
|
||||
if clientAuthType, ok := clientAuthMap[strings.ToLower(clientAuth)]; ok {
|
||||
return clientAuthType
|
||||
}
|
||||
errors.LogWarning(context.Background(), "unsupported clientAuth: "+clientAuth)
|
||||
return tls.NoClientCert
|
||||
}
|
||||
|
||||
func IsFromMitm(str string) bool {
|
||||
return strings.ToLower(str) == "frommitm"
|
||||
}
|
||||
|
||||
@@ -28,8 +28,6 @@ const (
|
||||
Certificate_ENCIPHERMENT Certificate_Usage = 0
|
||||
Certificate_AUTHORITY_VERIFY Certificate_Usage = 1
|
||||
Certificate_AUTHORITY_ISSUE Certificate_Usage = 2
|
||||
Certificate_MTLS_CLIENT_CERT Certificate_Usage = 3
|
||||
Certificate_MTLS_CLIENT_CA Certificate_Usage = 4
|
||||
)
|
||||
|
||||
// Enum value maps for Certificate_Usage.
|
||||
@@ -38,15 +36,11 @@ var (
|
||||
0: "ENCIPHERMENT",
|
||||
1: "AUTHORITY_VERIFY",
|
||||
2: "AUTHORITY_ISSUE",
|
||||
3: "MTLS_CLIENT_CERT",
|
||||
4: "MTLS_CLIENT_CA",
|
||||
}
|
||||
Certificate_Usage_value = map[string]int32{
|
||||
"ENCIPHERMENT": 0,
|
||||
"AUTHORITY_VERIFY": 1,
|
||||
"AUTHORITY_ISSUE": 2,
|
||||
"MTLS_CLIENT_CERT": 3,
|
||||
"MTLS_CLIENT_CA": 4,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -92,10 +86,8 @@ type Certificate struct {
|
||||
// If true, one-Time Loading
|
||||
OneTimeLoading bool `protobuf:"varint,7,opt,name=One_time_loading,json=OneTimeLoading,proto3" json:"One_time_loading,omitempty"`
|
||||
BuildChain bool `protobuf:"varint,8,opt,name=build_chain,json=buildChain,proto3" json:"build_chain,omitempty"`
|
||||
// Abused proto data to storage some runtime data
|
||||
ExtraData []byte `protobuf:"bytes,9,opt,name=extra_data,json=extraData,proto3" json:"extra_data,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Certificate) Reset() {
|
||||
@@ -184,13 +176,6 @@ func (x *Certificate) GetBuildChain() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *Certificate) GetExtraData() []byte {
|
||||
if x != nil {
|
||||
return x.ExtraData
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// List of certificates to be served on server.
|
||||
@@ -221,7 +206,6 @@ type Config struct {
|
||||
EchConfigList string `protobuf:"bytes,19,opt,name=ech_config_list,json=echConfigList,proto3" json:"ech_config_list,omitempty"`
|
||||
EchSocketSettings *internet.SocketConfig `protobuf:"bytes,21,opt,name=ech_socket_settings,json=echSocketSettings,proto3" json:"ech_socket_settings,omitempty"`
|
||||
PinnedPeerCertSha256 [][]byte `protobuf:"bytes,22,rep,name=pinned_peer_cert_sha256,json=pinnedPeerCertSha256,proto3" json:"pinned_peer_cert_sha256,omitempty"`
|
||||
ClientAuth string `protobuf:"bytes,23,opt,name=client_auth,json=clientAuth,proto3" json:"client_auth,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -375,18 +359,11 @@ func (x *Config) GetPinnedPeerCertSha256() [][]byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetClientAuth() string {
|
||||
if x != nil {
|
||||
return x.ClientAuth
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_transport_internet_tls_config_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_transport_internet_tls_config_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"#transport/internet/tls/config.proto\x12\x1bxray.transport.internet.tls\x1a\x1ftransport/internet/config.proto\"\xcc\x03\n" +
|
||||
"#transport/internet/tls/config.proto\x12\x1bxray.transport.internet.tls\x1a\x1ftransport/internet/config.proto\"\x83\x03\n" +
|
||||
"\vCertificate\x12 \n" +
|
||||
"\vcertificate\x18\x01 \x01(\fR\vcertificate\x12\x10\n" +
|
||||
"\x03key\x18\x02 \x01(\fR\x03key\x12D\n" +
|
||||
@@ -396,15 +373,11 @@ const file_transport_internet_tls_config_proto_rawDesc = "" +
|
||||
"\bkey_path\x18\x06 \x01(\tR\akeyPath\x12(\n" +
|
||||
"\x10One_time_loading\x18\a \x01(\bR\x0eOneTimeLoading\x12\x1f\n" +
|
||||
"\vbuild_chain\x18\b \x01(\bR\n" +
|
||||
"buildChain\x12\x1d\n" +
|
||||
"\n" +
|
||||
"extra_data\x18\t \x01(\fR\textraData\"n\n" +
|
||||
"buildChain\"D\n" +
|
||||
"\x05Usage\x12\x10\n" +
|
||||
"\fENCIPHERMENT\x10\x00\x12\x14\n" +
|
||||
"\x10AUTHORITY_VERIFY\x10\x01\x12\x13\n" +
|
||||
"\x0fAUTHORITY_ISSUE\x10\x02\x12\x14\n" +
|
||||
"\x10MTLS_CLIENT_CERT\x10\x03\x12\x12\n" +
|
||||
"\x0eMTLS_CLIENT_CA\x10\x04\"\xc7\x06\n" +
|
||||
"\x0fAUTHORITY_ISSUE\x10\x02\"\xa6\x06\n" +
|
||||
"\x06Config\x12J\n" +
|
||||
"\vcertificate\x18\x02 \x03(\v2(.xray.transport.internet.tls.CertificateR\vcertificate\x12\x1f\n" +
|
||||
"\vserver_name\x18\x03 \x01(\tR\n" +
|
||||
@@ -425,9 +398,7 @@ const file_transport_internet_tls_config_proto_rawDesc = "" +
|
||||
"\x0fech_server_keys\x18\x12 \x01(\fR\rechServerKeys\x12&\n" +
|
||||
"\x0fech_config_list\x18\x13 \x01(\tR\rechConfigList\x12U\n" +
|
||||
"\x13ech_socket_settings\x18\x15 \x01(\v2%.xray.transport.internet.SocketConfigR\x11echSocketSettings\x125\n" +
|
||||
"\x17pinned_peer_cert_sha256\x18\x16 \x03(\fR\x14pinnedPeerCertSha256\x12\x1f\n" +
|
||||
"\vclient_auth\x18\x17 \x01(\tR\n" +
|
||||
"clientAuthBs\n" +
|
||||
"\x17pinned_peer_cert_sha256\x18\x16 \x03(\fR\x14pinnedPeerCertSha256Bs\n" +
|
||||
"\x1fcom.xray.transport.internet.tlsP\x01Z0github.com/xtls/xray-core/transport/internet/tls\xaa\x02\x1bXray.Transport.Internet.Tlsb\x06proto3"
|
||||
|
||||
var (
|
||||
|
||||
@@ -19,8 +19,6 @@ message Certificate {
|
||||
ENCIPHERMENT = 0;
|
||||
AUTHORITY_VERIFY = 1;
|
||||
AUTHORITY_ISSUE = 2;
|
||||
MTLS_CLIENT_CERT = 3;
|
||||
MTLS_CLIENT_CA = 4;
|
||||
}
|
||||
|
||||
Usage usage = 3;
|
||||
@@ -37,9 +35,6 @@ message Certificate {
|
||||
bool One_time_loading = 7;
|
||||
|
||||
bool build_chain = 8;
|
||||
|
||||
// Abused proto data to storage some runtime data
|
||||
bytes extra_data = 9;
|
||||
}
|
||||
|
||||
message Config {
|
||||
@@ -91,6 +86,4 @@ message Config {
|
||||
SocketConfig ech_socket_settings = 21;
|
||||
|
||||
repeated bytes pinned_peer_cert_sha256 = 22;
|
||||
|
||||
string client_auth = 23;
|
||||
}
|
||||
|
||||
@@ -47,9 +47,6 @@ func (c *Config) getCertPool() (*x509.CertPool, error) {
|
||||
return nil, errors.New("system root").AtWarning().Base(err)
|
||||
}
|
||||
for _, cert := range c.Certificate {
|
||||
if cert.Usage != Certificate_AUTHORITY_VERIFY {
|
||||
continue
|
||||
}
|
||||
if !pool.AppendCertsFromPEM(cert.Certificate) {
|
||||
return nil, errors.New("append cert to root").AtWarning().Base(err)
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ func QueryRecord(domain string, server string, sockopt *internet.SocketConfig) (
|
||||
// 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
|
||||
// but if the cache is too old, wait for update
|
||||
if configRecord.expire.Equal(time.Time{}) || configRecord.expire.Add(time.Hour*4).Before(time.Now()) {
|
||||
if configRecord.expire == (time.Time{}) || configRecord.expire.Add(time.Hour*4).Before(time.Now()) {
|
||||
return echConfigCache.Update(domain, server, false, sockopt)
|
||||
} else {
|
||||
// If someone already acquired the lock, it means it is updating, do not start another update goroutine
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
package tls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/ocsp"
|
||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
)
|
||||
|
||||
var certsCache = utils.NewWeakCacheMap[uintptr, Certificate]()
|
||||
|
||||
var startHotReload sync.Once
|
||||
|
||||
func setupHotReload(entry *Certificate) {
|
||||
startHotReload.Do(func() {
|
||||
go handleHotReload()
|
||||
})
|
||||
// ensure the cache before use
|
||||
entry.getX509KeyPair()
|
||||
if entry.OneTimeLoading {
|
||||
return
|
||||
}
|
||||
uptr := uintptr(unsafe.Pointer(entry))
|
||||
if _, ok := certsCache.Load(uptr); !ok {
|
||||
certsCache.Store(uptr, entry)
|
||||
}
|
||||
}
|
||||
|
||||
func handleHotReload() {
|
||||
// should be enough?
|
||||
t := time.NewTicker(600 * time.Second)
|
||||
for {
|
||||
certsCache.Range(updateCert)
|
||||
<-t.C
|
||||
}
|
||||
}
|
||||
|
||||
func updateCert(_ uintptr, entry *Certificate) bool {
|
||||
extraData := entry.extraData()
|
||||
reloadInterval := int64(entry.OcspStapling)
|
||||
if reloadInterval <= 0 {
|
||||
reloadInterval = 3600
|
||||
}
|
||||
if extraData.lastReload+reloadInterval >= time.Now().Unix() {
|
||||
return true
|
||||
} else {
|
||||
extraData.lastReload = time.Now().Unix()
|
||||
}
|
||||
if entry.CertificatePath != "" && entry.KeyPath != "" {
|
||||
newCert, err := filesystem.ReadCert(entry.CertificatePath)
|
||||
if err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "failed to parse certificate")
|
||||
return true
|
||||
}
|
||||
newKey, err := filesystem.ReadCert(entry.KeyPath)
|
||||
if err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "failed to parse key")
|
||||
return true
|
||||
}
|
||||
if string(newCert) != string(entry.Certificate) || string(newKey) != string(entry.Key) {
|
||||
entry.Certificate = newCert
|
||||
entry.Key = newKey
|
||||
}
|
||||
}
|
||||
entry.parseX509KeyPair()
|
||||
if entry.OcspStapling > 0 {
|
||||
keyPair := entry.getX509KeyPair()
|
||||
if keyPair == nil {
|
||||
return true
|
||||
}
|
||||
if newOCSPData, err := ocsp.GetOCSPForCert(keyPair.Certificate); err != nil {
|
||||
errors.LogWarningInner(context.Background(), err, "ignoring invalid OCSP")
|
||||
} else if OCSPData := extraData.ocspData.Load(); OCSPData == nil || !slices.Equal(newOCSPData, *OCSPData) {
|
||||
extraData.ocspData.Store(&newOCSPData)
|
||||
}
|
||||
entry.parseX509KeyPair()
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -146,45 +146,6 @@ func GeneraticUClient(c net.Conn, config *tls.Config) *utls.UConn {
|
||||
return utls.UClient(c, copyConfig(config), utls.HelloChrome_Auto)
|
||||
}
|
||||
|
||||
// Adapt a crypto/tls GetClientCertificate callback to the utls signature.
|
||||
func uGetClientCertificate(originFunc func(*tls.CertificateRequestInfo) (*tls.Certificate, error)) func(*utls.CertificateRequestInfo) (*utls.Certificate, error) {
|
||||
if originFunc == nil {
|
||||
return nil
|
||||
}
|
||||
return func(info *utls.CertificateRequestInfo) (*utls.Certificate, error) {
|
||||
schemes := make([]tls.SignatureScheme, len(info.SignatureSchemes))
|
||||
for i, s := range info.SignatureSchemes {
|
||||
schemes[i] = tls.SignatureScheme(s)
|
||||
}
|
||||
cert, err := originFunc(&tls.CertificateRequestInfo{
|
||||
AcceptableCAs: info.AcceptableCAs,
|
||||
SignatureSchemes: schemes,
|
||||
Version: info.Version,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cert == nil {
|
||||
return &utls.Certificate{}, nil
|
||||
}
|
||||
var uSchemes []utls.SignatureScheme
|
||||
if cert.SupportedSignatureAlgorithms != nil {
|
||||
uSchemes = make([]utls.SignatureScheme, len(cert.SupportedSignatureAlgorithms))
|
||||
for i, s := range cert.SupportedSignatureAlgorithms {
|
||||
uSchemes[i] = utls.SignatureScheme(s)
|
||||
}
|
||||
}
|
||||
return &utls.Certificate{
|
||||
Certificate: cert.Certificate,
|
||||
PrivateKey: cert.PrivateKey,
|
||||
SupportedSignatureAlgorithms: uSchemes,
|
||||
OCSPStaple: cert.OCSPStaple,
|
||||
SignedCertificateTimestamps: cert.SignedCertificateTimestamps,
|
||||
Leaf: cert.Leaf,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func copyConfig(c *tls.Config) *utls.Config {
|
||||
config := &utls.Config{
|
||||
Rand: c.Rand,
|
||||
@@ -195,7 +156,6 @@ func copyConfig(c *tls.Config) *utls.Config {
|
||||
KeyLogWriter: c.KeyLogWriter,
|
||||
EncryptedClientHelloConfigList: c.EncryptedClientHelloConfigList,
|
||||
NextProtos: c.NextProtos,
|
||||
GetClientCertificate: uGetClientCertificate(c.GetClientCertificate),
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user