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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,6 +11,7 @@ import (
"sync" "sync"
"github.com/metacubex/mihomo/common/once" "github.com/metacubex/mihomo/common/once"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/ntp" "github.com/metacubex/mihomo/ntp"
) )
@ -79,6 +80,8 @@ type Option struct {
TLSConfig *tls.Config TLSConfig *tls.Config
Fingerprint string Fingerprint string
ZeroTrust bool ZeroTrust bool
Certificate string
PrivateKey string
} }
func GetTLSConfig(opt Option) (tlsConfig *tls.Config, err error) { 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 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 return tlsConfig, nil
} }

View File

@ -12,6 +12,8 @@ import (
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"math/big" "math/big"
"os"
"time"
) )
type Path interface { type Path interface {
@ -56,6 +58,33 @@ func LoadTLSKeyPair(certificate, privateKey string, path Path) (tls.Certificate,
return cert, nil 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 type KeyPairType string
const ( const (
@ -85,7 +114,11 @@ func NewRandomTLSKeyPair(keyPairType KeyPairType) (certificate string, privateKe
return 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) certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, key.Public(), key)
if err != nil { if err != nil {
return 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, RootCAs: config.RootCAs,
NextProtos: config.NextProtos, NextProtos: config.NextProtos,
ServerName: config.ServerName, ServerName: config.ServerName,
ClientAuth: utls.ClientAuthType(config.ClientAuth),
ClientCAs: config.ClientCAs,
InsecureSkipVerify: config.InsecureSkipVerify, InsecureSkipVerify: config.InsecureSkipVerify,
CipherSuites: config.CipherSuites, CipherSuites: config.CipherSuites,
MinVersion: config.MinVersion, MinVersion: config.MinVersion,

View File

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

View File

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

View File

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

View File

@ -57,17 +57,19 @@ type Memory struct {
} }
type Config struct { type Config struct {
Addr string Addr string
TLSAddr string TLSAddr string
UnixAddr string UnixAddr string
PipeAddr string PipeAddr string
Secret string Secret string
Certificate string Certificate string
PrivateKey string PrivateKey string
EchKey string ClientAuthType string
DohServer string ClientAuthCert string
IsDebug bool EchKey string
Cors Cors DohServer string
IsDebug bool
Cors Cors
} }
type Cors struct { type Cors struct {
@ -205,6 +207,20 @@ func startTLS(cfg *Config) {
tlsConfig := &tlsC.Config{Time: ntp.Now} tlsConfig := &tlsC.Config{Time: ntp.Now}
tlsConfig.NextProtos = []string{"h2", "http/1.1"} tlsConfig.NextProtos = []string{"h2", "http/1.1"}
tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)} 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 != "" { if cfg.EchKey != "" {
err = ech.LoadECHKey(cfg.EchKey, tlsConfig, C.Path) 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{ sl = &Listener{
config: config, config: config,

View File

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

View File

@ -7,11 +7,13 @@ import (
// AuthServer for http/socks/mixed server // AuthServer for http/socks/mixed server
type AuthServer struct { type AuthServer struct {
Enable bool Enable bool
Listen string Listen string
AuthStore auth.AuthStore AuthStore auth.AuthStore
Certificate string Certificate string
PrivateKey string PrivateKey string
EchKey string ClientAuthType string
RealityConfig reality.Config 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"` ObfsPassword string `yaml:"obfs-password" json:"obfs-password,omitempty"`
Certificate string `yaml:"certificate" json:"certificate"` Certificate string `yaml:"certificate" json:"certificate"`
PrivateKey string `yaml:"private-key" json:"private-key"` 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"` EchKey string `yaml:"ech-key" json:"ech-key,omitempty"`
MaxIdleTime int `yaml:"max-idle-time" json:"max-idle-time,omitempty"` MaxIdleTime int `yaml:"max-idle-time" json:"max-idle-time,omitempty"`
ALPN []string `yaml:"alpn" json:"alpn,omitempty"` ALPN []string `yaml:"alpn" json:"alpn,omitempty"`

View File

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

View File

@ -13,6 +13,8 @@ type TuicServer struct {
Users map[string]string `yaml:"users" json:"users,omitempty"` Users map[string]string `yaml:"users" json:"users,omitempty"`
Certificate string `yaml:"certificate" json:"certificate"` Certificate string `yaml:"certificate" json:"certificate"`
PrivateKey string `yaml:"private-key" json:"private-key"` 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"` EchKey string `yaml:"ech-key" json:"ech-key"`
CongestionController string `yaml:"congestion-controller" json:"congestion-controller,omitempty"` CongestionController string `yaml:"congestion-controller" json:"congestion-controller,omitempty"`
MaxIdleTime int `yaml:"max-idle-time" json:"max-idle-time,omitempty"` MaxIdleTime int `yaml:"max-idle-time" json:"max-idle-time,omitempty"`

View File

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

View File

@ -21,6 +21,8 @@ type VmessServer struct {
GrpcServiceName string GrpcServiceName string
Certificate string Certificate string
PrivateKey string PrivateKey string
ClientAuthType string
ClientAuthCert string
EchKey string EchKey string
RealityConfig reality.Config RealityConfig reality.Config
MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"` 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 config.RealityConfig.PrivateKey != "" {
if tlsConfig.Certificates != nil { if tlsConfig.Certificates != nil {
return nil, errors.New("certificate is unavailable in reality") return nil, errors.New("certificate is unavailable in reality")

View File

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

View File

@ -70,4 +70,25 @@ func TestInboundAnyTLS_TLS(t *testing.T) {
} }
testInboundAnyTLS(t, inboundOptions, outboundOptions) 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 remoteAddr = netip.MustParseAddr("1.2.3.4")
var userUUID = utils.NewUUIDV4().String() var userUUID = utils.NewUUIDV4().String()
var tlsCertificate, tlsPrivateKey, tlsFingerprint, _ = ca.NewRandomTLSKeyPair(ca.KeyPairTypeP256) var tlsCertificate, tlsPrivateKey, tlsFingerprint, _ = ca.NewRandomTLSKeyPair(ca.KeyPairTypeP256)
var tlsAuthCertificate, tlsAuthPrivateKey, _, _ = ca.NewRandomTLSKeyPair(ca.KeyPairTypeP256)
var tlsConfigCert, _ = tls.X509KeyPair([]byte(tlsCertificate), []byte(tlsPrivateKey)) var tlsConfigCert, _ = tls.X509KeyPair([]byte(tlsCertificate), []byte(tlsPrivateKey))
var tlsConfig = &tls.Config{Certificates: []tls.Certificate{tlsConfigCert}, NextProtos: []string{"h2", "http/1.1"}} var tlsConfig = &tls.Config{Certificates: []tls.Certificate{tlsConfigCert}, NextProtos: []string{"h2", "http/1.1"}}
var tlsClientConfig, _ = ca.GetTLSConfig(ca.Option{Fingerprint: tlsFingerprint}) var tlsClientConfig, _ = ca.GetTLSConfig(ca.Option{Fingerprint: tlsFingerprint})

View File

@ -13,11 +13,13 @@ import (
type HTTPOption struct { type HTTPOption struct {
BaseOption BaseOption
Users AuthUsers `inbound:"users,omitempty"` Users AuthUsers `inbound:"users,omitempty"`
Certificate string `inbound:"certificate,omitempty"` Certificate string `inbound:"certificate,omitempty"`
PrivateKey string `inbound:"private-key,omitempty"` PrivateKey string `inbound:"private-key,omitempty"`
EchKey string `inbound:"ech-key,omitempty"` ClientAuthType string `inbound:"client-auth-type,omitempty"`
RealityConfig RealityConfig `inbound:"reality-config,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 { 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(), ",") { for _, addr := range strings.Split(h.RawAddress(), ",") {
l, err := http.NewWithConfig( l, err := http.NewWithConfig(
LC.AuthServer{ LC.AuthServer{
Enable: true, Enable: true,
Listen: addr, Listen: addr,
AuthStore: h.config.Users.GetAuthStore(), AuthStore: h.config.Users.GetAuthStore(),
Certificate: h.config.Certificate, Certificate: h.config.Certificate,
PrivateKey: h.config.PrivateKey, PrivateKey: h.config.PrivateKey,
EchKey: h.config.EchKey, ClientAuthType: h.config.ClientAuthType,
RealityConfig: h.config.RealityConfig.Build(), ClientAuthCert: h.config.ClientAuthCert,
EchKey: h.config.EchKey,
RealityConfig: h.config.RealityConfig.Build(),
}, },
tunnel, tunnel,
h.Additions()..., h.Additions()...,

View File

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

View File

@ -51,14 +51,7 @@ func testInboundHysteria2(t *testing.T, inboundOptions inbound.Hysteria2Option,
tunnel.DoTest(t, out) tunnel.DoTest(t, out)
} }
func TestInboundHysteria2_TLS(t *testing.T) { func testInboundHysteria2TLS(t *testing.T, inboundOptions inbound.Hysteria2Option, outboundOptions outbound.Hysteria2Option) {
inboundOptions := inbound.Hysteria2Option{
Certificate: tlsCertificate,
PrivateKey: tlsPrivateKey,
}
outboundOptions := outbound.Hysteria2Option{
Fingerprint: tlsFingerprint,
}
testInboundHysteria2(t, inboundOptions, outboundOptions) testInboundHysteria2(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) { t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions inboundOptions := inboundOptions
@ -70,6 +63,38 @@ func TestInboundHysteria2_TLS(t *testing.T) {
} }
testInboundHysteria2(t, inboundOptions, outboundOptions) 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) { func TestInboundHysteria2_Salamander(t *testing.T) {
@ -84,17 +109,7 @@ func TestInboundHysteria2_Salamander(t *testing.T) {
Obfs: "salamander", Obfs: "salamander",
ObfsPassword: userUUID, ObfsPassword: userUUID,
} }
testInboundHysteria2(t, inboundOptions, outboundOptions) testInboundHysteria2TLS(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)
})
} }
func TestInboundHysteria2_Brutal(t *testing.T) { func TestInboundHysteria2_Brutal(t *testing.T) {
@ -109,15 +124,5 @@ func TestInboundHysteria2_Brutal(t *testing.T) {
Up: "30 Mbps", Up: "30 Mbps",
Down: "200 Mbps", Down: "200 Mbps",
} }
testInboundHysteria2(t, inboundOptions, outboundOptions) testInboundHysteria2TLS(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)
})
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -99,4 +99,25 @@ func TestInboundTuic_TLS(t *testing.T) {
} }
testInboundTuic(t, inboundOptions, outboundOptions) 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"` GrpcServiceName string `inbound:"grpc-service-name,omitempty"`
Certificate string `inbound:"certificate,omitempty"` Certificate string `inbound:"certificate,omitempty"`
PrivateKey string `inbound:"private-key,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"` EchKey string `inbound:"ech-key,omitempty"`
RealityConfig RealityConfig `inbound:"reality-config,omitempty"` RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
MuxOption MuxOption `inbound:"mux-option,omitempty"` MuxOption MuxOption `inbound:"mux-option,omitempty"`
@ -64,6 +66,8 @@ func NewVless(options *VlessOption) (*Vless, error) {
GrpcServiceName: options.GrpcServiceName, GrpcServiceName: options.GrpcServiceName,
Certificate: options.Certificate, Certificate: options.Certificate,
PrivateKey: options.PrivateKey, PrivateKey: options.PrivateKey,
ClientAuthType: options.ClientAuthType,
ClientAuthCert: options.ClientAuthCert,
EchKey: options.EchKey, EchKey: options.EchKey,
RealityConfig: options.RealityConfig.Build(), RealityConfig: options.RealityConfig.Build(),
MuxOption: options.MuxOption.Build(), MuxOption: options.MuxOption.Build(),

View File

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

View File

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

View File

@ -66,15 +66,7 @@ func TestInboundVMess_Basic(t *testing.T) {
testInboundVMess(t, inboundOptions, outboundOptions) testInboundVMess(t, inboundOptions, outboundOptions)
} }
func TestInboundVMess_TLS(t *testing.T) { func testInboundVMessTLS(t *testing.T, inboundOptions inbound.VmessOption, outboundOptions outbound.VmessOption) {
inboundOptions := inbound.VmessOption{
Certificate: tlsCertificate,
PrivateKey: tlsPrivateKey,
}
outboundOptions := outbound.VmessOption{
TLS: true,
Fingerprint: tlsFingerprint,
}
testInboundVMess(t, inboundOptions, outboundOptions) testInboundVMess(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) { t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions inboundOptions := inboundOptions
@ -86,6 +78,39 @@ func TestInboundVMess_TLS(t *testing.T) {
} }
testInboundVMess(t, inboundOptions, outboundOptions) 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) { func TestInboundVMess_Ws(t *testing.T) {
@ -172,17 +197,7 @@ func TestInboundVMess_Wss1(t *testing.T) {
Path: "/ws", Path: "/ws",
}, },
} }
testInboundVMess(t, inboundOptions, outboundOptions) testInboundVMessTLS(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)
})
} }
func TestInboundVMess_Wss2(t *testing.T) { func TestInboundVMess_Wss2(t *testing.T) {
@ -200,17 +215,7 @@ func TestInboundVMess_Wss2(t *testing.T) {
Path: "/ws", Path: "/ws",
}, },
} }
testInboundVMess(t, inboundOptions, outboundOptions) testInboundVMessTLS(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)
})
} }
func TestInboundVMess_Grpc1(t *testing.T) { func TestInboundVMess_Grpc1(t *testing.T) {
@ -225,17 +230,7 @@ func TestInboundVMess_Grpc1(t *testing.T) {
Network: "grpc", Network: "grpc",
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"}, GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
} }
testInboundVMess(t, inboundOptions, outboundOptions) testInboundVMessTLS(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)
})
} }
func TestInboundVMess_Grpc2(t *testing.T) { func TestInboundVMess_Grpc2(t *testing.T) {
@ -251,17 +246,7 @@ func TestInboundVMess_Grpc2(t *testing.T) {
Network: "grpc", Network: "grpc",
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"}, GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
} }
testInboundVMess(t, inboundOptions, outboundOptions) testInboundVMessTLS(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)
})
} }
func TestInboundVMess_Reality(t *testing.T) { 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 config.RealityConfig.PrivateKey != "" {
if tlsConfig.Certificates != nil { if tlsConfig.Certificates != nil {
return nil, errors.New("certificate is unavailable in reality") 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, MinVersion: tlsC.VersionTLS13,
} }
tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)} 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 != "" { if config.EchKey != "" {
err = ech.LoadECHKey(config.EchKey, tlsConfig, C.Path) 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 config.RealityConfig.PrivateKey != "" {
if tlsConfig.Certificates != nil { if tlsConfig.Certificates != nil {
return nil, errors.New("certificate is unavailable in reality") 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 config.RealityConfig.PrivateKey != "" {
if tlsConfig.Certificates != nil { if tlsConfig.Certificates != nil {
return nil, errors.New("certificate is unavailable in reality") 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 config.RealityConfig.PrivateKey != "" {
if tlsConfig.Certificates != nil { if tlsConfig.Certificates != nil {
return nil, errors.New("certificate is unavailable in reality") 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 config.RealityConfig.PrivateKey != "" {
if tlsConfig.Certificates != nil { if tlsConfig.Certificates != nil {
return nil, errors.New("certificate is unavailable in reality") 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, MinVersion: tlsC.VersionTLS13,
} }
tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)} 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 != "" { if config.EchKey != "" {
err = ech.LoadECHKey(config.EchKey, tlsConfig, C.Path) err = ech.LoadECHKey(config.EchKey, tlsConfig, C.Path)

View File

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

View File

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

View File

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

View File

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