-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,265 @@ | ||
package tls | ||
|
||
import ( | ||
"context" | ||
"crypto/tls" | ||
"encoding/pem" | ||
"net" | ||
"os" | ||
"strings" | ||
|
||
cftls "github.com/sagernet/cloudflare-tls" | ||
"github.com/sagernet/sing-box/adapter" | ||
"github.com/sagernet/sing-box/log" | ||
"github.com/sagernet/sing-box/option" | ||
E "github.com/sagernet/sing/common/exceptions" | ||
|
||
"github.com/fsnotify/fsnotify" | ||
) | ||
|
||
type echServerConfig struct { | ||
config *cftls.Config | ||
logger log.Logger | ||
acmeService adapter.Service | ||
certificate []byte | ||
key []byte | ||
certificatePath string | ||
keyPath string | ||
watcher *fsnotify.Watcher | ||
} | ||
|
||
func (c *echServerConfig) ServerName() string { | ||
return c.config.ServerName | ||
} | ||
|
||
func (c *echServerConfig) SetServerName(serverName string) { | ||
c.config.ServerName = serverName | ||
} | ||
|
||
func (c *echServerConfig) NextProtos() []string { | ||
return c.config.NextProtos | ||
} | ||
|
||
func (c *echServerConfig) SetNextProtos(nextProto []string) { | ||
c.config.NextProtos = nextProto | ||
} | ||
|
||
func (c *echServerConfig) Config() (*STDConfig, error) { | ||
return nil, E.New("unsupported usage for ECH") | ||
} | ||
|
||
func (c *echServerConfig) Client(conn net.Conn) (Conn, error) { | ||
return &echConnWrapper{cftls.Client(conn, c.config)}, nil | ||
Check failure on line 52 in common/tls/ech_server.go GitHub Actions / Build
Check failure on line 52 in common/tls/ech_server.go GitHub Actions / Build
Check failure on line 52 in common/tls/ech_server.go GitHub Actions / Build
Check failure on line 52 in common/tls/ech_server.go GitHub Actions / cross (linux-mips64el, linux, mips64le)
Check failure on line 52 in common/tls/ech_server.go GitHub Actions / Debug build (Go 1.20)
Check failure on line 52 in common/tls/ech_server.go GitHub Actions / cross (freebsd-arm64, freebsd, arm64)
Check failure on line 52 in common/tls/ech_server.go GitHub Actions / cross (linux-armv6, linux, arm, 6)
Check failure on line 52 in common/tls/ech_server.go GitHub Actions / cross (linux-armv7, linux, arm, 7)
Check failure on line 52 in common/tls/ech_server.go GitHub Actions / cross (linux-mips64, linux, mips64)
Check failure on line 52 in common/tls/ech_server.go GitHub Actions / cross (linux-armv5, linux, arm, 5)
Check failure on line 52 in common/tls/ech_server.go GitHub Actions / cross (linux-386, linux, 386)
Check failure on line 52 in common/tls/ech_server.go GitHub Actions / cross (darwin-arm64, darwin, arm64)
Check failure on line 52 in common/tls/ech_server.go GitHub Actions / cross (freebsd-386, freebsd, 386)
Check failure on line 52 in common/tls/ech_server.go GitHub Actions / cross (linux-amd64-v3, linux, amd64, v3)
Check failure on line 52 in common/tls/ech_server.go GitHub Actions / cross (linux-arm64, linux, arm64)
Check failure on line 52 in common/tls/ech_server.go GitHub Actions / cross (darwin-amd64-v3, darwin, amd64, v3)
Check failure on line 52 in common/tls/ech_server.go GitHub Actions / cross (freebsd-amd64-v3, freebsd, amd64, v3)
Check failure on line 52 in common/tls/ech_server.go GitHub Actions / cross (freebsd-amd64, freebsd, amd64, v1)
Check failure on line 52 in common/tls/ech_server.go GitHub Actions / cross (darwin-amd64, darwin, amd64, v1)
Check failure on line 52 in common/tls/ech_server.go GitHub Actions / cross (linux-amd64, linux, amd64, v1)
Check failure on line 52 in common/tls/ech_server.go GitHub Actions / cross (linux-mipsel-hardfloat, linux, mipsle, hardfloat)
Check failure on line 52 in common/tls/ech_server.go GitHub Actions / Debug build
Check failure on line 52 in common/tls/ech_server.go GitHub Actions / cross (linux-mipsel-softfloat, linux, mipsle, softfloat)
Check failure on line 52 in common/tls/ech_server.go GitHub Actions / cross (linux-s390x, linux, s390x)
Check failure on line 52 in common/tls/ech_server.go GitHub Actions / cross (windows-amd64-v3, windows, amd64, v3)
Check failure on line 52 in common/tls/ech_server.go GitHub Actions / cross (windows-amd64, windows, amd64, v1)
Check failure on line 52 in common/tls/ech_server.go GitHub Actions / cross (linux-mips-hardfloat, linux, mips, hardfloat)
Check failure on line 52 in common/tls/ech_server.go GitHub Actions / cross (windows-arm32v7, windows, arm, 7)
Check failure on line 52 in common/tls/ech_server.go GitHub Actions / cross (linux-mips-softfloat, linux, mips, softfloat)
Check failure on line 52 in common/tls/ech_server.go GitHub Actions / cross (windows-386, windows, 386)
|
||
} | ||
|
||
func (c *echServerConfig) Server(conn net.Conn) (Conn, error) { | ||
return &echConnWrapper{cftls.Server(conn, c.config)}, nil | ||
Check failure on line 56 in common/tls/ech_server.go GitHub Actions / Build
Check failure on line 56 in common/tls/ech_server.go GitHub Actions / Build
Check failure on line 56 in common/tls/ech_server.go GitHub Actions / Build
Check failure on line 56 in common/tls/ech_server.go GitHub Actions / cross (linux-mips64el, linux, mips64le)
Check failure on line 56 in common/tls/ech_server.go GitHub Actions / Debug build (Go 1.20)
Check failure on line 56 in common/tls/ech_server.go GitHub Actions / cross (freebsd-arm64, freebsd, arm64)
Check failure on line 56 in common/tls/ech_server.go GitHub Actions / cross (linux-armv6, linux, arm, 6)
Check failure on line 56 in common/tls/ech_server.go GitHub Actions / cross (linux-armv7, linux, arm, 7)
Check failure on line 56 in common/tls/ech_server.go GitHub Actions / cross (linux-mips64, linux, mips64)
Check failure on line 56 in common/tls/ech_server.go GitHub Actions / cross (linux-armv5, linux, arm, 5)
Check failure on line 56 in common/tls/ech_server.go GitHub Actions / cross (linux-386, linux, 386)
Check failure on line 56 in common/tls/ech_server.go GitHub Actions / cross (darwin-arm64, darwin, arm64)
Check failure on line 56 in common/tls/ech_server.go GitHub Actions / cross (freebsd-386, freebsd, 386)
Check failure on line 56 in common/tls/ech_server.go GitHub Actions / cross (linux-amd64-v3, linux, amd64, v3)
Check failure on line 56 in common/tls/ech_server.go GitHub Actions / cross (linux-arm64, linux, arm64)
Check failure on line 56 in common/tls/ech_server.go GitHub Actions / cross (darwin-amd64-v3, darwin, amd64, v3)
Check failure on line 56 in common/tls/ech_server.go GitHub Actions / cross (freebsd-amd64-v3, freebsd, amd64, v3)
Check failure on line 56 in common/tls/ech_server.go GitHub Actions / cross (freebsd-amd64, freebsd, amd64, v1)
Check failure on line 56 in common/tls/ech_server.go GitHub Actions / cross (darwin-amd64, darwin, amd64, v1)
Check failure on line 56 in common/tls/ech_server.go GitHub Actions / cross (linux-amd64, linux, amd64, v1)
Check failure on line 56 in common/tls/ech_server.go GitHub Actions / cross (linux-mipsel-hardfloat, linux, mipsle, hardfloat)
Check failure on line 56 in common/tls/ech_server.go GitHub Actions / Debug build
Check failure on line 56 in common/tls/ech_server.go GitHub Actions / cross (linux-mipsel-softfloat, linux, mipsle, softfloat)
Check failure on line 56 in common/tls/ech_server.go GitHub Actions / cross (linux-s390x, linux, s390x)
Check failure on line 56 in common/tls/ech_server.go GitHub Actions / cross (windows-amd64-v3, windows, amd64, v3)
Check failure on line 56 in common/tls/ech_server.go GitHub Actions / cross (windows-amd64, windows, amd64, v1)
Check failure on line 56 in common/tls/ech_server.go GitHub Actions / cross (linux-mips-hardfloat, linux, mips, hardfloat)
Check failure on line 56 in common/tls/ech_server.go GitHub Actions / cross (windows-arm32v7, windows, arm, 7)
Check failure on line 56 in common/tls/ech_server.go GitHub Actions / cross (linux-mips-softfloat, linux, mips, softfloat)
Check failure on line 56 in common/tls/ech_server.go GitHub Actions / cross (windows-386, windows, 386)
|
||
} | ||
|
||
func (c *echServerConfig) Clone() Config { | ||
return &echServerConfig{ | ||
config: c.config.Clone(), | ||
} | ||
} | ||
|
||
func (c *echServerConfig) Start() error { | ||
if c.acmeService != nil { | ||
return c.acmeService.Start() | ||
} else { | ||
if c.certificatePath == "" && c.keyPath == "" { | ||
return nil | ||
} | ||
err := c.startWatcher() | ||
if err != nil { | ||
c.logger.Warn("create fsnotify watcher: ", err) | ||
} | ||
return nil | ||
} | ||
} | ||
|
||
func (c *echServerConfig) startWatcher() error { | ||
watcher, err := fsnotify.NewWatcher() | ||
if err != nil { | ||
return err | ||
} | ||
if c.certificatePath != "" { | ||
err = watcher.Add(c.certificatePath) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
if c.keyPath != "" { | ||
err = watcher.Add(c.keyPath) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
c.watcher = watcher | ||
go c.loopUpdate() | ||
return nil | ||
} | ||
|
||
func (c *echServerConfig) loopUpdate() { | ||
for { | ||
select { | ||
case event, ok := <-c.watcher.Events: | ||
if !ok { | ||
return | ||
} | ||
if event.Op&fsnotify.Write != fsnotify.Write { | ||
continue | ||
} | ||
err := c.reloadKeyPair() | ||
if err != nil { | ||
c.logger.Error(E.Cause(err, "reload TLS key pair")) | ||
} | ||
case err, ok := <-c.watcher.Errors: | ||
if !ok { | ||
return | ||
} | ||
c.logger.Error(E.Cause(err, "fsnotify error")) | ||
} | ||
} | ||
} | ||
|
||
func (c *echServerConfig) reloadKeyPair() error { | ||
if c.certificatePath != "" { | ||
certificate, err := os.ReadFile(c.certificatePath) | ||
if err != nil { | ||
return E.Cause(err, "reload certificate from ", c.certificatePath) | ||
} | ||
c.certificate = certificate | ||
} | ||
if c.keyPath != "" { | ||
key, err := os.ReadFile(c.keyPath) | ||
if err != nil { | ||
return E.Cause(err, "reload key from ", c.keyPath) | ||
} | ||
c.key = key | ||
} | ||
keyPair, err := cftls.X509KeyPair(c.certificate, c.key) | ||
if err != nil { | ||
return E.Cause(err, "reload key pair") | ||
} | ||
c.config.Certificates = []cftls.Certificate{keyPair} | ||
c.logger.Info("reloaded TLS certificate") | ||
return nil | ||
} | ||
|
||
func (c *echServerConfig) Close() error { | ||
if c.acmeService != nil { | ||
return c.acmeService.Close() | ||
} | ||
if c.watcher != nil { | ||
return c.watcher.Close() | ||
} | ||
return nil | ||
} | ||
|
||
func NewECHServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) { | ||
if !options.Enabled { | ||
return nil, nil | ||
} | ||
var tlsConfig cftls.Config | ||
var acmeService adapter.Service | ||
if options.ACME != nil && len(options.ACME.Domain) > 0 { | ||
return nil, E.New("acme is unavailable in ech") | ||
} | ||
tlsConfig.Time = router.TimeFunc() | ||
if options.ServerName != "" { | ||
tlsConfig.ServerName = options.ServerName | ||
} | ||
if len(options.ALPN) > 0 { | ||
tlsConfig.NextProtos = append(options.ALPN, tlsConfig.NextProtos...) | ||
} | ||
if options.MinVersion != "" { | ||
minVersion, err := ParseTLSVersion(options.MinVersion) | ||
if err != nil { | ||
return nil, E.Cause(err, "parse min_version") | ||
} | ||
tlsConfig.MinVersion = minVersion | ||
} | ||
if options.MaxVersion != "" { | ||
maxVersion, err := ParseTLSVersion(options.MaxVersion) | ||
if err != nil { | ||
return nil, E.Cause(err, "parse max_version") | ||
} | ||
tlsConfig.MaxVersion = maxVersion | ||
} | ||
if options.CipherSuites != nil { | ||
find: | ||
for _, cipherSuite := range options.CipherSuites { | ||
for _, tlsCipherSuite := range tls.CipherSuites() { | ||
if cipherSuite == tlsCipherSuite.Name { | ||
tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID) | ||
continue find | ||
} | ||
} | ||
return nil, E.New("unknown cipher_suite: ", cipherSuite) | ||
} | ||
} | ||
var certificate []byte | ||
var key []byte | ||
if acmeService == nil { | ||
if options.Certificate != "" { | ||
certificate = []byte(options.Certificate) | ||
} else if options.CertificatePath != "" { | ||
content, err := os.ReadFile(options.CertificatePath) | ||
if err != nil { | ||
return nil, E.Cause(err, "read certificate") | ||
} | ||
certificate = content | ||
} | ||
if options.Key != "" { | ||
key = []byte(options.Key) | ||
} else if options.KeyPath != "" { | ||
content, err := os.ReadFile(options.KeyPath) | ||
if err != nil { | ||
return nil, E.Cause(err, "read key") | ||
} | ||
key = content | ||
} | ||
|
||
if certificate == nil { | ||
return nil, E.New("missing certificate") | ||
} else if key == nil { | ||
return nil, E.New("missing key") | ||
} | ||
|
||
keyPair, err := cftls.X509KeyPair(certificate, key) | ||
if err != nil { | ||
return nil, E.Cause(err, "parse x509 key pair") | ||
} | ||
tlsConfig.Certificates = []cftls.Certificate{keyPair} | ||
} | ||
|
||
block, rest := pem.Decode([]byte(strings.Join(options.ECH.Key, "\n"))) | ||
if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 { | ||
return nil, E.New("invalid ECH keys pem") | ||
} | ||
|
||
echKeys, err := cftls.EXP_UnmarshalECHKeys(block.Bytes) | ||
if err != nil { | ||
return nil, E.Cause(err, "parse ECH keys") | ||
} | ||
|
||
echKeySet, err := cftls.EXP_NewECHKeySet(echKeys) | ||
if err != nil { | ||
return nil, E.Cause(err, "create ECH key set") | ||
} | ||
|
||
tlsConfig.ECHEnabled = true | ||
tlsConfig.PQSignatureSchemesEnabled = options.ECH.PQSignatureSchemesEnabled | ||
tlsConfig.DynamicRecordSizingDisabled = options.ECH.DynamicRecordSizingDisabled | ||
tlsConfig.ServerECHProvider = echKeySet | ||
|
||
return &echServerConfig{ | ||
config: &tlsConfig, | ||
logger: logger, | ||
acmeService: acmeService, | ||
certificate: certificate, | ||
key: key, | ||
certificatePath: options.CertificatePath, | ||
keyPath: options.KeyPath, | ||
}, nil | ||
} |