feat: add mTLS support for client & server

`certificate` and `private-key` for proxies
`client-auth-type` and `client-auth-cert` for listeners
This commit is contained in:
wwqgtxx 2025-09-20 00:19:07 +08:00
parent 40b2cde2b2
commit 0dc5e3051d
54 changed files with 763 additions and 323 deletions

View File

@ -36,6 +36,8 @@ type AnyTLSOption struct {
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
Certificate string `proxy:"certificate,omitempty"`
PrivateKey string `proxy:"private-key,omitempty"`
UDP bool `proxy:"udp,omitempty"`
IdleSessionCheckInterval int `proxy:"idle-session-check-interval,omitempty"`
IdleSessionTimeout int `proxy:"idle-session-timeout,omitempty"`
@ -120,6 +122,8 @@ func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) {
SkipCertVerify: option.SkipCertVerify,
NextProtos: option.ALPN,
FingerPrint: option.Fingerprint,
Certificate: option.Certificate,
PrivateKey: option.PrivateKey,
ClientFingerprint: option.ClientFingerprint,
ECH: echConfig,
}

View File

@ -37,6 +37,8 @@ type HttpOption struct {
SNI string `proxy:"sni,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
Certificate string `proxy:"certificate,omitempty"`
PrivateKey string `proxy:"private-key,omitempty"`
Headers map[string]string `proxy:"headers,omitempty"`
}
@ -173,6 +175,8 @@ func NewHttp(option HttpOption) (*Http, error) {
ServerName: sni,
},
Fingerprint: option.Fingerprint,
Certificate: option.Certificate,
PrivateKey: option.PrivateKey,
})
if err != nil {
return nil, err

View File

@ -125,6 +125,8 @@ type HysteriaOption struct {
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
Certificate string `proxy:"certificate,omitempty"`
PrivateKey string `proxy:"private-key,omitempty"`
ALPN []string `proxy:"alpn,omitempty"`
ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"`
ReceiveWindow int `proxy:"recv-window,omitempty"`
@ -165,6 +167,8 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
MinVersion: tls.VersionTLS13,
},
Fingerprint: option.Fingerprint,
Certificate: option.Certificate,
PrivateKey: option.PrivateKey,
})
if err != nil {
return nil, err

View File

@ -55,6 +55,8 @@ type Hysteria2Option struct {
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
Certificate string `proxy:"certificate,omitempty"`
PrivateKey string `proxy:"private-key,omitempty"`
ALPN []string `proxy:"alpn,omitempty"`
CWND int `proxy:"cwnd,omitempty"`
UdpMTU int `proxy:"udp-mtu,omitempty"`
@ -146,6 +148,8 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
MinVersion: tls.VersionTLS13,
},
Fingerprint: option.Fingerprint,
Certificate: option.Certificate,
PrivateKey: option.PrivateKey,
})
if err != nil {
return nil, err

View File

@ -65,6 +65,8 @@ type v2rayObfsOption struct {
TLS bool `obfs:"tls,omitempty"`
ECHOpts ECHOptions `obfs:"ech-opts,omitempty"`
Fingerprint string `obfs:"fingerprint,omitempty"`
Certificate string `obfs:"certificate,omitempty"`
PrivateKey string `obfs:"private-key,omitempty"`
Headers map[string]string `obfs:"headers,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
Mux bool `obfs:"mux,omitempty"`
@ -79,6 +81,8 @@ type gostObfsOption struct {
TLS bool `obfs:"tls,omitempty"`
ECHOpts ECHOptions `obfs:"ech-opts,omitempty"`
Fingerprint string `obfs:"fingerprint,omitempty"`
Certificate string `obfs:"certificate,omitempty"`
PrivateKey string `obfs:"private-key,omitempty"`
Headers map[string]string `obfs:"headers,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
Mux bool `obfs:"mux,omitempty"`
@ -88,6 +92,8 @@ type shadowTLSOption struct {
Password string `obfs:"password,omitempty"`
Host string `obfs:"host"`
Fingerprint string `obfs:"fingerprint,omitempty"`
Certificate string `obfs:"certificate,omitempty"`
PrivateKey string `obfs:"private-key,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
Version int `obfs:"version,omitempty"`
ALPN []string `obfs:"alpn,omitempty"`
@ -302,6 +308,8 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
v2rayOption.TLS = true
v2rayOption.SkipCertVerify = opts.SkipCertVerify
v2rayOption.Fingerprint = opts.Fingerprint
v2rayOption.Certificate = opts.Certificate
v2rayOption.PrivateKey = opts.PrivateKey
echConfig, err := opts.ECHOpts.Parse()
if err != nil {
@ -330,6 +338,8 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
gostOption.TLS = true
gostOption.SkipCertVerify = opts.SkipCertVerify
gostOption.Fingerprint = opts.Fingerprint
gostOption.Certificate = opts.Certificate
gostOption.PrivateKey = opts.PrivateKey
echConfig, err := opts.ECHOpts.Parse()
if err != nil {
@ -350,6 +360,8 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
Password: opt.Password,
Host: opt.Host,
Fingerprint: opt.Fingerprint,
Certificate: opt.Certificate,
PrivateKey: opt.PrivateKey,
ClientFingerprint: option.ClientFingerprint,
SkipCertVerify: opt.SkipCertVerify,
Version: opt.Version,

View File

@ -39,6 +39,8 @@ type Socks5Option struct {
UDP bool `proxy:"udp,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
Certificate string `proxy:"certificate,omitempty"`
PrivateKey string `proxy:"private-key,omitempty"`
}
// StreamConnContext implements C.ProxyAdapter
@ -200,6 +202,8 @@ func NewSocks5(option Socks5Option) (*Socks5, error) {
ServerName: option.Server,
},
Fingerprint: option.Fingerprint,
Certificate: option.Certificate,
PrivateKey: option.PrivateKey,
})
if err != nil {
return nil, err

View File

@ -48,6 +48,8 @@ type TrojanOption struct {
SNI string `proxy:"sni,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
Certificate string `proxy:"certificate,omitempty"`
PrivateKey string `proxy:"private-key,omitempty"`
UDP bool `proxy:"udp,omitempty"`
Network string `proxy:"network,omitempty"`
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
@ -108,6 +110,8 @@ func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.
ServerName: t.option.SNI,
},
Fingerprint: t.option.Fingerprint,
Certificate: t.option.Certificate,
PrivateKey: t.option.PrivateKey,
})
if err != nil {
return nil, err
@ -127,6 +131,8 @@ func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.
Host: t.option.SNI,
SkipCertVerify: t.option.SkipCertVerify,
FingerPrint: t.option.Fingerprint,
Certificate: t.option.Certificate,
PrivateKey: t.option.PrivateKey,
ClientFingerprint: t.option.ClientFingerprint,
NextProtos: alpn,
ECH: t.echConfig,
@ -372,6 +378,8 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
ServerName: option.SNI,
},
Fingerprint: option.Fingerprint,
Certificate: option.Certificate,
PrivateKey: option.PrivateKey,
})
if err != nil {
return nil, err

View File

@ -55,6 +55,8 @@ type TuicOption struct {
CWND int `proxy:"cwnd,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
Certificate string `proxy:"certificate,omitempty"`
PrivateKey string `proxy:"private-key,omitempty"`
ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"`
ReceiveWindow int `proxy:"recv-window,omitempty"`
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
@ -170,6 +172,8 @@ func NewTuic(option TuicOption) (*Tuic, error) {
MinVersion: tls.VersionTLS13,
},
Fingerprint: option.Fingerprint,
Certificate: option.Certificate,
PrivateKey: option.PrivateKey,
})
if err != nil {
return nil, err

View File

@ -67,6 +67,8 @@ type VlessOption struct {
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
Certificate string `proxy:"certificate,omitempty"`
PrivateKey string `proxy:"private-key,omitempty"`
ServerName string `proxy:"servername,omitempty"`
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
}
@ -103,6 +105,8 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
NextProtos: []string{"http/1.1"},
},
Fingerprint: v.option.Fingerprint,
Certificate: v.option.Certificate,
PrivateKey: v.option.PrivateKey,
})
if err != nil {
return nil, err
@ -206,6 +210,8 @@ func (v *Vless) streamTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (ne
Host: host,
SkipCertVerify: v.option.SkipCertVerify,
FingerPrint: v.option.Fingerprint,
Certificate: v.option.Certificate,
PrivateKey: v.option.PrivateKey,
ClientFingerprint: v.option.ClientFingerprint,
ECH: v.echConfig,
Reality: v.realityConfig,
@ -505,6 +511,8 @@ func NewVless(option VlessOption) (*Vless, error) {
ServerName: v.option.ServerName,
},
Fingerprint: v.option.Fingerprint,
Certificate: v.option.Certificate,
PrivateKey: v.option.PrivateKey,
})
if err != nil {
return nil, err

View File

@ -58,6 +58,8 @@ type VmessOption struct {
ALPN []string `proxy:"alpn,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
Certificate string `proxy:"certificate,omitempty"`
PrivateKey string `proxy:"private-key,omitempty"`
ServerName string `proxy:"servername,omitempty"`
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
@ -130,6 +132,8 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
NextProtos: []string{"http/1.1"},
},
Fingerprint: v.option.Fingerprint,
Certificate: v.option.Certificate,
PrivateKey: v.option.PrivateKey,
})
if err != nil {
return nil, err
@ -179,6 +183,8 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
Host: host,
SkipCertVerify: v.option.SkipCertVerify,
FingerPrint: v.option.Fingerprint,
Certificate: v.option.Certificate,
PrivateKey: v.option.PrivateKey,
NextProtos: []string{"h2"},
ClientFingerprint: v.option.ClientFingerprint,
Reality: v.realityConfig,
@ -209,6 +215,8 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
Host: host,
SkipCertVerify: v.option.SkipCertVerify,
FingerPrint: v.option.Fingerprint,
Certificate: v.option.Certificate,
PrivateKey: v.option.PrivateKey,
ClientFingerprint: v.option.ClientFingerprint,
ECH: v.echConfig,
Reality: v.realityConfig,
@ -508,6 +516,8 @@ func NewVmess(option VmessOption) (*Vmess, error) {
ServerName: v.option.ServerName,
},
Fingerprint: v.option.Fingerprint,
Certificate: v.option.Certificate,
PrivateKey: v.option.PrivateKey,
})
if err != nil {
return nil, err

View File

@ -11,6 +11,7 @@ import (
"sync"
"github.com/metacubex/mihomo/common/once"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/ntp"
)
@ -79,6 +80,8 @@ type Option struct {
TLSConfig *tls.Config
Fingerprint string
ZeroTrust bool
Certificate string
PrivateKey string
}
func GetTLSConfig(opt Option) (tlsConfig *tls.Config, err error) {
@ -101,6 +104,15 @@ func GetTLSConfig(opt Option) (tlsConfig *tls.Config, err error) {
}
tlsConfig.InsecureSkipVerify = true
}
if len(opt.Certificate) > 0 || len(opt.PrivateKey) > 0 {
var cert tls.Certificate
cert, err = LoadTLSKeyPair(opt.Certificate, opt.PrivateKey, C.Path)
if err != nil {
return nil, err
}
tlsConfig.Certificates = []tls.Certificate{cert}
}
return tlsConfig, nil
}

View File

@ -12,6 +12,8 @@ import (
"encoding/pem"
"fmt"
"math/big"
"os"
"time"
)
type Path interface {
@ -56,6 +58,33 @@ func LoadTLSKeyPair(certificate, privateKey string, path Path) (tls.Certificate,
return cert, nil
}
func LoadCertificates(certificate string, path Path) (*x509.CertPool, error) {
pool := x509.NewCertPool()
if pool.AppendCertsFromPEM([]byte(certificate)) {
return pool, nil
}
painTextErr := fmt.Errorf("invalid certificate: %s", certificate)
if path == nil {
return nil, painTextErr
}
certificate = path.Resolve(certificate)
var loadErr error
if !path.IsSafePath(certificate) {
loadErr = path.ErrNotSafePath(certificate)
} else {
certPEMBlock, err := os.ReadFile(certificate)
if pool.AppendCertsFromPEM(certPEMBlock) {
return pool, nil
}
loadErr = err
}
if loadErr != nil {
return nil, fmt.Errorf("parse certificate failed, maybe format error:%s, or path error: %s", painTextErr.Error(), loadErr.Error())
}
return pool, nil
}
type KeyPairType string
const (
@ -85,7 +114,11 @@ func NewRandomTLSKeyPair(keyPairType KeyPairType) (certificate string, privateKe
return
}
template := x509.Certificate{SerialNumber: big.NewInt(1)}
template := x509.Certificate{
SerialNumber: big.NewInt(1),
NotBefore: time.Now().Add(-time.Hour * 24 * 365),
NotAfter: time.Now().Add(time.Hour * 24 * 365),
}
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, key.Public(), key)
if err != nil {
return

45
component/tls/auth.go Normal file
View File

@ -0,0 +1,45 @@
package tls
import (
utls "github.com/metacubex/utls"
)
type ClientAuthType = utls.ClientAuthType
const (
NoClientCert = utls.NoClientCert
RequestClientCert = utls.RequestClientCert
RequireAnyClientCert = utls.RequireAnyClientCert
VerifyClientCertIfGiven = utls.VerifyClientCertIfGiven
RequireAndVerifyClientCert = utls.RequireAndVerifyClientCert
)
func ClientAuthTypeFromString(s string) ClientAuthType {
switch s {
case "request":
return RequestClientCert
case "require-any":
return RequireAnyClientCert
case "verify-if-given":
return VerifyClientCertIfGiven
case "require-and-verify":
return RequireAndVerifyClientCert
default:
return NoClientCert
}
}
func ClientAuthTypeToString(t ClientAuthType) string {
switch t {
case RequestClientCert:
return "request"
case RequireAnyClientCert:
return "require-any"
case VerifyClientCertIfGiven:
return "verify-if-given"
case RequireAndVerifyClientCert:
return "require-and-verify"
default:
return ""
}
}

View File

@ -135,6 +135,8 @@ func UConfig(config *tls.Config) *utls.Config {
RootCAs: config.RootCAs,
NextProtos: config.NextProtos,
ServerName: config.ServerName,
ClientAuth: utls.ClientAuthType(config.ClientAuth),
ClientCAs: config.ClientCAs,
InsecureSkipVerify: config.InsecureSkipVerify,
CipherSuites: config.CipherSuites,
MinVersion: config.MinVersion,

View File

@ -174,6 +174,8 @@ type Profile struct {
type TLS struct {
Certificate string
PrivateKey string
ClientAuthType string
ClientAuthCert string
EchKey string
CustomTrustCert []string
}
@ -368,6 +370,8 @@ type RawSniffingConfig struct {
type RawTLS struct {
Certificate string `yaml:"certificate" json:"certificate"`
PrivateKey string `yaml:"private-key" json:"private-key"`
ClientAuthType string `yaml:"client-auth-type" json:"client-auth-type"`
ClientAuthCert string `yaml:"client-auth-cert" json:"client-auth-cert"`
EchKey string `yaml:"ech-key" json:"ech-key"`
CustomTrustCert []string `yaml:"custom-certifactes" json:"custom-certifactes"`
}
@ -827,6 +831,8 @@ func parseTLS(cfg *RawConfig) (*TLS, error) {
return &TLS{
Certificate: cfg.TLS.Certificate,
PrivateKey: cfg.TLS.PrivateKey,
ClientAuthType: cfg.TLS.ClientAuthType,
ClientAuthCert: cfg.TLS.ClientAuthCert,
EchKey: cfg.TLS.EchKey,
CustomTrustCert: cfg.TLS.CustomTrustCert,
}, nil

View File

@ -48,6 +48,9 @@ ipv6: true # 开启 IPv6 总开关,关闭阻断所有 IPv6 链接和屏蔽 DNS
tls:
certificate: string # 证书 PEM 格式,或者 证书的路径
private-key: string # 证书对应的私钥 PEM 格式,或者私钥路径
# 下面两项为mTLS配置项如果client-auth-type设置为 "verify-if-given" 或 "require-and-verify" 则client-auth-cert必须不为空
# client-auth-type: "" # 可选值:""、"request"、"require-any"、"verify-if-given"、"require-and-verify"
# client-auth-cert: string # 证书 PEM 格式,或者 证书的路径
# 如果填写则开启ech可由 mihomo generate ech-keypair <明文域名> 生成)
# ech-key: |
# -----BEGIN ECH KEYS-----
@ -350,6 +353,9 @@ proxies: # socks5
# password: password
# tls: true
# fingerprint: xxxx # 配置指纹将实现 SSL Pining 效果, 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
# 下面两项如果填写则开启 mTLS需要同时填写
# certificate: ./client.crt # 证书 PEM 格式,或者 证书的路径
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
# skip-cert-verify: true
# udp: true
# ip-version: ipv6
@ -365,6 +371,9 @@ proxies: # socks5
# skip-cert-verify: true
# sni: custom.com
# fingerprint: xxxx # 配置指纹将实现 SSL Pining 效果, 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
# 下面两项如果填写则开启 mTLS需要同时填写
# certificate: ./client.crt # 证书 PEM 格式,或者 证书的路径
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
# ip-version: dual
# Snell
@ -433,6 +442,9 @@ proxies: # socks5
mode: websocket # no QUIC now
# tls: true # wss
# fingerprint: xxxx # 配置指纹将实现 SSL Pining 效果, 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
# 下面两项如果填写则开启 mTLS需要同时填写
# certificate: ./client.crt # 证书 PEM 格式,或者 证书的路径
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
# ech-opts:
# enable: true # 必须手动开启
# # 如果config为空则通过dns解析不为空则通过该值指定格式为经过base64编码的ech参数dig +short TYPE65 tls-ech.dev
@ -471,6 +483,9 @@ proxies: # socks5
mode: websocket
# tls: true # wss
# fingerprint: xxxx # 配置指纹将实现 SSL Pining 效果, 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
# 下面两项如果填写则开启 mTLS需要同时填写
# certificate: ./client.crt # 证书 PEM 格式,或者 证书的路径
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
# skip-cert-verify: true
# host: bing.com
# path: "/"
@ -531,6 +546,9 @@ proxies: # socks5
# udp: true
# tls: true
# fingerprint: xxxx # 配置指纹将实现 SSL Pining 效果, 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
# 下面两项如果填写则开启 mTLS需要同时填写
# certificate: ./client.crt # 证书 PEM 格式,或者 证书的路径
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
# client-fingerprint: chrome # Available: "chrome","firefox","safari","ios","random", currently only support TLS transport in TCP/GRPC/WS/HTTP for VLESS/Vmess and trojan.
# skip-cert-verify: true
# servername: example.com # priority over wss host
@ -558,6 +576,9 @@ proxies: # socks5
network: h2
tls: true
# fingerprint: xxxx # 配置指纹将实现 SSL Pining 效果, 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
# 下面两项如果填写则开启 mTLS需要同时填写
# certificate: ./client.crt # 证书 PEM 格式,或者 证书的路径
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
h2-opts:
host:
- http.example.com
@ -593,6 +614,9 @@ proxies: # socks5
network: grpc
tls: true
# fingerprint: xxxx # 配置指纹将实现 SSL Pining 效果, 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
# 下面两项如果填写则开启 mTLS需要同时填写
# certificate: ./client.crt # 证书 PEM 格式,或者 证书的路径
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
servername: example.com
# skip-cert-verify: true
grpc-opts:
@ -608,6 +632,9 @@ proxies: # socks5
network: tcp
servername: example.com # AKA SNI
# skip-cert-verify: true
# 下面两项如果填写则开启 mTLS需要同时填写
# certificate: ./client.crt # 证书 PEM 格式,或者 证书的路径
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
# fingerprint: xxxx # 配置指纹将实现 SSL Pining 效果, 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
# client-fingerprint: random # Available: "chrome","firefox","safari","random","none"
# ech-opts:
@ -625,6 +652,9 @@ proxies: # socks5
udp: true
flow: xtls-rprx-vision
client-fingerprint: chrome
# 下面两项如果填写则开启 mTLS需要同时填写
# certificate: ./client.crt # 证书 PEM 格式,或者 证书的路径
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
# fingerprint: xxxx # 配置指纹将实现 SSL Pining 效果, 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
# skip-cert-verify: true
@ -696,6 +726,9 @@ proxies: # socks5
servername: example.com # priority over wss host
# skip-cert-verify: true
# fingerprint: xxxx # 配置指纹将实现 SSL Pining 效果, 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
# 下面两项如果填写则开启 mTLS需要同时填写
# certificate: ./client.crt # 证书 PEM 格式,或者 证书的路径
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
ws-opts:
path: "/"
headers:
@ -711,6 +744,9 @@ proxies: # socks5
password: yourpsk
# client-fingerprint: random # Available: "chrome","firefox","safari","random","none"
# fingerprint: xxxx # 配置指纹将实现 SSL Pining 效果, 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
# 下面两项如果填写则开启 mTLS需要同时填写
# certificate: ./client.crt # 证书 PEM 格式,或者 证书的路径
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
# udp: true
# sni: example.com # aka server name
# alpn:
@ -735,6 +771,9 @@ proxies: # socks5
sni: example.com
# skip-cert-verify: true
# fingerprint: xxxx # 配置指纹将实现 SSL Pining 效果, 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
# 下面两项如果填写则开启 mTLS需要同时填写
# certificate: ./client.crt # 证书 PEM 格式,或者 证书的路径
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
udp: true
grpc-opts:
grpc-service-name: "example"
@ -748,6 +787,9 @@ proxies: # socks5
sni: example.com
# skip-cert-verify: true
# fingerprint: xxxx # 配置指纹将实现 SSL Pining 效果, 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
# 下面两项如果填写则开启 mTLS需要同时填写
# certificate: ./client.crt # 证书 PEM 格式,或者 证书的路径
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
udp: true
# ws-opts:
# path: /path
@ -767,6 +809,9 @@ proxies: # socks5
# sni: example.com # aka server name
# skip-cert-verify: true
# fingerprint: xxxx # 配置指纹将实现 SSL Pining 效果, 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
# 下面两项如果填写则开启 mTLS需要同时填写
# certificate: ./client.crt # 证书 PEM 格式,或者 证书的路径
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
#hysteria
- name: "hysteria"
@ -791,6 +836,9 @@ proxies: # socks5
# recv-window: 52428800
# disable-mtu-discovery: false
# fingerprint: xxxx # 配置指纹将实现 SSL Pining 效果, 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
# 下面两项如果填写则开启 mTLS需要同时填写
# certificate: ./client.crt # 证书 PEM 格式,或者 证书的路径
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
# fast-open: true # 支持 TCP 快速打开,默认为 false
#hysteria2
@ -813,6 +861,9 @@ proxies: # socks5
# config: AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA
# skip-cert-verify: false
# fingerprint: xxxx # 配置指纹将实现 SSL Pining 效果, 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
# 下面两项如果填写则开启 mTLS需要同时填写
# certificate: ./client.crt # 证书 PEM 格式,或者 证书的路径
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
# alpn:
# - h3
###quic-go特殊配置项不要随意修改除非你知道你在干什么###
@ -1193,8 +1244,11 @@ listeners:
# - username: aaa
# password: aaa
# 下面两项如果填写则开启 tls需要同时填写
# certificate: ./server.crt
# private-key: ./server.key
# certificate: ./server.crt # 证书 PEM 格式,或者 证书的路径
# private-key: ./server.key # 证书对应的私钥 PEM 格式,或者私钥路径
# 下面两项为mTLS配置项如果client-auth-type设置为 "verify-if-given" 或 "require-and-verify" 则client-auth-cert必须不为空
# client-auth-type: "" # 可选值:""、"request"、"require-any"、"verify-if-given"、"require-and-verify"
# client-auth-cert: string # 证书 PEM 格式,或者 证书的路径
# 如果填写则开启ech可由 mihomo generate ech-keypair <明文域名> 生成)
# ech-key: |
# -----BEGIN ECH KEYS-----
@ -1213,8 +1267,11 @@ listeners:
# - username: aaa
# password: aaa
# 下面两项如果填写则开启 tls需要同时填写
# certificate: ./server.crt
# private-key: ./server.key
# certificate: ./server.crt # 证书 PEM 格式,或者 证书的路径
# private-key: ./server.key # 证书对应的私钥 PEM 格式,或者私钥路径
# 下面两项为mTLS配置项如果client-auth-type设置为 "verify-if-given" 或 "require-and-verify" 则client-auth-cert必须不为空
# client-auth-type: "" # 可选值:""、"request"、"require-any"、"verify-if-given"、"require-and-verify"
# client-auth-cert: string # 证书 PEM 格式,或者 证书的路径
# 如果填写则开启ech可由 mihomo generate ech-keypair <明文域名> 生成)
# ech-key: |
# -----BEGIN ECH KEYS-----
@ -1234,8 +1291,11 @@ listeners:
# - username: aaa
# password: aaa
# 下面两项如果填写则开启 tls需要同时填写
# certificate: ./server.crt
# private-key: ./server.key
# certificate: ./server.crt # 证书 PEM 格式,或者 证书的路径
# private-key: ./server.key # 证书对应的私钥 PEM 格式,或者私钥路径
# 下面两项为mTLS配置项如果client-auth-type设置为 "verify-if-given" 或 "require-and-verify" 则client-auth-cert必须不为空
# client-auth-type: "" # 可选值:""、"request"、"require-any"、"verify-if-given"、"require-and-verify"
# client-auth-cert: string # 证书 PEM 格式,或者 证书的路径
# 如果填写则开启ech可由 mihomo generate ech-keypair <明文域名> 生成)
# ech-key: |
# -----BEGIN ECH KEYS-----
@ -1290,8 +1350,11 @@ listeners:
# ws-path: "/" # 如果不为空则开启 websocket 传输层
# grpc-service-name: "GunService" # 如果不为空则开启 grpc 传输层
# 下面两项如果填写则开启 tls需要同时填写
# certificate: ./server.crt
# private-key: ./server.key
# certificate: ./server.crt # 证书 PEM 格式,或者 证书的路径
# private-key: ./server.key # 证书对应的私钥 PEM 格式,或者私钥路径
# 下面两项为mTLS配置项如果client-auth-type设置为 "verify-if-given" 或 "require-and-verify" 则client-auth-cert必须不为空
# client-auth-type: "" # 可选值:""、"request"、"require-any"、"verify-if-given"、"require-and-verify"
# client-auth-cert: string # 证书 PEM 格式,或者 证书的路径
# 如果填写则开启ech可由 mihomo generate ech-keypair <明文域名> 生成)
# ech-key: |
# -----BEGIN ECH KEYS-----
@ -1329,8 +1392,11 @@ listeners:
# users: # tuicV5 填写(可以同时填写 token
# 00000000-0000-0000-0000-000000000000: PASSWORD_0
# 00000000-0000-0000-0000-000000000001: PASSWORD_1
# certificate: ./server.crt
# private-key: ./server.key
# certificate: ./server.crt # 证书 PEM 格式,或者 证书的路径
# private-key: ./server.key # 证书对应的私钥 PEM 格式,或者私钥路径
# 下面两项为mTLS配置项如果client-auth-type设置为 "verify-if-given" 或 "require-and-verify" 则client-auth-cert必须不为空
# client-auth-type: "" # 可选值:""、"request"、"require-any"、"verify-if-given"、"require-and-verify"
# client-auth-cert: string # 证书 PEM 格式,或者 证书的路径
# 如果填写则开启ech可由 mihomo generate ech-keypair <明文域名> 生成)
# ech-key: |
# -----BEGIN ECH KEYS-----
@ -1380,8 +1446,11 @@ listeners:
# -------------------------
# decryption: "mlkem768x25519plus.native/xorpub/random.600s(300-600s)/0s.(padding len).(padding gap).(X25519 PrivateKey).(ML-KEM-768 Seed)..."
# 下面两项如果填写则开启 tls需要同时填写
# certificate: ./server.crt
# private-key: ./server.key
# certificate: ./server.crt # 证书 PEM 格式,或者 证书的路径
# private-key: ./server.key # 证书对应的私钥 PEM 格式,或者私钥路径
# 下面两项为mTLS配置项如果client-auth-type设置为 "verify-if-given" 或 "require-and-verify" 则client-auth-cert必须不为空
# client-auth-type: "" # 可选值:""、"request"、"require-any"、"verify-if-given"、"require-and-verify"
# client-auth-cert: string # 证书 PEM 格式,或者 证书的路径
# 如果填写则开启ech可由 mihomo generate ech-keypair <明文域名> 生成)
# ech-key: |
# -----BEGIN ECH KEYS-----
@ -1417,8 +1486,11 @@ listeners:
username1: password1
username2: password2
# "certificate" and "private-key" are required
certificate: ./server.crt
certificate: ./server.crt # 证书 PEM 格式,或者 证书的路径
private-key: ./server.key
# 下面两项为mTLS配置项如果client-auth-type设置为 "verify-if-given" 或 "require-and-verify" 则client-auth-cert必须不为空
# client-auth-type: "" # 可选值:""、"request"、"require-any"、"verify-if-given"、"require-and-verify"
# client-auth-cert: string # 证书 PEM 格式,或者 证书的路径
# 如果填写则开启ech可由 mihomo generate ech-keypair <明文域名> 生成)
# ech-key: |
# -----BEGIN ECH KEYS-----
@ -1440,8 +1512,11 @@ listeners:
# ws-path: "/" # 如果不为空则开启 websocket 传输层
# grpc-service-name: "GunService" # 如果不为空则开启 grpc 传输层
# 下面两项如果填写则开启 tls需要同时填写
certificate: ./server.crt
private-key: ./server.key
certificate: ./server.crt # 证书 PEM 格式,或者 证书的路径
private-key: ./server.key # 证书对应的私钥 PEM 格式,或者私钥路径
# 下面两项为mTLS配置项如果client-auth-type设置为 "verify-if-given" 或 "require-and-verify" 则client-auth-cert必须不为空
# client-auth-type: "" # 可选值:""、"request"、"require-any"、"verify-if-given"、"require-and-verify"
# client-auth-cert: string # 证书 PEM 格式,或者 证书的路径
# 如果填写则开启ech可由 mihomo generate ech-keypair <明文域名> 生成)
# ech-key: |
# -----BEGIN ECH KEYS-----
@ -1482,8 +1557,11 @@ listeners:
users:
00000000-0000-0000-0000-000000000000: PASSWORD_0
00000000-0000-0000-0000-000000000001: PASSWORD_1
# certificate: ./server.crt
# private-key: ./server.key
# certificate: ./server.crt # 证书 PEM 格式,或者 证书的路径
# private-key: ./server.key # 证书对应的私钥 PEM 格式,或者私钥路径
# 下面两项为mTLS配置项如果client-auth-type设置为 "verify-if-given" 或 "require-and-verify" 则client-auth-cert必须不为空
# client-auth-type: "" # 可选值:""、"request"、"require-any"、"verify-if-given"、"require-and-verify"
# client-auth-cert: string # 证书 PEM 格式,或者 证书的路径
# 如果填写则开启ech可由 mihomo generate ech-keypair <明文域名> 生成)
# ech-key: |
# -----BEGIN ECH KEYS-----

View File

@ -57,6 +57,8 @@ func applyRoute(cfg *config.Config) {
Secret: cfg.Controller.Secret,
Certificate: cfg.TLS.Certificate,
PrivateKey: cfg.TLS.PrivateKey,
ClientAuthType: cfg.TLS.ClientAuthType,
ClientAuthCert: cfg.TLS.ClientAuthCert,
EchKey: cfg.TLS.EchKey,
DohServer: cfg.Controller.ExternalDohServer,
IsDebug: cfg.General.LogLevel == log.DEBUG,

View File

@ -64,6 +64,8 @@ type Config struct {
Secret string
Certificate string
PrivateKey string
ClientAuthType string
ClientAuthCert string
EchKey string
DohServer string
IsDebug bool
@ -205,6 +207,20 @@ func startTLS(cfg *Config) {
tlsConfig := &tlsC.Config{Time: ntp.Now}
tlsConfig.NextProtos = []string{"h2", "http/1.1"}
tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)}
tlsConfig.ClientAuth = tlsC.ClientAuthTypeFromString(cfg.ClientAuthType)
if len(cfg.ClientAuthCert) > 0 {
if tlsConfig.ClientAuth == tlsC.NoClientCert {
tlsConfig.ClientAuth = tlsC.RequireAndVerifyClientCert
}
}
if tlsConfig.ClientAuth == tlsC.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tlsC.RequireAndVerifyClientCert {
pool, err := ca.LoadCertificates(cfg.ClientAuthCert, C.Path)
if err != nil {
log.Errorln("External controller tls listen error: %s", err)
return
}
tlsConfig.ClientCAs = pool
}
if cfg.EchKey != "" {
err = ech.LoadECHKey(cfg.EchKey, tlsConfig, C.Path)

View File

@ -58,6 +58,19 @@ func New(config LC.AnyTLSServer, tunnel C.Tunnel, additions ...inbound.Addition)
}
}
}
tlsConfig.ClientAuth = tlsC.ClientAuthTypeFromString(config.ClientAuthType)
if len(config.ClientAuthCert) > 0 {
if tlsConfig.ClientAuth == tlsC.NoClientCert {
tlsConfig.ClientAuth = tlsC.RequireAndVerifyClientCert
}
}
if tlsConfig.ClientAuth == tlsC.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tlsC.RequireAndVerifyClientCert {
pool, err := ca.LoadCertificates(config.ClientAuthCert, C.Path)
if err != nil {
return nil, err
}
tlsConfig.ClientCAs = pool
}
sl = &Listener{
config: config,

View File

@ -10,6 +10,8 @@ type AnyTLSServer struct {
Users map[string]string `yaml:"users" json:"users,omitempty"`
Certificate string `yaml:"certificate" json:"certificate"`
PrivateKey string `yaml:"private-key" json:"private-key"`
ClientAuthType string `yaml:"client-auth-type" json:"client-auth-type,omitempty"`
ClientAuthCert string `yaml:"client-auth-cert" json:"client-auth-cert,omitempty"`
EchKey string `yaml:"ech-key" json:"ech-key"`
PaddingScheme string `yaml:"padding-scheme" json:"padding-scheme,omitempty"`
}

View File

@ -12,6 +12,8 @@ type AuthServer struct {
AuthStore auth.AuthStore
Certificate string
PrivateKey string
ClientAuthType string
ClientAuthCert string
EchKey string
RealityConfig reality.Config
}

View File

@ -14,6 +14,8 @@ type Hysteria2Server struct {
ObfsPassword string `yaml:"obfs-password" json:"obfs-password,omitempty"`
Certificate string `yaml:"certificate" json:"certificate"`
PrivateKey string `yaml:"private-key" json:"private-key"`
ClientAuthType string `yaml:"client-auth-type" json:"client-auth-type,omitempty"`
ClientAuthCert string `yaml:"client-auth-cert" json:"client-auth-cert,omitempty"`
EchKey string `yaml:"ech-key" json:"ech-key,omitempty"`
MaxIdleTime int `yaml:"max-idle-time" json:"max-idle-time,omitempty"`
ALPN []string `yaml:"alpn" json:"alpn,omitempty"`

View File

@ -20,6 +20,8 @@ type TrojanServer struct {
GrpcServiceName string
Certificate string
PrivateKey string
ClientAuthType string
ClientAuthCert string
EchKey string
RealityConfig reality.Config
MuxOption sing.MuxOption

View File

@ -13,6 +13,8 @@ type TuicServer struct {
Users map[string]string `yaml:"users" json:"users,omitempty"`
Certificate string `yaml:"certificate" json:"certificate"`
PrivateKey string `yaml:"private-key" json:"private-key"`
ClientAuthType string `yaml:"client-auth-type" json:"client-auth-type,omitempty"`
ClientAuthCert string `yaml:"client-auth-cert" json:"client-auth-cert,omitempty"`
EchKey string `yaml:"ech-key" json:"ech-key"`
CongestionController string `yaml:"congestion-controller" json:"congestion-controller,omitempty"`
MaxIdleTime int `yaml:"max-idle-time" json:"max-idle-time,omitempty"`

View File

@ -22,6 +22,8 @@ type VlessServer struct {
GrpcServiceName string
Certificate string
PrivateKey string
ClientAuthType string
ClientAuthCert string
EchKey string
RealityConfig reality.Config
MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"`

View File

@ -21,6 +21,8 @@ type VmessServer struct {
GrpcServiceName string
Certificate string
PrivateKey string
ClientAuthType string
ClientAuthCert string
EchKey string
RealityConfig reality.Config
MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"`

View File

@ -83,6 +83,19 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
}
}
}
tlsConfig.ClientAuth = tlsC.ClientAuthTypeFromString(config.ClientAuthType)
if len(config.ClientAuthCert) > 0 {
if tlsConfig.ClientAuth == tlsC.NoClientCert {
tlsConfig.ClientAuth = tlsC.RequireAndVerifyClientCert
}
}
if tlsConfig.ClientAuth == tlsC.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tlsC.RequireAndVerifyClientCert {
pool, err := ca.LoadCertificates(config.ClientAuthCert, C.Path)
if err != nil {
return nil, err
}
tlsConfig.ClientCAs = pool
}
if config.RealityConfig.PrivateKey != "" {
if tlsConfig.Certificates != nil {
return nil, errors.New("certificate is unavailable in reality")

View File

@ -14,6 +14,8 @@ type AnyTLSOption struct {
Users map[string]string `inbound:"users,omitempty"`
Certificate string `inbound:"certificate"`
PrivateKey string `inbound:"private-key"`
ClientAuthType string `inbound:"client-auth-type,omitempty"`
ClientAuthCert string `inbound:"client-auth-cert,omitempty"`
EchKey string `inbound:"ech-key,omitempty"`
PaddingScheme string `inbound:"padding-scheme,omitempty"`
}
@ -43,6 +45,8 @@ func NewAnyTLS(options *AnyTLSOption) (*AnyTLS, error) {
Users: options.Users,
Certificate: options.Certificate,
PrivateKey: options.PrivateKey,
ClientAuthType: options.ClientAuthType,
ClientAuthCert: options.ClientAuthCert,
EchKey: options.EchKey,
PaddingScheme: options.PaddingScheme,
},

View File

@ -70,4 +70,25 @@ func TestInboundAnyTLS_TLS(t *testing.T) {
}
testInboundAnyTLS(t, inboundOptions, outboundOptions)
})
t.Run("mTLS", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.ClientAuthCert = tlsAuthCertificate
outboundOptions.Certificate = tlsAuthCertificate
outboundOptions.PrivateKey = tlsAuthPrivateKey
testInboundAnyTLS(t, inboundOptions, outboundOptions)
})
t.Run("mTLS+ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.ClientAuthCert = tlsAuthCertificate
outboundOptions.Certificate = tlsAuthCertificate
outboundOptions.PrivateKey = tlsAuthPrivateKey
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundAnyTLS(t, inboundOptions, outboundOptions)
})
}

