package mtls import ( "bytes" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/x509" "crypto/x509/pkix" "encoding/pem" "path/filepath" "testing" ) // newTestCA creates a CA in the given temporary directory. func newTestCA(t *testing.T, dir string) *CA { t.Helper() ca, err := NewCA(CAConfig{ KeyPath: filepath.Join(dir, "ca.key"), CertPath: filepath.Join(dir, "ca.crt"), }) if err != nil { t.Fatalf("NewCA: %v", err) } return ca } // newTestCSR generates an ECDSA P-256 key and a CSR PEM for testing. func newTestCSR(t *testing.T) ([]byte, *ecdsa.PrivateKey) { t.Helper() key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { t.Fatalf("generate CSR key: %v", err) } tmpl := &x509.CertificateRequest{ Subject: pkix.Name{CommonName: "ignored-by-signer"}, } csrDER, err := x509.CreateCertificateRequest(rand.Reader, tmpl, key) if err != nil { t.Fatalf("CreateCertificateRequest: %v", err) } csrPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrDER}) return csrPEM, key } func TestCA_GenerateAndLoad(t *testing.T) { dir := t.TempDir() // First call: generate a new CA. ca1 := newTestCA(t, dir) if len(ca1.CAPEM()) == 0 { t.Fatal("CAPEM returned empty") } // Second call with same paths: must load from disk without error. ca2, err := NewCA(CAConfig{ KeyPath: filepath.Join(dir, "ca.key"), CertPath: filepath.Join(dir, "ca.crt"), }) if err != nil { t.Fatalf("NewCA reload: %v", err) } // The CA certificates must be byte-identical. if !bytes.Equal(ca1.CAPEM(), ca2.CAPEM()) { t.Fatal("reloaded CA cert differs from generated CA cert") } } func TestCA_SignCSR_CNOverride(t *testing.T) { dir := t.TempDir() ca := newTestCA(t, dir) csrPEM, _ := newTestCSR(t) nodeUUID := "node-11111111-2222-3333-4444-555555555555" certPEM, err := ca.SignCSR(csrPEM, nodeUUID) if err != nil { t.Fatalf("SignCSR: %v", err) } block, _ := pem.Decode(certPEM) if block == nil { t.Fatal("SignCSR returned empty PEM") } cert, err := x509.ParseCertificate(block.Bytes) if err != nil { t.Fatalf("ParseCertificate: %v", err) } // CN must match nodeUUID regardless of what the CSR contained. if cert.Subject.CommonName != nodeUUID { t.Errorf("CN = %q; want %q", cert.Subject.CommonName, nodeUUID) } } func TestCA_SignCSR_VerifiesAgainstCAPool(t *testing.T) { dir := t.TempDir() ca := newTestCA(t, dir) csrPEM, _ := newTestCSR(t) certPEM, err := ca.SignCSR(csrPEM, "node-test") if err != nil { t.Fatalf("SignCSR: %v", err) } block, _ := pem.Decode(certPEM) cert, err := x509.ParseCertificate(block.Bytes) if err != nil { t.Fatalf("ParseCertificate: %v", err) } // Build a pool with only our CA and verify the issued cert against it. pool := x509.NewCertPool() pool.AddCert(ca.CACert()) _, err = cert.Verify(x509.VerifyOptions{ Roots: pool, KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, }) if err != nil { t.Errorf("cert.Verify: %v", err) } } func TestCA_SignCSR_RejectsInvalidPEM(t *testing.T) { dir := t.TempDir() ca := newTestCA(t, dir) _, err := ca.SignCSR([]byte("not-a-pem"), "any-uuid") if err == nil { t.Fatal("expected error for invalid CSR PEM, got nil") } }