mirror of
https://github.com/bolucat/Archive.git
synced 2026-04-22 16:07:49 +08:00
121 lines
3.8 KiB
Go
121 lines
3.8 KiB
Go
//go:build linux || darwin
|
|
|
|
package tlsspoof
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/pem"
|
|
"io"
|
|
"math/big"
|
|
"net"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// generateSelfSignedCert returns a TLS certificate valid for the given SAN.
|
|
func generateSelfSignedCert(t *testing.T, commonName string, sans ...string) tls.Certificate {
|
|
t.Helper()
|
|
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
require.NoError(t, err)
|
|
serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
|
|
require.NoError(t, err)
|
|
template := x509.Certificate{
|
|
SerialNumber: serial,
|
|
Subject: pkix.Name{CommonName: commonName},
|
|
NotBefore: time.Now().Add(-time.Hour),
|
|
NotAfter: time.Now().Add(time.Hour),
|
|
KeyUsage: x509.KeyUsageDigitalSignature,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
|
DNSNames: sans,
|
|
}
|
|
der, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
|
|
require.NoError(t, err)
|
|
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})
|
|
keyDER, err := x509.MarshalPKCS8PrivateKey(priv)
|
|
require.NoError(t, err)
|
|
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER})
|
|
cert, err := tls.X509KeyPair(certPEM, keyPEM)
|
|
require.NoError(t, err)
|
|
return cert
|
|
}
|
|
|
|
// TestIntegrationConn_RealTLSHandshake drives a real crypto/tls ClientHello
|
|
// through the spoofer and asserts the on-wire fake packet carries the fake SNI
|
|
// while the server receives the real SNI. This exercises the full
|
|
// `tls.Client(wrapped, config).Handshake()` path rather than a static hex
|
|
// payload, matching what user-facing code hits.
|
|
func TestIntegrationConn_RealTLSHandshake(t *testing.T) {
|
|
requireRoot(t)
|
|
const realSNI = "real.test"
|
|
const fakeSNI = "fake.test"
|
|
|
|
serverCert := generateSelfSignedCert(t, realSNI, realSNI)
|
|
tlsConfig := &tls.Config{Certificates: []tls.Certificate{serverCert}}
|
|
|
|
listener, err := tls.Listen("tcp4", "127.0.0.1:0", tlsConfig)
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() { listener.Close() })
|
|
|
|
serverSNI := make(chan string, 1)
|
|
go func() {
|
|
conn, err := listener.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
tlsConn := conn.(*tls.Conn)
|
|
_ = tlsConn.SetDeadline(time.Now().Add(3 * time.Second))
|
|
if handshakeErr := tlsConn.Handshake(); handshakeErr != nil {
|
|
serverSNI <- "handshake-error:" + handshakeErr.Error()
|
|
return
|
|
}
|
|
serverSNI <- tlsConn.ConnectionState().ServerName
|
|
_, _ = io.Copy(io.Discard, conn)
|
|
}()
|
|
|
|
addr := listener.Addr().(*net.TCPAddr)
|
|
serverPort := uint16(addr.Port)
|
|
raw, err := net.Dial("tcp4", addr.String())
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() { raw.Close() })
|
|
|
|
spoofer, err := NewSpoofer(raw, MethodWrongSequence)
|
|
require.NoError(t, err)
|
|
wrapped, err := NewConn(raw, spoofer, fakeSNI)
|
|
require.NoError(t, err)
|
|
|
|
clientConfig := &tls.Config{
|
|
ServerName: realSNI,
|
|
InsecureSkipVerify: true,
|
|
}
|
|
tlsClient := tls.Client(wrapped, clientConfig)
|
|
t.Cleanup(func() { tlsClient.Close() })
|
|
|
|
seen := tcpdumpObserverMulti(t, loopbackInterface, serverPort,
|
|
[]string{realSNI, fakeSNI}, func() {
|
|
_ = tlsClient.SetDeadline(time.Now().Add(3 * time.Second))
|
|
err := tlsClient.Handshake()
|
|
require.NoError(t, err, "TLS handshake must succeed (wrong-sequence fake is dropped by peer)")
|
|
}, 4*time.Second)
|
|
|
|
require.True(t, seen[realSNI],
|
|
"real ClientHello on the wire must contain original SNI %q", realSNI)
|
|
require.True(t, seen[fakeSNI],
|
|
"fake ClientHello on the wire must contain fake SNI %q", fakeSNI)
|
|
|
|
select {
|
|
case sniOnServer := <-serverSNI:
|
|
require.Equal(t, realSNI, sniOnServer,
|
|
"TLS server must see the real SNI (fake packet dropped by peer TCP stack)")
|
|
case <-time.After(3 * time.Second):
|
|
t.Fatal("TLS server did not complete handshake")
|
|
}
|
|
}
|