Add certgen.TestCA for in-memory test certificate infrastructure
Provides a P-256 CA that issues leaf certificates for TLS testing with full verification enabled. No files written to disk. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
223
certlib/certgen/testca_test.go
Normal file
223
certlib/certgen/testca_test.go
Normal file
@@ -0,0 +1,223 @@
|
||||
package certgen
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewTestCA(t *testing.T) {
|
||||
ca, err := NewTestCA()
|
||||
if err != nil {
|
||||
t.Fatalf("NewTestCA: %v", err)
|
||||
}
|
||||
|
||||
cert := ca.Certificate()
|
||||
if !cert.IsCA {
|
||||
t.Fatal("expected CA certificate")
|
||||
}
|
||||
if cert.Subject.CommonName != "Test CA" {
|
||||
t.Fatalf("got CN %q, want %q", cert.Subject.CommonName, "Test CA")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCertificatePEMRoundtrip(t *testing.T) {
|
||||
ca, err := NewTestCA()
|
||||
if err != nil {
|
||||
t.Fatalf("NewTestCA: %v", err)
|
||||
}
|
||||
|
||||
pemBytes := ca.CertificatePEM()
|
||||
block, _ := pem.Decode(pemBytes)
|
||||
if block == nil {
|
||||
t.Fatal("failed to decode PEM")
|
||||
}
|
||||
if block.Type != "CERTIFICATE" {
|
||||
t.Fatalf("got PEM type %q, want CERTIFICATE", block.Type)
|
||||
}
|
||||
|
||||
parsed, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
t.Fatalf("parse certificate: %v", err)
|
||||
}
|
||||
if !parsed.Equal(ca.Certificate()) {
|
||||
t.Fatal("parsed certificate does not match original")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCertPool(t *testing.T) {
|
||||
ca, err := NewTestCA()
|
||||
if err != nil {
|
||||
t.Fatalf("NewTestCA: %v", err)
|
||||
}
|
||||
|
||||
pool := ca.CertPool()
|
||||
|
||||
// Verify the CA cert validates against its own pool.
|
||||
chains, err := ca.Certificate().Verify(x509.VerifyOptions{
|
||||
Roots: pool,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("verify CA cert against its own pool: %v", err)
|
||||
}
|
||||
if len(chains) == 0 {
|
||||
t.Fatal("expected at least one chain")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue(t *testing.T) {
|
||||
ca, err := NewTestCA()
|
||||
if err != nil {
|
||||
t.Fatalf("NewTestCA: %v", err)
|
||||
}
|
||||
|
||||
dnsNames := []string{"example.test", "www.example.test"}
|
||||
ips := []net.IP{net.IPv4(10, 0, 0, 1)}
|
||||
|
||||
key, cert, err := ca.Issue(dnsNames, ips)
|
||||
if err != nil {
|
||||
t.Fatalf("Issue: %v", err)
|
||||
}
|
||||
if key == nil {
|
||||
t.Fatal("expected non-nil key")
|
||||
}
|
||||
if cert.IsCA {
|
||||
t.Fatal("leaf cert should not be CA")
|
||||
}
|
||||
if cert.Subject.CommonName != "example.test" {
|
||||
t.Fatalf("got CN %q, want %q", cert.Subject.CommonName, "example.test")
|
||||
}
|
||||
|
||||
// Verify the leaf cert chains to the CA.
|
||||
_, err = cert.Verify(x509.VerifyOptions{
|
||||
Roots: ca.CertPool(),
|
||||
DNSName: "example.test",
|
||||
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("verify leaf cert: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssueServerTLS(t *testing.T) {
|
||||
ca, err := NewTestCA()
|
||||
if err != nil {
|
||||
t.Fatalf("NewTestCA: %v", err)
|
||||
}
|
||||
|
||||
key, cert, err := ca.IssueServer()
|
||||
if err != nil {
|
||||
t.Fatalf("IssueServer: %v", err)
|
||||
}
|
||||
|
||||
// Start a TLS server with the issued cert.
|
||||
serverCfg := ca.ServerTLSConfig(key, cert)
|
||||
srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
srv.TLS = &serverCfg
|
||||
srv.StartTLS()
|
||||
defer srv.Close()
|
||||
|
||||
// Create a client that verifies the server cert against the CA.
|
||||
clientCfg := ca.TLSConfig()
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &clientCfg,
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := client.Get(srv.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("GET: %v", err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("got status %d, want 200", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLSConfigReturnsByValue(t *testing.T) {
|
||||
ca, err := NewTestCA()
|
||||
if err != nil {
|
||||
t.Fatalf("NewTestCA: %v", err)
|
||||
}
|
||||
|
||||
cfg1 := ca.TLSConfig()
|
||||
cfg2 := ca.TLSConfig()
|
||||
|
||||
// Modifying one should not affect the other.
|
||||
cfg1.ServerName = "modified"
|
||||
if cfg2.ServerName == "modified" {
|
||||
t.Fatal("TLSConfig should return independent values")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLSConfigEnforcesTLS13(t *testing.T) {
|
||||
ca, err := NewTestCA()
|
||||
if err != nil {
|
||||
t.Fatalf("NewTestCA: %v", err)
|
||||
}
|
||||
|
||||
cfg := ca.TLSConfig()
|
||||
if cfg.MinVersion != tls.VersionTLS13 {
|
||||
t.Fatalf("got MinVersion %d, want TLS 1.3 (%d)", cfg.MinVersion, tls.VersionTLS13)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMustTestCA(t *testing.T) {
|
||||
// Should not panic.
|
||||
ca := MustTestCA()
|
||||
if ca.Certificate() == nil {
|
||||
t.Fatal("expected non-nil certificate")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrivateKeyPEM(t *testing.T) {
|
||||
ca, err := NewTestCA()
|
||||
if err != nil {
|
||||
t.Fatalf("NewTestCA: %v", err)
|
||||
}
|
||||
|
||||
key, _, err := ca.IssueServer()
|
||||
if err != nil {
|
||||
t.Fatalf("IssueServer: %v", err)
|
||||
}
|
||||
|
||||
pemBytes, err := PrivateKeyPEM(key)
|
||||
if err != nil {
|
||||
t.Fatalf("PrivateKeyPEM: %v", err)
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(pemBytes)
|
||||
if block == nil {
|
||||
t.Fatal("failed to decode PEM")
|
||||
}
|
||||
if block.Type != "PRIVATE KEY" {
|
||||
t.Fatalf("got PEM type %q, want PRIVATE KEY", block.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUntrustedCAFails(t *testing.T) {
|
||||
ca1 := MustTestCA()
|
||||
ca2 := MustTestCA()
|
||||
|
||||
// Issue a cert from ca1, try to verify against ca2's pool.
|
||||
_, cert, err := ca1.IssueServer()
|
||||
if err != nil {
|
||||
t.Fatalf("IssueServer: %v", err)
|
||||
}
|
||||
|
||||
_, err = cert.Verify(x509.VerifyOptions{
|
||||
Roots: ca2.CertPool(),
|
||||
DNSName: "localhost",
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected verification to fail with wrong CA")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user