package mtls import ( "context" "crypto/x509" "encoding/pem" "testing" "github.com/alicebob/miniredis/v2" "github.com/redis/go-redis/v9" ) // noopDB satisfies the DB interface without persisting anything. type noopDB struct{} func (noopDB) RecordRevocation(_ context.Context, _ string) error { return nil } func newTestCRL(t *testing.T) (*CRL, *miniredis.Miniredis) { t.Helper() mr := miniredis.RunT(t) rdb := redis.NewClient(&redis.Options{Addr: mr.Addr()}) t.Cleanup(func() { rdb.Close() }) return NewCRL(rdb, noopDB{}), mr } func TestCRL_RevokeAndIsRevoked(t *testing.T) { crl, _ := newTestCRL(t) ctx := context.Background() const nodeUUID = "node-revoked-uuid" if crl.IsRevoked(nodeUUID) { t.Fatal("IsRevoked = true before Revoke; want false") } if err := crl.Revoke(ctx, nodeUUID); err != nil { t.Fatalf("Revoke: %v", err) } if !crl.IsRevoked(nodeUUID) { t.Fatal("IsRevoked = false after Revoke; want true") } } func TestCRL_IsRevoked_UnknownNode(t *testing.T) { crl, _ := newTestCRL(t) if crl.IsRevoked("never-revoked") { t.Fatal("IsRevoked = true for unknown node; want false") } } func TestCRL_VerifyPeerCertificate_Revoked(t *testing.T) { dir := t.TempDir() ca := newTestCA(t, dir) crl, _ := newTestCRL(t) ctx := context.Background() nodeUUID := "node-revoked-cert" csrPEM, _ := newTestCSR(t) certPEM, err := ca.SignCSR(csrPEM, nodeUUID) if err != nil { t.Fatalf("SignCSR: %v", err) } block, _ := pem.Decode(certPEM) clientCert, err := x509.ParseCertificate(block.Bytes) if err != nil { t.Fatalf("ParseCertificate: %v", err) } // Build verified chains as the TLS stack would. pool := x509.NewCertPool() pool.AddCert(ca.CACert()) chains, err := clientCert.Verify(x509.VerifyOptions{ Roots: pool, KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, }) if err != nil { t.Fatalf("cert.Verify: %v", err) } // Before revocation: VerifyPeerCertificate must pass. if err := crl.VerifyPeerCertificate(nil, chains); err != nil { t.Fatalf("VerifyPeerCertificate before revoke: %v", err) } // Revoke the node. if err := crl.Revoke(ctx, nodeUUID); err != nil { t.Fatalf("Revoke: %v", err) } // After revocation: VerifyPeerCertificate must reject. if err := crl.VerifyPeerCertificate(nil, chains); err == nil { t.Fatal("VerifyPeerCertificate after revoke: expected error, got nil") } } func TestCRL_VerifyPeerCertificate_NoClientCert(t *testing.T) { crl, _ := newTestCRL(t) // Empty verifiedChains = no client cert presented (e.g. during Enroll). if err := crl.VerifyPeerCertificate(nil, nil); err != nil { t.Errorf("VerifyPeerCertificate with no chains: %v", err) } }