Skip to content

Commit d35ce59

Browse files
committed
Fix missing mTLS support in client options
1 parent 7c295ac commit d35ce59

File tree

5 files changed

+148
-8
lines changed

5 files changed

+148
-8
lines changed

common/tls/std_client.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,35 @@ func NewSTDClient(ctx context.Context, logger logger.ContextLogger, serverAddres
169169
}
170170
tlsConfig.RootCAs = certPool
171171
}
172+
var clientCertificate []byte
173+
if len(options.ClientCertificate) > 0 {
174+
clientCertificate = []byte(strings.Join(options.ClientCertificate, "\n"))
175+
} else if options.ClientCertificatePath != "" {
176+
content, err := os.ReadFile(options.ClientCertificatePath)
177+
if err != nil {
178+
return nil, E.Cause(err, "read client certificate")
179+
}
180+
clientCertificate = content
181+
}
182+
var clientKey []byte
183+
if len(options.ClientKey) > 0 {
184+
clientKey = []byte(strings.Join(options.ClientKey, "\n"))
185+
} else if options.ClientKeyPath != "" {
186+
content, err := os.ReadFile(options.ClientKeyPath)
187+
if err != nil {
188+
return nil, E.Cause(err, "read client key")
189+
}
190+
clientKey = content
191+
}
192+
if len(clientCertificate) > 0 && len(clientKey) > 0 {
193+
keyPair, err := tls.X509KeyPair(clientCertificate, clientKey)
194+
if err != nil {
195+
return nil, E.Cause(err, "parse client x509 key pair")
196+
}
197+
tlsConfig.Certificates = []tls.Certificate{keyPair}
198+
} else if len(clientCertificate) > 0 || len(clientKey) > 0 {
199+
return nil, E.New("client certificate and client key must be provided together")
200+
}
172201
var config Config = &STDClientConfig{ctx, &tlsConfig, options.Fragment, time.Duration(options.FragmentFallbackDelay), options.RecordFragment}
173202
if options.ECH != nil && options.ECH.Enabled {
174203
var err error

common/tls/utls_client.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,35 @@ func NewUTLSClient(ctx context.Context, logger logger.ContextLogger, serverAddre
222222
}
223223
tlsConfig.RootCAs = certPool
224224
}
225+
var clientCertificate []byte
226+
if len(options.ClientCertificate) > 0 {
227+
clientCertificate = []byte(strings.Join(options.ClientCertificate, "\n"))
228+
} else if options.ClientCertificatePath != "" {
229+
content, err := os.ReadFile(options.ClientCertificatePath)
230+
if err != nil {
231+
return nil, E.Cause(err, "read client certificate")
232+
}
233+
clientCertificate = content
234+
}
235+
var clientKey []byte
236+
if len(options.ClientKey) > 0 {
237+
clientKey = []byte(strings.Join(options.ClientKey, "\n"))
238+
} else if options.ClientKeyPath != "" {
239+
content, err := os.ReadFile(options.ClientKeyPath)
240+
if err != nil {
241+
return nil, E.Cause(err, "read client key")
242+
}
243+
clientKey = content
244+
}
245+
if len(clientCertificate) > 0 && len(clientKey) > 0 {
246+
keyPair, err := utls.X509KeyPair(clientCertificate, clientKey)
247+
if err != nil {
248+
return nil, E.Cause(err, "parse client x509 key pair")
249+
}
250+
tlsConfig.Certificates = []utls.Certificate{keyPair}
251+
} else if len(clientCertificate) > 0 || len(clientKey) > 0 {
252+
return nil, E.New("client certificate and client key must be provided together")
253+
}
225254
id, err := uTLSClientHelloID(options.UTLS.Fingerprint)
226255
if err != nil {
227256
return nil, err

docs/configuration/shared/tls.md

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ icon: material/new-box
44

55
!!! quote "Changes in sing-box 1.13.0"
66

7-
:material-plus: [kernel_tx](#kernel_tx)
8-
:material-plus: [kernel_rx](#kernel_rx)
9-
:material-plus: [curve_preferences](#curve_preferences)
10-
:material-plus: [certificate_public_key_sha256](#certificate_public_key_sha256)
11-
:material-plus: [client_authentication](#client_authentication)
12-
:material-plus: [client_certificate](#client_certificate)
13-
:material-plus: [client_certificate_path](#client_certificate_path)
7+
:material-plus: [kernel_tx](#kernel_tx)
8+
:material-plus: [kernel_rx](#kernel_rx)
9+
:material-plus: [curve_preferences](#curve_preferences)
10+
:material-plus: [certificate_public_key_sha256](#certificate_public_key_sha256)
11+
:material-plus: [client_certificate](#client_certificate)
12+
:material-plus: [client_certificate_path](#client_certificate_path)
13+
:material-plus: [client_key](#client_key)
14+
:material-plus: [client_key_path](#client_key_path)
15+
:material-plus: [client_authentication](#client_authentication)
1416
:material-plus: [client_certificate_public_key_sha256](#client_certificate_public_key_sha256)
1517

1618
!!! quote "Changes in sing-box 1.12.0"
@@ -101,9 +103,14 @@ icon: material/new-box
101103
"min_version": "",
102104
"max_version": "",
103105
"cipher_suites": [],
106+
"curve_preferences": [],
104107
"certificate": "",
105108
"certificate_path": "",
106109
"certificate_public_key_sha256": [],
110+
"client_certificate": [],
111+
"client_certificate_path": "",
112+
"client_key": [],
113+
"client_key_path": "",
107114
"fragment": false,
108115
"fragment_fallback_delay": "",
109116
"record_fragment": false,
@@ -258,6 +265,38 @@ openssl x509 -in certificate.pem -pubkey -noout | openssl pkey -pubin -outform d
258265
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
259266
```
260267

268+
#### client_certificate
269+
270+
!!! question "Since sing-box 1.13.0"
271+
272+
==Client only==
273+
274+
Client certificate chain line array, in PEM format.
275+
276+
#### client_certificate_path
277+
278+
!!! question "Since sing-box 1.13.0"
279+
280+
==Client only==
281+
282+
The path to client certificate chain, in PEM format.
283+
284+
#### client_key
285+
286+
!!! question "Since sing-box 1.13.0"
287+
288+
==Client only==
289+
290+
Client private key line array, in PEM format.
291+
292+
#### client_key_path
293+
294+
!!! question "Since sing-box 1.13.0"
295+
296+
==Client only==
297+
298+
The path to client private key, in PEM format.
299+
261300
#### key
262301

263302
==Server only==

docs/configuration/shared/tls.zh.md

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ icon: material/new-box
88
:material-plus: [kernel_rx](#kernel_rx)
99
:material-plus: [curve_preferences](#curve_preferences)
1010
:material-plus: [certificate_public_key_sha256](#certificate_public_key_sha256)
11-
:material-plus: [client_authentication](#client_authentication)
1211
:material-plus: [client_certificate](#client_certificate)
1312
:material-plus: [client_certificate_path](#client_certificate_path)
13+
:material-plus: [client_key](#client_key)
14+
:material-plus: [client_key_path](#client_key_path)
15+
:material-plus: [client_authentication](#client_authentication)
1416
:material-plus: [client_certificate_public_key_sha256](#client_certificate_public_key_sha256)
1517

1618
!!! quote "sing-box 1.12.0 中的更改"
@@ -101,9 +103,14 @@ icon: material/new-box
101103
"min_version": "",
102104
"max_version": "",
103105
"cipher_suites": [],
106+
"curve_preferences": [],
104107
"certificate": "",
105108
"certificate_path": "",
106109
"certificate_public_key_sha256": [],
110+
"client_certificate": [],
111+
"client_certificate_path": "",
112+
"client_key": [],
113+
"client_key_path": "",
107114
"fragment": false,
108115
"fragment_fallback_delay": "",
109116
"record_fragment": false,
@@ -253,6 +260,38 @@ openssl x509 -in certificate.pem -pubkey -noout | openssl pkey -pubin -outform d
253260
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
254261
```
255262

263+
#### client_certificate
264+
265+
!!! question "自 sing-box 1.13.0 起"
266+
267+
==仅客户端==
268+
269+
客户端证书链行数组,PEM 格式。
270+
271+
#### client_certificate_path
272+
273+
!!! question "自 sing-box 1.13.0 起"
274+
275+
==仅客户端==
276+
277+
客户端证书链路径,PEM 格式。
278+
279+
#### client_key
280+
281+
!!! question "自 sing-box 1.13.0 起"
282+
283+
==仅客户端==
284+
285+
客户端私钥行数组,PEM 格式。
286+
287+
#### client_key_path
288+
289+
!!! question "自 sing-box 1.13.0 起"
290+
291+
==仅客户端==
292+
293+
客户端私钥路径,PEM 格式。
294+
256295
#### key
257296

258297
==仅服务器==

option/tls.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ type OutboundTLSOptions struct {
107107
Certificate badoption.Listable[string] `json:"certificate,omitempty"`
108108
CertificatePath string `json:"certificate_path,omitempty"`
109109
CertificatePublicKeySHA256 badoption.Listable[[]byte] `json:"certificate_public_key_sha256,omitempty"`
110+
ClientCertificate badoption.Listable[string] `json:"client_certificate,omitempty"`
111+
ClientCertificatePath string `json:"client_certificate_path,omitempty"`
112+
ClientKey badoption.Listable[string] `json:"client_key,omitempty"`
113+
ClientKeyPath string `json:"client_key_path,omitempty"`
110114
Fragment bool `json:"fragment,omitempty"`
111115
FragmentFallbackDelay badoption.Duration `json:"fragment_fallback_delay,omitempty"`
112116
RecordFragment bool `json:"record_fragment,omitempty"`

0 commit comments

Comments
 (0)