View File

@ -37,6 +37,7 @@ var httpData = make([]byte, 2*pool.RelayBufferSize)
var remoteAddr = netip.MustParseAddr("1.2.3.4")
var userUUID = utils.NewUUIDV4().String()
var tlsCertificate, tlsPrivateKey, tlsFingerprint, _ = ca.NewRandomTLSKeyPair(ca.KeyPairTypeP256)
var tlsAuthCertificate, tlsAuthPrivateKey, _, _ = ca.NewRandomTLSKeyPair(ca.KeyPairTypeP256)
var tlsConfigCert, _ = tls.X509KeyPair([]byte(tlsCertificate), []byte(tlsPrivateKey))
var tlsConfig = &tls.Config{Certificates: []tls.Certificate{tlsConfigCert}, NextProtos: []string{"h2", "http/1.1"}}
var tlsClientConfig, _ = ca.GetTLSConfig(ca.Option{Fingerprint: tlsFingerprint})

View File

@ -16,6 +16,8 @@ type HTTPOption struct {
Users AuthUsers `inbound:"users,omitempty"`
Certificate string `inbound:"certificate,omitempty"`
PrivateKey string `inbound:"private-key,omitempty"`
ClientAuthType string `inbound:"client-auth-type,omitempty"`
ClientAuthCert string `inbound:"client-auth-cert,omitempty"`
EchKey string `inbound:"ech-key,omitempty"`
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
}
@ -65,6 +67,8 @@ func (h *HTTP) Listen(tunnel C.Tunnel) error {
AuthStore: h.config.Users.GetAuthStore(),
Certificate: h.config.Certificate,
PrivateKey: h.config.PrivateKey,
ClientAuthType: h.config.ClientAuthType,
ClientAuthCert: h.config.ClientAuthCert,
EchKey: h.config.EchKey,
RealityConfig: h.config.RealityConfig.Build(),
},

