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

@ -50,16 +50,18 @@ func applyRoute(cfg *config.Config) {
route.SetUIPath(cfg.Controller.ExternalUI)
}
route.ReCreateServer(&route.Config{
Addr: cfg.Controller.ExternalController,
TLSAddr: cfg.Controller.ExternalControllerTLS,
UnixAddr: cfg.Controller.ExternalControllerUnix,
PipeAddr: cfg.Controller.ExternalControllerPipe,
Secret: cfg.Controller.Secret,
Certificate: cfg.TLS.Certificate,
PrivateKey: cfg.TLS.PrivateKey,
EchKey: cfg.TLS.EchKey,
DohServer: cfg.Controller.ExternalDohServer,
IsDebug: cfg.General.LogLevel == log.DEBUG,
Addr: cfg.Controller.ExternalController,
TLSAddr: cfg.Controller.ExternalControllerTLS,
UnixAddr: cfg.Controller.ExternalControllerUnix,
PipeAddr: cfg.Controller.ExternalControllerPipe,
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,
Cors: route.Cors{
AllowOrigins: cfg.Controller.Cors.AllowOrigins,
AllowPrivateNetwork: cfg.Controller.Cors.AllowPrivateNetwork,

View File

@ -57,17 +57,19 @@ type Memory struct {
}
type Config struct {
Addr string
TLSAddr string
UnixAddr string
PipeAddr string
Secret string
Certificate string
PrivateKey string
EchKey string
DohServer string
IsDebug bool
Cors Cors
Addr string
TLSAddr string
UnixAddr string
PipeAddr string
Secret string
Certificate string
PrivateKey string
ClientAuthType string
ClientAuthCert string
EchKey string
DohServer string
IsDebug bool
Cors Cors
}
type Cors struct {
@ -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

@ -5,13 +5,15 @@ import (
)
type AnyTLSServer struct {
Enable bool `yaml:"enable" json:"enable"`
Listen string `yaml:"listen" json:"listen"`
Users map[string]string `yaml:"users" json:"users,omitempty"`
Certificate string `yaml:"certificate" json:"certificate"`
PrivateKey string `yaml:"private-key" json:"private-key"`
EchKey string `yaml:"ech-key" json:"ech-key"`
PaddingScheme string `yaml:"padding-scheme" json:"padding-scheme,omitempty"`
Enable bool `yaml:"enable" json:"enable"`
Listen string `yaml:"listen" json:"listen"`
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"`
}
func (t AnyTLSServer) String() string {

View File

@ -7,11 +7,13 @@ import (
// AuthServer for http/socks/mixed server
type AuthServer struct {
Enable bool
Listen string
AuthStore auth.AuthStore
Certificate string
PrivateKey string
EchKey string
RealityConfig reality.Config
Enable bool
Listen string
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

@ -11,11 +11,13 @@ import (
type AnyTLSOption struct {
BaseOption
Users map[string]string `inbound:"users,omitempty"`
Certificate string `inbound:"certificate"`
PrivateKey string `inbound:"private-key"`
EchKey string `inbound:"ech-key,omitempty"`
PaddingScheme string `inbound:"padding-scheme,omitempty"`
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"`
}
func (o AnyTLSOption) Equal(config C.InboundConfig) bool {
@ -38,13 +40,15 @@ func NewAnyTLS(options *AnyTLSOption) (*AnyTLS, error) {
Base: base,
config: options,
vs: LC.AnyTLSServer{
Enable: true,
Listen: base.RawAddress(),
Users: options.Users,
Certificate: options.Certificate,
PrivateKey: options.PrivateKey,
EchKey: options.EchKey,
PaddingScheme: options.PaddingScheme,
Enable: true,
Listen: base.RawAddress(),
Users: options.Users,
Certificate: options.Certificate,
PrivateKey: options.PrivateKey,
ClientAuthType: options.ClientAuthType,
ClientAuthCert: options.ClientAuthCert,
EchKey: options.EchKey,
PaddingScheme: options.PaddingScheme,
},
}, nil
}

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

@ -13,11 +13,13 @@ import (
type HTTPOption struct {
BaseOption
Users AuthUsers `inbound:"users,omitempty"`
Certificate string `inbound:"certificate,omitempty"`
PrivateKey string `inbound:"private-key,omitempty"`
EchKey string `inbound:"ech-key,omitempty"`
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
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"`
}
func (o HTTPOption) Equal(config C.InboundConfig) bool {
@ -60,13 +62,15 @@ func (h *HTTP) Listen(tunnel C.Tunnel) error {
for _, addr := range strings.Split(h.RawAddress(), ",") {
l, err := http.NewWithConfig(
LC.AuthServer{
Enable: true,
Listen: addr,
AuthStore: h.config.Users.GetAuthStore(),
Certificate: h.config.Certificate,
PrivateKey: h.config.PrivateKey,
EchKey: h.config.EchKey,
RealityConfig: h.config.RealityConfig.Build(),
Enable: true,
Listen: addr,
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(),
},
tunnel,
h.Additions()...,

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

@ -14,12 +14,14 @@ import (
type MixedOption struct {
BaseOption
Users AuthUsers `inbound:"users,omitempty"`
UDP bool `inbound:"udp,omitempty"`
Certificate string `inbound:"certificate,omitempty"`
PrivateKey string `inbound:"private-key,omitempty"`
EchKey string `inbound:"ech-key,omitempty"`
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
Users AuthUsers `inbound:"users,omitempty"`
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"`
}
func (o MixedOption) Equal(config C.InboundConfig) bool {
@ -65,13 +67,15 @@ func (m *Mixed) Listen(tunnel C.Tunnel) error {
for _, addr := range strings.Split(m.RawAddress(), ",") {
l, err := mixed.NewWithConfig(
LC.AuthServer{
Enable: true,
Listen: addr,
AuthStore: m.config.Users.GetAuthStore(),
Certificate: m.config.Certificate,
PrivateKey: m.config.PrivateKey,
EchKey: m.config.EchKey,
RealityConfig: m.config.RealityConfig.Build(),
Enable: true,
Listen: addr,
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(),
},
tunnel,
m.Additions()...,

View File

@ -13,12 +13,14 @@ import (
type SocksOption struct {
BaseOption
Users AuthUsers `inbound:"users,omitempty"`
UDP bool `inbound:"udp,omitempty"`
Certificate string `inbound:"certificate,omitempty"`
PrivateKey string `inbound:"private-key,omitempty"`
EchKey string `inbound:"ech-key,omitempty"`
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
Users AuthUsers `inbound:"users,omitempty"`
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"`
}
func (o SocksOption) Equal(config C.InboundConfig) bool {
@ -85,13 +87,15 @@ func (s *Socks) Listen(tunnel C.Tunnel) error {
for _, addr := range strings.Split(s.RawAddress(), ",") {
stl, err := socks.NewWithConfig(
LC.AuthServer{
Enable: true,
Listen: addr,
AuthStore: s.config.Users.GetAuthStore(),
Certificate: s.config.Certificate,
PrivateKey: s.config.PrivateKey,
EchKey: s.config.EchKey,
RealityConfig: s.config.RealityConfig.Build(),
Enable: true,
Listen: addr,
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(),
},
tunnel,
s.Additions()...,

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)
t.Run("xtls-rprx-vision", func(t *testing.T) {
outboundOptions := outboundOptions
outboundOptions.Flow = "xtls-rprx-vision"
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)
t.Run("xtls-rprx-vision", func(t *testing.T) {
outboundOptions := outboundOptions
outboundOptions.Flow = "xtls-rprx-vision"
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