Fix missing mTLS support in client options

This commit is contained in:
世界
2025-10-09 23:10:34 +08:00
parent 79bbce3db3
commit 3dc285be8c
5 changed files with 148 additions and 8 deletions

View File

@@ -169,6 +169,35 @@ func NewSTDClient(ctx context.Context, logger logger.ContextLogger, serverAddres
}
tlsConfig.RootCAs = certPool
}
var clientCertificate []byte
if len(options.ClientCertificate) > 0 {
clientCertificate = []byte(strings.Join(options.ClientCertificate, "\n"))
} else if options.ClientCertificatePath != "" {
content, err := os.ReadFile(options.ClientCertificatePath)
if err != nil {
return nil, E.Cause(err, "read client certificate")
}
clientCertificate = content
}
var clientKey []byte
if len(options.ClientKey) > 0 {
clientKey = []byte(strings.Join(options.ClientKey, "\n"))
} else if options.ClientKeyPath != "" {
content, err := os.ReadFile(options.ClientKeyPath)
if err != nil {
return nil, E.Cause(err, "read client key")
}
clientKey = content
}
if len(clientCertificate) > 0 && len(clientKey) > 0 {
keyPair, err := tls.X509KeyPair(clientCertificate, clientKey)
if err != nil {
return nil, E.Cause(err, "parse client x509 key pair")
}
tlsConfig.Certificates = []tls.Certificate{keyPair}
} else if len(clientCertificate) > 0 || len(clientKey) > 0 {
return nil, E.New("client certificate and client key must be provided together")
}
var config Config = &STDClientConfig{ctx, &tlsConfig, options.Fragment, time.Duration(options.FragmentFallbackDelay), options.RecordFragment}
if options.ECH != nil && options.ECH.Enabled {
var err error

View File

@@ -222,6 +222,35 @@ func NewUTLSClient(ctx context.Context, logger logger.ContextLogger, serverAddre
}
tlsConfig.RootCAs = certPool
}
var clientCertificate []byte
if len(options.ClientCertificate) > 0 {
clientCertificate = []byte(strings.Join(options.ClientCertificate, "\n"))
} else if options.ClientCertificatePath != "" {
content, err := os.ReadFile(options.ClientCertificatePath)
if err != nil {
return nil, E.Cause(err, "read client certificate")
}
clientCertificate = content
}
var clientKey []byte
if len(options.ClientKey) > 0 {
clientKey = []byte(strings.Join(options.ClientKey, "\n"))
} else if options.ClientKeyPath != "" {
content, err := os.ReadFile(options.ClientKeyPath)
if err != nil {
return nil, E.Cause(err, "read client key")
}
clientKey = content
}
if len(clientCertificate) > 0 && len(clientKey) > 0 {
keyPair, err := utls.X509KeyPair(clientCertificate, clientKey)
if err != nil {
return nil, E.Cause(err, "parse client x509 key pair")
}
tlsConfig.Certificates = []utls.Certificate{keyPair}
} else if len(clientCertificate) > 0 || len(clientKey) > 0 {
return nil, E.New("client certificate and client key must be provided together")
}
id, err := uTLSClientHelloID(options.UTLS.Fingerprint)
if err != nil {
return nil, err

View File

@@ -4,13 +4,15 @@ icon: material/new-box
!!! quote "Changes in sing-box 1.13.0"
:material-plus: [kernel_tx](#kernel_tx)
:material-plus: [kernel_rx](#kernel_rx)
:material-plus: [curve_preferences](#curve_preferences)
:material-plus: [certificate_public_key_sha256](#certificate_public_key_sha256)
:material-plus: [client_authentication](#client_authentication)
:material-plus: [client_certificate](#client_certificate)
:material-plus: [client_certificate_path](#client_certificate_path)
:material-plus: [kernel_tx](#kernel_tx)
:material-plus: [kernel_rx](#kernel_rx)
:material-plus: [curve_preferences](#curve_preferences)
:material-plus: [certificate_public_key_sha256](#certificate_public_key_sha256)
:material-plus: [client_certificate](#client_certificate)
:material-plus: [client_certificate_path](#client_certificate_path)
:material-plus: [client_key](#client_key)
:material-plus: [client_key_path](#client_key_path)
:material-plus: [client_authentication](#client_authentication)
:material-plus: [client_certificate_public_key_sha256](#client_certificate_public_key_sha256)
!!! quote "Changes in sing-box 1.12.0"
@@ -101,9 +103,14 @@ icon: material/new-box
"min_version": "",
"max_version": "",
"cipher_suites": [],
"curve_preferences": [],
"certificate": "",
"certificate_path": "",
"certificate_public_key_sha256": [],
"client_certificate": [],
"client_certificate_path": "",
"client_key": [],
"client_key_path": "",
"fragment": false,
"fragment_fallback_delay": "",
"record_fragment": false,
@@ -258,6 +265,38 @@ openssl x509 -in certificate.pem -pubkey -noout | openssl pkey -pubin -outform d
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
```
#### client_certificate
!!! question "Since sing-box 1.13.0"
==Client only==
Client certificate chain line array, in PEM format.
#### client_certificate_path
!!! question "Since sing-box 1.13.0"
==Client only==
The path to client certificate chain, in PEM format.
#### client_key
!!! question "Since sing-box 1.13.0"
==Client only==
Client private key line array, in PEM format.
#### client_key_path
!!! question "Since sing-box 1.13.0"
==Client only==
The path to client private key, in PEM format.
#### key
==Server only==

View File

@@ -8,9 +8,11 @@ icon: material/new-box
:material-plus: [kernel_rx](#kernel_rx)
:material-plus: [curve_preferences](#curve_preferences)
:material-plus: [certificate_public_key_sha256](#certificate_public_key_sha256)
:material-plus: [client_authentication](#client_authentication)
:material-plus: [client_certificate](#client_certificate)
:material-plus: [client_certificate_path](#client_certificate_path)
:material-plus: [client_key](#client_key)
:material-plus: [client_key_path](#client_key_path)
:material-plus: [client_authentication](#client_authentication)
:material-plus: [client_certificate_public_key_sha256](#client_certificate_public_key_sha256)
!!! quote "sing-box 1.12.0 中的更改"
@@ -101,9 +103,14 @@ icon: material/new-box
"min_version": "",
"max_version": "",
"cipher_suites": [],
"curve_preferences": [],
"certificate": "",
"certificate_path": "",
"certificate_public_key_sha256": [],
"client_certificate": [],
"client_certificate_path": "",
"client_key": [],
"client_key_path": "",
"fragment": false,
"fragment_fallback_delay": "",
"record_fragment": false,
@@ -253,6 +260,38 @@ openssl x509 -in certificate.pem -pubkey -noout | openssl pkey -pubin -outform d
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
```
#### client_certificate
!!! question "自 sing-box 1.13.0 起"
==仅客户端==
客户端证书链行数组PEM 格式。
#### client_certificate_path
!!! question "自 sing-box 1.13.0 起"
==仅客户端==
客户端证书链路径PEM 格式。
#### client_key
!!! question "自 sing-box 1.13.0 起"
==仅客户端==
客户端私钥行数组PEM 格式。
#### client_key_path
!!! question "自 sing-box 1.13.0 起"
==仅客户端==
客户端私钥路径PEM 格式。
#### key
==仅服务器==

View File

@@ -107,6 +107,10 @@ type OutboundTLSOptions struct {
Certificate badoption.Listable[string] `json:"certificate,omitempty"`
CertificatePath string `json:"certificate_path,omitempty"`
CertificatePublicKeySHA256 badoption.Listable[[]byte] `json:"certificate_public_key_sha256,omitempty"`
ClientCertificate badoption.Listable[string] `json:"client_certificate,omitempty"`
ClientCertificatePath string `json:"client_certificate_path,omitempty"`
ClientKey badoption.Listable[string] `json:"client_key,omitempty"`
ClientKeyPath string `json:"client_key_path,omitempty"`
Fragment bool `json:"fragment,omitempty"`
FragmentFallbackDelay badoption.Duration `json:"fragment_fallback_delay,omitempty"`
RecordFragment bool `json:"record_fragment,omitempty"`