View File

@ -16,6 +16,8 @@ type Hysteria2Option struct {
ObfsPassword string `inbound:"obfs-password,omitempty"`
Certificate string `inbound:"certificate"`
PrivateKey string `inbound:"private-key"`
ClientAuthType string `inbound:"client-auth-type,omitempty"`
ClientAuthCert string `inbound:"client-auth-cert,omitempty"`
EchKey string `inbound:"ech-key,omitempty"`
MaxIdleTime int `inbound:"max-idle-time,omitempty"`
ALPN []string `inbound:"alpn,omitempty"`
@ -61,6 +63,8 @@ func NewHysteria2(options *Hysteria2Option) (*Hysteria2, error) {
ObfsPassword: options.ObfsPassword,
Certificate: options.Certificate,
PrivateKey: options.PrivateKey,
ClientAuthType: options.ClientAuthType,
ClientAuthCert: options.ClientAuthCert,
EchKey: options.EchKey,
MaxIdleTime: options.MaxIdleTime,
ALPN: options.ALPN,

View File

@ -51,14 +51,7 @@ func testInboundHysteria2(t *testing.T, inboundOptions inbound.Hysteria2Option,
tunnel.DoTest(t, out)
}
func TestInboundHysteria2_TLS(t *testing.T) {
inboundOptions := inbound.Hysteria2Option{
Certificate: tlsCertificate,
PrivateKey: tlsPrivateKey,
}
outboundOptions := outbound.Hysteria2Option{
Fingerprint: tlsFingerprint,
}
func testInboundHysteria2TLS(t *testing.T, inboundOptions inbound.Hysteria2Option, outboundOptions outbound.Hysteria2Option) {
testInboundHysteria2(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
@ -70,6 +63,38 @@ func TestInboundHysteria2_TLS(t *testing.T) {
}
testInboundHysteria2(t, inboundOptions, outboundOptions)
})
t.Run("mTLS", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.ClientAuthCert = tlsAuthCertificate
outboundOptions.Certificate = tlsAuthCertificate
outboundOptions.PrivateKey = tlsAuthPrivateKey
testInboundHysteria2(t, inboundOptions, outboundOptions)
})
t.Run("mTLS+ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.ClientAuthCert = tlsAuthCertificate
outboundOptions.Certificate = tlsAuthCertificate
outboundOptions.PrivateKey = tlsAuthPrivateKey
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundHysteria2(t, inboundOptions, outboundOptions)
})
}
func TestInboundHysteria2_TLS(t *testing.T) {
inboundOptions := inbound.Hysteria2Option{
Certificate: tlsCertificate,
PrivateKey: tlsPrivateKey,
}
outboundOptions := outbound.Hysteria2Option{
Fingerprint: tlsFingerprint,
}
testInboundHysteria2TLS(t, inboundOptions, outboundOptions)
}
func TestInboundHysteria2_Salamander(t *testing.T) {
@ -84,17 +109,7 @@ func TestInboundHysteria2_Salamander(t *testing.T) {
Obfs: "salamander",
ObfsPassword: userUUID,
}
testInboundHysteria2(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundHysteria2(t, inboundOptions, outboundOptions)
})
testInboundHysteria2TLS(t, inboundOptions, outboundOptions)
}
func TestInboundHysteria2_Brutal(t *testing.T) {
@ -109,15 +124,5 @@ func TestInboundHysteria2_Brutal(t *testing.T) {
Up: "30 Mbps",
Down: "200 Mbps",
}
testInboundHysteria2(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundHysteria2(t, inboundOptions, outboundOptions)
})
testInboundHysteria2TLS(t, inboundOptions, outboundOptions)
}

