Feature: add force-cert-verify to general config
force verify TLS Certificate, prevent machine-in-the-middle attacks.
This commit is contained in:
parent
03499fcea6
commit
d11d28c358
13
README.md
13
README.md
@ -36,6 +36,13 @@
|
|||||||
Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki).
|
Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki).
|
||||||
|
|
||||||
## Advanced usage for this branch
|
## Advanced usage for this branch
|
||||||
|
### General configuration
|
||||||
|
```yaml
|
||||||
|
sniffing: true # Sniff TLS SNI
|
||||||
|
|
||||||
|
force-cert-verify: true # force verify TLS Certificate, prevent machine-in-the-middle attacks
|
||||||
|
```
|
||||||
|
|
||||||
### MITM configuration
|
### MITM configuration
|
||||||
A root CA certificate is required, the
|
A root CA certificate is required, the
|
||||||
MITM proxy server will generate a CA certificate file and a CA private key file in your Clash home directory, you can use your own certificate replace it.
|
MITM proxy server will generate a CA certificate file and a CA private key file in your Clash home directory, you can use your own certificate replace it.
|
||||||
@ -247,12 +254,6 @@ proxies:
|
|||||||
# skip-cert-verify: true
|
# skip-cert-verify: true
|
||||||
```
|
```
|
||||||
|
|
||||||
### Sniffing configuration
|
|
||||||
Sniff TLS SNI.
|
|
||||||
```yaml
|
|
||||||
sniffing: true
|
|
||||||
```
|
|
||||||
|
|
||||||
### IPTABLES configuration
|
### IPTABLES configuration
|
||||||
Work on Linux OS who's supported `iptables`
|
Work on Linux OS who's supported `iptables`
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
func ParseProxy(mapping map[string]any, forceCertVerify bool) (C.Proxy, error) {
|
||||||
decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true})
|
decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true})
|
||||||
proxyType, existType := mapping["type"].(string)
|
proxyType, existType := mapping["type"].(string)
|
||||||
if !existType {
|
if !existType {
|
||||||
@ -40,6 +40,9 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if forceCertVerify {
|
||||||
|
socksOption.SkipCertVerify = false
|
||||||
|
}
|
||||||
proxy = outbound.NewSocks5(*socksOption)
|
proxy = outbound.NewSocks5(*socksOption)
|
||||||
case "http":
|
case "http":
|
||||||
httpOption := &outbound.HttpOption{}
|
httpOption := &outbound.HttpOption{}
|
||||||
@ -47,6 +50,9 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if forceCertVerify {
|
||||||
|
httpOption.SkipCertVerify = false
|
||||||
|
}
|
||||||
proxy = outbound.NewHttp(*httpOption)
|
proxy = outbound.NewHttp(*httpOption)
|
||||||
case "vmess":
|
case "vmess":
|
||||||
vmessOption := &outbound.VmessOption{
|
vmessOption := &outbound.VmessOption{
|
||||||
@ -59,6 +65,9 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if forceCertVerify {
|
||||||
|
vmessOption.SkipCertVerify = false
|
||||||
|
}
|
||||||
proxy, err = outbound.NewVmess(*vmessOption)
|
proxy, err = outbound.NewVmess(*vmessOption)
|
||||||
case "vless":
|
case "vless":
|
||||||
vlessOption := &outbound.VlessOption{}
|
vlessOption := &outbound.VlessOption{}
|
||||||
@ -66,6 +75,9 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if forceCertVerify {
|
||||||
|
vlessOption.SkipCertVerify = false
|
||||||
|
}
|
||||||
proxy, err = outbound.NewVless(*vlessOption)
|
proxy, err = outbound.NewVless(*vlessOption)
|
||||||
case "snell":
|
case "snell":
|
||||||
snellOption := &outbound.SnellOption{}
|
snellOption := &outbound.SnellOption{}
|
||||||
@ -80,6 +92,9 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if forceCertVerify {
|
||||||
|
trojanOption.SkipCertVerify = false
|
||||||
|
}
|
||||||
proxy, err = outbound.NewTrojan(*trojanOption)
|
proxy, err = outbound.NewTrojan(*trojanOption)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
|
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
|
||||||
|
@ -20,15 +20,16 @@ type healthCheckSchema struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type proxyProviderSchema struct {
|
type proxyProviderSchema struct {
|
||||||
Type string `provider:"type"`
|
Type string `provider:"type"`
|
||||||
Path string `provider:"path"`
|
Path string `provider:"path"`
|
||||||
URL string `provider:"url,omitempty"`
|
URL string `provider:"url,omitempty"`
|
||||||
Interval int `provider:"interval,omitempty"`
|
Interval int `provider:"interval,omitempty"`
|
||||||
Filter string `provider:"filter,omitempty"`
|
Filter string `provider:"filter,omitempty"`
|
||||||
HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
|
HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
|
||||||
|
ForceCertVerify bool `provider:"force-cert-verify,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvider, error) {
|
func ParseProxyProvider(name string, mapping map[string]any, forceCertVerify bool) (types.ProxyProvider, error) {
|
||||||
decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true})
|
decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true})
|
||||||
|
|
||||||
schema := &proxyProviderSchema{
|
schema := &proxyProviderSchema{
|
||||||
@ -36,6 +37,11 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
|
|||||||
Lazy: true,
|
Lazy: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if forceCertVerify {
|
||||||
|
schema.ForceCertVerify = true
|
||||||
|
}
|
||||||
|
|
||||||
if err := decoder.Decode(mapping, schema); err != nil {
|
if err := decoder.Decode(mapping, schema); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -60,5 +66,5 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
|
|||||||
|
|
||||||
interval := time.Duration(uint(schema.Interval)) * time.Second
|
interval := time.Duration(uint(schema.Interval)) * time.Second
|
||||||
filter := schema.Filter
|
filter := schema.Filter
|
||||||
return NewProxySetProvider(name, interval, filter, vehicle, hc)
|
return NewProxySetProvider(name, interval, filter, vehicle, hc, schema.ForceCertVerify)
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,7 @@ func stopProxyProvider(pd *ProxySetProvider) {
|
|||||||
_ = pd.fetcher.Destroy()
|
_ = pd.fetcher.Destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProxySetProvider(name string, interval time.Duration, filter string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
|
func NewProxySetProvider(name string, interval time.Duration, filter string, vehicle types.Vehicle, hc *HealthCheck, forceCertVerify bool) (*ProxySetProvider, error) {
|
||||||
filterReg, err := regexp.Compile(filter)
|
filterReg, err := regexp.Compile(filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid filter regex: %w", err)
|
return nil, fmt.Errorf("invalid filter regex: %w", err)
|
||||||
@ -111,7 +111,7 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, veh
|
|||||||
healthCheck: hc,
|
healthCheck: hc,
|
||||||
}
|
}
|
||||||
|
|
||||||
fetcher := newFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, filterReg), proxiesOnUpdate(pd))
|
fetcher := newFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, filterReg, forceCertVerify), proxiesOnUpdate(pd))
|
||||||
pd.fetcher = fetcher
|
pd.fetcher = fetcher
|
||||||
|
|
||||||
wrapper := &ProxySetProvider{pd}
|
wrapper := &ProxySetProvider{pd}
|
||||||
@ -202,7 +202,7 @@ func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func proxiesParseAndFilter(filter string, filterReg *regexp.Regexp) parser[[]C.Proxy] {
|
func proxiesParseAndFilter(filter string, filterReg *regexp.Regexp, forceCertVerify bool) parser[[]C.Proxy] {
|
||||||
return func(buf []byte) ([]C.Proxy, error) {
|
return func(buf []byte) ([]C.Proxy, error) {
|
||||||
schema := &ProxySchema{}
|
schema := &ProxySchema{}
|
||||||
|
|
||||||
@ -219,7 +219,7 @@ func proxiesParseAndFilter(filter string, filterReg *regexp.Regexp) parser[[]C.P
|
|||||||
if name, ok := mapping["name"]; ok && len(filter) > 0 && !filterReg.MatchString(name.(string)) {
|
if name, ok := mapping["name"]; ok && len(filter) > 0 && !filterReg.MatchString(name.(string)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
proxy, err := adapter.ParseProxy(mapping)
|
proxy, err := adapter.ParseProxy(mapping, forceCertVerify)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("proxy %d error: %w", idx, err)
|
return nil, fmt.Errorf("proxy %d error: %w", idx, err)
|
||||||
}
|
}
|
||||||
|
@ -184,6 +184,7 @@ type RawConfig struct {
|
|||||||
Interface string `yaml:"interface-name"`
|
Interface string `yaml:"interface-name"`
|
||||||
RoutingMark int `yaml:"routing-mark"`
|
RoutingMark int `yaml:"routing-mark"`
|
||||||
Sniffing bool `yaml:"sniffing"`
|
Sniffing bool `yaml:"sniffing"`
|
||||||
|
ForceCertVerify bool `yaml:"force-cert-verify"`
|
||||||
|
|
||||||
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
|
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
|
||||||
Hosts map[string]string `yaml:"hosts"`
|
Hosts map[string]string `yaml:"hosts"`
|
||||||
@ -211,16 +212,17 @@ func Parse(buf []byte) (*Config, error) {
|
|||||||
func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||||
// config with default value
|
// config with default value
|
||||||
rawCfg := &RawConfig{
|
rawCfg := &RawConfig{
|
||||||
AllowLan: false,
|
AllowLan: false,
|
||||||
Sniffing: false,
|
Sniffing: false,
|
||||||
BindAddress: "*",
|
ForceCertVerify: false,
|
||||||
Mode: T.Rule,
|
BindAddress: "*",
|
||||||
Authentication: []string{},
|
Mode: T.Rule,
|
||||||
LogLevel: log.INFO,
|
Authentication: []string{},
|
||||||
Hosts: map[string]string{},
|
LogLevel: log.INFO,
|
||||||
Rule: []string{},
|
Hosts: map[string]string{},
|
||||||
Proxy: []map[string]any{},
|
Rule: []string{},
|
||||||
ProxyGroup: []map[string]any{},
|
Proxy: []map[string]any{},
|
||||||
|
ProxyGroup: []map[string]any{},
|
||||||
Tun: Tun{
|
Tun: Tun{
|
||||||
Enable: false,
|
Enable: false,
|
||||||
Device: "",
|
Device: "",
|
||||||
@ -388,6 +390,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
|||||||
proxiesConfig := cfg.Proxy
|
proxiesConfig := cfg.Proxy
|
||||||
groupsConfig := cfg.ProxyGroup
|
groupsConfig := cfg.ProxyGroup
|
||||||
providersConfig := cfg.ProxyProvider
|
providersConfig := cfg.ProxyProvider
|
||||||
|
forceCertVerify := cfg.ForceCertVerify
|
||||||
|
|
||||||
var proxyList []string
|
var proxyList []string
|
||||||
|
|
||||||
@ -397,7 +400,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
|||||||
|
|
||||||
// parse proxy
|
// parse proxy
|
||||||
for idx, mapping := range proxiesConfig {
|
for idx, mapping := range proxiesConfig {
|
||||||
proxy, err := adapter.ParseProxy(mapping)
|
proxy, err := adapter.ParseProxy(mapping, forceCertVerify)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("proxy %d: %w", idx, err)
|
return nil, nil, fmt.Errorf("proxy %d: %w", idx, err)
|
||||||
}
|
}
|
||||||
@ -429,7 +432,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
|||||||
return nil, nil, fmt.Errorf("can not defined a provider called `%s`", provider.ReservedName)
|
return nil, nil, fmt.Errorf("can not defined a provider called `%s`", provider.ReservedName)
|
||||||
}
|
}
|
||||||
|
|
||||||
pd, err := provider.ParseProxyProvider(name, mapping)
|
pd, err := provider.ParseProxyProvider(name, mapping, forceCertVerify)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("parse proxy provider %s error: %w", name, err)
|
return nil, nil, fmt.Errorf("parse proxy provider %s error: %w", name, err)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user