Files
VerseVDI-Data-Plane/cmd/internal/gatewaytls/tls_test.go
sechmachine e764b0d96d
Verify Data Plane / gateway (push) Successful in 4m35s
feat(gateway): enforce phase 3d tls trust
2026-08-11 20:15:15 +07:00

475 lines
21 KiB
Go

package gatewaytls
import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/hex"
"encoding/pem"
"math/big"
"net"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
const (
testGatewayID = "7d0d1308-5e50-45ef-984c-4508ca899f5a"
testCredentialID = "ba4d93f8-d692-4210-97db-5a561bfb32d3"
)
type testCA struct {
certificate *x509.Certificate
key *ecdsa.PrivateKey
der []byte
}
func TestLoadSeparatesTrustPoolsAndDerivesGatewayIdentity(t *testing.T) {
streamingCA := newTestCA(t, "streaming-ca")
controlCA := newTestCA(t, "control-ca")
identityCA := newTestCA(t, "identity-ca")
uri := mustURL(t, "spiffe://versevdi/gateway/"+testGatewayID+"/credential/"+testCredentialID)
leafDER, key := newLeaf(t, identityCA, "gateway.example", []*url.URL{uri}, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
certFile, keyFile := writeKeyPair(t, leafDER, identityCA.der, key)
streamingFile := writeCertificate(t, "streaming-ca.pem", streamingCA.der)
controlFile := writeCertificate(t, "control-ca.pem", controlCA.der)
material, err := Load(certFile, keyFile, streamingFile, controlFile, testGatewayID, "gateway.example")
if err != nil {
t.Fatalf("Load() error = %v", err)
}
digest := sha256.Sum256(leafDER)
if material.CertificateIdentity != "sha256:"+hex.EncodeToString(digest[:]) {
t.Fatalf("certificate identity = %q", material.CertificateIdentity)
}
if material.ServerTLS.MinVersion != tls.VersionTLS13 || material.ControlTLS.MinVersion != tls.VersionTLS13 || material.ServerTLS.ClientAuth != tls.RequireAndVerifyClientCert ||
material.ServerTLS.ClientCAs == nil || material.ControlTLS.RootCAs == nil ||
len(material.ServerTLS.Certificates) != 1 || len(material.ControlTLS.Certificates) != 1 {
t.Fatalf("TLS material = server:%#v control:%#v", material.ServerTLS, material.ControlTLS)
}
if got := material.ServerTLS.ClientCAs.Subjects(); len(got) != 1 || string(got[0]) != string(streamingCA.certificate.RawSubject) {
t.Fatalf("streaming ClientCAs = %x", got)
}
if got := material.ControlTLS.RootCAs.Subjects(); len(got) != 1 || string(got[0]) != string(controlCA.certificate.RawSubject) {
t.Fatalf("control RootCAs = %x", got)
}
if string(material.ServerTLS.Certificates[0].Certificate[0]) != string(material.ControlTLS.Certificates[0].Certificate[0]) {
t.Fatal("server and control TLS did not use the same gateway leaf")
}
trustedControlLeaf, _ := newLeaf(t, controlCA, "control.example", nil, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth})
parsedTrusted, _ := x509.ParseCertificate(trustedControlLeaf)
if _, err := parsedTrusted.Verify(x509.VerifyOptions{Roots: material.ControlTLS.RootCAs, DNSName: "control.example"}); err != nil {
t.Fatalf("control CA did not verify control server: %v", err)
}
wrongControlLeaf, _ := newLeaf(t, streamingCA, "control.example", nil, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth})
parsedWrong, _ := x509.ParseCertificate(wrongControlLeaf)
if _, err := parsedWrong.Verify(x509.VerifyOptions{Roots: material.ControlTLS.RootCAs, DNSName: "control.example"}); err == nil {
t.Fatal("control RootCAs trusted the streaming CA")
}
}
func TestLoadTrustsExactSelfSignedControlLeaf(t *testing.T) {
streamingCA := newTestCA(t, "streaming-ca")
identityCA := newTestCA(t, "identity-ca")
uri := mustURL(t, "spiffe://versevdi/gateway/"+testGatewayID+"/credential/"+testCredentialID)
gatewayLeaf, gatewayKey := newLeaf(t, identityCA, "gateway.example", []*url.URL{uri}, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
certFile, keyFile := writeKeyPair(t, gatewayLeaf, identityCA.der, gatewayKey)
streamingFile := writeCertificate(t, "streaming-ca.pem", streamingCA.der)
controlLeaf, controlKey := newSelfSignedServerLeaf(t, net.ParseIP("127.0.0.1"))
controlFile := writeCertificate(t, "control-leaf.pem", controlLeaf)
material, err := Load(certFile, keyFile, streamingFile, controlFile, testGatewayID, "gateway.example")
if err != nil {
t.Fatalf("Load() error = %v", err)
}
if material.ControlTLS.MinVersion != tls.VersionTLS13 {
t.Fatalf("ControlTLS.MinVersion = %d, want TLS 1.3", material.ControlTLS.MinVersion)
}
clientCAs := x509.NewCertPool()
clientCAs.AddCert(identityCA.certificate)
trusted := newControlServer(t, controlLeaf, controlKey, clientCAs)
defer trusted.Close()
response, err := (&http.Client{Transport: &http.Transport{TLSClientConfig: material.ControlTLS}}).Get(trusted.URL)
if err != nil {
t.Fatalf("pinned self-signed control leaf handshake: %v", err)
}
response.Body.Close()
if response.StatusCode != http.StatusNoContent {
t.Fatalf("trusted control response status = %d", response.StatusCode)
}
wrongLeaf, wrongKey := newSelfSignedServerLeaf(t, net.ParseIP("127.0.0.1"))
wrong := newControlServer(t, wrongLeaf, wrongKey, clientCAs)
defer wrong.Close()
if _, err := (&http.Client{Transport: &http.Transport{TLSClientConfig: material.ControlTLS}}).Get(wrong.URL); err == nil {
t.Fatal("different self-signed leaf with the same SAN completed handshake")
}
mismatchedLeaf, mismatchedKey := newSelfSignedServerLeaf(t, net.ParseIP("127.0.0.2"))
mismatchedFile := writeCertificate(t, "mismatched-control-leaf.pem", mismatchedLeaf)
mismatchedMaterial, err := Load(certFile, keyFile, streamingFile, mismatchedFile, testGatewayID, "gateway.example")
if err != nil {
t.Fatalf("Load() mismatched control leaf error = %v", err)
}
mismatched := newControlServer(t, mismatchedLeaf, mismatchedKey, clientCAs)
defer mismatched.Close()
if _, err := (&http.Client{Transport: &http.Transport{TLSClientConfig: mismatchedMaterial.ControlTLS}}).Get(mismatched.URL); err == nil {
t.Fatal("control leaf without the request hostname/SAN completed handshake")
}
if _, err := Load(certFile, keyFile, controlFile, controlFile, testGatewayID, "gateway.example"); err == nil {
t.Fatal("Load() accepted a non-CA leaf as streaming trust")
}
}
func TestLoadTrustsExactCAIssuedControlLeaf(t *testing.T) {
streamingCA := newTestCA(t, "streaming-ca")
controlCA := newTestCA(t, "control-ca")
identityCA := newTestCA(t, "identity-ca")
uri := mustURL(t, "spiffe://versevdi/gateway/"+testGatewayID+"/credential/"+testCredentialID)
gatewayLeaf, gatewayKey := newLeaf(t, identityCA, "gateway.example", []*url.URL{uri}, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
certFile, keyFile := writeKeyPair(t, gatewayLeaf, identityCA.der, gatewayKey)
streamingFile := writeCertificate(t, "streaming-ca.pem", streamingCA.der)
activeLeaf, activeKey := newCAIssuedControlLeaf(t, controlCA, net.ParseIP("127.0.0.1"))
controlFile := writeCertificate(t, "control-leaf.pem", activeLeaf)
material, err := Load(certFile, keyFile, streamingFile, controlFile, testGatewayID, "gateway.example")
if err != nil {
t.Fatalf("Load() error = %v", err)
}
clientCAs := x509.NewCertPool()
clientCAs.AddCert(identityCA.certificate)
trusted := newControlServer(t, activeLeaf, activeKey, clientCAs, controlCA.der)
defer trusted.Close()
response, err := (&http.Client{Transport: &http.Transport{TLSClientConfig: material.ControlTLS}}).Get(trusted.URL)
if err != nil {
t.Fatalf("pinned CA-issued control leaf handshake: %v", err)
}
response.Body.Close()
if response.TLS == nil || response.TLS.Version != tls.VersionTLS13 {
t.Fatalf("pinned control leaf TLS version = %v, want TLS 1.3", response.TLS)
}
if response.StatusCode != http.StatusNoContent {
t.Fatalf("trusted control response status = %d", response.StatusCode)
}
siblingLeaf, siblingKey := newCAIssuedControlLeaf(t, controlCA, net.ParseIP("127.0.0.1"))
sibling := newControlServer(t, siblingLeaf, siblingKey, clientCAs, controlCA.der)
defer sibling.Close()
if _, err := (&http.Client{Transport: &http.Transport{TLSClientConfig: material.ControlTLS}}).Get(sibling.URL); err == nil {
t.Fatal("sibling control leaf from the same issuer and SAN completed handshake")
}
}
func TestLoadRejectsControlLeafBundlesAndNonServerAuth(t *testing.T) {
streamingCA := newTestCA(t, "streaming-ca")
controlCA := newTestCA(t, "control-ca")
identityCA := newTestCA(t, "identity-ca")
uri := mustURL(t, "spiffe://versevdi/gateway/"+testGatewayID+"/credential/"+testCredentialID)
gatewayLeaf, gatewayKey := newLeaf(t, identityCA, "gateway.example", []*url.URL{uri}, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
certFile, keyFile := writeKeyPair(t, gatewayLeaf, identityCA.der, gatewayKey)
streamingFile := writeCertificate(t, "streaming-ca.pem", streamingCA.der)
serverLeaf, _ := newCAIssuedControlLeaf(t, controlCA, net.ParseIP("127.0.0.1"))
nonServerLeaf, _ := newLeaf(t, controlCA, "control.example", nil, []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth})
for _, test := range []struct {
name string
certificates [][]byte
}{
{"leaf and issuer chain", [][]byte{serverLeaf, controlCA.der}},
{"non ServerAuth leaf", [][]byte{nonServerLeaf}},
} {
t.Run(test.name, func(t *testing.T) {
controlFile := writeCertificateBundle(t, "control.pem", test.certificates...)
if _, err := Load(certFile, keyFile, streamingFile, controlFile, testGatewayID, "gateway.example"); err == nil {
t.Fatal("Load() accepted invalid control trust material")
}
})
}
}
func TestLoadRejectsWrongPublicNameURIAndLeafUsage(t *testing.T) {
streamingCA := newTestCA(t, "streaming-ca")
controlCA := newTestCA(t, "control-ca")
identityCA := newTestCA(t, "identity-ca")
streamingFile := writeCertificate(t, "streaming-ca.pem", streamingCA.der)
controlFile := writeCertificate(t, "control-ca.pem", controlCA.der)
tests := []struct {
name string
uriGatewayID string
publicIdentity string
leafDNSName string
usages []x509.ExtKeyUsage
}{
{"wrong public name", testGatewayID, "other.example", "gateway.example", []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}},
{"noncanonical public identity", testGatewayID, " gateway.example ", "gateway.example", []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}},
{"wildcard is not an exact public identity", testGatewayID, "gateway.example", "*.example", []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}},
{"wrong URI gateway", "c1f5d25a-47c6-44ed-b9be-e0e8e87d4ae2", "gateway.example", "gateway.example", []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}},
{"missing client auth", testGatewayID, "gateway.example", "gateway.example", []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
uri := mustURL(t, "spiffe://versevdi/gateway/"+test.uriGatewayID+"/credential/"+testCredentialID)
leafDER, key := newLeaf(t, identityCA, test.leafDNSName, []*url.URL{uri}, test.usages)
certFile, keyFile := writeKeyPair(t, leafDER, identityCA.der, key)
if _, err := Load(certFile, keyFile, streamingFile, controlFile, testGatewayID, test.publicIdentity); err == nil {
t.Fatal("Load() accepted invalid gateway certificate identity")
}
})
}
}
func TestLoadRejectsNonCanonicalPublicDNSIdentity(t *testing.T) {
streamingCA := newTestCA(t, "streaming-ca")
controlCA := newTestCA(t, "control-ca")
identityCA := newTestCA(t, "identity-ca")
streamingFile := writeCertificate(t, "streaming-ca.pem", streamingCA.der)
controlFile := writeCertificate(t, "control-ca.pem", controlCA.der)
uri := mustURL(t, "spiffe://versevdi/gateway/"+testGatewayID+"/credential/"+testCredentialID)
tooLongLabel := strings.Repeat("a", 64) + ".example"
tooLongName := strings.Repeat("a", 63) + "." + strings.Repeat("b", 63) + "." + strings.Repeat("c", 63) + "." + strings.Repeat("d", 62)
for _, test := range []struct {
name string
leafDNSName string
publicIdentity string
}{
{"uppercase exact SAN", "Gateway.example", "Gateway.example"},
{"empty label", "gateway..example", "gateway..example"},
{"leading hyphen", "-gateway.example", "-gateway.example"},
{"trailing hyphen", "gateway-.example", "gateway-.example"},
{"wildcard", "*.example", "*.example"},
{"label exceeds 63 bytes", tooLongLabel, tooLongLabel},
{"name exceeds 253 bytes", tooLongName, tooLongName},
{"IP address", "127.0.0.1", "127.0.0.1"},
{"port", "gateway.example:443", "gateway.example:443"},
{"path", "gateway.example/path", "gateway.example/path"},
{"whitespace", "gateway .example", "gateway .example"},
{"trailing dot", "gateway.example.", "gateway.example."},
{"Unicode", "gateway.example", "gäteway.example"},
} {
t.Run(test.name, func(t *testing.T) {
leafDER, key := newLeaf(t, identityCA, test.leafDNSName, []*url.URL{uri}, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
certFile, keyFile := writeKeyPair(t, leafDER, identityCA.der, key)
if _, err := Load(certFile, keyFile, streamingFile, controlFile, testGatewayID, test.publicIdentity); err == nil {
t.Fatal("Load() accepted a noncanonical public DNS identity")
}
})
}
}
func TestLoadAcceptsCanonicalPublicDNSIdentity(t *testing.T) {
streamingCA := newTestCA(t, "streaming-ca")
controlCA := newTestCA(t, "control-ca")
identityCA := newTestCA(t, "identity-ca")
streamingFile := writeCertificate(t, "streaming-ca.pem", streamingCA.der)
controlFile := writeCertificate(t, "control-ca.pem", controlCA.der)
uri := mustURL(t, "spiffe://versevdi/gateway/"+testGatewayID+"/credential/"+testCredentialID)
for _, publicIdentity := range []string{"gateway.example", "gateway"} {
t.Run(publicIdentity, func(t *testing.T) {
leafDER, key := newLeaf(t, identityCA, publicIdentity, []*url.URL{uri}, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
certFile, keyFile := writeKeyPair(t, leafDER, identityCA.der, key)
if _, err := Load(certFile, keyFile, streamingFile, controlFile, testGatewayID, publicIdentity); err != nil {
t.Fatalf("Load() error = %v", err)
}
})
}
}
func TestLoadRejectsLegacyAndNoncanonicalGatewayURIs(t *testing.T) {
streamingCA := newTestCA(t, "streaming-ca")
controlCA := newTestCA(t, "control-ca")
identityCA := newTestCA(t, "identity-ca")
streamingFile := writeCertificate(t, "streaming-ca.pem", streamingCA.der)
controlFile := writeCertificate(t, "control-ca.pem", controlCA.der)
canonical := "spiffe://versevdi/gateway/" + testGatewayID + "/credential/" + testCredentialID
for _, test := range []struct {
name string
uri string
}{
{"legacy URN", "urn:versevdi:gateway:" + testGatewayID + ":credential:" + testCredentialID},
{"opaque URI", "spiffe:gateway/" + testGatewayID + "/credential/" + testCredentialID},
{"leading path slash", "spiffe://versevdi//gateway/" + testGatewayID + "/credential/" + testCredentialID},
{"trailing path slash", canonical + "/"},
{"percent encoded credential", "spiffe://versevdi/gateway/" + testGatewayID + "/credential/%62a4d93f8-d692-4210-97db-5a561bfb32d3"},
{"force query", canonical + "?"},
{"query", canonical + "?version=1"},
{"fragment", canonical + "#fragment"},
{"user", "spiffe://gateway@versevdi/gateway/" + testGatewayID + "/credential/" + testCredentialID},
} {
t.Run(test.name, func(t *testing.T) {
uri := mustURL(t, test.uri)
leafDER, key := newLeaf(t, identityCA, "gateway.example", []*url.URL{uri}, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
certFile, keyFile := writeKeyPair(t, leafDER, identityCA.der, key)
if _, err := Load(certFile, keyFile, streamingFile, controlFile, testGatewayID, "gateway.example"); err == nil {
t.Fatal("Load() accepted a legacy or noncanonical gateway URI")
}
})
}
t.Run("uppercase UUIDs", func(t *testing.T) {
gatewayID := strings.ToUpper(testGatewayID)
uri := mustURL(t, "spiffe://versevdi/gateway/"+gatewayID+"/credential/"+strings.ToUpper(testCredentialID))
leafDER, key := newLeaf(t, identityCA, "gateway.example", []*url.URL{uri}, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
certFile, keyFile := writeKeyPair(t, leafDER, identityCA.der, key)
if _, err := Load(certFile, keyFile, streamingFile, controlFile, gatewayID, "gateway.example"); err == nil {
t.Fatal("Load() accepted noncanonical uppercase UUIDs")
}
})
}
func newTestCA(t *testing.T, commonName string) testCA {
t.Helper()
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
}
template := &x509.Certificate{
SerialNumber: big.NewInt(time.Now().UnixNano()), Subject: pkix.Name{CommonName: commonName},
NotBefore: time.Now().Add(-time.Hour), NotAfter: time.Now().Add(time.Hour),
IsCA: true, BasicConstraintsValid: true, KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature,
}
der, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
if err != nil {
t.Fatal(err)
}
certificate, err := x509.ParseCertificate(der)
if err != nil {
t.Fatal(err)
}
return testCA{certificate: certificate, key: key, der: der}
}
func newLeaf(t *testing.T, ca testCA, dnsName string, uris []*url.URL, usages []x509.ExtKeyUsage) ([]byte, ed25519.PrivateKey) {
t.Helper()
publicKey, key, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatal(err)
}
template := &x509.Certificate{
SerialNumber: big.NewInt(time.Now().UnixNano()), Subject: pkix.Name{CommonName: dnsName}, DNSNames: []string{dnsName}, URIs: uris,
NotBefore: time.Now().Add(-time.Hour), NotAfter: time.Now().Add(time.Hour),
BasicConstraintsValid: true, KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: usages,
}
der, err := x509.CreateCertificate(rand.Reader, template, ca.certificate, publicKey, ca.key)
if err != nil {
t.Fatal(err)
}
return der, key
}
func newSelfSignedServerLeaf(t *testing.T, ip net.IP) ([]byte, *ecdsa.PrivateKey) {
t.Helper()
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
}
template := &x509.Certificate{
SerialNumber: big.NewInt(time.Now().UnixNano()), Subject: pkix.Name{CommonName: "control.example"}, IPAddresses: []net.IP{ip},
NotBefore: time.Now().Add(-time.Hour), NotAfter: time.Now().Add(time.Hour),
BasicConstraintsValid: true, KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
}
der, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
if err != nil {
t.Fatal(err)
}
return der, key
}
func newCAIssuedControlLeaf(t *testing.T, ca testCA, ip net.IP) ([]byte, ed25519.PrivateKey) {
t.Helper()
publicKey, key, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatal(err)
}
template := &x509.Certificate{
SerialNumber: big.NewInt(time.Now().UnixNano()), Subject: pkix.Name{CommonName: "control.example"}, IPAddresses: []net.IP{ip},
NotBefore: time.Now().Add(-time.Hour), NotAfter: time.Now().Add(time.Hour),
BasicConstraintsValid: true, KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
}
der, err := x509.CreateCertificate(rand.Reader, template, ca.certificate, publicKey, ca.key)
if err != nil {
t.Fatal(err)
}
return der, key
}
func newControlServer(t *testing.T, leaf []byte, key any, clientCAs *x509.CertPool, chain ...[]byte) *httptest.Server {
t.Helper()
server := httptest.NewUnstartedServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
if request.TLS == nil || len(request.TLS.PeerCertificates) == 0 {
http.Error(response, "mTLS required", http.StatusUnauthorized)
return
}
response.WriteHeader(http.StatusNoContent)
}))
certificates := append([][]byte{leaf}, chain...)
server.TLS = &tls.Config{
MinVersion: tls.VersionTLS13, Certificates: []tls.Certificate{{Certificate: certificates, PrivateKey: key}},
ClientAuth: tls.RequireAndVerifyClientCert, ClientCAs: clientCAs,
}
server.StartTLS()
return server
}
func writeKeyPair(t *testing.T, leafDER, issuerDER []byte, key ed25519.PrivateKey) (string, string) {
t.Helper()
directory := t.TempDir()
certFile := filepath.Join(directory, "gateway.pem")
keyFile := filepath.Join(directory, "gateway-key.pem")
certificatePEM := append(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafDER}), pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: issuerDER})...)
privateDER, err := x509.MarshalPKCS8PrivateKey(key)
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(certFile, certificatePEM, 0o600); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(keyFile, pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privateDER}), 0o600); err != nil {
t.Fatal(err)
}
return certFile, keyFile
}
func writeCertificate(t *testing.T, name string, der []byte) string {
return writeCertificateBundle(t, name, der)
}
func writeCertificateBundle(t *testing.T, name string, certificates ...[]byte) string {
t.Helper()
path := filepath.Join(t.TempDir(), name)
var encoded []byte
for _, certificate := range certificates {
encoded = append(encoded, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certificate})...)
}
if err := os.WriteFile(path, encoded, 0o600); err != nil {
t.Fatal(err)
}
return path
}
func mustURL(t *testing.T, value string) *url.URL {
t.Helper()
parsed, err := url.Parse(value)
if err != nil {
t.Fatal(err)
}
return parsed
}