View File

@ -18,6 +18,8 @@ type MixedOption struct {
UDP bool `inbound:"udp,omitempty"`
Certificate string `inbound:"certificate,omitempty"`
PrivateKey string `inbound:"private-key,omitempty"`
ClientAuthType string `inbound:"client-auth-type,omitempty"`
ClientAuthCert string `inbound:"client-auth-cert,omitempty"`
EchKey string `inbound:"ech-key,omitempty"`
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
}
@ -70,6 +72,8 @@ func (m *Mixed) Listen(tunnel C.Tunnel) error {
AuthStore: m.config.Users.GetAuthStore(),
Certificate: m.config.Certificate,
PrivateKey: m.config.PrivateKey,
ClientAuthType: m.config.ClientAuthType,
ClientAuthCert: m.config.ClientAuthCert,
EchKey: m.config.EchKey,
RealityConfig: m.config.RealityConfig.Build(),
},

View File

@ -17,6 +17,8 @@ type SocksOption struct {
UDP bool `inbound:"udp,omitempty"`
Certificate string `inbound:"certificate,omitempty"`
PrivateKey string `inbound:"private-key,omitempty"`
ClientAuthType string `inbound:"client-auth-type,omitempty"`
ClientAuthCert string `inbound:"client-auth-cert,omitempty"`
EchKey string `inbound:"ech-key,omitempty"`
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
}
@ -90,6 +92,8 @@ func (s *Socks) Listen(tunnel C.Tunnel) error {
AuthStore: s.config.Users.GetAuthStore(),
Certificate: s.config.Certificate,
PrivateKey: s.config.PrivateKey,
ClientAuthType: s.config.ClientAuthType,
ClientAuthCert: s.config.ClientAuthCert,
EchKey: s.config.EchKey,
RealityConfig: s.config.RealityConfig.Build(),
},

