Files
sing-box-extended-mirror/common/tls/windows_client_test.go
T

2506 lines
65 KiB
Go
Raw Normal View History

2026-04-24 09:04:27 +08:00
//go:build windows
package tls
import (
"bytes"
"context"
"crypto/sha256"
stdtls "crypto/tls"
"crypto/x509"
"errors"
"io"
"net"
"os"
"strings"
"sync"
"testing"
"time"
"github.com/sagernet/sing-box/common/schannel"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/json/badoption"
"github.com/sagernet/sing/common/logger"
N "github.com/sagernet/sing/common/network"
)
const windowsTLSTestTimeout = 5 * time.Second
var (
_ N.ExtendedConn = (*windowsTLSConn)(nil)
_ N.ReadWaitCreator = (*windowsTLSConn)(nil)
)
func newTestWindowsTLSConn(rawConn net.Conn) *windowsTLSConn {
return &windowsTLSConn{rawConn: rawConn}
}
// writePostHandshakeReply wraps writePostHandshakeReplyLocked with the
// writeAccess locking and auto-close behavior that drivePostHandshake
// composes from beginPostHandshakeWrite/finishPostHandshakeWrite plus the
// writeFailed → Close branch.
// Kept here as a test seam.
func (c *windowsTLSConn) writePostHandshakeReply(data []byte) error {
c.writeAccess.Lock()
defer c.writeAccess.Unlock()
err := c.writePostHandshakeReplyLocked(data)
if err != nil {
_ = c.Close()
}
return err
}
type windowsTLSServerResult struct {
state stdtls.ConnectionState
err error
}
type windowsTestDeadlineConn struct {
access sync.Mutex
readCalled chan struct{}
writeCalled chan struct{}
readCalledOnce sync.Once
writeCalledOnce sync.Once
readDeadline time.Time
writeDeadline time.Time
readDeadlines []time.Time
writeDeadlines []time.Time
}
type windowsTestWriteGateConn struct {
writeCalled chan struct{}
releaseWrite chan struct{}
}
type windowsTestIOConn struct {
access sync.Mutex
readErr error
writeErr error
writeN int
writeCalls int
closed bool
}
func (c *windowsTestDeadlineConn) Read(_ []byte) (int, error) {
if c.readCalled != nil {
c.readCalledOnce.Do(func() {
close(c.readCalled)
})
}
for {
c.access.Lock()
deadline := c.readDeadline
c.access.Unlock()
if deadline.IsZero() {
time.Sleep(5 * time.Millisecond)
continue
}
if !deadline.After(time.Now()) {
return 0, os.ErrDeadlineExceeded
}
time.Sleep(time.Until(deadline))
return 0, os.ErrDeadlineExceeded
}
}
func (c *windowsTestDeadlineConn) Write(_ []byte) (int, error) {
if c.writeCalled != nil {
c.writeCalledOnce.Do(func() {
close(c.writeCalled)
})
}
for {
c.access.Lock()
deadline := c.writeDeadline
c.access.Unlock()
if deadline.IsZero() {
time.Sleep(5 * time.Millisecond)
continue
}
if !deadline.After(time.Now()) {
return 0, os.ErrDeadlineExceeded
}
time.Sleep(time.Until(deadline))
return 0, os.ErrDeadlineExceeded
}
}
func (c *windowsTestDeadlineConn) Close() error {
return nil
}
func (c *windowsTestDeadlineConn) LocalAddr() net.Addr {
return windowsTestAddr("local")
}
func (c *windowsTestDeadlineConn) RemoteAddr() net.Addr {
return windowsTestAddr("remote")
}
func (c *windowsTestDeadlineConn) SetDeadline(t time.Time) error {
c.access.Lock()
c.readDeadline = t
c.writeDeadline = t
c.readDeadlines = append(c.readDeadlines, t)
c.writeDeadlines = append(c.writeDeadlines, t)
c.access.Unlock()
return nil
}
func (c *windowsTestDeadlineConn) SetReadDeadline(t time.Time) error {
c.access.Lock()
c.readDeadline = t
c.readDeadlines = append(c.readDeadlines, t)
c.access.Unlock()
return nil
}
func (c *windowsTestDeadlineConn) SetWriteDeadline(t time.Time) error {
c.access.Lock()
c.writeDeadline = t
c.writeDeadlines = append(c.writeDeadlines, t)
c.access.Unlock()
return nil
}
func (c *windowsTestDeadlineConn) recordedWriteDeadlines() []time.Time {
c.access.Lock()
defer c.access.Unlock()
return append([]time.Time(nil), c.writeDeadlines...)
}
func (c *windowsTestDeadlineConn) recordedReadDeadlines() []time.Time {
c.access.Lock()
defer c.access.Unlock()
return append([]time.Time(nil), c.readDeadlines...)
}
func (c *windowsTestWriteGateConn) Read(_ []byte) (int, error) {
return 0, io.EOF
}
func (c *windowsTestWriteGateConn) Write(p []byte) (int, error) {
close(c.writeCalled)
<-c.releaseWrite
return len(p), nil
}
func (c *windowsTestWriteGateConn) Close() error {
return nil
}
func (c *windowsTestWriteGateConn) LocalAddr() net.Addr {
return windowsTestAddr("local")
}
func (c *windowsTestWriteGateConn) RemoteAddr() net.Addr {
return windowsTestAddr("remote")
}
func (c *windowsTestWriteGateConn) SetDeadline(time.Time) error {
return nil
}
func (c *windowsTestWriteGateConn) SetReadDeadline(time.Time) error {
return nil
}
func (c *windowsTestWriteGateConn) SetWriteDeadline(time.Time) error {
return nil
}
func (c *windowsTestIOConn) Read(_ []byte) (int, error) {
return 0, c.readErr
}
func (c *windowsTestIOConn) Write(p []byte) (int, error) {
c.access.Lock()
defer c.access.Unlock()
c.writeCalls++
if c.writeErr == nil {
return len(p), nil
}
n := c.writeN
if n <= 0 || n > len(p) {
n = 0
}
return n, c.writeErr
}
func (c *windowsTestIOConn) Close() error {
c.access.Lock()
c.closed = true
c.access.Unlock()
return nil
}
func (c *windowsTestIOConn) LocalAddr() net.Addr {
return windowsTestAddr("local")
}
func (c *windowsTestIOConn) RemoteAddr() net.Addr {
return windowsTestAddr("remote")
}
func (c *windowsTestIOConn) SetDeadline(time.Time) error {
return nil
}
func (c *windowsTestIOConn) SetReadDeadline(time.Time) error {
return nil
}
func (c *windowsTestIOConn) SetWriteDeadline(time.Time) error {
return nil
}
func (c *windowsTestIOConn) isClosed() bool {
c.access.Lock()
defer c.access.Unlock()
return c.closed
}
func (c *windowsTestIOConn) totalWriteCalls() int {
c.access.Lock()
defer c.access.Unlock()
return c.writeCalls
}
type windowsTestAddr string
func (a windowsTestAddr) Network() string {
return "test"
}
func (a windowsTestAddr) String() string {
return string(a)
}
type windowsOpaqueConn struct {
net.Conn
}
func TestWindowsClientHandshakeTLS12(t *testing.T) {
serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost")
serverResult, serverAddress := startWindowsTLSTestServer(t, &stdtls.Config{
Certificates: []stdtls.Certificate{serverCertificate},
MinVersion: stdtls.VersionTLS12,
MaxVersion: stdtls.VersionTLS12,
NextProtos: []string{"h2"},
})
clientConn, err := newWindowsTestClientConn(t, serverAddress, option.OutboundTLSOptions{
Enabled: true,
Engine: C.TLSEngineWindows,
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)
}
if !clientState.HandshakeComplete {
t.Fatal("HandshakeComplete is false")
}
if len(clientState.PeerCertificates) == 0 {
t.Fatal("no peer certificates")
}
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 TestWindowsClientHandshakeWrappedConn(t *testing.T) {
serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost")
serverResult, serverAddress := startWindowsTLSTestServer(t, &stdtls.Config{
Certificates: []stdtls.Certificate{serverCertificate},
MinVersion: stdtls.VersionTLS12,
MaxVersion: stdtls.VersionTLS12,
})
ctx, cancel := context.WithTimeout(context.Background(), windowsTLSTestTimeout)
t.Cleanup(cancel)
clientConfig, err := NewClientWithOptions(ClientOptions{
Context: ctx,
Logger: logger.NOP(),
Options: option.OutboundTLSOptions{
Enabled: true,
Engine: C.TLSEngineWindows,
ServerName: "localhost",
MinVersion: "1.2",
Certificate: badoption.Listable[string]{serverCertificatePEM},
},
})
if err != nil {
t.Fatal(err)
}
rawConn, err := net.DialTimeout(N.NetworkTCP, serverAddress, windowsTLSTestTimeout)
if err != nil {
t.Fatal(err)
}
tlsConn, err := ClientHandshake(ctx, windowsOpaqueConn{Conn: rawConn}, clientConfig)
if err != nil {
rawConn.Close()
t.Fatal(err)
}
_ = tlsConn.Close()
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)
}
}
func TestWindowsClientHandshakeTLS13(t *testing.T) {
serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost")
serverResult, serverAddress := startWindowsTLSTestServer(t, &stdtls.Config{
Certificates: []stdtls.Certificate{serverCertificate},
MinVersion: stdtls.VersionTLS13,
MaxVersion: stdtls.VersionTLS13,
NextProtos: []string{"h2"},
})
clientConn, err := newWindowsTestClientConn(t, serverAddress, option.OutboundTLSOptions{
Enabled: true,
Engine: C.TLSEngineWindows,
ServerName: "localhost",
MinVersion: "1.3",
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.VersionTLS13 {
t.Fatalf("expected TLS 1.3, got %x", clientState.Version)
}
if clientState.NegotiatedProtocol != "h2" {
t.Fatalf("expected negotiated protocol h2, got %q", clientState.NegotiatedProtocol)
}
result := <-serverResult
if result.err != nil {
t.Fatal(result.err)
}
if result.state.Version != stdtls.VersionTLS13 {
t.Fatalf("server negotiated unexpected version: %x", result.state.Version)
}
}
func TestWindowsClientHandshakeALPNNoOverlap(t *testing.T) {
serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost")
_, serverAddress := startWindowsTLSTestServer(t, &stdtls.Config{
Certificates: []stdtls.Certificate{serverCertificate},
MinVersion: stdtls.VersionTLS12,
MaxVersion: stdtls.VersionTLS12,
NextProtos: []string{"http/1.1"},
})
clientConn, err := newWindowsTestClientConn(t, serverAddress, option.OutboundTLSOptions{
Enabled: true,
Engine: C.TLSEngineWindows,
ServerName: "localhost",
MinVersion: "1.2",
MaxVersion: "1.2",
ALPN: badoption.Listable[string]{"h2"},
Certificate: badoption.Listable[string]{serverCertificatePEM},
})
// Go's TLS server returns a TLS alert when the client advertises ALPN but
// the server has no overlap. The handshake fails.
if err == nil {
_ = clientConn.Close()
t.Fatal("expected handshake to fail with no ALPN overlap")
}
}
func TestWindowsClientHandshakeMultipleALPN(t *testing.T) {
serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost")
_, serverAddress := startWindowsTLSTestServer(t, &stdtls.Config{
Certificates: []stdtls.Certificate{serverCertificate},
MinVersion: stdtls.VersionTLS12,
NextProtos: []string{"h2", "http/1.1"},
})
clientConn, err := newWindowsTestClientConn(t, serverAddress, option.OutboundTLSOptions{
Enabled: true,
Engine: C.TLSEngineWindows,
ServerName: "localhost",
MinVersion: "1.2",
ALPN: badoption.Listable[string]{"spdy/3", "h2"},
Certificate: badoption.Listable[string]{serverCertificatePEM},
})
if err != nil {
t.Fatal(err)
}
defer clientConn.Close()
// Schannel follows the standard selection: first protocol offered by
// the client that the server also supports. Here: spdy/3 is not in the
// server list but h2 is, so h2 wins.
if got := clientConn.ConnectionState().NegotiatedProtocol; got != "h2" {
t.Fatalf("expected h2, got %q", got)
}
}
func TestWindowsClientHandshakeRejectsVersionMismatch(t *testing.T) {
serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost")
serverResult, serverAddress := startWindowsTLSTestServer(t, &stdtls.Config{
Certificates: []stdtls.Certificate{serverCertificate},
MinVersion: stdtls.VersionTLS13,
MaxVersion: stdtls.VersionTLS13,
})
clientConn, err := newWindowsTestClientConn(t, serverAddress, option.OutboundTLSOptions{
Enabled: true,
Engine: C.TLSEngineWindows,
ServerName: "localhost",
MaxVersion: "1.2",
Certificate: badoption.Listable[string]{serverCertificatePEM},
})
if err == nil {
clientConn.Close()
t.Fatal("expected version mismatch handshake to fail")
}
result := <-serverResult
if result.err == nil {
t.Fatal("expected server handshake to fail on version mismatch")
}
}
func TestWindowsClientHandshakeRejectsServerNameMismatch(t *testing.T) {
serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost")
_, serverAddress := startWindowsTLSTestServer(t, &stdtls.Config{
Certificates: []stdtls.Certificate{serverCertificate},
})
clientConn, err := newWindowsTestClientConn(t, serverAddress, option.OutboundTLSOptions{
Enabled: true,
Engine: C.TLSEngineWindows,
ServerName: "example.com",
Certificate: badoption.Listable[string]{serverCertificatePEM},
})
if err == nil {
clientConn.Close()
t.Fatal("expected server name mismatch handshake to fail")
}
}
func TestWindowsClientHandshakeRejectsUntrustedCA(t *testing.T) {
serverCertificate, _ := newWindowsTestCertificate(t, "localhost")
_, serverAddress := startWindowsTLSTestServer(t, &stdtls.Config{
Certificates: []stdtls.Certificate{serverCertificate},
})
clientConn, err := newWindowsTestClientConn(t, serverAddress, option.OutboundTLSOptions{
Enabled: true,
Engine: C.TLSEngineWindows,
ServerName: "localhost",
})
if err == nil {
clientConn.Close()
t.Fatal("expected untrusted CA handshake to fail")
}
}
func TestWindowsClientHandshakeInsecureSkipsValidation(t *testing.T) {
serverCertificate, _ := newWindowsTestCertificate(t, "localhost")
_, serverAddress := startWindowsTLSTestServer(t, &stdtls.Config{
Certificates: []stdtls.Certificate{serverCertificate},
})
// Server name mismatch but insecure=true → handshake succeeds.
clientConn, err := newWindowsTestClientConn(t, serverAddress, option.OutboundTLSOptions{
Enabled: true,
Engine: C.TLSEngineWindows,
ServerName: "example.com",
Insecure: true,
})
if err != nil {
t.Fatal(err)
}
defer clientConn.Close()
if !clientConn.ConnectionState().HandshakeComplete {
t.Fatal("expected handshake to complete with insecure=true")
}
}
func TestWindowsClientHandshakeHonorsPublicKeyPinSuccess(t *testing.T) {
serverCertificate, _ := newWindowsTestCertificate(t, "localhost")
pin := publicKeyPin(t, serverCertificate.Leaf)
_, serverAddress := startWindowsTLSTestServer(t, &stdtls.Config{
Certificates: []stdtls.Certificate{serverCertificate},
})
clientConn, err := newWindowsTestClientConn(t, serverAddress, option.OutboundTLSOptions{
Enabled: true,
Engine: C.TLSEngineWindows,
ServerName: "localhost",
CertificatePublicKeySHA256: [][]byte{pin},
})
if err != nil {
t.Fatal(err)
}
defer clientConn.Close()
}
func TestWindowsClientHandshakeHonorsPublicKeyPinFailure(t *testing.T) {
serverCertificate, _ := newWindowsTestCertificate(t, "localhost")
wrongPin := sha256.Sum256([]byte("not the public key"))
_, serverAddress := startWindowsTLSTestServer(t, &stdtls.Config{
Certificates: []stdtls.Certificate{serverCertificate},
})
clientConn, err := newWindowsTestClientConn(t, serverAddress, option.OutboundTLSOptions{
Enabled: true,
Engine: C.TLSEngineWindows,
ServerName: "localhost",
CertificatePublicKeySHA256: [][]byte{wrongPin[:]},
})
if err == nil {
clientConn.Close()
t.Fatal("expected public-key pin mismatch to fail")
}
}
func TestWindowsClientHandshakeContextCancellation(t *testing.T) {
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { listener.Close() })
clientHelloRead := make(chan struct{}, 1)
serverDone := make(chan struct{})
defer close(serverDone)
go func() {
c, acceptErr := listener.Accept()
if acceptErr != nil {
return
}
defer c.Close()
buffer := make([]byte, 8192)
n, readErr := c.Read(buffer)
if n > 0 {
clientHelloRead <- struct{}{}
}
if readErr != nil {
return
}
<-serverDone
}()
_, serverCertificatePEM := newWindowsTestCertificate(t, "localhost")
ctx, cancel := context.WithCancel(context.Background())
clientConfig, err := NewClientWithOptions(ClientOptions{
Context: ctx,
Logger: logger.NOP(),
Options: option.OutboundTLSOptions{
Enabled: true,
Engine: C.TLSEngineWindows,
ServerName: "localhost",
Certificate: badoption.Listable[string]{serverCertificatePEM},
},
})
if err != nil {
t.Fatal(err)
}
conn, err := net.DialTimeout("tcp", listener.Addr().String(), windowsTLSTestTimeout)
if err != nil {
t.Fatal(err)
}
defer conn.Close()
handshakeDone := make(chan error, 1)
go func() {
_, err := ClientHandshake(ctx, conn, clientConfig)
handshakeDone <- err
}()
select {
case <-clientHelloRead:
case <-time.After(2 * time.Second):
t.Fatal("server did not receive the client hello")
}
cancel()
select {
case err := <-handshakeDone:
if !errors.Is(err, context.Canceled) {
t.Fatalf("expected context.Canceled, got %v", err)
}
case <-time.After(2 * time.Second):
t.Fatal("handshake did not return after cancellation")
}
}
func TestWindowsClientHandshakeTimeout(t *testing.T) {
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { listener.Close() })
// Accept but never respond.
go func() {
c, acceptErr := listener.Accept()
if acceptErr != nil {
return
}
defer c.Close()
time.Sleep(3 * time.Second)
}()
_, serverCertificatePEM := newWindowsTestCertificate(t, "localhost")
ctx, cancel := context.WithTimeout(context.Background(), windowsTLSTestTimeout)
defer cancel()
clientConfig, err := NewClientWithOptions(ClientOptions{
Context: ctx,
Logger: logger.NOP(),
Options: option.OutboundTLSOptions{
Enabled: true,
Engine: C.TLSEngineWindows,
ServerName: "localhost",
HandshakeTimeout: badoption.Duration(300 * time.Millisecond),
Certificate: badoption.Listable[string]{serverCertificatePEM},
},
})
if err != nil {
t.Fatal(err)
}
conn, err := net.DialTimeout("tcp", listener.Addr().String(), windowsTLSTestTimeout)
if err != nil {
t.Fatal(err)
}
defer conn.Close()
start := time.Now()
_, err = ClientHandshake(ctx, conn, clientConfig)
elapsed := time.Since(start)
if err == nil {
t.Fatal("expected handshake to time out")
}
if elapsed > 2*time.Second {
t.Fatalf("handshake took %v, expected ~300ms timeout", elapsed)
}
}
func TestWindowsClientRoundtrip(t *testing.T) {
clientConn, serverDone := startWindowsEchoServer(t, stdtls.VersionTLS12)
defer clientConn.Close()
_, err := clientConn.Write([]byte("ping"))
if err != nil {
t.Fatalf("write: %v", err)
}
reply := make([]byte, 4)
_, err = io.ReadFull(clientConn, reply)
if err != nil {
t.Fatalf("read: %v", err)
}
if string(reply) != "ping" {
t.Fatalf("unexpected reply: %q", string(reply))
}
clientConn.Close()
<-serverDone
}
func TestWindowsClientRoundtripTLS13(t *testing.T) {
clientConn, serverDone := startWindowsEchoServer(t, stdtls.VersionTLS13)
defer clientConn.Close()
payload := []byte("hello tls 1.3")
_, err := clientConn.Write(payload)
if err != nil {
t.Fatalf("write: %v", err)
}
reply := make([]byte, len(payload))
_, err = io.ReadFull(clientConn, reply)
if err != nil {
t.Fatalf("read: %v", err)
}
if !bytes.Equal(payload, reply) {
t.Fatalf("unexpected reply: %q", string(reply))
}
clientConn.Close()
<-serverDone
}
func TestWindowsClientReadBuffer(t *testing.T) {
payload := []byte("windows tls read buffer payload")
clientConn, serverErr := startWindowsPayloadServer(t, stdtls.VersionTLS12, payload)
defer clientConn.Close()
const (
frontHeadroom = 8
rearHeadroom = 8
)
buffer := buf.NewSize(len(payload) + frontHeadroom + rearHeadroom)
defer buffer.Release()
buffer.Resize(frontHeadroom, 0)
buffer.Reserve(rearHeadroom)
err := clientConn.ReadBuffer(buffer)
if err != nil {
t.Fatalf("ReadBuffer: %v", err)
}
if buffer.Start() != frontHeadroom {
t.Fatalf("expected front headroom %d, got %d", frontHeadroom, buffer.Start())
}
if !bytes.Equal(buffer.Bytes(), payload) {
t.Fatalf("unexpected payload: %q", string(buffer.Bytes()))
}
if err = <-serverErr; err != nil {
t.Fatal(err)
}
}
func TestWindowsClientWriteBuffer(t *testing.T) {
clientConn, serverDone := startWindowsEchoEngineServer(t, stdtls.VersionTLS12)
defer clientConn.Close()
payload := []byte("windows tls write buffer payload")
buffer := buf.NewSize(len(payload))
_, err := buffer.Write(payload)
if err != nil {
t.Fatal(err)
}
err = clientConn.WriteBuffer(buffer)
if err != nil {
t.Fatalf("WriteBuffer: %v", err)
}
if buffer.RawCap() != 0 {
t.Fatalf("expected WriteBuffer to release buffer, raw cap %d", buffer.RawCap())
}
reply := make([]byte, len(payload))
_, err = io.ReadFull(clientConn, reply)
if err != nil {
t.Fatalf("read echo: %v", err)
}
if !bytes.Equal(reply, payload) {
t.Fatalf("unexpected echo: %q", string(reply))
}
clientConn.Close()
<-serverDone
}
func TestWindowsClientCreateReadWaiter(t *testing.T) {
payload := []byte("windows tls read waiter payload")
clientConn, serverErr := startWindowsPayloadServer(t, stdtls.VersionTLS12, payload)
defer clientConn.Close()
readWaiter, created := bufio.CreateReadWaiter(clientConn)
if !created {
t.Fatal("expected read waiter")
}
readWaiter.InitializeReadWaiter(N.ReadWaitOptions{
FrontHeadroom: 7,
RearHeadroom: 5,
MTU: len(payload),
})
buffer, err := readWaiter.WaitReadBuffer()
if err != nil {
t.Fatalf("WaitReadBuffer: %v", err)
}
defer buffer.Release()
if buffer.Start() != 7 {
t.Fatalf("expected front headroom 7, got %d", buffer.Start())
}
if buffer.FreeLen() < 5 {
t.Fatalf("expected rear headroom at least 5, got %d", buffer.FreeLen())
}
if !bytes.Equal(buffer.Bytes(), payload) {
t.Fatalf("unexpected payload: %q", string(buffer.Bytes()))
}
if err = <-serverErr; err != nil {
t.Fatal(err)
}
}
func TestWindowsClientCreateReadWaiterFallback(t *testing.T) {
tlsConn := newTestWindowsTLSConn(&windowsTestIOConn{})
_, created := tlsConn.CreateReadWaiter()
if created {
t.Fatal("expected read waiter fallback")
}
}
func TestWindowsClientTLS13PostHandshakeConcurrentWrite(t *testing.T) {
serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost")
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { listener.Close() })
const payloadSize = 4 << 20
const prefixSize = 32 << 10
reply := []byte("tls13 post-handshake reply")
serverErr := make(chan error, 1)
prefixRead := make(chan struct{})
go func() {
conn, acceptErr := listener.Accept()
if acceptErr != nil {
serverErr <- acceptErr
return
}
defer conn.Close()
tlsConn := stdtls.Server(conn, &stdtls.Config{
Certificates: []stdtls.Certificate{serverCertificate},
MinVersion: stdtls.VersionTLS13,
MaxVersion: stdtls.VersionTLS13,
})
defer tlsConn.Close()
err := tlsConn.SetDeadline(time.Now().Add(2 * windowsTLSTestTimeout))
if err != nil {
serverErr <- err
return
}
err = tlsConn.Handshake()
if err != nil {
serverErr <- err
return
}
prefix := make([]byte, prefixSize)
_, err = io.ReadFull(tlsConn, prefix)
if err != nil {
serverErr <- err
return
}
close(prefixRead)
_, err = tlsConn.Write(reply)
if err != nil {
serverErr <- err
return
}
_, err = io.Copy(io.Discard, io.LimitReader(tlsConn, int64(payloadSize-prefixSize)))
if err != nil {
serverErr <- err
return
}
serverErr <- nil
}()
clientConn, err := newWindowsTestClientConn(t, listener.Addr().String(), option.OutboundTLSOptions{
Enabled: true,
Engine: C.TLSEngineWindows,
ServerName: "localhost",
MinVersion: "1.3",
Certificate: badoption.Listable[string]{serverCertificatePEM},
})
if err != nil {
t.Fatal(err)
}
defer clientConn.Close()
payload := make([]byte, payloadSize)
for index := range payload {
payload[index] = byte(index % 251)
}
writeDone := make(chan error, 1)
go func() {
_, err := clientConn.Write(payload)
writeDone <- err
}()
select {
case <-prefixRead:
case <-time.After(2 * time.Second):
t.Fatal("server did not observe the client write")
}
replyBuffer := make([]byte, len(reply))
_, err = io.ReadFull(clientConn, replyBuffer)
if err != nil {
t.Fatalf("read: %v", err)
}
if !bytes.Equal(reply, replyBuffer) {
t.Fatalf("unexpected reply: %q", string(replyBuffer))
}
writeErr := <-writeDone
if writeErr != nil {
t.Fatalf("write: %v", writeErr)
}
serverErrValue := <-serverErr
if serverErrValue != nil {
t.Fatal(serverErrValue)
}
}
func TestWindowsClientLargeMessage(t *testing.T) {
clientConn, serverDone := startWindowsEchoServer(t, stdtls.VersionTLS12)
defer clientConn.Close()
// 1 MiB exercises multiple TLS records and the chunking logic.
// Writes must run concurrently with reads to avoid TCP-buffer deadlock.
payload := make([]byte, 1<<20)
for index := range payload {
payload[index] = byte(index % 251)
}
writeErr := make(chan error, 1)
go func() {
_, err := clientConn.Write(payload)
writeErr <- err
}()
reply := make([]byte, len(payload))
_, err := io.ReadFull(clientConn, reply)
if err != nil {
t.Fatalf("read: %v", err)
}
writeResult := <-writeErr
if writeResult != nil {
t.Fatalf("write: %v", writeResult)
}
if !bytes.Equal(payload, reply) {
t.Fatal("payload mismatch after round-trip")
}
clientConn.Close()
<-serverDone
}
func TestWindowsClientFullDuplexLargePayload(t *testing.T) {
serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost")
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { listener.Close() })
const payloadSize = 2 << 20
clientPayload := make([]byte, payloadSize)
serverPayload := make([]byte, payloadSize)
for index := range clientPayload {
clientPayload[index] = byte(index % 251)
serverPayload[index] = byte((index + 97) % 251)
}
serverErr := make(chan error, 1)
go func() {
conn, acceptErr := listener.Accept()
if acceptErr != nil {
serverErr <- acceptErr
return
}
defer conn.Close()
tlsConn := stdtls.Server(conn, &stdtls.Config{
Certificates: []stdtls.Certificate{serverCertificate},
MinVersion: stdtls.VersionTLS12,
MaxVersion: stdtls.VersionTLS12,
})
defer tlsConn.Close()
err := tlsConn.SetDeadline(time.Now().Add(2 * windowsTLSTestTimeout))
if err != nil {
serverErr <- err
return
}
err = tlsConn.Handshake()
if err != nil {
serverErr <- err
return
}
readDone := make(chan error, 1)
writeDone := make(chan error, 1)
go func() {
received := make([]byte, len(clientPayload))
_, readErr := io.ReadFull(tlsConn, received)
if readErr == nil && !bytes.Equal(received, clientPayload) {
readErr = errors.New("client payload mismatch")
}
readDone <- readErr
}()
go func() {
_, writeErr := tlsConn.Write(serverPayload)
writeDone <- writeErr
}()
if readErr := <-readDone; readErr != nil {
serverErr <- readErr
return
}
serverErr <- <-writeDone
}()
clientConn, err := newWindowsTestEngineConn(t, listener.Addr().String(), option.OutboundTLSOptions{
Enabled: true,
Engine: C.TLSEngineWindows,
ServerName: "localhost",
MinVersion: "1.2",
Certificate: badoption.Listable[string]{serverCertificatePEM},
})
if err != nil {
t.Fatal(err)
}
defer clientConn.Close()
writeDone := make(chan error, 1)
go func() {
n, writeErr := clientConn.Write(clientPayload)
if writeErr == nil && n != len(clientPayload) {
writeErr = io.ErrShortWrite
}
writeDone <- writeErr
}()
reply := make([]byte, len(serverPayload))
_, err = io.ReadFull(clientConn, reply)
if err != nil {
t.Fatalf("read: %v", err)
}
if !bytes.Equal(reply, serverPayload) {
t.Fatal("server payload mismatch")
}
if writeErr := <-writeDone; writeErr != nil {
t.Fatalf("write: %v", writeErr)
}
if err = <-serverErr; err != nil {
t.Fatal(err)
}
}
func TestWindowsClientMultipleRoundtrips(t *testing.T) {
clientConn, serverDone := startWindowsEchoServer(t, stdtls.VersionTLS12)
defer clientConn.Close()
2026-05-14 22:53:22 +08:00
for i := range 100 {
2026-04-24 09:04:27 +08:00
payload := []byte("msg" + string(rune('A'+(i%26))))
_, err := clientConn.Write(payload)
if err != nil {
t.Fatalf("write %d: %v", i, err)
}
reply := make([]byte, len(payload))
_, err = io.ReadFull(clientConn, reply)
if err != nil {
t.Fatalf("read %d: %v", i, err)
}
if !bytes.Equal(payload, reply) {
t.Fatalf("iteration %d: expected %q got %q", i, payload, reply)
}
}
clientConn.Close()
<-serverDone
}
func TestWindowsClientConcurrentReadWrite(t *testing.T) {
clientConn, serverDone := startWindowsEchoServer(t, stdtls.VersionTLS12)
defer clientConn.Close()
const messageCount = 200
const messageSize = 64
payloads := make([][]byte, messageCount)
for i := range payloads {
buffer := make([]byte, messageSize)
for j := range buffer {
buffer[j] = byte(i + j)
}
payloads[i] = buffer
}
readErr := make(chan error, 1)
readBack := make(chan []byte, messageCount)
go func() {
2026-05-14 22:53:22 +08:00
for range messageCount {
2026-04-24 09:04:27 +08:00
reply := make([]byte, messageSize)
_, err := io.ReadFull(clientConn, reply)
if err != nil {
readErr <- err
return
}
readBack <- reply
}
readErr <- nil
}()
for i, payload := range payloads {
_, err := clientConn.Write(payload)
if err != nil {
t.Fatalf("write %d: %v", i, err)
}
}
readResult := <-readErr
if readResult != nil {
t.Fatal(readResult)
}
2026-05-14 22:53:22 +08:00
for i := range messageCount {
2026-04-24 09:04:27 +08:00
got := <-readBack
if !bytes.Equal(payloads[i], got) {
t.Fatalf("iteration %d: payload mismatch", i)
}
}
clientConn.Close()
<-serverDone
}
func TestWindowsClientServerCloseReturnsEOF(t *testing.T) {
serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost")
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { listener.Close() })
done := make(chan struct{})
go func() {
defer close(done)
conn, acceptErr := listener.Accept()
if acceptErr != nil {
return
}
defer conn.Close()
tlsConn := stdtls.Server(conn, &stdtls.Config{
Certificates: []stdtls.Certificate{serverCertificate},
MinVersion: stdtls.VersionTLS12,
MaxVersion: stdtls.VersionTLS12,
})
_ = tlsConn.Handshake()
// Send close_notify then exit.
_ = tlsConn.Close()
}()
clientConn, err := newWindowsTestClientConn(t, listener.Addr().String(), option.OutboundTLSOptions{
Enabled: true,
Engine: C.TLSEngineWindows,
ServerName: "localhost",
MinVersion: "1.2",
Certificate: badoption.Listable[string]{serverCertificatePEM},
})
if err != nil {
t.Fatal(err)
}
defer clientConn.Close()
buffer := make([]byte, 16)
_, err = clientConn.Read(buffer)
if !errors.Is(err, io.EOF) {
t.Fatalf("expected io.EOF, got %v", err)
}
<-done
}
func TestWindowsClientCloseUnblocksRead(t *testing.T) {
serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost")
_, serverAddress := startWindowsTLSSilentServer(t, &stdtls.Config{
Certificates: []stdtls.Certificate{serverCertificate},
MinVersion: stdtls.VersionTLS12,
})
clientConn, err := newWindowsTestClientConn(t, serverAddress, option.OutboundTLSOptions{
Enabled: true,
Engine: C.TLSEngineWindows,
ServerName: "localhost",
MinVersion: "1.2",
Certificate: badoption.Listable[string]{serverCertificatePEM},
})
if err != nil {
t.Fatal(err)
}
defer clientConn.Close()
readDone := make(chan error, 1)
go func() {
buffer := make([]byte, 16)
_, err := clientConn.Read(buffer)
readDone <- err
}()
time.Sleep(100 * time.Millisecond)
clientConn.Close()
select {
case err := <-readDone:
if err == nil {
t.Fatal("expected Read to return an error after Close")
}
case <-time.After(2 * time.Second):
t.Fatal("Read did not return within 2s after Close")
}
}
func TestWindowsClientReadAfterCloseReturnsError(t *testing.T) {
clientConn, serverDone := startWindowsEchoServer(t, stdtls.VersionTLS12)
clientConn.Close()
<-serverDone
buffer := make([]byte, 16)
_, err := clientConn.Read(buffer)
if err == nil {
t.Fatal("expected Read after Close to return error")
}
}
func TestWindowsClientReadAfterCloseDoesNotServeBufferedPlaintext(t *testing.T) {
serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost")
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { listener.Close() })
serverDone := make(chan struct{})
serverErr := make(chan error, 1)
payload := bytes.Repeat([]byte("buffered plaintext "), 32)
go func() {
conn, acceptErr := listener.Accept()
if acceptErr != nil {
serverErr <- acceptErr
return
}
defer conn.Close()
tlsConn := stdtls.Server(conn, &stdtls.Config{
Certificates: []stdtls.Certificate{serverCertificate},
MinVersion: stdtls.VersionTLS12,
MaxVersion: stdtls.VersionTLS12,
})
defer tlsConn.Close()
err := tlsConn.SetDeadline(time.Now().Add(windowsTLSTestTimeout))
if err != nil {
serverErr <- err
return
}
err = tlsConn.Handshake()
if err != nil {
serverErr <- err
return
}
_, err = tlsConn.Write(payload)
if err != nil {
serverErr <- err
return
}
<-serverDone
serverErr <- nil
}()
clientConn, err := newWindowsTestClientConn(t, listener.Addr().String(), option.OutboundTLSOptions{
Enabled: true,
Engine: C.TLSEngineWindows,
ServerName: "localhost",
MinVersion: "1.2",
Certificate: badoption.Listable[string]{serverCertificatePEM},
})
if err != nil {
t.Fatal(err)
}
buffer := make([]byte, 8)
n, err := clientConn.Read(buffer)
if err != nil {
t.Fatalf("first read: %v", err)
}
if n != len(buffer) {
t.Fatalf("expected first read to fill the buffer, got %d", n)
}
clientConn.Close()
close(serverDone)
_, err = clientConn.Read(make([]byte, len(payload)))
if !errors.Is(err, net.ErrClosed) {
t.Fatalf("expected net.ErrClosed, got %v", err)
}
serverErrValue := <-serverErr
if serverErrValue != nil {
t.Fatal(serverErrValue)
}
}
func TestWindowsClientWriteAfterCloseReturnsError(t *testing.T) {
clientConn, serverDone := startWindowsEchoServer(t, stdtls.VersionTLS12)
clientConn.Close()
<-serverDone
_, err := clientConn.Write([]byte("after close"))
if err == nil {
t.Fatal("expected Write after Close to return error")
}
}
func TestWindowsClientReadDeadline(t *testing.T) {
serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost")
serverDone, serverAddress := startWindowsTLSSilentServer(t, &stdtls.Config{
Certificates: []stdtls.Certificate{serverCertificate},
MinVersion: stdtls.VersionTLS12,
MaxVersion: stdtls.VersionTLS12,
})
clientConn, err := newWindowsTestClientConn(t, serverAddress, option.OutboundTLSOptions{
Enabled: true,
Engine: C.TLSEngineWindows,
ServerName: "localhost",
MinVersion: "1.2",
Certificate: badoption.Listable[string]{serverCertificatePEM},
})
if err != nil {
t.Fatal(err)
}
defer clientConn.Close()
defer close(serverDone)
err = clientConn.SetReadDeadline(time.Now().Add(200 * time.Millisecond))
if err != nil {
t.Fatalf("SetReadDeadline: %v", err)
}
readDone := make(chan error, 1)
buffer := make([]byte, 64)
go func() {
_, readErr := clientConn.Read(buffer)
readDone <- readErr
}()
select {
case readErr := <-readDone:
if !errors.Is(readErr, os.ErrDeadlineExceeded) {
t.Fatalf("expected os.ErrDeadlineExceeded, got %v", readErr)
}
case <-time.After(2 * time.Second):
t.Fatal("Read did not return within 2s after deadline")
}
}
func TestWindowsClientSetReadDeadlinePreExpired(t *testing.T) {
serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost")
serverDone, serverAddress := startWindowsTLSSilentServer(t, &stdtls.Config{
Certificates: []stdtls.Certificate{serverCertificate},
MinVersion: stdtls.VersionTLS12,
MaxVersion: stdtls.VersionTLS12,
})
clientConn, err := newWindowsTestClientConn(t, serverAddress, option.OutboundTLSOptions{
Enabled: true,
Engine: C.TLSEngineWindows,
ServerName: "localhost",
MinVersion: "1.2",
Certificate: badoption.Listable[string]{serverCertificatePEM},
})
if err != nil {
t.Fatal(err)
}
defer clientConn.Close()
defer close(serverDone)
err = clientConn.SetReadDeadline(time.Now().Add(-time.Second))
if err != nil {
t.Fatalf("SetReadDeadline past: %v", err)
}
buffer := make([]byte, 16)
_, err = clientConn.Read(buffer)
if !errors.Is(err, os.ErrDeadlineExceeded) {
t.Fatalf("expected os.ErrDeadlineExceeded, got %v", err)
}
// Clearing the deadline must restore normal blocking behaviour.
err = clientConn.SetReadDeadline(time.Time{})
if err != nil {
t.Fatalf("SetReadDeadline zero: %v", err)
}
err = clientConn.SetReadDeadline(time.Now().Add(200 * time.Millisecond))
if err != nil {
t.Fatalf("SetReadDeadline future: %v", err)
}
start := time.Now()
_, err = clientConn.Read(buffer)
elapsed := time.Since(start)
if !errors.Is(err, os.ErrDeadlineExceeded) {
t.Fatalf("expected os.ErrDeadlineExceeded after re-arm, got %v", err)
}
if elapsed < 150*time.Millisecond {
t.Fatalf("Read returned too fast (%v), pre-expired flag leaked", elapsed)
}
}
func TestWindowsClientSetDeadlinePropagatesToRawConn(t *testing.T) {
rawConn := &windowsTestDeadlineConn{}
tlsConn := newTestWindowsTLSConn(rawConn)
deadline := time.Now().Add(time.Second)
err := tlsConn.SetDeadline(deadline)
if err != nil {
t.Fatalf("SetDeadline: %v", err)
}
readDeadlines := rawConn.recordedReadDeadlines()
if len(readDeadlines) != 1 {
t.Fatalf("expected 1 read deadline update, got %d", len(readDeadlines))
}
if !readDeadlines[0].Equal(deadline) {
t.Fatalf("expected read deadline %v, got %v", deadline, readDeadlines[0])
}
writeDeadlines := rawConn.recordedWriteDeadlines()
if len(writeDeadlines) != 1 {
t.Fatalf("expected 1 write deadline update, got %d", len(writeDeadlines))
}
if !writeDeadlines[0].Equal(deadline) {
t.Fatalf("expected write deadline %v, got %v", deadline, writeDeadlines[0])
}
}
func TestWindowsClientSetReadDeadlineCancelsBlockedRead(t *testing.T) {
rawConn := &windowsTestDeadlineConn{
readCalled: make(chan struct{}),
}
tlsConn := newTestWindowsTLSConn(rawConn)
tlsConn.readScratch = make([]byte, 16)
readErrCh := make(chan error, 1)
go func() {
_, err := tlsConn.Read(make([]byte, 1))
readErrCh <- err
}()
select {
case <-rawConn.readCalled:
case <-time.After(time.Second):
t.Fatal("Read did not reach the raw connection")
}
deadline := time.Now().Add(150 * time.Millisecond)
err := tlsConn.SetReadDeadline(deadline)
if err != nil {
t.Fatalf("SetReadDeadline: %v", err)
}
select {
case err = <-readErrCh:
if !errors.Is(err, os.ErrDeadlineExceeded) {
t.Fatalf("expected os.ErrDeadlineExceeded, got %v", err)
}
case <-time.After(2 * time.Second):
t.Fatal("Read did not return after SetReadDeadline")
}
readDeadlines := rawConn.recordedReadDeadlines()
if len(readDeadlines) != 1 {
t.Fatalf("expected 1 read deadline update, got %d", len(readDeadlines))
}
if !readDeadlines[0].Equal(deadline) {
t.Fatalf("expected read deadline %v, got %v", deadline, readDeadlines[0])
}
}
func TestWindowsClientSetWriteDeadlineCancelsBlockedWrite(t *testing.T) {
serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost")
serverDone, serverAddress := startWindowsTLSSilentServer(t, &stdtls.Config{
Certificates: []stdtls.Certificate{serverCertificate},
MinVersion: stdtls.VersionTLS12,
MaxVersion: stdtls.VersionTLS12,
})
defer close(serverDone)
tlsConn, err := newWindowsTestEngineConn(t, serverAddress, option.OutboundTLSOptions{
Enabled: true,
Engine: C.TLSEngineWindows,
ServerName: "localhost",
MinVersion: "1.2",
Certificate: badoption.Listable[string]{serverCertificatePEM},
})
if err != nil {
t.Fatal(err)
}
originalRawConn := tlsConn.rawConn
rawConn := &windowsTestDeadlineConn{
writeCalled: make(chan struct{}),
}
tlsConn.rawConn = rawConn
t.Cleanup(func() {
_ = originalRawConn.Close()
_ = tlsConn.Close()
})
writeErrCh := make(chan error, 1)
go func() {
_, err := tlsConn.Write([]byte("ping"))
writeErrCh <- err
}()
select {
case <-rawConn.writeCalled:
case <-time.After(time.Second):
t.Fatal("Write did not reach the raw connection")
}
deadline := time.Now().Add(150 * time.Millisecond)
err = tlsConn.SetWriteDeadline(deadline)
if err != nil {
t.Fatalf("SetWriteDeadline: %v", err)
}
select {
case err = <-writeErrCh:
if !errors.Is(err, os.ErrDeadlineExceeded) {
t.Fatalf("expected os.ErrDeadlineExceeded, got %v", err)
}
case <-time.After(2 * time.Second):
t.Fatal("Write did not return after SetWriteDeadline")
}
writeDeadlines := rawConn.recordedWriteDeadlines()
if len(writeDeadlines) != 1 {
t.Fatalf("expected 1 write deadline update, got %d", len(writeDeadlines))
}
if !writeDeadlines[0].Equal(deadline) {
t.Fatalf("expected write deadline %v, got %v", deadline, writeDeadlines[0])
}
}
func TestWindowsClientPostHandshakeReplyUsesReadDeadline(t *testing.T) {
rawConn := &windowsTestDeadlineConn{}
tlsConn := newTestWindowsTLSConn(rawConn)
readDeadline := time.Now().Add(150 * time.Millisecond)
err := tlsConn.SetReadDeadline(readDeadline)
if err != nil {
t.Fatalf("SetReadDeadline: %v", err)
}
start := time.Now()
err = tlsConn.writePostHandshakeReply([]byte("reply"))
elapsed := time.Since(start)
if !errors.Is(err, os.ErrDeadlineExceeded) {
t.Fatalf("expected os.ErrDeadlineExceeded, got %v", err)
}
if elapsed < 100*time.Millisecond {
t.Fatalf("post-handshake write returned too fast: %v", elapsed)
}
deadlines := rawConn.recordedWriteDeadlines()
if len(deadlines) != 2 {
t.Fatalf("expected 2 write deadline updates, got %d", len(deadlines))
}
if !deadlines[0].Equal(readDeadline) {
t.Fatalf("expected first write deadline %v, got %v", readDeadline, deadlines[0])
}
if !deadlines[1].IsZero() {
t.Fatalf("expected write deadline cleanup, got %v", deadlines[1])
}
}
func TestWindowsClientPostHandshakeReplyPreExpiredReadDeadline(t *testing.T) {
rawConn := &windowsTestDeadlineConn{}
tlsConn := newTestWindowsTLSConn(rawConn)
err := tlsConn.SetReadDeadline(time.Now().Add(-time.Second))
if err != nil {
t.Fatalf("SetReadDeadline: %v", err)
}
start := time.Now()
err = tlsConn.writePostHandshakeReply([]byte("reply"))
elapsed := time.Since(start)
if !errors.Is(err, os.ErrDeadlineExceeded) {
t.Fatalf("expected os.ErrDeadlineExceeded, got %v", err)
}
if elapsed > 50*time.Millisecond {
t.Fatalf("pre-expired post-handshake write returned too slowly: %v", elapsed)
}
deadlines := rawConn.recordedWriteDeadlines()
if len(deadlines) != 0 {
t.Fatalf("expected no write deadline update for pre-expired read deadline, got %d", len(deadlines))
}
}
func TestDriveStepsPreservesBufferedHandshakeBytes(t *testing.T) {
scratch := make([]byte, 8)
copy(scratch, "abc")
readCalls := 0
stepCalls := 0
leftover, err := driveSteps(
scratch[:3],
func(input []byte) (schannel.StepResult, error) {
stepCalls++
switch stepCalls {
case 1:
if string(input) != "abc" {
t.Fatalf("first step input = %q, want %q", input, "abc")
}
return schannel.StepResult{Incomplete: true}, nil
case 2:
if string(input) != "abcdef" {
t.Fatalf("second step input = %q, want %q", input, "abcdef")
}
return schannel.StepResult{Consumed: len(input), Done: true}, nil
default:
t.Fatalf("unexpected step call %d", stepCalls)
return schannel.StepResult{}, nil
}
},
func() ([]byte, error) {
readCalls++
copy(scratch, "def")
return scratch[:3], nil
},
func([]byte) error { return nil },
)
if err != nil {
t.Fatal(err)
}
if readCalls != 1 {
t.Fatalf("readMore called %d times, want 1", readCalls)
}
if len(leftover) != 0 {
t.Fatalf("leftover = %q, want empty", leftover)
}
}
func TestWindowsTLSRawReadEOFAtRecordBoundary(t *testing.T) {
rawConn := &windowsTestIOConn{readErr: io.EOF}
_, err := readTLSRaw(rawConn, make([]byte, 16), false)
if !errors.Is(err, io.EOF) {
t.Fatalf("expected io.EOF, got %v", err)
}
}
func TestWindowsTLSRawReadEOFWithPendingRecord(t *testing.T) {
rawConn := &windowsTestIOConn{readErr: io.EOF}
_, err := readTLSRaw(rawConn, make([]byte, 16), true)
if !errors.Is(err, io.ErrUnexpectedEOF) {
t.Fatalf("expected io.ErrUnexpectedEOF, got %v", err)
}
}
func TestWindowsClientPostHandshakeReplyWaitsForWriteAccess(t *testing.T) {
rawConn := &windowsTestWriteGateConn{
writeCalled: make(chan struct{}),
releaseWrite: make(chan struct{}),
}
tlsConn := newTestWindowsTLSConn(rawConn)
tlsConn.writeAccess.Lock()
errCh := make(chan error, 1)
go func() {
errCh <- tlsConn.writePostHandshakeReply([]byte("reply"))
}()
select {
case <-rawConn.writeCalled:
t.Fatal("post-handshake write bypassed writeAccess")
case <-time.After(100 * time.Millisecond):
}
tlsConn.writeAccess.Unlock()
select {
case <-rawConn.writeCalled:
case <-time.After(time.Second):
t.Fatal("post-handshake write did not resume after writeAccess release")
}
close(rawConn.releaseWrite)
err := <-errCh
if err != nil {
t.Fatal(err)
}
}
func TestWindowsClientPostHandshakeWritePreemptsNewWrite(t *testing.T) {
tlsConn := newTestWindowsTLSConn(&windowsTestIOConn{})
err := tlsConn.beginWrite()
if err != nil {
t.Fatal(err)
}
postHandshakeReady := make(chan error, 1)
go func() {
postHandshakeReady <- tlsConn.beginPostHandshakeWrite()
}()
deadline := time.After(time.Second)
for {
tlsConn.writeState.Lock()
pending := tlsConn.postHandshake
tlsConn.writeState.Unlock()
if pending {
break
}
select {
case <-deadline:
t.Fatal("post-handshake write did not become pending")
default:
time.Sleep(time.Millisecond)
}
}
writeReady := make(chan error, 1)
go func() {
writeReady <- tlsConn.beginWrite()
}()
tlsConn.finishWrite()
select {
case err = <-postHandshakeReady:
if err != nil {
t.Fatal(err)
}
case err = <-writeReady:
t.Fatalf("new write preempted post-handshake write: %v", err)
case <-time.After(time.Second):
t.Fatal("post-handshake write did not resume")
}
select {
case err = <-writeReady:
t.Fatalf("new write acquired before post-handshake finished: %v", err)
case <-time.After(100 * time.Millisecond):
}
tlsConn.finishPostHandshakeWrite()
select {
case err = <-writeReady:
if err != nil {
t.Fatal(err)
}
case <-time.After(time.Second):
t.Fatal("new write did not resume after post-handshake write")
}
tlsConn.finishWrite()
}
func TestWindowsClientPostHandshakeReplyErrorClosesConn(t *testing.T) {
rawConn := &windowsTestIOConn{
writeErr: os.ErrDeadlineExceeded,
writeN: 1,
}
tlsConn := newTestWindowsTLSConn(rawConn)
err := tlsConn.writePostHandshakeReply([]byte("reply"))
if !errors.Is(err, os.ErrDeadlineExceeded) {
t.Fatalf("expected os.ErrDeadlineExceeded, got %v", err)
}
if !rawConn.isClosed() {
t.Fatal("expected raw conn to be closed")
}
if !tlsConn.isClosed() {
t.Fatal("expected tls conn to be closed")
}
}
func TestWindowsClientWriteErrorClosesConn(t *testing.T) {
serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost")
serverDone, serverAddress := startWindowsTLSSilentServer(t, &stdtls.Config{
Certificates: []stdtls.Certificate{serverCertificate},
MinVersion: stdtls.VersionTLS12,
MaxVersion: stdtls.VersionTLS12,
})
defer close(serverDone)
tlsConn, err := newWindowsTestEngineConn(t, serverAddress, option.OutboundTLSOptions{
Enabled: true,
Engine: C.TLSEngineWindows,
ServerName: "localhost",
MinVersion: "1.2",
Certificate: badoption.Listable[string]{serverCertificatePEM},
})
if err != nil {
t.Fatal(err)
}
originalRawConn := tlsConn.rawConn
rawConn := &windowsTestIOConn{
writeErr: os.ErrDeadlineExceeded,
writeN: 1,
}
tlsConn.rawConn = rawConn
t.Cleanup(func() {
_ = originalRawConn.Close()
_ = tlsConn.Close()
})
_, err = tlsConn.Write([]byte("ping"))
if !errors.Is(err, os.ErrDeadlineExceeded) {
t.Fatalf("expected os.ErrDeadlineExceeded, got %v", err)
}
if !rawConn.isClosed() {
t.Fatal("expected raw conn to be closed")
}
if !tlsConn.isClosed() {
t.Fatal("expected tls conn to be closed")
}
_, err = tlsConn.Write([]byte("again"))
if !errors.Is(err, net.ErrClosed) {
t.Fatalf("expected net.ErrClosed on second write, got %v", err)
}
if rawConn.totalWriteCalls() != 1 {
t.Fatalf("expected exactly 1 raw write, got %d", rawConn.totalWriteCalls())
}
_, err = tlsConn.Read(make([]byte, 1))
if !errors.Is(err, net.ErrClosed) {
t.Fatalf("expected net.ErrClosed on read after write failure, got %v", err)
}
}
func TestWindowsClientConnectionStateFields(t *testing.T) {
serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost")
_, serverAddress := startWindowsTLSTestServer(t, &stdtls.Config{
Certificates: []stdtls.Certificate{serverCertificate},
MinVersion: stdtls.VersionTLS12,
NextProtos: []string{"h2"},
})
clientConn, err := newWindowsTestClientConn(t, serverAddress, option.OutboundTLSOptions{
Enabled: true,
Engine: C.TLSEngineWindows,
ServerName: "localhost",
MinVersion: "1.2",
ALPN: badoption.Listable[string]{"h2"},
Certificate: badoption.Listable[string]{serverCertificatePEM},
})
if err != nil {
t.Fatal(err)
}
defer clientConn.Close()
state := clientConn.ConnectionState()
if state.ServerName != "localhost" {
t.Errorf("ServerName: expected localhost, got %q", state.ServerName)
}
if state.NegotiatedProtocol != "h2" {
t.Errorf("NegotiatedProtocol: expected h2, got %q", state.NegotiatedProtocol)
}
if !state.HandshakeComplete {
t.Error("HandshakeComplete: expected true")
}
if state.Version < stdtls.VersionTLS12 || state.Version > stdtls.VersionTLS13 {
t.Errorf("Version: expected TLS 1.2–1.3, got %x", state.Version)
}
if len(state.PeerCertificates) == 0 {
t.Fatal("PeerCertificates: expected at least one certificate")
}
// CipherSuite may be 0 when the Schannel name does not map to a Go
// constant; just ensure it's consistent with the protocol.
if state.Version == stdtls.VersionTLS13 && state.CipherSuite != 0 {
switch state.CipherSuite {
case stdtls.TLS_AES_128_GCM_SHA256, stdtls.TLS_AES_256_GCM_SHA384, stdtls.TLS_CHACHA20_POLY1305_SHA256:
default:
t.Errorf("unexpected TLS 1.3 cipher suite: %x", state.CipherSuite)
}
}
}
func TestWindowsClientNetConnReturnsUnderlying(t *testing.T) {
clientConn, serverDone := startWindowsEchoServer(t, stdtls.VersionTLS12)
defer func() { <-serverDone }()
defer clientConn.Close()
underlying := clientConn.NetConn()
if _, isTCP := underlying.(*net.TCPConn); !isTCP {
t.Fatalf("NetConn returned %T, expected *net.TCPConn", underlying)
}
}
func TestNewWindowsClientMissingServerName(t *testing.T) {
_, err := NewClientWithOptions(ClientOptions{
Context: context.Background(),
Logger: logger.NOP(),
Options: option.OutboundTLSOptions{
Enabled: true,
Engine: C.TLSEngineWindows,
},
})
if err == nil {
t.Fatal("expected missing server_name error")
}
}
func TestNewWindowsClientInsecureAllowsMissingServerName(t *testing.T) {
_, err := NewClientWithOptions(ClientOptions{
Context: context.Background(),
Logger: logger.NOP(),
Options: option.OutboundTLSOptions{
Enabled: true,
Engine: C.TLSEngineWindows,
Insecure: true,
},
})
if err != nil {
t.Fatal(err)
}
}
func TestWindowsClientConfigSTDConfigReturnsError(t *testing.T) {
config, err := NewClientWithOptions(ClientOptions{
Context: context.Background(),
Logger: logger.NOP(),
Options: option.OutboundTLSOptions{
Enabled: true,
Engine: C.TLSEngineWindows,
ServerName: "localhost",
},
})
if err != nil {
t.Fatal(err)
}
_, err = config.STDConfig()
if err == nil {
t.Fatal("expected STDConfig() to return error for Windows engine")
}
if !strings.Contains(err.Error(), "system TLS engine") {
t.Fatalf("expected error to name the engine, got %q", err.Error())
}
}
func TestWindowsClientConfigClientReturnsErrInvalid(t *testing.T) {
config, err := NewClientWithOptions(ClientOptions{
Context: context.Background(),
Logger: logger.NOP(),
Options: option.OutboundTLSOptions{
Enabled: true,
Engine: C.TLSEngineWindows,
ServerName: "localhost",
},
})
if err != nil {
t.Fatal(err)
}
_, err = config.Client(nil)
if !errors.Is(err, os.ErrInvalid) {
t.Fatalf("expected os.ErrInvalid, got %v", err)
}
}
func TestWindowsClientConfigClone(t *testing.T) {
config, err := NewClientWithOptions(ClientOptions{
Context: context.Background(),
Logger: logger.NOP(),
Options: option.OutboundTLSOptions{
Enabled: true,
Engine: C.TLSEngineWindows,
ServerName: "localhost",
ALPN: badoption.Listable[string]{"h2", "http/1.1"},
},
})
if err != nil {
t.Fatal(err)
}
clone := config.Clone()
// Mutating the clone must not affect the original.
clone.SetServerName("other")
clone.SetNextProtos([]string{"h3"})
if config.ServerName() == "other" {
t.Error("Clone shares server name with original")
}
if len(config.NextProtos()) != 2 {
t.Error("Clone shares ALPN slice with original")
}
}
func TestValidateWindowsTLSOptionsRejections(t *testing.T) {
cases := []struct {
name string
options option.OutboundTLSOptions
needle string
}{
{"reality", option.OutboundTLSOptions{
Enabled: true, Engine: C.TLSEngineWindows, ServerName: "x",
Reality: &option.OutboundRealityOptions{Enabled: true, ShortID: "abc"},
}, "reality"},
{"utls", option.OutboundTLSOptions{
Enabled: true, Engine: C.TLSEngineWindows, ServerName: "x",
UTLS: &option.OutboundUTLSOptions{Enabled: true},
}, "utls"},
{"ech", option.OutboundTLSOptions{
Enabled: true, Engine: C.TLSEngineWindows, ServerName: "x",
ECH: &option.OutboundECHOptions{Enabled: true},
}, "ech"},
{"disable_sni", option.OutboundTLSOptions{
Enabled: true, Engine: C.TLSEngineWindows, ServerName: "x",
DisableSNI: true,
}, "disable_sni"},
{"cipher_suites", option.OutboundTLSOptions{
Enabled: true, Engine: C.TLSEngineWindows, ServerName: "x",
CipherSuites: []string{"TLS_AES_128_GCM_SHA256"},
}, "cipher_suites"},
{"curve_preferences", option.OutboundTLSOptions{
Enabled: true, Engine: C.TLSEngineWindows, ServerName: "x",
CurvePreferences: []option.CurvePreference{option.CurvePreference(29)},
}, "curve_preferences"},
{"client_certificate", option.OutboundTLSOptions{
Enabled: true, Engine: C.TLSEngineWindows, ServerName: "x",
ClientCertificate: badoption.Listable[string]{"pem"},
}, "client certificate"},
{"fragment", option.OutboundTLSOptions{
Enabled: true, Engine: C.TLSEngineWindows, ServerName: "x",
Fragment: true,
}, "tls fragment"},
{"record_fragment", option.OutboundTLSOptions{
Enabled: true, Engine: C.TLSEngineWindows, ServerName: "x",
RecordFragment: true,
}, "tls fragment"},
{"kernel_tx", option.OutboundTLSOptions{
Enabled: true, Engine: C.TLSEngineWindows, ServerName: "x",
KernelTx: true,
}, "ktls"},
{"kernel_rx", option.OutboundTLSOptions{
Enabled: true, Engine: C.TLSEngineWindows, ServerName: "x",
KernelRx: true,
}, "ktls"},
{"spoof", option.OutboundTLSOptions{
Enabled: true, Engine: C.TLSEngineWindows, ServerName: "x",
Spoof: "decoy.example",
}, "spoof"},
{"pin_and_cert_conflict", option.OutboundTLSOptions{
Enabled: true, Engine: C.TLSEngineWindows, ServerName: "x",
Certificate: badoption.Listable[string]{"-----BEGIN CERTIFICATE-----"},
CertificatePublicKeySHA256: [][]byte{make([]byte, 32)},
}, "certificate_public_key_sha256"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
_, err := NewClientWithOptions(ClientOptions{
Context: context.Background(),
Logger: logger.NOP(),
Options: tc.options,
})
if err == nil {
t.Fatalf("expected error containing %q, got nil", tc.needle)
}
if !strings.Contains(strings.ToLower(err.Error()), strings.ToLower(tc.needle)) {
t.Fatalf("expected error to contain %q, got %q", tc.needle, err.Error())
}
})
}
}
func startWindowsTLSSilentServer(t *testing.T, tlsConfig *stdtls.Config) (chan<- struct{}, 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(windowsTLSTestTimeout))
if err != nil {
t.Fatal(err)
}
}
done := make(chan struct{})
go func() {
conn, acceptErr := listener.Accept()
if acceptErr != nil {
return
}
defer conn.Close()
deadlineErr := conn.SetDeadline(time.Now().Add(windowsTLSTestTimeout))
if deadlineErr != nil {
return
}
tlsConn := stdtls.Server(conn, tlsConfig)
defer tlsConn.Close()
handshakeErr := tlsConn.Handshake()
if handshakeErr != nil {
return
}
handshakeErr = conn.SetDeadline(time.Time{})
if handshakeErr != nil {
return
}
<-done
}()
return done, listener.Addr().String()
}
// sharedWindowsTestCertificate caches a localhost certificate so the RSA key
// generation runs once per test binary instead of once per test.
var sharedWindowsTestCertificate = sync.OnceValues(func() (stdtls.Certificate, string) {
return generateWindowsTestCertificate("localhost")
})
func newWindowsTestCertificate(t *testing.T, serverName string) (stdtls.Certificate, string) {
t.Helper()
if serverName == "localhost" {
return sharedWindowsTestCertificate()
}
return generateWindowsTestCertificate(serverName)
}
func generateWindowsTestCertificate(serverName string) (stdtls.Certificate, string) {
privateKeyPEM, certificatePEM, err := GenerateCertificate(nil, nil, time.Now, serverName, time.Now().Add(time.Hour))
if err != nil {
panic(err)
}
certificate, err := stdtls.X509KeyPair(certificatePEM, privateKeyPEM)
if err != nil {
panic(err)
}
leaf, err := x509.ParseCertificate(certificate.Certificate[0])
if err != nil {
panic(err)
}
certificate.Leaf = leaf
return certificate, string(certificatePEM)
}
func publicKeyPin(t *testing.T, cert *x509.Certificate) []byte {
t.Helper()
pub, err := x509.MarshalPKIXPublicKey(cert.PublicKey)
if err != nil {
t.Fatal(err)
}
sum := sha256.Sum256(pub)
return sum[:]
}
func startWindowsTLSTestServer(t *testing.T, tlsConfig *stdtls.Config) (<-chan windowsTLSServerResult, 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(windowsTLSTestTimeout))
if err != nil {
t.Fatal(err)
}
}
result := make(chan windowsTLSServerResult, 1)
go func() {
defer close(result)
conn, err := listener.Accept()
if err != nil {
result <- windowsTLSServerResult{err: err}
return
}
defer conn.Close()
err = conn.SetDeadline(time.Now().Add(windowsTLSTestTimeout))
if err != nil {
result <- windowsTLSServerResult{err: err}
return
}
tlsConn := stdtls.Server(conn, tlsConfig)
defer tlsConn.Close()
err = tlsConn.Handshake()
if err != nil {
result <- windowsTLSServerResult{err: err}
return
}
result <- windowsTLSServerResult{state: tlsConn.ConnectionState()}
}()
return result, listener.Addr().String()
}
func newWindowsTestClientConn(t *testing.T, serverAddress string, options option.OutboundTLSOptions) (Conn, error) {
t.Helper()
ctx, cancel := context.WithTimeout(context.Background(), windowsTLSTestTimeout)
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, windowsTLSTestTimeout)
if err != nil {
return nil, err
}
tlsConn, err := ClientHandshake(ctx, conn, clientConfig)
if err != nil {
conn.Close()
return nil, err
}
return tlsConn, nil
}
func newWindowsTestEngineConn(t *testing.T, serverAddress string, options option.OutboundTLSOptions) (*windowsTLSConn, error) {
t.Helper()
ctx, cancel := context.WithTimeout(context.Background(), windowsTLSTestTimeout)
t.Cleanup(cancel)
clientConfig, err := NewClientWithOptions(ClientOptions{
Context: ctx,
Logger: logger.NOP(),
ServerAddress: "",
Options: options,
})
if err != nil {
return nil, err
}
engineConfig, ok := clientConfig.(*windowsClientConfig)
if !ok {
return nil, errors.New("unexpected windows config type")
}
conn, err := net.DialTimeout("tcp", serverAddress, windowsTLSTestTimeout)
if err != nil {
return nil, err
}
tlsConn, err := engineConfig.ClientHandshake(ctx, conn)
if err != nil {
conn.Close()
return nil, err
}
engineConn, ok := tlsConn.(*windowsTLSConn)
if !ok {
tlsConn.Close()
return nil, errors.New("unexpected windows conn type")
}
return engineConn, nil
}
func startWindowsPayloadServer(t *testing.T, minVersion uint16, payload []byte) (*windowsTLSConn, <-chan error) {
t.Helper()
serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost")
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { listener.Close() })
serverErr := make(chan error, 1)
go func() {
conn, acceptErr := listener.Accept()
if acceptErr != nil {
serverErr <- acceptErr
return
}
defer conn.Close()
tlsConn := stdtls.Server(conn, &stdtls.Config{
Certificates: []stdtls.Certificate{serverCertificate},
MinVersion: minVersion,
MaxVersion: minVersion,
})
defer tlsConn.Close()
handshakeErr := tlsConn.Handshake()
if handshakeErr != nil {
serverErr <- handshakeErr
return
}
_, writeErr := tlsConn.Write(payload)
serverErr <- writeErr
}()
version := "1.2"
if minVersion == stdtls.VersionTLS13 {
version = "1.3"
}
clientConn, err := newWindowsTestEngineConn(t, listener.Addr().String(), option.OutboundTLSOptions{
Enabled: true,
Engine: C.TLSEngineWindows,
ServerName: "localhost",
MinVersion: version,
Certificate: badoption.Listable[string]{serverCertificatePEM},
})
if err != nil {
t.Fatal(err)
}
return clientConn, serverErr
}
// startWindowsEchoServer brings up a TLS echo server with a self-signed cert
// and dials an engine client against it. The returned channel closes after
// the server goroutine exits.
func startWindowsEchoServer(t *testing.T, minVersion uint16) (Conn, <-chan struct{}) {
t.Helper()
serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost")
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { listener.Close() })
done := make(chan struct{})
go func() {
defer close(done)
conn, acceptErr := listener.Accept()
if acceptErr != nil {
return
}
defer conn.Close()
tlsConn := stdtls.Server(conn, &stdtls.Config{
Certificates: []stdtls.Certificate{serverCertificate},
MinVersion: minVersion,
MaxVersion: minVersion,
})
defer tlsConn.Close()
handshakeErr := tlsConn.Handshake()
if handshakeErr != nil {
return
}
buffer := make([]byte, 32*1024)
for {
n, readErr := tlsConn.Read(buffer)
if n > 0 {
_, writeErr := tlsConn.Write(buffer[:n])
if writeErr != nil {
return
}
}
if readErr != nil {
return
}
}
}()
version := "1.2"
if minVersion == stdtls.VersionTLS13 {
version = "1.3"
}
clientConn, err := newWindowsTestClientConn(t, listener.Addr().String(), option.OutboundTLSOptions{
Enabled: true,
Engine: C.TLSEngineWindows,
ServerName: "localhost",
MinVersion: version,
Certificate: badoption.Listable[string]{serverCertificatePEM},
})
if err != nil {
t.Fatal(err)
}
return clientConn, done
}
func startWindowsEchoEngineServer(t *testing.T, minVersion uint16) (*windowsTLSConn, <-chan struct{}) {
t.Helper()
serverCertificate, serverCertificatePEM := newWindowsTestCertificate(t, "localhost")
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { listener.Close() })
done := make(chan struct{})
go func() {
defer close(done)
conn, acceptErr := listener.Accept()
if acceptErr != nil {
return
}
defer conn.Close()
tlsConn := stdtls.Server(conn, &stdtls.Config{
Certificates: []stdtls.Certificate{serverCertificate},
MinVersion: minVersion,
MaxVersion: minVersion,
})
defer tlsConn.Close()
handshakeErr := tlsConn.Handshake()
if handshakeErr != nil {
return
}
buffer := make([]byte, 32*1024)
for {
n, readErr := tlsConn.Read(buffer)
if n > 0 {
_, writeErr := tlsConn.Write(buffer[:n])
if writeErr != nil {
return
}
}
if readErr != nil {
return
}
}
}()
version := "1.2"
if minVersion == stdtls.VersionTLS13 {
version = "1.3"
}
clientConn, err := newWindowsTestEngineConn(t, listener.Addr().String(), option.OutboundTLSOptions{
Enabled: true,
Engine: C.TLSEngineWindows,
ServerName: "localhost",
MinVersion: version,
Certificate: badoption.Listable[string]{serverCertificatePEM},
})
if err != nil {
t.Fatal(err)
}
return clientConn, done
}