mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-09-19 17:50:12 +00:00
131 lines
4.0 KiB
Go
131 lines
4.0 KiB
Go
package fixture
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"errors"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestFixture_VerifiesLocalhostTLS(t *testing.T) {
|
|
// Given
|
|
fixture, err := NewLocalTLSFixture(time.Now())
|
|
requireNoFixtureError(t, err)
|
|
address, closeServer := startLocalTLSServer(t, fixture)
|
|
defer closeServer()
|
|
client := localTLSClient(fixture.ClientConfig("localhost"), address)
|
|
|
|
// When
|
|
response, err := client.Get("https://localhost:" + portOf(t, address) + "/ready")
|
|
requireNoFixtureError(t, err)
|
|
defer response.Body.Close()
|
|
body, err := io.ReadAll(response.Body)
|
|
requireNoFixtureError(t, err)
|
|
|
|
// Then
|
|
if response.StatusCode != http.StatusOK || string(body) != "tls-ready" {
|
|
t.Fatalf("TLS response = %d %q", response.StatusCode, body)
|
|
}
|
|
if fixture.ClientConfig("localhost").InsecureSkipVerify {
|
|
t.Fatal("TLS fixture disabled certificate verification")
|
|
}
|
|
if len(fixture.CAPEM()) == 0 || len(fixture.CertificatePEM()) == 0 || len(fixture.PrivateKeyPEM()) == 0 {
|
|
t.Fatal("TLS fixture did not expose certificate material")
|
|
}
|
|
assertLocalCertificateProperties(t, fixture)
|
|
}
|
|
|
|
func assertLocalCertificateProperties(t *testing.T, fixture LocalTLSFixture) {
|
|
t.Helper()
|
|
caBlock, _ := pem.Decode(fixture.CAPEM())
|
|
if caBlock == nil {
|
|
t.Fatal("fixture CA PEM is invalid")
|
|
}
|
|
caCertificate, err := x509.ParseCertificate(caBlock.Bytes)
|
|
requireNoFixtureError(t, err)
|
|
if !caCertificate.IsCA || !caCertificate.BasicConstraintsValid || caCertificate.KeyUsage&x509.KeyUsageCertSign == 0 {
|
|
t.Fatalf("fixture CA constraints are invalid: %+v", caCertificate)
|
|
}
|
|
leafBlock, _ := pem.Decode(fixture.CertificatePEM())
|
|
if leafBlock == nil {
|
|
t.Fatal("fixture leaf PEM is invalid")
|
|
}
|
|
leafCertificate, err := x509.ParseCertificate(leafBlock.Bytes)
|
|
requireNoFixtureError(t, err)
|
|
if err := leafCertificate.VerifyHostname("localhost"); err != nil {
|
|
t.Fatalf("verify localhost SAN: %v", err)
|
|
}
|
|
if err := leafCertificate.VerifyHostname("127.0.0.1"); err != nil {
|
|
t.Fatalf("verify loopback SAN: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestFixture_RejectsTLSNameMismatch(t *testing.T) {
|
|
// Given
|
|
fixture, err := NewLocalTLSFixture(time.Now())
|
|
requireNoFixtureError(t, err)
|
|
address, closeServer := startLocalTLSServer(t, fixture)
|
|
defer closeServer()
|
|
client := localTLSClient(fixture.ClientConfig("wronghost.invalid"), address)
|
|
|
|
// When
|
|
_, err = client.Get("https://wronghost.invalid:" + portOf(t, address) + "/ready")
|
|
|
|
// Then
|
|
var hostnameError x509.HostnameError
|
|
if !errors.As(err, &hostnameError) {
|
|
t.Fatalf("TLS mismatch error = %v", err)
|
|
}
|
|
}
|
|
|
|
func startLocalTLSServer(t *testing.T, fixture LocalTLSFixture) (string, func()) {
|
|
t.Helper()
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
requireNoFixtureError(t, err)
|
|
tlsListener := fixture.Listener(listener)
|
|
server := &http.Server{Handler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
|
if request.URL.Path != "/ready" {
|
|
http.NotFound(writer, request)
|
|
return
|
|
}
|
|
_, _ = io.WriteString(writer, "tls-ready")
|
|
})}
|
|
serveDone := make(chan error, 1)
|
|
go func() { serveDone <- server.Serve(tlsListener) }()
|
|
closeServer := func() {
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
requireNoFixtureError(t, server.Shutdown(ctx))
|
|
serveErr := <-serveDone
|
|
if serveErr != nil && !errors.Is(serveErr, http.ErrServerClosed) {
|
|
t.Fatalf("serve TLS: %v", serveErr)
|
|
}
|
|
}
|
|
return listener.Addr().String(), closeServer
|
|
}
|
|
|
|
func localTLSClient(config *tls.Config, address string) *http.Client {
|
|
transport := &http.Transport{
|
|
TLSClientConfig: config,
|
|
DialContext: func(ctx context.Context, network, _ string) (net.Conn, error) {
|
|
var dialer net.Dialer
|
|
return dialer.DialContext(ctx, network, address)
|
|
},
|
|
}
|
|
return &http.Client{Transport: transport, Timeout: 2 * time.Second}
|
|
}
|
|
|
|
func portOf(t *testing.T, address string) string {
|
|
t.Helper()
|
|
_, port, err := net.SplitHostPort(address)
|
|
requireNoFixtureError(t, err)
|
|
return strings.TrimSpace(port)
|
|
}
|