View File

@ -16,6 +16,8 @@ type TrojanOption struct {
GrpcServiceName string `inbound:"grpc-service-name,omitempty"`
Certificate string `inbound:"certificate,omitempty"`
PrivateKey string `inbound:"private-key,omitempty"`
ClientAuthType string `inbound:"client-auth-type,omitempty"`
ClientAuthCert string `inbound:"client-auth-cert,omitempty"`
EchKey string `inbound:"ech-key,omitempty"`
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
MuxOption MuxOption `inbound:"mux-option,omitempty"`
@ -68,6 +70,8 @@ func NewTrojan(options *TrojanOption) (*Trojan, error) {
GrpcServiceName: options.GrpcServiceName,
Certificate: options.Certificate,
PrivateKey: options.PrivateKey,
ClientAuthType: options.ClientAuthType,
ClientAuthCert: options.ClientAuthCert,
EchKey: options.EchKey,
RealityConfig: options.RealityConfig.Build(),
MuxOption: options.MuxOption.Build(),

View File

@ -58,14 +58,7 @@ func testInboundTrojan(t *testing.T, inboundOptions inbound.TrojanOption, outbou
testSingMux(t, tunnel, out)
}
func TestInboundTrojan_TLS(t *testing.T) {
inboundOptions := inbound.TrojanOption{
Certificate: tlsCertificate,
PrivateKey: tlsPrivateKey,
}
outboundOptions := outbound.TrojanOption{
Fingerprint: tlsFingerprint,
}
func testInboundTrojanTLS(t *testing.T, inboundOptions inbound.TrojanOption, outboundOptions outbound.TrojanOption) {
testInboundTrojan(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
@ -77,6 +70,38 @@ func TestInboundTrojan_TLS(t *testing.T) {
}
testInboundTrojan(t, inboundOptions, outboundOptions)
})
t.Run("mTLS", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.ClientAuthCert = tlsAuthCertificate
outboundOptions.Certificate = tlsAuthCertificate
outboundOptions.PrivateKey = tlsAuthPrivateKey
testInboundTrojan(t, inboundOptions, outboundOptions)
})
t.Run("mTLS+ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.ClientAuthCert = tlsAuthCertificate
outboundOptions.Certificate = tlsAuthCertificate
outboundOptions.PrivateKey = tlsAuthPrivateKey
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundTrojan(t, inboundOptions, outboundOptions)
})
}
func TestInboundTrojan_TLS(t *testing.T) {
inboundOptions := inbound.TrojanOption{
Certificate: tlsCertificate,
PrivateKey: tlsPrivateKey,
}
outboundOptions := outbound.TrojanOption{
Fingerprint: tlsFingerprint,
}
testInboundTrojanTLS(t, inboundOptions, outboundOptions)
}
func TestInboundTrojan_Wss1(t *testing.T) {
@ -92,17 +117,7 @@ func TestInboundTrojan_Wss1(t *testing.T) {
Path: "/ws",
},
}
testInboundTrojan(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundTrojan(t, inboundOptions, outboundOptions)
})
testInboundTrojanTLS(t, inboundOptions, outboundOptions)
}
func TestInboundTrojan_Wss2(t *testing.T) {
@ -119,17 +134,7 @@ func TestInboundTrojan_Wss2(t *testing.T) {
Path: "/ws",
},
}
testInboundTrojan(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundTrojan(t, inboundOptions, outboundOptions)
})
testInboundTrojanTLS(t, inboundOptions, outboundOptions)
}
func TestInboundTrojan_Grpc1(t *testing.T) {
@ -143,17 +148,7 @@ func TestInboundTrojan_Grpc1(t *testing.T) {
Network: "grpc",
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
}
testInboundTrojan(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundTrojan(t, inboundOptions, outboundOptions)
})
testInboundTrojanTLS(t, inboundOptions, outboundOptions)
}
func TestInboundTrojan_Grpc2(t *testing.T) {
@ -168,17 +163,7 @@ func TestInboundTrojan_Grpc2(t *testing.T) {
Network: "grpc",
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
}
testInboundTrojan(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundTrojan(t, inboundOptions, outboundOptions)
})
testInboundTrojanTLS(t, inboundOptions, outboundOptions)
}
func TestInboundTrojan_Reality(t *testing.T) {
@ -242,17 +227,7 @@ func TestInboundTrojan_TLS_TrojanSS(t *testing.T) {
Password: "password",
},
}
testInboundTrojan(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundTrojan(t, inboundOptions, outboundOptions)
})
testInboundTrojanTLS(t, inboundOptions, outboundOptions)
}
func TestInboundTrojan_Wss_TrojanSS(t *testing.T) {
@ -278,15 +253,5 @@ func TestInboundTrojan_Wss_TrojanSS(t *testing.T) {
Path: "/ws",
},
}
testInboundTrojan(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundTrojan(t, inboundOptions, outboundOptions)
})
testInboundTrojanTLS(t, inboundOptions, outboundOptions)
}

