mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-14 04:38:28 +10:00
206 lines
5.4 KiB
Go
206 lines
5.4 KiB
Go
//go:build darwin && cgo
|
|
|
|
package tls
|
|
|
|
import (
|
|
"context"
|
|
stdtls "crypto/tls"
|
|
"net"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/sagernet/sing-box/option"
|
|
"github.com/sagernet/sing/common/json/badoption"
|
|
"github.com/sagernet/sing/common/logger"
|
|
)
|
|
|
|
const appleTLSTestTimeout = 5 * time.Second
|
|
|
|
type appleTLSServerResult struct {
|
|
state stdtls.ConnectionState
|
|
err error
|
|
}
|
|
|
|
func TestAppleClientHandshakeAppliesALPNAndVersion(t *testing.T) {
|
|
serverCertificate, serverCertificatePEM := newAppleTestCertificate(t, "localhost")
|
|
serverResult, serverAddress := startAppleTLSTestServer(t, &stdtls.Config{
|
|
Certificates: []stdtls.Certificate{serverCertificate},
|
|
MinVersion: stdtls.VersionTLS12,
|
|
MaxVersion: stdtls.VersionTLS12,
|
|
NextProtos: []string{"h2"},
|
|
})
|
|
|
|
clientConn, err := newAppleTestClientConn(t, serverAddress, option.OutboundTLSOptions{
|
|
Enabled: true,
|
|
Engine: "apple",
|
|
ServerName: "localhost",
|
|
MinVersion: "1.2",
|
|
MaxVersion: "1.2",
|
|
ALPN: badoption.Listable[string]{"h2"},
|
|
Certificate: badoption.Listable[string]{serverCertificatePEM},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer clientConn.Close()
|
|
|
|
clientState := clientConn.ConnectionState()
|
|
if clientState.Version != stdtls.VersionTLS12 {
|
|
t.Fatalf("unexpected negotiated version: %x", clientState.Version)
|
|
}
|
|
if clientState.NegotiatedProtocol != "h2" {
|
|
t.Fatalf("unexpected negotiated protocol: %q", clientState.NegotiatedProtocol)
|
|
}
|
|
|
|
result := <-serverResult
|
|
if result.err != nil {
|
|
t.Fatal(result.err)
|
|
}
|
|
if result.state.Version != stdtls.VersionTLS12 {
|
|
t.Fatalf("server negotiated unexpected version: %x", result.state.Version)
|
|
}
|
|
if result.state.NegotiatedProtocol != "h2" {
|
|
t.Fatalf("server negotiated unexpected protocol: %q", result.state.NegotiatedProtocol)
|
|
}
|
|
}
|
|
|
|
func TestAppleClientHandshakeRejectsVersionMismatch(t *testing.T) {
|
|
serverCertificate, serverCertificatePEM := newAppleTestCertificate(t, "localhost")
|
|
serverResult, serverAddress := startAppleTLSTestServer(t, &stdtls.Config{
|
|
Certificates: []stdtls.Certificate{serverCertificate},
|
|
MinVersion: stdtls.VersionTLS13,
|
|
MaxVersion: stdtls.VersionTLS13,
|
|
})
|
|
|
|
clientConn, err := newAppleTestClientConn(t, serverAddress, option.OutboundTLSOptions{
|
|
Enabled: true,
|
|
Engine: "apple",
|
|
ServerName: "localhost",
|
|
MaxVersion: "1.2",
|
|
Certificate: badoption.Listable[string]{serverCertificatePEM},
|
|
})
|
|
if err == nil {
|
|
clientConn.Close()
|
|
t.Fatal("expected version mismatch handshake to fail")
|
|
}
|
|
|
|
if result := <-serverResult; result.err == nil {
|
|
t.Fatal("expected server handshake to fail on version mismatch")
|
|
}
|
|
}
|
|
|
|
func TestAppleClientHandshakeRejectsServerNameMismatch(t *testing.T) {
|
|
serverCertificate, serverCertificatePEM := newAppleTestCertificate(t, "localhost")
|
|
serverResult, serverAddress := startAppleTLSTestServer(t, &stdtls.Config{
|
|
Certificates: []stdtls.Certificate{serverCertificate},
|
|
})
|
|
|
|
clientConn, err := newAppleTestClientConn(t, serverAddress, option.OutboundTLSOptions{
|
|
Enabled: true,
|
|
Engine: "apple",
|
|
ServerName: "example.com",
|
|
Certificate: badoption.Listable[string]{serverCertificatePEM},
|
|
})
|
|
if err == nil {
|
|
clientConn.Close()
|
|
t.Fatal("expected server name mismatch handshake to fail")
|
|
}
|
|
|
|
if result := <-serverResult; result.err == nil {
|
|
t.Fatal("expected server handshake to fail on server name mismatch")
|
|
}
|
|
}
|
|
|
|
func newAppleTestCertificate(t *testing.T, serverName string) (stdtls.Certificate, string) {
|
|
t.Helper()
|
|
|
|
privateKeyPEM, certificatePEM, err := GenerateCertificate(nil, nil, time.Now, serverName, time.Now().Add(time.Hour))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
certificate, err := stdtls.X509KeyPair(certificatePEM, privateKeyPEM)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return certificate, string(certificatePEM)
|
|
}
|
|
|
|
func startAppleTLSTestServer(t *testing.T, tlsConfig *stdtls.Config) (<-chan appleTLSServerResult, string) {
|
|
t.Helper()
|
|
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() {
|
|
listener.Close()
|
|
})
|
|
|
|
if tcpListener, isTCP := listener.(*net.TCPListener); isTCP {
|
|
err = tcpListener.SetDeadline(time.Now().Add(appleTLSTestTimeout))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
result := make(chan appleTLSServerResult, 1)
|
|
go func() {
|
|
defer close(result)
|
|
|
|
conn, err := listener.Accept()
|
|
if err != nil {
|
|
result <- appleTLSServerResult{err: err}
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
|
|
err = conn.SetDeadline(time.Now().Add(appleTLSTestTimeout))
|
|
if err != nil {
|
|
result <- appleTLSServerResult{err: err}
|
|
return
|
|
}
|
|
|
|
tlsConn := stdtls.Server(conn, tlsConfig)
|
|
defer tlsConn.Close()
|
|
|
|
err = tlsConn.Handshake()
|
|
if err != nil {
|
|
result <- appleTLSServerResult{err: err}
|
|
return
|
|
}
|
|
|
|
result <- appleTLSServerResult{state: tlsConn.ConnectionState()}
|
|
}()
|
|
|
|
return result, listener.Addr().String()
|
|
}
|
|
|
|
func newAppleTestClientConn(t *testing.T, serverAddress string, options option.OutboundTLSOptions) (Conn, error) {
|
|
t.Helper()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), appleTLSTestTimeout)
|
|
t.Cleanup(cancel)
|
|
|
|
clientConfig, err := NewClientWithOptions(ClientOptions{
|
|
Context: ctx,
|
|
Logger: logger.NOP(),
|
|
ServerAddress: "",
|
|
Options: options,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
conn, err := net.DialTimeout("tcp", serverAddress, appleTLSTestTimeout)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tlsConn, err := ClientHandshake(ctx, conn, clientConfig)
|
|
if err != nil {
|
|
conn.Close()
|
|
return nil, err
|
|
}
|
|
return tlsConn, nil
|
|
}
|