Ver Fonte

Fix missing mTLS support in client options

世界 há 1 semana atrás
pai
commit
d35ce5961f

+ 29 - 0
common/tls/std_client.go

@@ -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

+ 29 - 0
common/tls/utls_client.go

@@ -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

+ 46 - 7
docs/configuration/shared/tls.md

@@ -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==

+ 40 - 1
docs/configuration/shared/tls.zh.md

@@ -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
 
 ==仅服务器==

+ 4 - 0
option/tls.go

@@ -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"`