mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-07-03 02:08:45 +00:00
Add test & support utls
This commit is contained in:
@@ -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 {
|
||||
return func(c *x509.Certificate) {
|
||||
c.Subject.Organization = []string{org}
|
||||
|
||||
@@ -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) {
|
||||
if runtime.GOOS == "windows" {
|
||||
// Not supported on Windows yet.
|
||||
|
||||
@@ -146,6 +146,45 @@ func GeneraticUClient(c net.Conn, config *tls.Config) *utls.UConn {
|
||||
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 {
|
||||
config := &utls.Config{
|
||||
Rand: c.Rand,
|
||||
@@ -156,6 +195,7 @@ func copyConfig(c *tls.Config) *utls.Config {
|
||||
KeyLogWriter: c.KeyLogWriter,
|
||||
EncryptedClientHelloConfigList: c.EncryptedClientHelloConfigList,
|
||||
NextProtos: c.NextProtos,
|
||||
GetClientCertificate: uGetClientCertificate(c.GetClientCertificate),
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user