mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-09-25 08:08:00 +03:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
676212c789 | ||
|
|
1ca32a7af8 | ||
|
|
9fa107ced3 | ||
|
|
65d50cc638 | ||
|
|
1b8d07f1e3 |
@@ -82,6 +82,12 @@ func KeyUsage(usage x509.KeyUsage) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ExtKeyUsage(usage []x509.ExtKeyUsage) Option {
|
||||||
|
return func(c *x509.Certificate) {
|
||||||
|
c.ExtKeyUsage = usage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Organization(org string) Option {
|
func Organization(org string) Option {
|
||||||
return func(c *x509.Certificate) {
|
return func(c *x509.Certificate) {
|
||||||
c.Subject.Organization = []string{org}
|
c.Subject.Organization = []string{org}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"maps"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"weak"
|
"weak"
|
||||||
@@ -43,3 +44,16 @@ func (c *WeakCacheMap[K, V]) Store(key K, value *V) {
|
|||||||
}
|
}
|
||||||
}, struct{}{})
|
}, struct{}{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *WeakCacheMap[K, V]) Range(f func(K, *V) bool) {
|
||||||
|
c.mu.Lock()
|
||||||
|
snapshot := maps.Clone(c.m)
|
||||||
|
c.mu.Unlock()
|
||||||
|
for k, v := range snapshot {
|
||||||
|
if value := v.Value(); value != nil {
|
||||||
|
if !f(k, value) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -603,6 +603,10 @@ func (c *TLSCertConfig) Build() (*tls.Certificate, error) {
|
|||||||
certificate.Usage = tls.Certificate_AUTHORITY_VERIFY
|
certificate.Usage = tls.Certificate_AUTHORITY_VERIFY
|
||||||
case "issue":
|
case "issue":
|
||||||
certificate.Usage = tls.Certificate_AUTHORITY_ISSUE
|
certificate.Usage = tls.Certificate_AUTHORITY_ISSUE
|
||||||
|
case "client-cert":
|
||||||
|
certificate.Usage = tls.Certificate_MTLS_CLIENT_CERT
|
||||||
|
case "client-ca":
|
||||||
|
certificate.Usage = tls.Certificate_MTLS_CLIENT_CA
|
||||||
default:
|
default:
|
||||||
certificate.Usage = tls.Certificate_ENCIPHERMENT
|
certificate.Usage = tls.Certificate_ENCIPHERMENT
|
||||||
}
|
}
|
||||||
@@ -653,6 +657,7 @@ type TLSConfig struct {
|
|||||||
ECHServerKeys string `json:"echServerKeys"`
|
ECHServerKeys string `json:"echServerKeys"`
|
||||||
ECHConfigList string `json:"echConfigList"`
|
ECHConfigList string `json:"echConfigList"`
|
||||||
ECHSocketSettings *SocketConfig `json:"echSockopt"`
|
ECHSocketSettings *SocketConfig `json:"echSockopt"`
|
||||||
|
ClientAuth string `json:"clientAuth"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build implements Buildable.
|
// Build implements Buildable.
|
||||||
@@ -741,6 +746,7 @@ func (c *TLSConfig) Build() (proto.Message, error) {
|
|||||||
config.EchSocketSettings = ss
|
config.EchSocketSettings = ss
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config.ClientAuth = c.ClientAuth
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -126,6 +126,243 @@ func TestSimpleTLSConnection(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMTLSConnection(t *testing.T) {
|
||||||
|
tcpServer := tcp.Server{
|
||||||
|
MsgProcessor: xor,
|
||||||
|
}
|
||||||
|
dest, err := tcpServer.Start()
|
||||||
|
common.Must(err)
|
||||||
|
defer tcpServer.Close()
|
||||||
|
|
||||||
|
serverCert, serverCertHash := cert.MustGenerate(nil, cert.CommonName("localhost"))
|
||||||
|
|
||||||
|
// CA that issues client certificates; the server trusts it to verify client certs.
|
||||||
|
// ExtKeyUsage must allow ClientAuth on the CA too, otherwise the chain fails the
|
||||||
|
// server's ClientAuth key-usage check.
|
||||||
|
clientCA, _ := cert.MustGenerate(nil, cert.Authority(true),
|
||||||
|
cert.KeyUsage(x509.KeyUsageCertSign|x509.KeyUsageDigitalSignature),
|
||||||
|
cert.ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}))
|
||||||
|
clientCACertPEM, _ := clientCA.ToPEM()
|
||||||
|
|
||||||
|
// Client certificate signed by the CA. It must carry ClientAuth ext key usage,
|
||||||
|
// otherwise crypto/tls rejects it during client-cert verification.
|
||||||
|
clientCert, _ := cert.MustGenerate(clientCA, cert.CommonName("client"),
|
||||||
|
cert.ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}))
|
||||||
|
clientCertPEM, clientKeyPEM := clientCert.ToPEM()
|
||||||
|
|
||||||
|
userID := protocol.NewID(uuid.New())
|
||||||
|
serverPort := tcp.PickPort()
|
||||||
|
serverConfig := &core.Config{
|
||||||
|
Inbound: []*core.InboundHandlerConfig{
|
||||||
|
{
|
||||||
|
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
|
||||||
|
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},
|
||||||
|
Listen: net.NewIPOrDomain(net.LocalHostIP),
|
||||||
|
StreamSettings: &internet.StreamConfig{
|
||||||
|
SecurityType: serial.GetMessageType(&tls.Config{}),
|
||||||
|
SecuritySettings: []*serial.TypedMessage{
|
||||||
|
serial.ToTypedMessage(&tls.Config{
|
||||||
|
Certificate: []*tls.Certificate{
|
||||||
|
tls.ParseCertificate(serverCert),
|
||||||
|
{
|
||||||
|
Certificate: clientCACertPEM,
|
||||||
|
Usage: tls.Certificate_MTLS_CLIENT_CA,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ClientAuth: "requireandverifyclientcert",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
ProxySettings: serial.ToTypedMessage(&inbound.Config{
|
||||||
|
User: []*protocol.User{
|
||||||
|
{
|
||||||
|
Account: serial.ToTypedMessage(&vmess.Account{
|
||||||
|
Id: userID.String(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Outbound: []*core.OutboundHandlerConfig{
|
||||||
|
{
|
||||||
|
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||||
|
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
clientPort := tcp.PickPort()
|
||||||
|
clientConfig := &core.Config{
|
||||||
|
Inbound: []*core.InboundHandlerConfig{
|
||||||
|
{
|
||||||
|
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
|
||||||
|
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},
|
||||||
|
Listen: net.NewIPOrDomain(net.LocalHostIP),
|
||||||
|
}),
|
||||||
|
ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
|
||||||
|
RewriteAddress: net.NewIPOrDomain(dest.Address),
|
||||||
|
RewritePort: uint32(dest.Port),
|
||||||
|
AllowedNetworks: []net.Network{net.Network_TCP},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Outbound: []*core.OutboundHandlerConfig{
|
||||||
|
{
|
||||||
|
ProxySettings: serial.ToTypedMessage(&outbound.Config{
|
||||||
|
Receiver: &protocol.ServerEndpoint{
|
||||||
|
Address: net.NewIPOrDomain(net.LocalHostIP),
|
||||||
|
Port: uint32(serverPort),
|
||||||
|
User: &protocol.User{
|
||||||
|
Account: serial.ToTypedMessage(&vmess.Account{
|
||||||
|
Id: userID.String(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
|
||||||
|
StreamSettings: &internet.StreamConfig{
|
||||||
|
SecurityType: serial.GetMessageType(&tls.Config{}),
|
||||||
|
SecuritySettings: []*serial.TypedMessage{
|
||||||
|
serial.ToTypedMessage(&tls.Config{
|
||||||
|
PinnedPeerCertSha256: [][]byte{serverCertHash[:]},
|
||||||
|
Certificate: []*tls.Certificate{
|
||||||
|
{
|
||||||
|
Certificate: clientCertPEM,
|
||||||
|
Key: clientKeyPEM,
|
||||||
|
Usage: tls.Certificate_MTLS_CLIENT_CERT,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
servers, err := InitializeServerConfigs(serverConfig, clientConfig)
|
||||||
|
common.Must(err)
|
||||||
|
defer CloseAllServers(servers)
|
||||||
|
|
||||||
|
if err := testTCPConn(clientPort, 1024, time.Second*20)(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMTLSConnectionMissingClientCert(t *testing.T) {
|
||||||
|
tcpServer := tcp.Server{
|
||||||
|
MsgProcessor: xor,
|
||||||
|
}
|
||||||
|
dest, err := tcpServer.Start()
|
||||||
|
common.Must(err)
|
||||||
|
defer tcpServer.Close()
|
||||||
|
|
||||||
|
serverCert, serverCertHash := cert.MustGenerate(nil, cert.CommonName("localhost"))
|
||||||
|
|
||||||
|
clientCA, _ := cert.MustGenerate(nil, cert.Authority(true),
|
||||||
|
cert.KeyUsage(x509.KeyUsageCertSign|x509.KeyUsageDigitalSignature))
|
||||||
|
clientCACertPEM, _ := clientCA.ToPEM()
|
||||||
|
|
||||||
|
userID := protocol.NewID(uuid.New())
|
||||||
|
serverPort := tcp.PickPort()
|
||||||
|
serverConfig := &core.Config{
|
||||||
|
Inbound: []*core.InboundHandlerConfig{
|
||||||
|
{
|
||||||
|
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
|
||||||
|
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},
|
||||||
|
Listen: net.NewIPOrDomain(net.LocalHostIP),
|
||||||
|
StreamSettings: &internet.StreamConfig{
|
||||||
|
SecurityType: serial.GetMessageType(&tls.Config{}),
|
||||||
|
SecuritySettings: []*serial.TypedMessage{
|
||||||
|
serial.ToTypedMessage(&tls.Config{
|
||||||
|
Certificate: []*tls.Certificate{
|
||||||
|
tls.ParseCertificate(serverCert),
|
||||||
|
{
|
||||||
|
Certificate: clientCACertPEM,
|
||||||
|
Usage: tls.Certificate_MTLS_CLIENT_CA,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ClientAuth: "requireandverifyclientcert",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
ProxySettings: serial.ToTypedMessage(&inbound.Config{
|
||||||
|
User: []*protocol.User{
|
||||||
|
{
|
||||||
|
Account: serial.ToTypedMessage(&vmess.Account{
|
||||||
|
Id: userID.String(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Outbound: []*core.OutboundHandlerConfig{
|
||||||
|
{
|
||||||
|
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||||
|
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
clientPort := tcp.PickPort()
|
||||||
|
clientConfig := &core.Config{
|
||||||
|
Inbound: []*core.InboundHandlerConfig{
|
||||||
|
{
|
||||||
|
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
|
||||||
|
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},
|
||||||
|
Listen: net.NewIPOrDomain(net.LocalHostIP),
|
||||||
|
}),
|
||||||
|
ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
|
||||||
|
RewriteAddress: net.NewIPOrDomain(dest.Address),
|
||||||
|
RewritePort: uint32(dest.Port),
|
||||||
|
AllowedNetworks: []net.Network{net.Network_TCP},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Outbound: []*core.OutboundHandlerConfig{
|
||||||
|
{
|
||||||
|
ProxySettings: serial.ToTypedMessage(&outbound.Config{
|
||||||
|
Receiver: &protocol.ServerEndpoint{
|
||||||
|
Address: net.NewIPOrDomain(net.LocalHostIP),
|
||||||
|
Port: uint32(serverPort),
|
||||||
|
User: &protocol.User{
|
||||||
|
Account: serial.ToTypedMessage(&vmess.Account{
|
||||||
|
Id: userID.String(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
|
||||||
|
StreamSettings: &internet.StreamConfig{
|
||||||
|
SecurityType: serial.GetMessageType(&tls.Config{}),
|
||||||
|
SecuritySettings: []*serial.TypedMessage{
|
||||||
|
serial.ToTypedMessage(&tls.Config{
|
||||||
|
// No MTLS_CLIENT_CERT: the client presents nothing,
|
||||||
|
// so the server must reject the handshake.
|
||||||
|
PinnedPeerCertSha256: [][]byte{serverCertHash[:]},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
servers, err := InitializeServerConfigs(serverConfig, clientConfig)
|
||||||
|
common.Must(err)
|
||||||
|
defer CloseAllServers(servers)
|
||||||
|
|
||||||
|
if err := testTCPConn(clientPort, 1024, time.Second*20)(); err == nil {
|
||||||
|
t.Fatal("expected handshake failure when the client presents no certificate")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAutoIssuingCertificate(t *testing.T) {
|
func TestAutoIssuingCertificate(t *testing.T) {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
// Not supported on Windows yet.
|
// Not supported on Windows yet.
|
||||||
|
|||||||
+175
-120
@@ -11,12 +11,12 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/ocsp"
|
|
||||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
|
||||||
"github.com/xtls/xray-core/common/protocol/tls/cert"
|
"github.com/xtls/xray-core/common/protocol/tls/cert"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
)
|
)
|
||||||
@@ -38,6 +38,9 @@ func ParseCertificate(c *cert.Certificate) *Certificate {
|
|||||||
func (c *Config) loadSelfCertPool() (*x509.CertPool, error) {
|
func (c *Config) loadSelfCertPool() (*x509.CertPool, error) {
|
||||||
root := x509.NewCertPool()
|
root := x509.NewCertPool()
|
||||||
for _, cert := range c.Certificate {
|
for _, cert := range c.Certificate {
|
||||||
|
if cert.Usage != Certificate_AUTHORITY_VERIFY {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if !root.AppendCertsFromPEM(cert.Certificate) {
|
if !root.AppendCertsFromPEM(cert.Certificate) {
|
||||||
return nil, errors.New("failed to append cert").AtWarning()
|
return nil, errors.New("failed to append cert").AtWarning()
|
||||||
}
|
}
|
||||||
@@ -45,91 +48,6 @@ func (c *Config) loadSelfCertPool() (*x509.CertPool, error) {
|
|||||||
return root, nil
|
return root, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildCertificates builds a list of TLS certificates from proto definition.
|
|
||||||
func (c *Config) BuildCertificates() []*tls.Certificate {
|
|
||||||
certs := make([]*tls.Certificate, 0, len(c.Certificate))
|
|
||||||
for _, entry := range c.Certificate {
|
|
||||||
if entry.Usage != Certificate_ENCIPHERMENT {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
getX509KeyPair := func() *tls.Certificate {
|
|
||||||
keyPair, err := tls.X509KeyPair(entry.Certificate, entry.Key)
|
|
||||||
if err != nil {
|
|
||||||
errors.LogWarningInner(context.Background(), err, "ignoring invalid X509 key pair")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0])
|
|
||||||
if err != nil {
|
|
||||||
errors.LogWarningInner(context.Background(), err, "ignoring invalid certificate")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &keyPair
|
|
||||||
}
|
|
||||||
if keyPair := getX509KeyPair(); keyPair != nil {
|
|
||||||
certs = append(certs, keyPair)
|
|
||||||
} else {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
index := len(certs) - 1
|
|
||||||
setupOcspTicker(entry, func(isReloaded, isOcspstapling bool) {
|
|
||||||
cert := certs[index]
|
|
||||||
if isReloaded {
|
|
||||||
if newKeyPair := getX509KeyPair(); newKeyPair != nil {
|
|
||||||
cert = newKeyPair
|
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if isOcspstapling {
|
|
||||||
if newOCSPData, err := ocsp.GetOCSPForCert(cert.Certificate); err != nil {
|
|
||||||
errors.LogWarningInner(context.Background(), err, "ignoring invalid OCSP")
|
|
||||||
} else if string(newOCSPData) != string(cert.OCSPStaple) {
|
|
||||||
cert.OCSPStaple = newOCSPData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
certs[index] = cert
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return certs
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupOcspTicker(entry *Certificate, callback func(isReloaded, isOcspstapling bool)) {
|
|
||||||
go func() {
|
|
||||||
if entry.OneTimeLoading {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var isOcspstapling bool
|
|
||||||
hotReloadCertInterval := uint64(3600)
|
|
||||||
if entry.OcspStapling != 0 {
|
|
||||||
hotReloadCertInterval = entry.OcspStapling
|
|
||||||
isOcspstapling = true
|
|
||||||
}
|
|
||||||
t := time.NewTicker(time.Duration(hotReloadCertInterval) * time.Second)
|
|
||||||
for {
|
|
||||||
var isReloaded bool
|
|
||||||
if entry.CertificatePath != "" && entry.KeyPath != "" {
|
|
||||||
newCert, err := filesystem.ReadCert(entry.CertificatePath)
|
|
||||||
if err != nil {
|
|
||||||
errors.LogErrorInner(context.Background(), err, "failed to parse certificate")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
newKey, err := filesystem.ReadCert(entry.KeyPath)
|
|
||||||
if err != nil {
|
|
||||||
errors.LogErrorInner(context.Background(), err, "failed to parse key")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if string(newCert) != string(entry.Certificate) || string(newKey) != string(entry.Key) {
|
|
||||||
entry.Certificate = newCert
|
|
||||||
entry.Key = newKey
|
|
||||||
isReloaded = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
callback(isReloaded, isOcspstapling)
|
|
||||||
<-t.C
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func isCertificateExpired(c *tls.Certificate) bool {
|
func isCertificateExpired(c *tls.Certificate) bool {
|
||||||
if c.Leaf == nil && len(c.Certificate) > 0 {
|
if c.Leaf == nil && len(c.Certificate) > 0 {
|
||||||
if pc, err := x509.ParseCertificate(c.Certificate[0]); err == nil {
|
if pc, err := x509.ParseCertificate(c.Certificate[0]); err == nil {
|
||||||
@@ -163,7 +81,28 @@ func (c *Config) getCustomCA() []*Certificate {
|
|||||||
for _, certificate := range c.Certificate {
|
for _, certificate := range c.Certificate {
|
||||||
if certificate.Usage == Certificate_AUTHORITY_ISSUE {
|
if certificate.Usage == Certificate_AUTHORITY_ISSUE {
|
||||||
certs = append(certs, certificate)
|
certs = append(certs, certificate)
|
||||||
setupOcspTicker(certificate, func(isReloaded, isOcspstapling bool) {})
|
setupHotReload(certificate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return certs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) getClientCert() []*Certificate {
|
||||||
|
certs := make([]*Certificate, 0, len(c.Certificate))
|
||||||
|
for _, certificate := range c.Certificate {
|
||||||
|
if certificate.Usage == Certificate_MTLS_CLIENT_CERT {
|
||||||
|
certs = append(certs, certificate)
|
||||||
|
setupHotReload(certificate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return certs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) getClientCA() []*Certificate {
|
||||||
|
certs := make([]*Certificate, 0, len(c.Certificate))
|
||||||
|
for _, certificate := range c.Certificate {
|
||||||
|
if certificate.Usage == Certificate_MTLS_CLIENT_CA {
|
||||||
|
certs = append(certs, certificate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return certs
|
return certs
|
||||||
@@ -243,34 +182,109 @@ func getGetCertificateFunc(c *tls.Config, ca []*Certificate) func(hello *tls.Cli
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getNewGetCertificateFunc(certs []*tls.Certificate, rejectUnknownSNI bool) func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
type extraProtoCertData struct {
|
||||||
return func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
parsed atomic.Pointer[tls.Certificate]
|
||||||
if len(certs) == 0 {
|
ocspData atomic.Pointer[[]byte]
|
||||||
return nil, errNoCertificates
|
lastReload int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// atomic.Pointer must be exactly one pointer word for the ParsedCache overlay below.
|
||||||
|
var _ [unsafe.Sizeof(unsafe.Pointer(nil))]byte = [unsafe.Sizeof(atomic.Pointer[extraProtoCertData]{})]byte{}
|
||||||
|
|
||||||
|
func (c *Certificate) extraData() *extraProtoCertData {
|
||||||
|
// wtf is this
|
||||||
|
slot := (*atomic.Pointer[extraProtoCertData])(unsafe.Pointer(&c.ExtraData))
|
||||||
|
if s := slot.Load(); s != nil {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
s := &extraProtoCertData{}
|
||||||
|
if slot.CompareAndSwap(nil, s) {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return slot.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Certificate) parseX509KeyPair() *tls.Certificate {
|
||||||
|
keyPair, err := tls.X509KeyPair(c.Certificate, c.Key)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogWarningInner(context.Background(), err, "ignoring invalid X509 key pair")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0])
|
||||||
|
if err != nil {
|
||||||
|
errors.LogWarningInner(context.Background(), err, "ignoring invalid certificate")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
st := c.extraData()
|
||||||
|
if OCSPData := st.ocspData.Load(); OCSPData != nil {
|
||||||
|
keyPair.OCSPStaple = *OCSPData
|
||||||
|
}
|
||||||
|
st.parsed.Store(&keyPair)
|
||||||
|
return &keyPair
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Certificate) getX509KeyPair() *tls.Certificate {
|
||||||
|
if keyPair := c.extraData().parsed.Load(); keyPair != nil {
|
||||||
|
return keyPair
|
||||||
|
}
|
||||||
|
return c.parseX509KeyPair()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) getCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
|
var defaultCert *tls.Certificate
|
||||||
|
for _, cert := range c.Certificate {
|
||||||
|
if cert.Usage == Certificate_ENCIPHERMENT {
|
||||||
|
defaultCert = cert.getX509KeyPair()
|
||||||
|
if defaultCert != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
sni := strings.ToLower(hello.ServerName)
|
}
|
||||||
if !rejectUnknownSNI && (len(certs) == 1 || sni == "") {
|
if defaultCert == nil {
|
||||||
return certs[0], nil
|
return nil, errNoCertificates
|
||||||
|
}
|
||||||
|
sni := strings.ToLower(hello.ServerName)
|
||||||
|
if !c.RejectUnknownSni && (len(c.Certificate) == 1 || sni == "") {
|
||||||
|
return defaultCert, nil
|
||||||
|
}
|
||||||
|
gsni := "*"
|
||||||
|
if index := strings.IndexByte(sni, '.'); index != -1 {
|
||||||
|
gsni += sni[index:]
|
||||||
|
}
|
||||||
|
for _, rawCertificate := range c.Certificate {
|
||||||
|
if rawCertificate.Usage != Certificate_ENCIPHERMENT {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
gsni := "*"
|
keyPair := rawCertificate.getX509KeyPair()
|
||||||
if index := strings.IndexByte(sni, '.'); index != -1 {
|
if keyPair == nil {
|
||||||
gsni += sni[index:]
|
continue
|
||||||
}
|
}
|
||||||
for _, keyPair := range certs {
|
if keyPair.Leaf.Subject.CommonName == sni || keyPair.Leaf.Subject.CommonName == gsni {
|
||||||
if keyPair.Leaf.Subject.CommonName == sni || keyPair.Leaf.Subject.CommonName == gsni {
|
return keyPair, nil
|
||||||
|
}
|
||||||
|
for _, name := range keyPair.Leaf.DNSNames {
|
||||||
|
if name == sni || name == gsni {
|
||||||
return keyPair, nil
|
return keyPair, nil
|
||||||
}
|
}
|
||||||
for _, name := range keyPair.Leaf.DNSNames {
|
|
||||||
if name == sni || name == gsni {
|
|
||||||
return keyPair, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if rejectUnknownSNI {
|
|
||||||
return nil, errNoCertificates
|
|
||||||
}
|
|
||||||
return certs[0], nil
|
|
||||||
}
|
}
|
||||||
|
if c.RejectUnknownSni {
|
||||||
|
return nil, errNoCertificates
|
||||||
|
}
|
||||||
|
return defaultCert, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) getClientCertificate(cri *tls.CertificateRequestInfo) (*tls.Certificate, error) {
|
||||||
|
for _, cert := range c.Certificate {
|
||||||
|
parsed := cert.getX509KeyPair()
|
||||||
|
if cert.Usage != Certificate_MTLS_CLIENT_CERT || parsed == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := cri.SupportsCertificate(parsed); err == nil {
|
||||||
|
return parsed, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errNoCertificates
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) parseServerName() string {
|
func (c *Config) parseServerName() string {
|
||||||
@@ -408,7 +422,24 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config {
|
|||||||
if len(caCerts) > 0 {
|
if len(caCerts) > 0 {
|
||||||
config.GetCertificate = getGetCertificateFunc(config, caCerts)
|
config.GetCertificate = getGetCertificateFunc(config, caCerts)
|
||||||
} else {
|
} else {
|
||||||
config.GetCertificate = getNewGetCertificateFunc(c.BuildCertificates(), c.RejectUnknownSni)
|
for _, cert := range c.Certificate {
|
||||||
|
if cert.Usage == Certificate_ENCIPHERMENT {
|
||||||
|
setupHotReload(cert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config.GetCertificate = c.getCertificate
|
||||||
|
}
|
||||||
|
if len(c.getClientCert()) > 0 {
|
||||||
|
config.GetClientCertificate = c.getClientCertificate
|
||||||
|
}
|
||||||
|
if clientCA := c.getClientCA(); len(clientCA) > 0 {
|
||||||
|
clientCAPool := x509.NewCertPool()
|
||||||
|
for _, cert := range clientCA {
|
||||||
|
if !clientCAPool.AppendCertsFromPEM(cert.Certificate) {
|
||||||
|
errors.LogError(context.Background(), errors.New("failed to append client CA certificate"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config.ClientCAs = clientCAPool
|
||||||
}
|
}
|
||||||
|
|
||||||
if sn := c.parseServerName(); len(sn) > 0 {
|
if sn := c.parseServerName(); len(sn) > 0 {
|
||||||
@@ -472,6 +503,11 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config.ClientAuth = ParseClientAuth(c.ClientAuth)
|
||||||
|
if config.ClientAuth >= tls.VerifyClientCertIfGiven && config.ClientCAs == nil {
|
||||||
|
errors.LogWarning(context.Background(), "clientAuth is set to ", c.ClientAuth, " but no client CA is provided")
|
||||||
|
}
|
||||||
|
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -517,17 +553,17 @@ func ConfigFromStreamSettings(settings *internet.MemoryStreamConfig) *Config {
|
|||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseCurveName(curveNames []string) []tls.CurveID {
|
var curveMap = map[string]tls.CurveID{
|
||||||
curveMap := map[string]tls.CurveID{
|
"curvep256": tls.CurveP256,
|
||||||
"curvep256": tls.CurveP256,
|
"curvep384": tls.CurveP384,
|
||||||
"curvep384": tls.CurveP384,
|
"curvep521": tls.CurveP521,
|
||||||
"curvep521": tls.CurveP521,
|
"x25519": tls.X25519,
|
||||||
"x25519": tls.X25519,
|
"x25519mlkem768": tls.X25519MLKEM768,
|
||||||
"x25519mlkem768": tls.X25519MLKEM768,
|
"secp256r1mlkem768": tls.SecP256r1MLKEM768,
|
||||||
"secp256r1mlkem768": tls.SecP256r1MLKEM768,
|
"secp384r1mlkem1024": tls.SecP384r1MLKEM1024,
|
||||||
"secp384r1mlkem1024": tls.SecP384r1MLKEM1024,
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
func ParseCurveName(curveNames []string) []tls.CurveID {
|
||||||
var curveIDs []tls.CurveID
|
var curveIDs []tls.CurveID
|
||||||
for _, name := range curveNames {
|
for _, name := range curveNames {
|
||||||
if curveID, ok := curveMap[strings.ToLower(name)]; ok {
|
if curveID, ok := curveMap[strings.ToLower(name)]; ok {
|
||||||
@@ -539,6 +575,25 @@ func ParseCurveName(curveNames []string) []tls.CurveID {
|
|||||||
return curveIDs
|
return curveIDs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var clientAuthMap = map[string]tls.ClientAuthType{
|
||||||
|
"noclientcert": tls.NoClientCert,
|
||||||
|
"requestclientcert": tls.RequestClientCert,
|
||||||
|
"requireanyclientcert": tls.RequireAnyClientCert,
|
||||||
|
"verifyclientcertifgiven": tls.VerifyClientCertIfGiven,
|
||||||
|
"requireandverifyclientcert": tls.RequireAndVerifyClientCert,
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseClientAuth(clientAuth string) tls.ClientAuthType {
|
||||||
|
if clientAuth == "" {
|
||||||
|
return tls.NoClientCert
|
||||||
|
}
|
||||||
|
if clientAuthType, ok := clientAuthMap[strings.ToLower(clientAuth)]; ok {
|
||||||
|
return clientAuthType
|
||||||
|
}
|
||||||
|
errors.LogWarning(context.Background(), "unsupported clientAuth: "+clientAuth)
|
||||||
|
return tls.NoClientCert
|
||||||
|
}
|
||||||
|
|
||||||
func IsFromMitm(str string) bool {
|
func IsFromMitm(str string) bool {
|
||||||
return strings.ToLower(str) == "frommitm"
|
return strings.ToLower(str) == "frommitm"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ const (
|
|||||||
Certificate_ENCIPHERMENT Certificate_Usage = 0
|
Certificate_ENCIPHERMENT Certificate_Usage = 0
|
||||||
Certificate_AUTHORITY_VERIFY Certificate_Usage = 1
|
Certificate_AUTHORITY_VERIFY Certificate_Usage = 1
|
||||||
Certificate_AUTHORITY_ISSUE Certificate_Usage = 2
|
Certificate_AUTHORITY_ISSUE Certificate_Usage = 2
|
||||||
|
Certificate_MTLS_CLIENT_CERT Certificate_Usage = 3
|
||||||
|
Certificate_MTLS_CLIENT_CA Certificate_Usage = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
// Enum value maps for Certificate_Usage.
|
// Enum value maps for Certificate_Usage.
|
||||||
@@ -36,11 +38,15 @@ var (
|
|||||||
0: "ENCIPHERMENT",
|
0: "ENCIPHERMENT",
|
||||||
1: "AUTHORITY_VERIFY",
|
1: "AUTHORITY_VERIFY",
|
||||||
2: "AUTHORITY_ISSUE",
|
2: "AUTHORITY_ISSUE",
|
||||||
|
3: "MTLS_CLIENT_CERT",
|
||||||
|
4: "MTLS_CLIENT_CA",
|
||||||
}
|
}
|
||||||
Certificate_Usage_value = map[string]int32{
|
Certificate_Usage_value = map[string]int32{
|
||||||
"ENCIPHERMENT": 0,
|
"ENCIPHERMENT": 0,
|
||||||
"AUTHORITY_VERIFY": 1,
|
"AUTHORITY_VERIFY": 1,
|
||||||
"AUTHORITY_ISSUE": 2,
|
"AUTHORITY_ISSUE": 2,
|
||||||
|
"MTLS_CLIENT_CERT": 3,
|
||||||
|
"MTLS_CLIENT_CA": 4,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -86,8 +92,10 @@ type Certificate struct {
|
|||||||
// If true, one-Time Loading
|
// If true, one-Time Loading
|
||||||
OneTimeLoading bool `protobuf:"varint,7,opt,name=One_time_loading,json=OneTimeLoading,proto3" json:"One_time_loading,omitempty"`
|
OneTimeLoading bool `protobuf:"varint,7,opt,name=One_time_loading,json=OneTimeLoading,proto3" json:"One_time_loading,omitempty"`
|
||||||
BuildChain bool `protobuf:"varint,8,opt,name=build_chain,json=buildChain,proto3" json:"build_chain,omitempty"`
|
BuildChain bool `protobuf:"varint,8,opt,name=build_chain,json=buildChain,proto3" json:"build_chain,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
// Abused proto data to storage some runtime data
|
||||||
sizeCache protoimpl.SizeCache
|
ExtraData []byte `protobuf:"bytes,9,opt,name=extra_data,json=extraData,proto3" json:"extra_data,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Certificate) Reset() {
|
func (x *Certificate) Reset() {
|
||||||
@@ -176,6 +184,13 @@ func (x *Certificate) GetBuildChain() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *Certificate) GetExtraData() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.ExtraData
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
// List of certificates to be served on server.
|
// List of certificates to be served on server.
|
||||||
@@ -206,6 +221,7 @@ type Config struct {
|
|||||||
EchConfigList string `protobuf:"bytes,19,opt,name=ech_config_list,json=echConfigList,proto3" json:"ech_config_list,omitempty"`
|
EchConfigList string `protobuf:"bytes,19,opt,name=ech_config_list,json=echConfigList,proto3" json:"ech_config_list,omitempty"`
|
||||||
EchSocketSettings *internet.SocketConfig `protobuf:"bytes,21,opt,name=ech_socket_settings,json=echSocketSettings,proto3" json:"ech_socket_settings,omitempty"`
|
EchSocketSettings *internet.SocketConfig `protobuf:"bytes,21,opt,name=ech_socket_settings,json=echSocketSettings,proto3" json:"ech_socket_settings,omitempty"`
|
||||||
PinnedPeerCertSha256 [][]byte `protobuf:"bytes,22,rep,name=pinned_peer_cert_sha256,json=pinnedPeerCertSha256,proto3" json:"pinned_peer_cert_sha256,omitempty"`
|
PinnedPeerCertSha256 [][]byte `protobuf:"bytes,22,rep,name=pinned_peer_cert_sha256,json=pinnedPeerCertSha256,proto3" json:"pinned_peer_cert_sha256,omitempty"`
|
||||||
|
ClientAuth string `protobuf:"bytes,23,opt,name=client_auth,json=clientAuth,proto3" json:"client_auth,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -359,11 +375,18 @@ func (x *Config) GetPinnedPeerCertSha256() [][]byte {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetClientAuth() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ClientAuth
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
var File_transport_internet_tls_config_proto protoreflect.FileDescriptor
|
var File_transport_internet_tls_config_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
const file_transport_internet_tls_config_proto_rawDesc = "" +
|
const file_transport_internet_tls_config_proto_rawDesc = "" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"#transport/internet/tls/config.proto\x12\x1bxray.transport.internet.tls\x1a\x1ftransport/internet/config.proto\"\x83\x03\n" +
|
"#transport/internet/tls/config.proto\x12\x1bxray.transport.internet.tls\x1a\x1ftransport/internet/config.proto\"\xcc\x03\n" +
|
||||||
"\vCertificate\x12 \n" +
|
"\vCertificate\x12 \n" +
|
||||||
"\vcertificate\x18\x01 \x01(\fR\vcertificate\x12\x10\n" +
|
"\vcertificate\x18\x01 \x01(\fR\vcertificate\x12\x10\n" +
|
||||||
"\x03key\x18\x02 \x01(\fR\x03key\x12D\n" +
|
"\x03key\x18\x02 \x01(\fR\x03key\x12D\n" +
|
||||||
@@ -373,11 +396,15 @@ const file_transport_internet_tls_config_proto_rawDesc = "" +
|
|||||||
"\bkey_path\x18\x06 \x01(\tR\akeyPath\x12(\n" +
|
"\bkey_path\x18\x06 \x01(\tR\akeyPath\x12(\n" +
|
||||||
"\x10One_time_loading\x18\a \x01(\bR\x0eOneTimeLoading\x12\x1f\n" +
|
"\x10One_time_loading\x18\a \x01(\bR\x0eOneTimeLoading\x12\x1f\n" +
|
||||||
"\vbuild_chain\x18\b \x01(\bR\n" +
|
"\vbuild_chain\x18\b \x01(\bR\n" +
|
||||||
"buildChain\"D\n" +
|
"buildChain\x12\x1d\n" +
|
||||||
|
"\n" +
|
||||||
|
"extra_data\x18\t \x01(\fR\textraData\"n\n" +
|
||||||
"\x05Usage\x12\x10\n" +
|
"\x05Usage\x12\x10\n" +
|
||||||
"\fENCIPHERMENT\x10\x00\x12\x14\n" +
|
"\fENCIPHERMENT\x10\x00\x12\x14\n" +
|
||||||
"\x10AUTHORITY_VERIFY\x10\x01\x12\x13\n" +
|
"\x10AUTHORITY_VERIFY\x10\x01\x12\x13\n" +
|
||||||
"\x0fAUTHORITY_ISSUE\x10\x02\"\xa6\x06\n" +
|
"\x0fAUTHORITY_ISSUE\x10\x02\x12\x14\n" +
|
||||||
|
"\x10MTLS_CLIENT_CERT\x10\x03\x12\x12\n" +
|
||||||
|
"\x0eMTLS_CLIENT_CA\x10\x04\"\xc7\x06\n" +
|
||||||
"\x06Config\x12J\n" +
|
"\x06Config\x12J\n" +
|
||||||
"\vcertificate\x18\x02 \x03(\v2(.xray.transport.internet.tls.CertificateR\vcertificate\x12\x1f\n" +
|
"\vcertificate\x18\x02 \x03(\v2(.xray.transport.internet.tls.CertificateR\vcertificate\x12\x1f\n" +
|
||||||
"\vserver_name\x18\x03 \x01(\tR\n" +
|
"\vserver_name\x18\x03 \x01(\tR\n" +
|
||||||
@@ -398,7 +425,9 @@ const file_transport_internet_tls_config_proto_rawDesc = "" +
|
|||||||
"\x0fech_server_keys\x18\x12 \x01(\fR\rechServerKeys\x12&\n" +
|
"\x0fech_server_keys\x18\x12 \x01(\fR\rechServerKeys\x12&\n" +
|
||||||
"\x0fech_config_list\x18\x13 \x01(\tR\rechConfigList\x12U\n" +
|
"\x0fech_config_list\x18\x13 \x01(\tR\rechConfigList\x12U\n" +
|
||||||
"\x13ech_socket_settings\x18\x15 \x01(\v2%.xray.transport.internet.SocketConfigR\x11echSocketSettings\x125\n" +
|
"\x13ech_socket_settings\x18\x15 \x01(\v2%.xray.transport.internet.SocketConfigR\x11echSocketSettings\x125\n" +
|
||||||
"\x17pinned_peer_cert_sha256\x18\x16 \x03(\fR\x14pinnedPeerCertSha256Bs\n" +
|
"\x17pinned_peer_cert_sha256\x18\x16 \x03(\fR\x14pinnedPeerCertSha256\x12\x1f\n" +
|
||||||
|
"\vclient_auth\x18\x17 \x01(\tR\n" +
|
||||||
|
"clientAuthBs\n" +
|
||||||
"\x1fcom.xray.transport.internet.tlsP\x01Z0github.com/xtls/xray-core/transport/internet/tls\xaa\x02\x1bXray.Transport.Internet.Tlsb\x06proto3"
|
"\x1fcom.xray.transport.internet.tlsP\x01Z0github.com/xtls/xray-core/transport/internet/tls\xaa\x02\x1bXray.Transport.Internet.Tlsb\x06proto3"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ message Certificate {
|
|||||||
ENCIPHERMENT = 0;
|
ENCIPHERMENT = 0;
|
||||||
AUTHORITY_VERIFY = 1;
|
AUTHORITY_VERIFY = 1;
|
||||||
AUTHORITY_ISSUE = 2;
|
AUTHORITY_ISSUE = 2;
|
||||||
|
MTLS_CLIENT_CERT = 3;
|
||||||
|
MTLS_CLIENT_CA = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
Usage usage = 3;
|
Usage usage = 3;
|
||||||
@@ -35,6 +37,9 @@ message Certificate {
|
|||||||
bool One_time_loading = 7;
|
bool One_time_loading = 7;
|
||||||
|
|
||||||
bool build_chain = 8;
|
bool build_chain = 8;
|
||||||
|
|
||||||
|
// Abused proto data to storage some runtime data
|
||||||
|
bytes extra_data = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Config {
|
message Config {
|
||||||
@@ -86,4 +91,6 @@ message Config {
|
|||||||
SocketConfig ech_socket_settings = 21;
|
SocketConfig ech_socket_settings = 21;
|
||||||
|
|
||||||
repeated bytes pinned_peer_cert_sha256 = 22;
|
repeated bytes pinned_peer_cert_sha256 = 22;
|
||||||
|
|
||||||
|
string client_auth = 23;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ func (c *Config) getCertPool() (*x509.CertPool, error) {
|
|||||||
return nil, errors.New("system root").AtWarning().Base(err)
|
return nil, errors.New("system root").AtWarning().Base(err)
|
||||||
}
|
}
|
||||||
for _, cert := range c.Certificate {
|
for _, cert := range c.Certificate {
|
||||||
|
if cert.Usage != Certificate_AUTHORITY_VERIFY {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if !pool.AppendCertsFromPEM(cert.Certificate) {
|
if !pool.AppendCertsFromPEM(cert.Certificate) {
|
||||||
return nil, errors.New("append cert to root").AtWarning().Base(err)
|
return nil, errors.New("append cert to root").AtWarning().Base(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ func QueryRecord(domain string, server string, sockopt *internet.SocketConfig) (
|
|||||||
// If expire is zero value, it means we are in initial state, wait for the query to finish
|
// If expire is zero value, it means we are in initial state, wait for the query to finish
|
||||||
// otherwise return old value immediately and update in a goroutine
|
// otherwise return old value immediately and update in a goroutine
|
||||||
// but if the cache is too old, wait for update
|
// but if the cache is too old, wait for update
|
||||||
if configRecord.expire == (time.Time{}) || configRecord.expire.Add(time.Hour*4).Before(time.Now()) {
|
if configRecord.expire.Equal(time.Time{}) || configRecord.expire.Add(time.Hour*4).Before(time.Now()) {
|
||||||
return echConfigCache.Update(domain, server, false, sockopt)
|
return echConfigCache.Update(domain, server, false, sockopt)
|
||||||
} else {
|
} else {
|
||||||
// If someone already acquired the lock, it means it is updating, do not start another update goroutine
|
// If someone already acquired the lock, it means it is updating, do not start another update goroutine
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"slices"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/ocsp"
|
||||||
|
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||||
|
"github.com/xtls/xray-core/common/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var certsCache = utils.NewWeakCacheMap[uintptr, Certificate]()
|
||||||
|
|
||||||
|
var startHotReload sync.Once
|
||||||
|
|
||||||
|
func setupHotReload(entry *Certificate) {
|
||||||
|
startHotReload.Do(func() {
|
||||||
|
go handleHotReload()
|
||||||
|
})
|
||||||
|
// ensure the cache before use
|
||||||
|
entry.getX509KeyPair()
|
||||||
|
if entry.OneTimeLoading {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uptr := uintptr(unsafe.Pointer(entry))
|
||||||
|
if _, ok := certsCache.Load(uptr); !ok {
|
||||||
|
certsCache.Store(uptr, entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleHotReload() {
|
||||||
|
// should be enough?
|
||||||
|
t := time.NewTicker(600 * time.Second)
|
||||||
|
for {
|
||||||
|
certsCache.Range(updateCert)
|
||||||
|
<-t.C
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateCert(_ uintptr, entry *Certificate) bool {
|
||||||
|
extraData := entry.extraData()
|
||||||
|
reloadInterval := int64(entry.OcspStapling)
|
||||||
|
if reloadInterval <= 0 {
|
||||||
|
reloadInterval = 3600
|
||||||
|
}
|
||||||
|
if extraData.lastReload+reloadInterval >= time.Now().Unix() {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
extraData.lastReload = time.Now().Unix()
|
||||||
|
}
|
||||||
|
if entry.CertificatePath != "" && entry.KeyPath != "" {
|
||||||
|
newCert, err := filesystem.ReadCert(entry.CertificatePath)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogErrorInner(context.Background(), err, "failed to parse certificate")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
newKey, err := filesystem.ReadCert(entry.KeyPath)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogErrorInner(context.Background(), err, "failed to parse key")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if string(newCert) != string(entry.Certificate) || string(newKey) != string(entry.Key) {
|
||||||
|
entry.Certificate = newCert
|
||||||
|
entry.Key = newKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entry.parseX509KeyPair()
|
||||||
|
if entry.OcspStapling > 0 {
|
||||||
|
keyPair := entry.getX509KeyPair()
|
||||||
|
if keyPair == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if newOCSPData, err := ocsp.GetOCSPForCert(keyPair.Certificate); err != nil {
|
||||||
|
errors.LogWarningInner(context.Background(), err, "ignoring invalid OCSP")
|
||||||
|
} else if OCSPData := extraData.ocspData.Load(); OCSPData == nil || !slices.Equal(newOCSPData, *OCSPData) {
|
||||||
|
extraData.ocspData.Store(&newOCSPData)
|
||||||
|
}
|
||||||
|
entry.parseX509KeyPair()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
@@ -146,6 +146,45 @@ func GeneraticUClient(c net.Conn, config *tls.Config) *utls.UConn {
|
|||||||
return utls.UClient(c, copyConfig(config), utls.HelloChrome_Auto)
|
return utls.UClient(c, copyConfig(config), utls.HelloChrome_Auto)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adapt a crypto/tls GetClientCertificate callback to the utls signature.
|
||||||
|
func uGetClientCertificate(originFunc func(*tls.CertificateRequestInfo) (*tls.Certificate, error)) func(*utls.CertificateRequestInfo) (*utls.Certificate, error) {
|
||||||
|
if originFunc == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return func(info *utls.CertificateRequestInfo) (*utls.Certificate, error) {
|
||||||
|
schemes := make([]tls.SignatureScheme, len(info.SignatureSchemes))
|
||||||
|
for i, s := range info.SignatureSchemes {
|
||||||
|
schemes[i] = tls.SignatureScheme(s)
|
||||||
|
}
|
||||||
|
cert, err := originFunc(&tls.CertificateRequestInfo{
|
||||||
|
AcceptableCAs: info.AcceptableCAs,
|
||||||
|
SignatureSchemes: schemes,
|
||||||
|
Version: info.Version,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if cert == nil {
|
||||||
|
return &utls.Certificate{}, nil
|
||||||
|
}
|
||||||
|
var uSchemes []utls.SignatureScheme
|
||||||
|
if cert.SupportedSignatureAlgorithms != nil {
|
||||||
|
uSchemes = make([]utls.SignatureScheme, len(cert.SupportedSignatureAlgorithms))
|
||||||
|
for i, s := range cert.SupportedSignatureAlgorithms {
|
||||||
|
uSchemes[i] = utls.SignatureScheme(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &utls.Certificate{
|
||||||
|
Certificate: cert.Certificate,
|
||||||
|
PrivateKey: cert.PrivateKey,
|
||||||
|
SupportedSignatureAlgorithms: uSchemes,
|
||||||
|
OCSPStaple: cert.OCSPStaple,
|
||||||
|
SignedCertificateTimestamps: cert.SignedCertificateTimestamps,
|
||||||
|
Leaf: cert.Leaf,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func copyConfig(c *tls.Config) *utls.Config {
|
func copyConfig(c *tls.Config) *utls.Config {
|
||||||
config := &utls.Config{
|
config := &utls.Config{
|
||||||
Rand: c.Rand,
|
Rand: c.Rand,
|
||||||
@@ -156,6 +195,7 @@ func copyConfig(c *tls.Config) *utls.Config {
|
|||||||
KeyLogWriter: c.KeyLogWriter,
|
KeyLogWriter: c.KeyLogWriter,
|
||||||
EncryptedClientHelloConfigList: c.EncryptedClientHelloConfigList,
|
EncryptedClientHelloConfigList: c.EncryptedClientHelloConfigList,
|
||||||
NextProtos: c.NextProtos,
|
NextProtos: c.NextProtos,
|
||||||
|
GetClientCertificate: uGetClientCertificate(c.GetClientCertificate),
|
||||||
}
|
}
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user