View File

@ -15,6 +15,8 @@ type TuicOption struct {
Users map[string]string `inbound:"users,omitempty"`
Certificate string `inbound:"certificate"`
PrivateKey string `inbound:"private-key"`
ClientAuthType string `inbound:"client-auth-type,omitempty"`
ClientAuthCert string `inbound:"client-auth-cert,omitempty"`
EchKey string `inbound:"ech-key,omitempty"`
CongestionController string `inbound:"congestion-controller,omitempty"`
MaxIdleTime int `inbound:"max-idle-time,omitempty"`
@ -51,6 +53,8 @@ func NewTuic(options *TuicOption) (*Tuic, error) {
Users: options.Users,
Certificate: options.Certificate,
PrivateKey: options.PrivateKey,
ClientAuthType: options.ClientAuthType,
ClientAuthCert: options.ClientAuthCert,
EchKey: options.EchKey,
CongestionController: options.CongestionController,
MaxIdleTime: options.MaxIdleTime,

View File

@ -99,4 +99,25 @@ func TestInboundTuic_TLS(t *testing.T) {
}
testInboundTuic(t, inboundOptions, outboundOptions)
})
t.Run("mTLS", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.ClientAuthCert = tlsAuthCertificate
outboundOptions.Certificate = tlsAuthCertificate
outboundOptions.PrivateKey = tlsAuthPrivateKey
testInboundTuic(t, inboundOptions, outboundOptions)
})
t.Run("mTLS+ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.ClientAuthCert = tlsAuthCertificate
outboundOptions.Certificate = tlsAuthCertificate
outboundOptions.PrivateKey = tlsAuthPrivateKey
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundTuic(t, inboundOptions, outboundOptions)
})
}

