mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-11 17:47:20 +10:00
Add curve preferences, pinned public key SHA256 and mTLS for TLS options
This commit is contained in:
@@ -68,7 +68,10 @@ func NewRealityServer(ctx context.Context, logger log.ContextLogger, options opt
|
||||
return nil, E.New("unknown cipher_suite: ", cipherSuite)
|
||||
}
|
||||
}
|
||||
if len(options.Certificate) > 0 || options.CertificatePath != "" {
|
||||
if len(options.CurvePreferences) > 0 {
|
||||
return nil, E.New("curve preferences is unavailable in reality")
|
||||
}
|
||||
if len(options.Certificate) > 0 || options.CertificatePath != "" || len(options.ClientCertificatePublicKeySHA256) > 0 {
|
||||
return nil, E.New("certificate is unavailable in reality")
|
||||
}
|
||||
if len(options.Key) > 0 || options.KeyPath != "" {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package tls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -108,6 +111,15 @@ func NewSTDClient(ctx context.Context, logger logger.ContextLogger, serverAddres
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(options.CertificatePublicKeySHA256) > 0 {
|
||||
if len(options.Certificate) > 0 || options.CertificatePath != "" {
|
||||
return nil, E.New("certificate_public_key_sha256 is conflict with certificate or certificate_path")
|
||||
}
|
||||
tlsConfig.InsecureSkipVerify = true
|
||||
tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||
return verifyPublicKeySHA256(options.CertificatePublicKeySHA256, rawCerts, tlsConfig.Time)
|
||||
}
|
||||
}
|
||||
if len(options.ALPN) > 0 {
|
||||
tlsConfig.NextProtos = options.ALPN
|
||||
}
|
||||
@@ -137,6 +149,9 @@ func NewSTDClient(ctx context.Context, logger logger.ContextLogger, serverAddres
|
||||
return nil, E.New("unknown cipher_suite: ", cipherSuite)
|
||||
}
|
||||
}
|
||||
for _, curve := range options.CurvePreferences {
|
||||
tlsConfig.CurvePreferences = append(tlsConfig.CurvePreferences, tls.CurveID(curve))
|
||||
}
|
||||
var certificate []byte
|
||||
if len(options.Certificate) > 0 {
|
||||
certificate = []byte(strings.Join(options.Certificate, "\n"))
|
||||
@@ -175,3 +190,22 @@ func NewSTDClient(ctx context.Context, logger logger.ContextLogger, serverAddres
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func verifyPublicKeySHA256(knownHashValues [][]byte, rawCerts [][]byte, timeFunc func() time.Time) error {
|
||||
leafCertificate, err := x509.ParseCertificate(rawCerts[0])
|
||||
if err != nil {
|
||||
return E.Cause(err, "failed to parse leaf certificate")
|
||||
}
|
||||
|
||||
pubKeyBytes, err := x509.MarshalPKIXPublicKey(leafCertificate.PublicKey)
|
||||
if err != nil {
|
||||
return E.Cause(err, "failed to marshal public key")
|
||||
}
|
||||
hashValue := sha256.Sum256(pubKeyBytes)
|
||||
for _, value := range knownHashValues {
|
||||
if bytes.Equal(value, hashValue[:]) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return E.New("unrecognized remote public key: ", base64.StdEncoding.EncodeToString(hashValue[:]))
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package tls
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -22,16 +23,17 @@ import (
|
||||
var errInsecureUnused = E.New("tls: insecure unused")
|
||||
|
||||
type STDServerConfig struct {
|
||||
access sync.RWMutex
|
||||
config *tls.Config
|
||||
logger log.Logger
|
||||
acmeService adapter.SimpleLifecycle
|
||||
certificate []byte
|
||||
key []byte
|
||||
certificatePath string
|
||||
keyPath string
|
||||
echKeyPath string
|
||||
watcher *fswatch.Watcher
|
||||
access sync.RWMutex
|
||||
config *tls.Config
|
||||
logger log.Logger
|
||||
acmeService adapter.SimpleLifecycle
|
||||
certificate []byte
|
||||
key []byte
|
||||
certificatePath string
|
||||
keyPath string
|
||||
clientCertificatePath []string
|
||||
echKeyPath string
|
||||
watcher *fswatch.Watcher
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) ServerName() string {
|
||||
@@ -111,6 +113,9 @@ func (c *STDServerConfig) startWatcher() error {
|
||||
if c.echKeyPath != "" {
|
||||
watchPath = append(watchPath, c.echKeyPath)
|
||||
}
|
||||
if len(c.clientCertificatePath) > 0 {
|
||||
watchPath = append(watchPath, c.clientCertificatePath...)
|
||||
}
|
||||
if len(watchPath) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -159,6 +164,30 @@ func (c *STDServerConfig) certificateUpdated(path string) error {
|
||||
c.config = config
|
||||
c.access.Unlock()
|
||||
c.logger.Info("reloaded TLS certificate")
|
||||
} else if common.Contains(c.clientCertificatePath, path) {
|
||||
clientCertificateCA := x509.NewCertPool()
|
||||
var reloaded bool
|
||||
for _, certPath := range c.clientCertificatePath {
|
||||
content, err := os.ReadFile(certPath)
|
||||
if err != nil {
|
||||
c.logger.Error(E.Cause(err, "reload certificate from ", c.clientCertificatePath))
|
||||
continue
|
||||
}
|
||||
if !clientCertificateCA.AppendCertsFromPEM(content) {
|
||||
c.logger.Error(E.New("invalid client certificate file: ", certPath))
|
||||
continue
|
||||
}
|
||||
reloaded = true
|
||||
}
|
||||
if !reloaded {
|
||||
return E.New("client certificates is empty")
|
||||
}
|
||||
c.access.Lock()
|
||||
config := c.config.Clone()
|
||||
config.ClientCAs = clientCertificateCA
|
||||
c.config = config
|
||||
c.access.Unlock()
|
||||
c.logger.Info("reloaded client certificates")
|
||||
} else if path == c.echKeyPath {
|
||||
echKey, err := os.ReadFile(c.echKeyPath)
|
||||
if err != nil {
|
||||
@@ -235,8 +264,14 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.
|
||||
return nil, E.New("unknown cipher_suite: ", cipherSuite)
|
||||
}
|
||||
}
|
||||
var certificate []byte
|
||||
var key []byte
|
||||
for _, curveID := range options.CurvePreferences {
|
||||
tlsConfig.CurvePreferences = append(tlsConfig.CurvePreferences, tls.CurveID(curveID))
|
||||
}
|
||||
tlsConfig.ClientAuth = tls.ClientAuthType(options.ClientAuthentication)
|
||||
var (
|
||||
certificate []byte
|
||||
key []byte
|
||||
)
|
||||
if acmeService == nil {
|
||||
if len(options.Certificate) > 0 {
|
||||
certificate = []byte(strings.Join(options.Certificate, "\n"))
|
||||
@@ -278,6 +313,43 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.
|
||||
tlsConfig.Certificates = []tls.Certificate{keyPair}
|
||||
}
|
||||
}
|
||||
if len(options.ClientCertificate) > 0 || len(options.ClientCertificatePath) > 0 {
|
||||
if tlsConfig.ClientAuth == tls.NoClientCert {
|
||||
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
}
|
||||
}
|
||||
if tlsConfig.ClientAuth == tls.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tls.RequireAndVerifyClientCert {
|
||||
if len(options.ClientCertificate) > 0 {
|
||||
clientCertificateCA := x509.NewCertPool()
|
||||
if !clientCertificateCA.AppendCertsFromPEM([]byte(strings.Join(options.ClientCertificate, "\n"))) {
|
||||
return nil, E.New("invalid client certificate strings")
|
||||
}
|
||||
tlsConfig.ClientCAs = clientCertificateCA
|
||||
} else if len(options.ClientCertificatePath) > 0 {
|
||||
clientCertificateCA := x509.NewCertPool()
|
||||
for _, path := range options.ClientCertificatePath {
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "read client certificate from ", path)
|
||||
}
|
||||
if !clientCertificateCA.AppendCertsFromPEM(content) {
|
||||
return nil, E.New("invalid client certificate file: ", path)
|
||||
}
|
||||
}
|
||||
tlsConfig.ClientCAs = clientCertificateCA
|
||||
} else if len(options.ClientCertificatePublicKeySHA256) > 0 {
|
||||
if tlsConfig.ClientAuth == tls.RequireAndVerifyClientCert {
|
||||
tlsConfig.ClientAuth = tls.RequireAnyClientCert
|
||||
} else if tlsConfig.ClientAuth == tls.VerifyClientCertIfGiven {
|
||||
tlsConfig.ClientAuth = tls.RequestClientCert
|
||||
}
|
||||
tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||
return verifyPublicKeySHA256(options.ClientCertificatePublicKeySHA256, rawCerts, tlsConfig.Time)
|
||||
}
|
||||
} else {
|
||||
return nil, E.New("missing client_certificate, client_certificate_path or client_certificate_public_key_sha256 for client authentication")
|
||||
}
|
||||
}
|
||||
var echKeyPath string
|
||||
if options.ECH != nil && options.ECH.Enabled {
|
||||
err = parseECHServerConfig(ctx, options, tlsConfig, &echKeyPath)
|
||||
@@ -286,14 +358,15 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.
|
||||
}
|
||||
}
|
||||
serverConfig := &STDServerConfig{
|
||||
config: tlsConfig,
|
||||
logger: logger,
|
||||
acmeService: acmeService,
|
||||
certificate: certificate,
|
||||
key: key,
|
||||
certificatePath: options.CertificatePath,
|
||||
keyPath: options.KeyPath,
|
||||
echKeyPath: echKeyPath,
|
||||
config: tlsConfig,
|
||||
logger: logger,
|
||||
acmeService: acmeService,
|
||||
certificate: certificate,
|
||||
key: key,
|
||||
certificatePath: options.CertificatePath,
|
||||
clientCertificatePath: options.ClientCertificatePath,
|
||||
keyPath: options.KeyPath,
|
||||
echKeyPath: echKeyPath,
|
||||
}
|
||||
serverConfig.config.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) {
|
||||
serverConfig.access.Lock()
|
||||
|
||||
@@ -167,6 +167,15 @@ func NewUTLSClient(ctx context.Context, logger logger.ContextLogger, serverAddre
|
||||
}
|
||||
tlsConfig.InsecureServerNameToVerify = serverName
|
||||
}
|
||||
if len(options.CertificatePublicKeySHA256) > 0 {
|
||||
if len(options.Certificate) > 0 || options.CertificatePath != "" {
|
||||
return nil, E.New("certificate_public_key_sha256 is conflict with certificate or certificate_path")
|
||||
}
|
||||
tlsConfig.InsecureSkipVerify = true
|
||||
tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||
return verifyPublicKeySHA256(options.CertificatePublicKeySHA256, rawCerts, tlsConfig.Time)
|
||||
}
|
||||
}
|
||||
if len(options.ALPN) > 0 {
|
||||
tlsConfig.NextProtos = options.ALPN
|
||||
}
|
||||
|
||||
@@ -5,7 +5,13 @@ 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: [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_certificate_public_key_sha256](#client_certificate_public_key_sha256)
|
||||
|
||||
!!! quote "Changes in sing-box 1.12.0"
|
||||
|
||||
@@ -29,8 +35,13 @@ icon: material/new-box
|
||||
"min_version": "",
|
||||
"max_version": "",
|
||||
"cipher_suites": [],
|
||||
"curve_preferences": [],
|
||||
"certificate": [],
|
||||
"certificate_path": "",
|
||||
"client_authentication": "",
|
||||
"client_certificate": [],
|
||||
"client_certificate_path": [],
|
||||
"client_certificate_public_key_sha256": [],
|
||||
"key": [],
|
||||
"key_path": "",
|
||||
"kernel_tx": false,
|
||||
@@ -92,6 +103,7 @@ icon: material/new-box
|
||||
"cipher_suites": [],
|
||||
"certificate": "",
|
||||
"certificate_path": "",
|
||||
"certificate_public_key_sha256": [],
|
||||
"fragment": false,
|
||||
"fragment_fallback_delay": "",
|
||||
"record_fragment": false,
|
||||
@@ -195,14 +207,29 @@ By default, the maximum version is currently TLS 1.3.
|
||||
|
||||
#### cipher_suites
|
||||
|
||||
A list of enabled TLS 1.0–1.2 cipher suites. The order of the list is ignored.
|
||||
List of enabled TLS 1.0–1.2 cipher suites. The order of the list is ignored.
|
||||
Note that TLS 1.3 cipher suites are not configurable.
|
||||
|
||||
If empty, a safe default list is used. The default cipher suites might change over time.
|
||||
|
||||
#### curve_preferences
|
||||
|
||||
!!! question "Since sing-box 1.13.0"
|
||||
|
||||
Set of supported key exchange mechanisms. The order of the list is ignored, and key exchange mechanisms are chosen
|
||||
from this list using an internal preference order by Golang.
|
||||
|
||||
Available values, also the default list:
|
||||
|
||||
* `P256`
|
||||
* `P384`
|
||||
* `P521`
|
||||
* `X25519`
|
||||
* `X25519MLKEM768`
|
||||
|
||||
#### certificate
|
||||
|
||||
The server certificate line array, in PEM format.
|
||||
Server certificates chain line array, in PEM format.
|
||||
|
||||
#### certificate_path
|
||||
|
||||
@@ -210,7 +237,26 @@ The server certificate line array, in PEM format.
|
||||
|
||||
Will be automatically reloaded if file modified.
|
||||
|
||||
The path to the server certificate, in PEM format.
|
||||
The path to server certificate chain, in PEM format.
|
||||
|
||||
|
||||
#### certificate_public_key_sha256
|
||||
|
||||
!!! question "Since sing-box 1.13.0"
|
||||
|
||||
==Client only==
|
||||
|
||||
List of SHA-256 hashes of server certificate public keys, in base64 format.
|
||||
|
||||
To generate the SHA-256 hash for a certificate's public key, use the following commands:
|
||||
|
||||
```bash
|
||||
# For a certificate file
|
||||
openssl x509 -in certificate.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
|
||||
|
||||
# For a certificate from a remote server
|
||||
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
|
||||
```
|
||||
|
||||
#### key
|
||||
|
||||
@@ -228,6 +274,63 @@ The server private key line array, in PEM format.
|
||||
|
||||
The path to the server private key, in PEM format.
|
||||
|
||||
#### client_authentication
|
||||
|
||||
!!! question "Since sing-box 1.13.0"
|
||||
|
||||
==Server only==
|
||||
|
||||
The type of client authentication to use.
|
||||
|
||||
Available values:
|
||||
|
||||
* `no` (default)
|
||||
* `request`
|
||||
* `require-any`
|
||||
* `verify-if-given`
|
||||
* `require-and-verify`
|
||||
|
||||
One of `client_certificate`, `client_certificate_path`, or `client_certificate_public_key_sha256` is required
|
||||
if this option is set to `verify-if-given`, or `require-and-verify`.
|
||||
|
||||
#### client_certificate
|
||||
|
||||
!!! question "Since sing-box 1.13.0"
|
||||
|
||||
==Server only==
|
||||
|
||||
Client certificate chain line array, in PEM format.
|
||||
|
||||
#### client_certificate_path
|
||||
|
||||
!!! question "Since sing-box 1.13.0"
|
||||
|
||||
==Server only==
|
||||
|
||||
!!! note ""
|
||||
|
||||
Will be automatically reloaded if file modified.
|
||||
|
||||
List of path to client certificate chain, in PEM format.
|
||||
|
||||
#### client_certificate_public_key_sha256
|
||||
|
||||
!!! question "Since sing-box 1.13.0"
|
||||
|
||||
==Server only==
|
||||
|
||||
List of SHA-256 hashes of client certificate public keys, in base64 format.
|
||||
|
||||
To generate the SHA-256 hash for a certificate's public key, use the following commands:
|
||||
|
||||
```bash
|
||||
# For a certificate file
|
||||
openssl x509 -in certificate.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
|
||||
|
||||
# For a certificate from a remote server
|
||||
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
|
||||
```
|
||||
|
||||
#### kernel_tx
|
||||
|
||||
!!! question "Since sing-box 1.13.0"
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.13.0 中的更改"
|
||||
|
||||
:material-plus: [kernel_tx](#kernel_tx)
|
||||
: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: [client_certificate_public_key_sha256](#client_certificate_public_key_sha256)
|
||||
|
||||
!!! quote "sing-box 1.12.0 中的更改"
|
||||
|
||||
:material-plus: [tls_fragment](#tls_fragment)
|
||||
:material-plus: [tls_fragment_fallback_delay](#tls_fragment_fallback_delay)
|
||||
:material-plus: [tls_record_fragment](#tls_record_fragment)
|
||||
:material-delete-clock: [ech.pq_signature_schemes_enabled](#pq_signature_schemes_enabled)
|
||||
:material-plus: [fragment](#fragment)
|
||||
:material-plus: [fragment_fallback_delay](#fragment_fallback_delay)
|
||||
:material-plus: [record_fragment](#record_fragment)
|
||||
:material-delete-clock: [ech.pq_signature_schemes_enabled](#pq_signature_schemes_enabled)
|
||||
:material-delete-clock: [ech.dynamic_record_sizing_disabled](#dynamic_record_sizing_disabled)
|
||||
|
||||
!!! quote "sing-box 1.10.0 中的更改"
|
||||
@@ -29,8 +35,13 @@ icon: material/alert-decagram
|
||||
"min_version": "",
|
||||
"max_version": "",
|
||||
"cipher_suites": [],
|
||||
"curve_preferences": [],
|
||||
"certificate": [],
|
||||
"certificate_path": "",
|
||||
"client_authentication": "",
|
||||
"client_certificate": [],
|
||||
"client_certificate_path": [],
|
||||
"client_certificate_public_key_sha256": [],
|
||||
"key": [],
|
||||
"key_path": "",
|
||||
"kernel_tx": false,
|
||||
@@ -90,17 +101,20 @@ icon: material/alert-decagram
|
||||
"min_version": "",
|
||||
"max_version": "",
|
||||
"cipher_suites": [],
|
||||
"certificate": [],
|
||||
"certificate": "",
|
||||
"certificate_path": "",
|
||||
"certificate_public_key_sha256": [],
|
||||
"fragment": false,
|
||||
"fragment_fallback_delay": "",
|
||||
"record_fragment": false,
|
||||
"ech": {
|
||||
"enabled": false,
|
||||
"pq_signature_schemes_enabled": false,
|
||||
"dynamic_record_sizing_disabled": false,
|
||||
"config": [],
|
||||
"config_path": ""
|
||||
"config_path": "",
|
||||
|
||||
// 废弃的
|
||||
"pq_signature_schemes_enabled": false,
|
||||
"dynamic_record_sizing_disabled": false
|
||||
},
|
||||
"utls": {
|
||||
"enabled": false,
|
||||
@@ -191,13 +205,27 @@ TLS 版本值:
|
||||
|
||||
#### cipher_suites
|
||||
|
||||
启用的 TLS 1.0-1.2密码套件的列表。列表的顺序被忽略。请注意,TLS 1.3 的密码套件是不可配置的。
|
||||
启用的 TLS 1.0–1.2 密码套件列表。列表的顺序被忽略。请注意,TLS 1.3 的密码套件是不可配置的。
|
||||
|
||||
如果为空,则使用安全的默认列表。默认密码套件可能会随着时间的推移而改变。
|
||||
|
||||
#### curve_preferences
|
||||
|
||||
!!! question "自 sing-box 1.13.0 起"
|
||||
|
||||
支持的密钥交换机制集合。列表的顺序被忽略,密钥交换机制通过 Golang 的内部偏好顺序从此列表中选择。
|
||||
|
||||
可用值,同时也是默认列表:
|
||||
|
||||
* `P256`
|
||||
* `P384`
|
||||
* `P521`
|
||||
* `X25519`
|
||||
* `X25519MLKEM768`
|
||||
|
||||
#### certificate
|
||||
|
||||
服务器 PEM 证书行数组。
|
||||
服务器证书链行数组,PEM 格式。
|
||||
|
||||
#### certificate_path
|
||||
|
||||
@@ -205,7 +233,25 @@ TLS 版本值:
|
||||
|
||||
文件更改时将自动重新加载。
|
||||
|
||||
服务器 PEM 证书路径。
|
||||
服务器证书链路径,PEM 格式。
|
||||
|
||||
#### certificate_public_key_sha256
|
||||
|
||||
!!! question "自 sing-box 1.13.0 起"
|
||||
|
||||
==仅客户端==
|
||||
|
||||
服务器证书公钥的 SHA-256 哈希列表,base64 格式。
|
||||
|
||||
要生成证书公钥的 SHA-256 哈希,请使用以下命令:
|
||||
|
||||
```bash
|
||||
# 对于证书文件
|
||||
openssl x509 -in certificate.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
|
||||
|
||||
# 对于远程服务器的证书
|
||||
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
|
||||
```
|
||||
|
||||
#### key
|
||||
|
||||
@@ -221,7 +267,68 @@ TLS 版本值:
|
||||
|
||||
==仅服务器==
|
||||
|
||||
服务器 PEM 私钥路径。
|
||||
!!! note ""
|
||||
|
||||
文件更改时将自动重新加载。
|
||||
|
||||
服务器私钥路径,PEM 格式。
|
||||
|
||||
#### client_authentication
|
||||
|
||||
!!! question "自 sing-box 1.13.0 起"
|
||||
|
||||
==仅服务器==
|
||||
|
||||
要使用的客户端身份验证类型。
|
||||
|
||||
可用值:
|
||||
|
||||
* `no`(默认)
|
||||
* `request`
|
||||
* `require-any`
|
||||
* `verify-if-given`
|
||||
* `require-and-verify`
|
||||
|
||||
如果此选项设置为 `verify-if-given` 或 `require-and-verify`,
|
||||
则需要 `client_certificate`、`client_certificate_path` 或 `client_certificate_public_key_sha256` 中的一个。
|
||||
|
||||
#### client_certificate
|
||||
|
||||
!!! question "自 sing-box 1.13.0 起"
|
||||
|
||||
==仅服务器==
|
||||
|
||||
客户端证书链行数组,PEM 格式。
|
||||
|
||||
#### client_certificate_path
|
||||
|
||||
!!! question "自 sing-box 1.13.0 起"
|
||||
|
||||
==仅服务器==
|
||||
|
||||
!!! note ""
|
||||
|
||||
文件更改时将自动重新加载。
|
||||
|
||||
客户端证书链路径列表,PEM 格式。
|
||||
|
||||
#### client_certificate_public_key_sha256
|
||||
|
||||
!!! question "自 sing-box 1.13.0 起"
|
||||
|
||||
==仅服务器==
|
||||
|
||||
客户端证书公钥的 SHA-256 哈希列表,base64 格式。
|
||||
|
||||
要生成证书公钥的 SHA-256 哈希,请使用以下命令:
|
||||
|
||||
```bash
|
||||
# 对于证书文件
|
||||
openssl x509 -in certificate.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
|
||||
|
||||
# 对于远程服务器的证书
|
||||
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
|
||||
```
|
||||
|
||||
#### kernel_tx
|
||||
|
||||
@@ -307,44 +414,11 @@ uTLS 是 "crypto/tls" 的一个分支,它提供了 ClientHello 指纹识别阻
|
||||
|
||||
默认使用 chrome 指纹。
|
||||
|
||||
## ECH 字段
|
||||
### ECH 字段
|
||||
|
||||
ECH (Encrypted Client Hello) 是一个 TLS 扩展,它允许客户端加密其 ClientHello 的第一部分
|
||||
信息。
|
||||
ECH (Encrypted Client Hello) 是一个 TLS 扩展,它允许客户端加密其 ClientHello 的第一部分信息。
|
||||
|
||||
ECH 配置和密钥可以通过 `sing-box generate ech-keypair [--pq-signature-schemes-enabled]` 生成。
|
||||
|
||||
#### key
|
||||
|
||||
==仅服务器==
|
||||
|
||||
ECH PEM 密钥行数组
|
||||
|
||||
#### key_path
|
||||
|
||||
==仅服务器==
|
||||
|
||||
!!! note ""
|
||||
|
||||
文件更改时将自动重新加载。
|
||||
|
||||
ECH PEM 密钥路径
|
||||
|
||||
#### config
|
||||
|
||||
==仅客户端==
|
||||
|
||||
ECH PEM 配置行数组
|
||||
|
||||
如果为空,将尝试从 DNS 加载。
|
||||
|
||||
#### config_path
|
||||
|
||||
==仅客户端==
|
||||
|
||||
ECH PEM 配置路径
|
||||
|
||||
如果为空,将尝试从 DNS 加载。
|
||||
ECH 密钥和配置可以通过 `sing-box generate ech-keypair` 生成。
|
||||
|
||||
#### pq_signature_schemes_enabled
|
||||
|
||||
@@ -354,8 +428,6 @@ ECH PEM 配置路径
|
||||
|
||||
启用对后量子对等证书签名方案的支持。
|
||||
|
||||
建议匹配 `sing-box generate ech-keypair` 的参数。
|
||||
|
||||
#### dynamic_record_sizing_disabled
|
||||
|
||||
!!! failure "已在 sing-box 1.12.0 废弃"
|
||||
@@ -364,57 +436,91 @@ ECH PEM 配置路径
|
||||
|
||||
禁用 TLS 记录的自适应大小调整。
|
||||
|
||||
如果为 true,则始终使用最大可能的 TLS 记录大小。
|
||||
如果为 false,则可能会调整 TLS 记录的大小以尝试改善延迟。
|
||||
当为 true 时,总是使用最大可能的 TLS 记录大小。
|
||||
当为 false 时,可能会调整 TLS 记录的大小以尝试改善延迟。
|
||||
|
||||
#### tls_fragment
|
||||
#### key
|
||||
|
||||
==仅服务器==
|
||||
|
||||
ECH 密钥行数组,PEM 格式。
|
||||
|
||||
#### key_path
|
||||
|
||||
==仅服务器==
|
||||
|
||||
!!! note ""
|
||||
|
||||
文件更改时将自动重新加载。
|
||||
|
||||
ECH 密钥路径,PEM 格式。
|
||||
|
||||
#### config
|
||||
|
||||
==仅客户端==
|
||||
|
||||
ECH 配置行数组,PEM 格式。
|
||||
|
||||
如果为空,将尝试从 DNS 加载。
|
||||
|
||||
#### config_path
|
||||
|
||||
==仅客户端==
|
||||
|
||||
ECH 配置路径,PEM 格式。
|
||||
|
||||
如果为空,将尝试从 DNS 加载。
|
||||
|
||||
#### fragment
|
||||
|
||||
!!! question "自 sing-box 1.12.0 起"
|
||||
|
||||
==仅客户端==
|
||||
|
||||
通过分段 TLS 握手数据包来绕过防火墙检测。
|
||||
通过分段 TLS 握手数据包来绕过防火墙。
|
||||
|
||||
此功能旨在规避基于**明文数据包匹配**的简单防火墙,不应该用于规避真的审查。
|
||||
此功能旨在规避基于**明文数据包匹配**的简单防火墙,不应该用于规避真正的审查。
|
||||
|
||||
由于性能不佳,请首先尝试 `tls_record_fragment`,且仅应用于已知被阻止的服务器名称。
|
||||
由于性能不佳,请首先尝试 `record_fragment`,且仅应用于已知被阻止的服务器名称。
|
||||
|
||||
在 Linux、Apple 平台和需要管理员权限的 Windows 系统上,可自动检测等待时间。
|
||||
若无法自动检测,将回退使用 `tls_fragment_fallback_delay` 指定的固定等待时间。
|
||||
在 Linux、Apple 平台和(需要管理员权限的)Windows 系统上,
|
||||
可以自动检测等待时间。否则,将回退到
|
||||
等待 `fragment_fallback_delay` 指定的固定时间。
|
||||
|
||||
此外,若实际等待时间小于 20 毫秒,同样会回退至固定等待时间模式,因为此时判定目标处于本地或透明代理之后。
|
||||
此外,如果实际等待时间少于 20ms,也会回退到等待固定时间,
|
||||
因为目标被认为是本地的或在透明代理后面。
|
||||
|
||||
#### tls_fragment_fallback_delay
|
||||
#### fragment_fallback_delay
|
||||
|
||||
!!! question "自 sing-box 1.12.0 起"
|
||||
|
||||
==仅客户端==
|
||||
|
||||
当 TLS 分片功能无法自动判定等待时间时使用的回退值。
|
||||
当 TLS 分段无法自动确定等待时间时使用的回退值。
|
||||
|
||||
默认使用 `500ms`。
|
||||
|
||||
#### tls_record_fragment
|
||||
|
||||
==仅客户端==
|
||||
#### record_fragment
|
||||
|
||||
!!! question "自 sing-box 1.12.0 起"
|
||||
|
||||
通过分段 TLS 握手数据包到多个 TLS 记录来绕过防火墙检测。
|
||||
==仅客户端==
|
||||
|
||||
将 TLS 握手分段为多个 TLS 记录以绕过防火墙。
|
||||
|
||||
### ACME 字段
|
||||
|
||||
#### domain
|
||||
|
||||
一组域名。
|
||||
域名列表。
|
||||
|
||||
默认禁用 ACME。
|
||||
如果为空则禁用 ACME。
|
||||
|
||||
#### data_directory
|
||||
|
||||
ACME 数据目录。
|
||||
ACME 数据存储目录。
|
||||
|
||||
默认使用 `$XDG_DATA_HOME/certmagic|$HOME/.local/share/certmagic`。
|
||||
如果为空则使用 `$XDG_DATA_HOME/certmagic|$HOME/.local/share/certmagic`。
|
||||
|
||||
#### default_server_name
|
||||
|
||||
@@ -452,12 +558,11 @@ ACME 数据目录。
|
||||
|
||||
#### external_account
|
||||
|
||||
EAB(外部帐户绑定)包含将 ACME 帐户绑定或映射到其他已知帐户所需的信息由 CA。
|
||||
EAB(外部帐户绑定)包含将 ACME 帐户绑定或映射到 CA 已知的其他帐户所需的信息。
|
||||
|
||||
外部帐户绑定“用于将 ACME 帐户与非 ACME 系统中的现有帐户相关联,例如 CA 客户数据库。
|
||||
外部帐户绑定"用于将 ACME 帐户与非 ACME 系统中的现有帐户相关联,例如 CA 客户数据库。
|
||||
|
||||
为了启用 ACME 帐户绑定,运行 ACME 服务器的 CA 需要向 ACME 客户端提供 MAC 密钥和密钥标识符,使用 ACME 之外的一些机制。
|
||||
§7.3.4
|
||||
为了启用 ACME 帐户绑定,运行 ACME 服务器的 CA 需要使用 ACME 之外的某种机制向 ACME 客户端提供 MAC 密钥和密钥标识符。§7.3.4
|
||||
|
||||
#### external_account.key_id
|
||||
|
||||
@@ -507,6 +612,8 @@ ACME DNS01 验证字段。如果配置,将禁用其他验证方法。
|
||||
|
||||
#### max_time_difference
|
||||
|
||||
服务器与和客户端之间允许的最大时间差。
|
||||
==仅服务器==
|
||||
|
||||
默认禁用检查。
|
||||
服务器和客户端之间的最大时间差。
|
||||
|
||||
如果为空则禁用检查。
|
||||
|
||||
180
option/tls.go
180
option/tls.go
@@ -1,24 +1,80 @@
|
||||
package option
|
||||
|
||||
import "github.com/sagernet/sing/common/json/badoption"
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/json/badoption"
|
||||
)
|
||||
|
||||
type InboundTLSOptions struct {
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
ServerName string `json:"server_name,omitempty"`
|
||||
Insecure bool `json:"insecure,omitempty"`
|
||||
ALPN badoption.Listable[string] `json:"alpn,omitempty"`
|
||||
MinVersion string `json:"min_version,omitempty"`
|
||||
MaxVersion string `json:"max_version,omitempty"`
|
||||
CipherSuites badoption.Listable[string] `json:"cipher_suites,omitempty"`
|
||||
Certificate badoption.Listable[string] `json:"certificate,omitempty"`
|
||||
CertificatePath string `json:"certificate_path,omitempty"`
|
||||
Key badoption.Listable[string] `json:"key,omitempty"`
|
||||
KeyPath string `json:"key_path,omitempty"`
|
||||
KernelTx bool `json:"kernel_tx,omitempty"`
|
||||
KernelRx bool `json:"kernel_rx,omitempty"`
|
||||
ACME *InboundACMEOptions `json:"acme,omitempty"`
|
||||
ECH *InboundECHOptions `json:"ech,omitempty"`
|
||||
Reality *InboundRealityOptions `json:"reality,omitempty"`
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
ServerName string `json:"server_name,omitempty"`
|
||||
Insecure bool `json:"insecure,omitempty"`
|
||||
ALPN badoption.Listable[string] `json:"alpn,omitempty"`
|
||||
MinVersion string `json:"min_version,omitempty"`
|
||||
MaxVersion string `json:"max_version,omitempty"`
|
||||
CipherSuites badoption.Listable[string] `json:"cipher_suites,omitempty"`
|
||||
CurvePreferences badoption.Listable[CurvePreference] `json:"curve_preferences,omitempty"`
|
||||
Certificate badoption.Listable[string] `json:"certificate,omitempty"`
|
||||
CertificatePath string `json:"certificate_path,omitempty"`
|
||||
ClientAuthentication ClientAuthType `json:"client_authentication,omitempty"`
|
||||
ClientCertificate badoption.Listable[string] `json:"client_certificate,omitempty"`
|
||||
ClientCertificatePath badoption.Listable[string] `json:"client_certificate_path,omitempty"`
|
||||
ClientCertificatePublicKeySHA256 badoption.Listable[[]byte] `json:"client_certificate_public_key_sha256,omitempty"`
|
||||
Key badoption.Listable[string] `json:"key,omitempty"`
|
||||
KeyPath string `json:"key_path,omitempty"`
|
||||
KernelTx bool `json:"kernel_tx,omitempty"`
|
||||
KernelRx bool `json:"kernel_rx,omitempty"`
|
||||
ACME *InboundACMEOptions `json:"acme,omitempty"`
|
||||
ECH *InboundECHOptions `json:"ech,omitempty"`
|
||||
Reality *InboundRealityOptions `json:"reality,omitempty"`
|
||||
}
|
||||
|
||||
type ClientAuthType tls.ClientAuthType
|
||||
|
||||
func (t ClientAuthType) MarshalJSON() ([]byte, error) {
|
||||
var stringValue string
|
||||
switch t {
|
||||
case ClientAuthType(tls.NoClientCert):
|
||||
stringValue = "no"
|
||||
case ClientAuthType(tls.RequestClientCert):
|
||||
stringValue = "request"
|
||||
case ClientAuthType(tls.RequireAnyClientCert):
|
||||
stringValue = "require-any"
|
||||
case ClientAuthType(tls.VerifyClientCertIfGiven):
|
||||
stringValue = "verify-if-given"
|
||||
case ClientAuthType(tls.RequireAndVerifyClientCert):
|
||||
stringValue = "require-and-verify"
|
||||
default:
|
||||
return nil, E.New("unknown client authentication type: ", int(t))
|
||||
}
|
||||
return json.Marshal(stringValue)
|
||||
}
|
||||
|
||||
func (t *ClientAuthType) UnmarshalJSON(data []byte) error {
|
||||
var stringValue string
|
||||
err := json.Unmarshal(data, &stringValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch stringValue {
|
||||
case "no":
|
||||
*t = ClientAuthType(tls.NoClientCert)
|
||||
case "request":
|
||||
*t = ClientAuthType(tls.RequestClientCert)
|
||||
case "require-any":
|
||||
*t = ClientAuthType(tls.RequireAnyClientCert)
|
||||
case "verify-if-given":
|
||||
*t = ClientAuthType(tls.VerifyClientCertIfGiven)
|
||||
case "require-and-verify":
|
||||
*t = ClientAuthType(tls.RequireAndVerifyClientCert)
|
||||
default:
|
||||
return E.New("unknown client authentication type: ", stringValue)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type InboundTLSOptionsContainer struct {
|
||||
@@ -39,24 +95,26 @@ func (o *InboundTLSOptionsContainer) ReplaceInboundTLSOptions(options *InboundTL
|
||||
}
|
||||
|
||||
type OutboundTLSOptions struct {
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
DisableSNI bool `json:"disable_sni,omitempty"`
|
||||
ServerName string `json:"server_name,omitempty"`
|
||||
Insecure bool `json:"insecure,omitempty"`
|
||||
ALPN badoption.Listable[string] `json:"alpn,omitempty"`
|
||||
MinVersion string `json:"min_version,omitempty"`
|
||||
MaxVersion string `json:"max_version,omitempty"`
|
||||
CipherSuites badoption.Listable[string] `json:"cipher_suites,omitempty"`
|
||||
Certificate badoption.Listable[string] `json:"certificate,omitempty"`
|
||||
CertificatePath string `json:"certificate_path,omitempty"`
|
||||
Fragment bool `json:"fragment,omitempty"`
|
||||
FragmentFallbackDelay badoption.Duration `json:"fragment_fallback_delay,omitempty"`
|
||||
RecordFragment bool `json:"record_fragment,omitempty"`
|
||||
KernelTx bool `json:"kernel_tx,omitempty"`
|
||||
KernelRx bool `json:"kernel_rx,omitempty"`
|
||||
ECH *OutboundECHOptions `json:"ech,omitempty"`
|
||||
UTLS *OutboundUTLSOptions `json:"utls,omitempty"`
|
||||
Reality *OutboundRealityOptions `json:"reality,omitempty"`
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
DisableSNI bool `json:"disable_sni,omitempty"`
|
||||
ServerName string `json:"server_name,omitempty"`
|
||||
Insecure bool `json:"insecure,omitempty"`
|
||||
ALPN badoption.Listable[string] `json:"alpn,omitempty"`
|
||||
MinVersion string `json:"min_version,omitempty"`
|
||||
MaxVersion string `json:"max_version,omitempty"`
|
||||
CipherSuites badoption.Listable[string] `json:"cipher_suites,omitempty"`
|
||||
CurvePreferences badoption.Listable[CurvePreference] `json:"curve_preferences,omitempty"`
|
||||
Certificate badoption.Listable[string] `json:"certificate,omitempty"`
|
||||
CertificatePath string `json:"certificate_path,omitempty"`
|
||||
CertificatePublicKeySHA256 badoption.Listable[[]byte] `json:"certificate_public_key_sha256,omitempty"`
|
||||
Fragment bool `json:"fragment,omitempty"`
|
||||
FragmentFallbackDelay badoption.Duration `json:"fragment_fallback_delay,omitempty"`
|
||||
RecordFragment bool `json:"record_fragment,omitempty"`
|
||||
KernelTx bool `json:"kernel_tx,omitempty"`
|
||||
KernelRx bool `json:"kernel_rx,omitempty"`
|
||||
ECH *OutboundECHOptions `json:"ech,omitempty"`
|
||||
UTLS *OutboundUTLSOptions `json:"utls,omitempty"`
|
||||
Reality *OutboundRealityOptions `json:"reality,omitempty"`
|
||||
}
|
||||
|
||||
type OutboundTLSOptionsContainer struct {
|
||||
@@ -76,6 +134,58 @@ func (o *OutboundTLSOptionsContainer) ReplaceOutboundTLSOptions(options *Outboun
|
||||
o.TLS = options
|
||||
}
|
||||
|
||||
type CurvePreference tls.CurveID
|
||||
|
||||
const (
|
||||
CurveP256 = 23
|
||||
CurveP384 = 24
|
||||
CurveP521 = 25
|
||||
X25519 = 29
|
||||
X25519MLKEM768 = 4588
|
||||
)
|
||||
|
||||
func (c CurvePreference) MarshalJSON() ([]byte, error) {
|
||||
var stringValue string
|
||||
switch c {
|
||||
case CurvePreference(CurveP256):
|
||||
stringValue = "P256"
|
||||
case CurvePreference(CurveP384):
|
||||
stringValue = "P384"
|
||||
case CurvePreference(CurveP521):
|
||||
stringValue = "P521"
|
||||
case CurvePreference(X25519):
|
||||
stringValue = "X25519"
|
||||
case CurvePreference(X25519MLKEM768):
|
||||
stringValue = "X25519MLKEM768"
|
||||
default:
|
||||
return nil, E.New("unknown curve id: ", int(c))
|
||||
}
|
||||
return json.Marshal(stringValue)
|
||||
}
|
||||
|
||||
func (c *CurvePreference) UnmarshalJSON(data []byte) error {
|
||||
var stringValue string
|
||||
err := json.Unmarshal(data, &stringValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch strings.ToUpper(stringValue) {
|
||||
case "P256":
|
||||
*c = CurvePreference(CurveP256)
|
||||
case "P384":
|
||||
*c = CurvePreference(CurveP384)
|
||||
case "P521":
|
||||
*c = CurvePreference(CurveP521)
|
||||
case "X25519":
|
||||
*c = CurvePreference(X25519)
|
||||
case "X25519MLKEM768":
|
||||
*c = CurvePreference(X25519MLKEM768)
|
||||
default:
|
||||
return E.New("unknown curve name: ", stringValue)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type InboundRealityOptions struct {
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
Handshake InboundRealityHandshakeOptions `json:"handshake,omitempty"`
|
||||
|
||||
Reference in New Issue
Block a user