View File

@ -17,6 +17,8 @@ type VlessOption struct {
GrpcServiceName string `inbound:"grpc-service-name,omitempty"`
Certificate string `inbound:"certificate,omitempty"`
PrivateKey string `inbound:"private-key,omitempty"`
ClientAuthType string `inbound:"client-auth-type,omitempty"`
ClientAuthCert string `inbound:"client-auth-cert,omitempty"`
EchKey string `inbound:"ech-key,omitempty"`
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
MuxOption MuxOption `inbound:"mux-option,omitempty"`
@ -64,6 +66,8 @@ func NewVless(options *VlessOption) (*Vless, error) {
GrpcServiceName: options.GrpcServiceName,
Certificate: options.Certificate,
PrivateKey: options.PrivateKey,
ClientAuthType: options.ClientAuthType,
ClientAuthCert: options.ClientAuthCert,
EchKey: options.EchKey,
RealityConfig: options.RealityConfig.Build(),
MuxOption: options.MuxOption.Build(),

View File

@ -59,21 +59,15 @@ func testInboundVless(t *testing.T, inboundOptions inbound.VlessOption, outbound
testSingMux(t, tunnel, out)
}
func TestInboundVless_TLS(t *testing.T) {
inboundOptions := inbound.VlessOption{
Certificate: tlsCertificate,
PrivateKey: tlsPrivateKey,
}
outboundOptions := outbound.VlessOption{
TLS: true,
Fingerprint: tlsFingerprint,
}
func testInboundVlessTLS(t *testing.T, inboundOptions inbound.VlessOption, outboundOptions outbound.VlessOption, testVision bool) {
testInboundVless(t, inboundOptions, outboundOptions)
if testVision {
t.Run("xtls-rprx-vision", func(t *testing.T) {
outboundOptions := outboundOptions
outboundOptions.Flow = "xtls-rprx-vision"
testInboundVless(t, inboundOptions, outboundOptions)
})
}
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
@ -83,12 +77,61 @@ func TestInboundVless_TLS(t *testing.T) {
Config: echConfigBase64,
}
testInboundVless(t, inboundOptions, outboundOptions)
if testVision {
t.Run("xtls-rprx-vision", func(t *testing.T) {
outboundOptions := outboundOptions
outboundOptions.Flow = "xtls-rprx-vision"
testInboundVless(t, inboundOptions, outboundOptions)
})
}
})
t.Run("mTLS", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.ClientAuthCert = tlsAuthCertificate
outboundOptions.Certificate = tlsAuthCertificate
outboundOptions.PrivateKey = tlsAuthPrivateKey
testInboundVless(t, inboundOptions, outboundOptions)
if testVision {
t.Run("xtls-rprx-vision", func(t *testing.T) {
outboundOptions := outboundOptions
outboundOptions.Flow = "xtls-rprx-vision"
testInboundVless(t, inboundOptions, outboundOptions)
})
}
})
t.Run("mTLS+ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.ClientAuthCert = tlsAuthCertificate
outboundOptions.Certificate = tlsAuthCertificate
outboundOptions.PrivateKey = tlsAuthPrivateKey
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundVless(t, inboundOptions, outboundOptions)
if testVision {
t.Run("xtls-rprx-vision", func(t *testing.T) {
outboundOptions := outboundOptions
outboundOptions.Flow = "xtls-rprx-vision"
testInboundVless(t, inboundOptions, outboundOptions)
})
}
})
}
func TestInboundVless_TLS(t *testing.T) {
inboundOptions := inbound.VlessOption{
Certificate: tlsCertificate,
PrivateKey: tlsPrivateKey,
}
outboundOptions := outbound.VlessOption{
TLS: true,
Fingerprint: tlsFingerprint,
}
testInboundVlessTLS(t, inboundOptions, outboundOptions, true)
}
func TestInboundVless_Encryption(t *testing.T) {
@ -183,17 +226,7 @@ func TestInboundVless_Wss1(t *testing.T) {
Network: "ws",
WSOpts: outbound.WSOptions{Path: "/ws"},
}
testInboundVless(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundVless(t, inboundOptions, outboundOptions)
})
testInboundVlessTLS(t, inboundOptions, outboundOptions, false)
}
func TestInboundVless_Wss2(t *testing.T) {
@ -209,17 +242,7 @@ func TestInboundVless_Wss2(t *testing.T) {
Network: "ws",
WSOpts: outbound.WSOptions{Path: "/ws"},
}
testInboundVless(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundVless(t, inboundOptions, outboundOptions)
})
testInboundVlessTLS(t, inboundOptions, outboundOptions, false)
}
func TestInboundVless_Grpc1(t *testing.T) {
@ -234,17 +257,7 @@ func TestInboundVless_Grpc1(t *testing.T) {
Network: "grpc",
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
}
testInboundVless(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundVless(t, inboundOptions, outboundOptions)
})
testInboundVlessTLS(t, inboundOptions, outboundOptions, false)
}
func TestInboundVless_Grpc2(t *testing.T) {
@ -260,17 +273,7 @@ func TestInboundVless_Grpc2(t *testing.T) {
Network: "grpc",
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
}
testInboundVless(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundVless(t, inboundOptions, outboundOptions)
})
testInboundVlessTLS(t, inboundOptions, outboundOptions, false)
}
func TestInboundVless_Reality(t *testing.T) {

View File

@ -16,6 +16,8 @@ type VmessOption struct {
GrpcServiceName string `inbound:"grpc-service-name,omitempty"`
Certificate string `inbound:"certificate,omitempty"`
PrivateKey string `inbound:"private-key,omitempty"`
ClientAuthType string `inbound:"client-auth-type,omitempty"`
ClientAuthCert string `inbound:"client-auth-cert,omitempty"`
EchKey string `inbound:"ech-key,omitempty"`
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
MuxOption MuxOption `inbound:"mux-option,omitempty"`
@ -62,6 +64,8 @@ func NewVmess(options *VmessOption) (*Vmess, error) {
GrpcServiceName: options.GrpcServiceName,
Certificate: options.Certificate,
PrivateKey: options.PrivateKey,
ClientAuthType: options.ClientAuthType,
ClientAuthCert: options.ClientAuthCert,
EchKey: options.EchKey,
RealityConfig: options.RealityConfig.Build(),
MuxOption: options.MuxOption.Build(),

View File

@ -66,15 +66,7 @@ func TestInboundVMess_Basic(t *testing.T) {
testInboundVMess(t, inboundOptions, outboundOptions)
}
func TestInboundVMess_TLS(t *testing.T) {
inboundOptions := inbound.VmessOption{
Certificate: tlsCertificate,
PrivateKey: tlsPrivateKey,
}
outboundOptions := outbound.VmessOption{
TLS: true,
Fingerprint: tlsFingerprint,
}
func testInboundVMessTLS(t *testing.T, inboundOptions inbound.VmessOption, outboundOptions outbound.VmessOption) {
testInboundVMess(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
@ -86,6 +78,39 @@ func TestInboundVMess_TLS(t *testing.T) {
}
testInboundVMess(t, inboundOptions, outboundOptions)
})
t.Run("mTLS", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.ClientAuthCert = tlsAuthCertificate
outboundOptions.Certificate = tlsAuthCertificate
outboundOptions.PrivateKey = tlsAuthPrivateKey
testInboundVMess(t, inboundOptions, outboundOptions)
})
t.Run("mTLS+ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.ClientAuthCert = tlsAuthCertificate
outboundOptions.Certificate = tlsAuthCertificate
outboundOptions.PrivateKey = tlsAuthPrivateKey
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundVMess(t, inboundOptions, outboundOptions)
})
}
func TestInboundVMess_TLS(t *testing.T) {
inboundOptions := inbound.VmessOption{
Certificate: tlsCertificate,
PrivateKey: tlsPrivateKey,
}
outboundOptions := outbound.VmessOption{
TLS: true,
Fingerprint: tlsFingerprint,
}
testInboundVMessTLS(t, inboundOptions, outboundOptions)
}
func TestInboundVMess_Ws(t *testing.T) {
@ -172,17 +197,7 @@ func TestInboundVMess_Wss1(t *testing.T) {
Path: "/ws",
},
}
testInboundVMess(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundVMess(t, inboundOptions, outboundOptions)
})
testInboundVMessTLS(t, inboundOptions, outboundOptions)
}
func TestInboundVMess_Wss2(t *testing.T) {
@ -200,17 +215,7 @@ func TestInboundVMess_Wss2(t *testing.T) {
Path: "/ws",
},
}
testInboundVMess(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundVMess(t, inboundOptions, outboundOptions)
})
testInboundVMessTLS(t, inboundOptions, outboundOptions)
}
func TestInboundVMess_Grpc1(t *testing.T) {
@ -225,17 +230,7 @@ func TestInboundVMess_Grpc1(t *testing.T) {
Network: "grpc",
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
}
testInboundVMess(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundVMess(t, inboundOptions, outboundOptions)
})
testInboundVMessTLS(t, inboundOptions, outboundOptions)
}
func TestInboundVMess_Grpc2(t *testing.T) {
@ -251,17 +246,7 @@ func TestInboundVMess_Grpc2(t *testing.T) {
Network: "grpc",
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
}
testInboundVMess(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundVMess(t, inboundOptions, outboundOptions)
})
testInboundVMessTLS(t, inboundOptions, outboundOptions)
}
func TestInboundVMess_Reality(t *testing.T) {

View File

@ -79,6 +79,19 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
}
}
}
tlsConfig.ClientAuth = tlsC.ClientAuthTypeFromString(config.ClientAuthType)
if len(config.ClientAuthCert) > 0 {
if tlsConfig.ClientAuth == tlsC.NoClientCert {
tlsConfig.ClientAuth = tlsC.RequireAndVerifyClientCert
}
}
if tlsConfig.ClientAuth == tlsC.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tlsC.RequireAndVerifyClientCert {
pool, err := ca.LoadCertificates(config.ClientAuthCert, C.Path)
if err != nil {
return nil, err
}
tlsConfig.ClientCAs = pool
}
if config.RealityConfig.PrivateKey != "" {
if tlsConfig.Certificates != nil {
return nil, errors.New("certificate is unavailable in reality")

View File

@ -66,6 +66,19 @@ func New(config LC.Hysteria2Server, tunnel C.Tunnel, additions ...inbound.Additi
MinVersion: tlsC.VersionTLS13,
}
tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)}
tlsConfig.ClientAuth = tlsC.ClientAuthTypeFromString(config.ClientAuthType)
if len(config.ClientAuthCert) > 0 {
if tlsConfig.ClientAuth == tlsC.NoClientCert {
tlsConfig.ClientAuth = tlsC.RequireAndVerifyClientCert
}
}
if tlsConfig.ClientAuth == tlsC.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tlsC.RequireAndVerifyClientCert {
pool, err := ca.LoadCertificates(config.ClientAuthCert, C.Path)
if err != nil {
return nil, err
}
tlsConfig.ClientCAs = pool
}
if config.EchKey != "" {
err = ech.LoadECHKey(config.EchKey, tlsConfig, C.Path)

View File

@ -94,6 +94,19 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
}
}
}
tlsConfig.ClientAuth = tlsC.ClientAuthTypeFromString(config.ClientAuthType)
if len(config.ClientAuthCert) > 0 {
if tlsConfig.ClientAuth == tlsC.NoClientCert {
tlsConfig.ClientAuth = tlsC.RequireAndVerifyClientCert
}
}
if tlsConfig.ClientAuth == tlsC.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tlsC.RequireAndVerifyClientCert {
pool, err := ca.LoadCertificates(config.ClientAuthCert, C.Path)
if err != nil {
return nil, err
}
tlsConfig.ClientCAs = pool
}
if config.RealityConfig.PrivateKey != "" {
if tlsConfig.Certificates != nil {
return nil, errors.New("certificate is unavailable in reality")

View File

@ -94,6 +94,19 @@ func New(config LC.VmessServer, tunnel C.Tunnel, additions ...inbound.Addition)
}
}
}
tlsConfig.ClientAuth = tlsC.ClientAuthTypeFromString(config.ClientAuthType)
if len(config.ClientAuthCert) > 0 {
if tlsConfig.ClientAuth == tlsC.NoClientCert {
tlsConfig.ClientAuth = tlsC.RequireAndVerifyClientCert
}
}
if tlsConfig.ClientAuth == tlsC.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tlsC.RequireAndVerifyClientCert {
pool, err := ca.LoadCertificates(config.ClientAuthCert, C.Path)
if err != nil {
return nil, err
}
tlsConfig.ClientCAs = pool
}
if config.RealityConfig.PrivateKey != "" {
if tlsConfig.Certificates != nil {
return nil, errors.New("certificate is unavailable in reality")

View File

@ -78,6 +78,19 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
}
}
}
tlsConfig.ClientAuth = tlsC.ClientAuthTypeFromString(config.ClientAuthType)
if len(config.ClientAuthCert) > 0 {
if tlsConfig.ClientAuth == tlsC.NoClientCert {
tlsConfig.ClientAuth = tlsC.RequireAndVerifyClientCert
}
}
if tlsConfig.ClientAuth == tlsC.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tlsC.RequireAndVerifyClientCert {
pool, err := ca.LoadCertificates(config.ClientAuthCert, C.Path)
if err != nil {
return nil, err
}
tlsConfig.ClientCAs = pool
}
if config.RealityConfig.PrivateKey != "" {
if tlsConfig.Certificates != nil {
return nil, errors.New("certificate is unavailable in reality")

View File

@ -89,6 +89,19 @@ func New(config LC.TrojanServer, tunnel C.Tunnel, additions ...inbound.Addition)
}
}
}
tlsConfig.ClientAuth = tlsC.ClientAuthTypeFromString(config.ClientAuthType)
if len(config.ClientAuthCert) > 0 {
if tlsConfig.ClientAuth == tlsC.NoClientCert {
tlsConfig.ClientAuth = tlsC.RequireAndVerifyClientCert
}
}
if tlsConfig.ClientAuth == tlsC.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tlsC.RequireAndVerifyClientCert {
pool, err := ca.LoadCertificates(config.ClientAuthCert, C.Path)
if err != nil {
return nil, err
}
tlsConfig.ClientCAs = pool
}
if config.RealityConfig.PrivateKey != "" {
if tlsConfig.Certificates != nil {
return nil, errors.New("certificate is unavailable in reality")

View File

@ -58,6 +58,19 @@ func New(config LC.TuicServer, tunnel C.Tunnel, additions ...inbound.Addition) (
MinVersion: tlsC.VersionTLS13,
}
tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)}
tlsConfig.ClientAuth = tlsC.ClientAuthTypeFromString(config.ClientAuthType)
if len(config.ClientAuthCert) > 0 {
if tlsConfig.ClientAuth == tlsC.NoClientCert {
tlsConfig.ClientAuth = tlsC.RequireAndVerifyClientCert
}
}
if tlsConfig.ClientAuth == tlsC.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tlsC.RequireAndVerifyClientCert {
pool, err := ca.LoadCertificates(config.ClientAuthCert, C.Path)
if err != nil {
return nil, err
}
tlsConfig.ClientCAs = pool
}
if config.EchKey != "" {
err = ech.LoadECHKey(config.EchKey, tlsConfig, C.Path)

View File

@ -22,6 +22,8 @@ type Option struct {
ECHConfig *ech.Config
SkipCertVerify bool
Fingerprint string
Certificate string
PrivateKey string
Mux bool
}
@ -67,6 +69,8 @@ func NewGostWebsocket(ctx context.Context, conn net.Conn, option *Option) (net.C
NextProtos: []string{"http/1.1"},
},
Fingerprint: option.Fingerprint,
Certificate: option.Certificate,
PrivateKey: option.PrivateKey,
})
if err != nil {
return nil, err

View File

@ -26,6 +26,8 @@ type ShadowTLSOption struct {
Password string
Host string
Fingerprint string
Certificate string
PrivateKey string
ClientFingerprint string
SkipCertVerify bool
Version int
@ -41,6 +43,8 @@ func NewShadowTLS(ctx context.Context, conn net.Conn, option *ShadowTLSOption) (
ServerName: option.Host,
},
Fingerprint: option.Fingerprint,
Certificate: option.Certificate,
PrivateKey: option.PrivateKey,
})
if err != nil {
return nil, err

View File

@ -21,6 +21,8 @@ type Option struct {
ECHConfig *ech.Config
SkipCertVerify bool
Fingerprint string
Certificate string
PrivateKey string
Mux bool
V2rayHttpUpgrade bool
V2rayHttpUpgradeFastOpen bool
@ -53,6 +55,8 @@ func NewV2rayObfs(ctx context.Context, conn net.Conn, option *Option) (net.Conn,
NextProtos: []string{"http/1.1"},
},
Fingerprint: option.Fingerprint,
Certificate: option.Certificate,
PrivateKey: option.PrivateKey,
})
if err != nil {
return nil, err

View File

@ -15,6 +15,8 @@ type TLSConfig struct {
Host string
SkipCertVerify bool
FingerPrint string
Certificate string
PrivateKey string
ClientFingerprint string
NextProtos []string
ECH *ech.Config
@ -33,6 +35,8 @@ func StreamTLSConn(ctx context.Context, conn net.Conn, cfg *TLSConfig) (net.Conn
NextProtos: cfg.NextProtos,
},
Fingerprint: cfg.FingerPrint,
Certificate: cfg.Certificate,
PrivateKey: cfg.PrivateKey,
})
if err != nil {
return nil, err