Files
certman/certificate/authority_test.go
Björn Benouarets 5f6640d8f3 feat: implement certificate authority service
- Add CertificateAuthorityService for CA management
- Support creation of root and intermediate CAs
- Implement CA certificate generation with proper X.509 attributes
- Add CA validation and verification functionality
- Include comprehensive test coverage for CA operations
- Support multiple CA types and configurations
- Add proper error handling and logging
2025-09-30 11:44:51 +02:00

691 lines
17 KiB
Go

package certificate
import (
"os"
"path/filepath"
"testing"
"git.secnex.io/secnex/certman/models"
"github.com/google/uuid"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
func setupTestDatabase(t *testing.T) (*gorm.DB, func()) {
// Create temporary database file
dbPath := filepath.Join(t.TempDir(), "test_certman.db")
db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
if err != nil {
t.Fatalf("Failed to connect to database: %v", err)
}
// Auto-migrate models
if err := db.AutoMigrate(
&models.Organization{},
&models.CertificateAuthority{},
); err != nil {
t.Fatalf("Failed to migrate database: %v", err)
}
cleanup := func() {
os.Remove(dbPath)
}
return db, cleanup
}
func setupTestService(t *testing.T) (*CertificateAuthorityService, *gorm.DB, func()) {
db, dbCleanup := setupTestDatabase(t)
certDir := filepath.Join(t.TempDir(), "certs")
privateDir := filepath.Join(t.TempDir(), "private")
service := NewCertificateAuthorityService(db, certDir, privateDir)
cleanup := func() {
dbCleanup()
os.RemoveAll(certDir)
os.RemoveAll(privateDir)
}
return service, db, cleanup
}
func createTestOrganization(t *testing.T, db *gorm.DB) *models.Organization {
org := &models.Organization{
Name: "Test Organization",
Description: "Test Organization for CA tests",
Status: models.OrganizationStatusActive,
}
if err := db.Create(org).Error; err != nil {
t.Fatalf("Failed to create test organization: %v", err)
}
return org
}
func TestCreateRootCA(t *testing.T) {
service, db, cleanup := setupTestService(t)
defer cleanup()
org := createTestOrganization(t, db)
req := &CreateRootCARequest{
Name: "Test Root CA",
Description: "Test Root CA for testing",
CommonName: "Test Root CA",
Organization: "Test Organization",
OrganizationalUnit: "IT Department",
Country: "DE",
State: "Bavaria",
Locality: "Munich",
Street: "Test Street 1",
Address: "Test Street 1, 80331 Munich",
PostalCode: "80331",
Email: "test@example.com",
OrganizationID: org.ID,
}
ca, err := service.CreateRootCA(req)
if err != nil {
t.Fatalf("Failed to create root CA: %v", err)
}
if ca == nil {
t.Fatal("Created CA should not be nil")
}
if !ca.Root {
t.Error("Created CA should be marked as root")
}
if ca.ParentID != nil {
t.Error("Root CA should not have a parent ID")
}
if ca.Name != req.Name {
t.Errorf("Expected name %s, got %s", req.Name, ca.Name)
}
if ca.AttributeCommonName != req.CommonName {
t.Errorf("Expected common name %s, got %s", req.CommonName, ca.AttributeCommonName)
}
if ca.FileID == "" {
t.Error("Certificate file ID should not be empty")
}
if ca.PrivateKeyID == "" {
t.Error("Private key file ID should not be empty")
}
}
func TestCreateIntermediateCA(t *testing.T) {
service, db, cleanup := setupTestService(t)
defer cleanup()
org := createTestOrganization(t, db)
// First create a root CA
rootReq := &CreateRootCARequest{
Name: "Test Root CA",
Description: "Test Root CA for testing",
CommonName: "Test Root CA",
Organization: "Test Organization",
OrganizationalUnit: "IT Department",
Country: "DE",
State: "Bavaria",
Locality: "Munich",
Street: "Test Street 1",
Address: "Test Street 1, 80331 Munich",
PostalCode: "80331",
Email: "test@example.com",
OrganizationID: org.ID,
}
rootCA, err := service.CreateRootCA(rootReq)
if err != nil {
t.Fatalf("Failed to create root CA: %v", err)
}
// Now create an intermediate CA
req := &CreateIntermediateCARequest{
Name: "Test Intermediate CA",
Description: "Test Intermediate CA for testing",
CommonName: "Test Intermediate CA",
Organization: "Test Organization",
OrganizationalUnit: "IT Department",
Country: "DE",
State: "Bavaria",
Locality: "Munich",
Street: "Test Street 1",
Address: "Test Street 1, 80331 Munich",
PostalCode: "80331",
Email: "intermediate@example.com",
OrganizationID: org.ID,
ParentCAID: rootCA.ID,
}
ca, err := service.CreateIntermediateCA(req)
if err != nil {
t.Fatalf("Failed to create intermediate CA: %v", err)
}
if ca == nil {
t.Fatal("Created CA should not be nil")
}
if ca.Root {
t.Error("Created CA should not be marked as root")
}
if ca.ParentID == nil {
t.Error("Intermediate CA should have a parent ID")
}
if ca.ParentID.String() != rootCA.ID.String() {
t.Errorf("Expected parent ID %s, got %s", rootCA.ID.String(), ca.ParentID.String())
}
if ca.Name != req.Name {
t.Errorf("Expected name %s, got %s", req.Name, ca.Name)
}
if ca.AttributeCommonName != req.CommonName {
t.Errorf("Expected common name %s, got %s", req.CommonName, ca.AttributeCommonName)
}
if ca.FileID == "" {
t.Error("Certificate file ID should not be empty")
}
if ca.PrivateKeyID == "" {
t.Error("Private key file ID should not be empty")
}
}
func TestGetCA(t *testing.T) {
service, db, cleanup := setupTestService(t)
defer cleanup()
org := createTestOrganization(t, db)
// Create a root CA
req := &CreateRootCARequest{
Name: "Test Root CA",
Description: "Test Root CA for testing",
CommonName: "Test Root CA",
Organization: "Test Organization",
OrganizationalUnit: "IT Department",
Country: "DE",
State: "Bavaria",
Locality: "Munich",
Street: "Test Street 1",
Address: "Test Street 1, 80331 Munich",
PostalCode: "80331",
Email: "test@example.com",
OrganizationID: org.ID,
}
createdCA, err := service.CreateRootCA(req)
if err != nil {
t.Fatalf("Failed to create root CA: %v", err)
}
// Retrieve the CA
retrievedCA, err := service.GetCA(createdCA.ID.String())
if err != nil {
t.Fatalf("Failed to get CA: %v", err)
}
if retrievedCA.ID != createdCA.ID {
t.Errorf("Expected ID %s, got %s", createdCA.ID.String(), retrievedCA.ID.String())
}
if retrievedCA.Name != createdCA.Name {
t.Errorf("Expected name %s, got %s", createdCA.Name, retrievedCA.Name)
}
}
func TestGetAllCAs(t *testing.T) {
service, db, cleanup := setupTestService(t)
defer cleanup()
org := createTestOrganization(t, db)
// Create multiple CAs
rootReq := &CreateRootCARequest{
Name: "Test Root CA",
Description: "Test Root CA for testing",
CommonName: "Test Root CA",
Organization: "Test Organization",
OrganizationalUnit: "IT Department",
Country: "DE",
State: "Bavaria",
Locality: "Munich",
Street: "Test Street 1",
Address: "Test Street 1, 80331 Munich",
PostalCode: "80331",
Email: "test@example.com",
OrganizationID: org.ID,
}
rootCA, err := service.CreateRootCA(rootReq)
if err != nil {
t.Fatalf("Failed to create root CA: %v", err)
}
intermediateReq := &CreateIntermediateCARequest{
Name: "Test Intermediate CA",
Description: "Test Intermediate CA for testing",
CommonName: "Test Intermediate CA",
Organization: "Test Organization",
OrganizationalUnit: "IT Department",
Country: "DE",
State: "Bavaria",
Locality: "Munich",
Street: "Test Street 1",
Address: "Test Street 1, 80331 Munich",
PostalCode: "80331",
Email: "intermediate@example.com",
OrganizationID: org.ID,
ParentCAID: rootCA.ID,
}
_, err = service.CreateIntermediateCA(intermediateReq)
if err != nil {
t.Fatalf("Failed to create intermediate CA: %v", err)
}
// Get all CAs
allCAs, err := service.GetAllCAs()
if err != nil {
t.Fatalf("Failed to get all CAs: %v", err)
}
if len(allCAs) != 2 {
t.Errorf("Expected 2 CAs, got %d", len(allCAs))
}
// Get root CAs
rootCAs, err := service.GetRootCAs()
if err != nil {
t.Fatalf("Failed to get root CAs: %v", err)
}
if len(rootCAs) != 1 {
t.Errorf("Expected 1 root CA, got %d", len(rootCAs))
}
// Get intermediate CAs
intermediateCAs, err := service.GetIntermediateCAs()
if err != nil {
t.Fatalf("Failed to get intermediate CAs: %v", err)
}
if len(intermediateCAs) != 1 {
t.Errorf("Expected 1 intermediate CA, got %d", len(intermediateCAs))
}
}
func TestGetCACertificate(t *testing.T) {
service, db, cleanup := setupTestService(t)
defer cleanup()
org := createTestOrganization(t, db)
// Create a root CA
req := &CreateRootCARequest{
Name: "Test Root CA",
Description: "Test Root CA for testing",
CommonName: "Test Root CA",
Organization: "Test Organization",
OrganizationalUnit: "IT Department",
Country: "DE",
State: "Bavaria",
Locality: "Munich",
Street: "Test Street 1",
Address: "Test Street 1, 80331 Munich",
PostalCode: "80331",
Email: "test@example.com",
OrganizationID: org.ID,
}
ca, err := service.CreateRootCA(req)
if err != nil {
t.Fatalf("Failed to create root CA: %v", err)
}
// Get CA certificate
cert, err := service.GetCACertificate(ca.ID.String())
if err != nil {
t.Fatalf("Failed to get CA certificate: %v", err)
}
if cert == nil {
t.Fatal("Certificate should not be nil")
}
if cert.Subject.CommonName != req.CommonName {
t.Errorf("Expected common name %s, got %s", req.CommonName, cert.Subject.CommonName)
}
if !cert.IsCA {
t.Error("Certificate should be marked as CA")
}
}
func TestGetCAPrivateKey(t *testing.T) {
service, db, cleanup := setupTestService(t)
defer cleanup()
org := createTestOrganization(t, db)
// Create a root CA
req := &CreateRootCARequest{
Name: "Test Root CA",
Description: "Test Root CA for testing",
CommonName: "Test Root CA",
Organization: "Test Organization",
OrganizationalUnit: "IT Department",
Country: "DE",
State: "Bavaria",
Locality: "Munich",
Street: "Test Street 1",
Address: "Test Street 1, 80331 Munich",
PostalCode: "80331",
Email: "test@example.com",
OrganizationID: org.ID,
}
ca, err := service.CreateRootCA(req)
if err != nil {
t.Fatalf("Failed to create root CA: %v", err)
}
// Get CA private key
privateKey, err := service.GetCAPrivateKey(ca.ID.String())
if err != nil {
t.Fatalf("Failed to get CA private key: %v", err)
}
if privateKey == nil {
t.Fatal("Private key should not be nil")
}
}
func TestValidateCAChain(t *testing.T) {
service, db, cleanup := setupTestService(t)
defer cleanup()
org := createTestOrganization(t, db)
// Create a root CA
rootReq := &CreateRootCARequest{
Name: "Test Root CA",
Description: "Test Root CA for testing",
CommonName: "Test Root CA",
Organization: "Test Organization",
OrganizationalUnit: "IT Department",
Country: "DE",
State: "Bavaria",
Locality: "Munich",
Street: "Test Street 1",
Address: "Test Street 1, 80331 Munich",
PostalCode: "80331",
Email: "test@example.com",
OrganizationID: org.ID,
}
rootCA, err := service.CreateRootCA(rootReq)
if err != nil {
t.Fatalf("Failed to create root CA: %v", err)
}
// Validate root CA chain
if err := service.ValidateCAChain(rootCA.ID.String()); err != nil {
t.Fatalf("Failed to validate root CA chain: %v", err)
}
// Create an intermediate CA
intermediateReq := &CreateIntermediateCARequest{
Name: "Test Intermediate CA",
Description: "Test Intermediate CA for testing",
CommonName: "Test Intermediate CA",
Organization: "Test Organization",
OrganizationalUnit: "IT Department",
Country: "DE",
State: "Bavaria",
Locality: "Munich",
Street: "Test Street 1",
Address: "Test Street 1, 80331 Munich",
PostalCode: "80331",
Email: "intermediate@example.com",
OrganizationID: org.ID,
ParentCAID: rootCA.ID,
}
intermediateCA, err := service.CreateIntermediateCA(intermediateReq)
if err != nil {
t.Fatalf("Failed to create intermediate CA: %v", err)
}
// Validate intermediate CA chain
if err := service.ValidateCAChain(intermediateCA.ID.String()); err != nil {
t.Fatalf("Failed to validate intermediate CA chain: %v", err)
}
}
func TestRevokeCA(t *testing.T) {
service, db, cleanup := setupTestService(t)
defer cleanup()
org := createTestOrganization(t, db)
// Create a root CA
req := &CreateRootCARequest{
Name: "Test Root CA",
Description: "Test Root CA for testing",
CommonName: "Test Root CA",
Organization: "Test Organization",
OrganizationalUnit: "IT Department",
Country: "DE",
State: "Bavaria",
Locality: "Munich",
Street: "Test Street 1",
Address: "Test Street 1, 80331 Munich",
PostalCode: "80331",
Email: "test@example.com",
OrganizationID: org.ID,
}
ca, err := service.CreateRootCA(req)
if err != nil {
t.Fatalf("Failed to create root CA: %v", err)
}
// Revoke CA
reason := "Test revocation"
if err := service.RevokeCA(ca.ID.String(), reason); err != nil {
t.Fatalf("Failed to revoke CA: %v", err)
}
// Verify CA was updated
updatedCA, err := service.GetCA(ca.ID.String())
if err != nil {
t.Fatalf("Failed to get updated CA: %v", err)
}
if updatedCA.Description == ca.Description {
t.Error("CA description should have been updated with revocation info")
}
}
func TestDeleteCA(t *testing.T) {
service, db, cleanup := setupTestService(t)
defer cleanup()
org := createTestOrganization(t, db)
// Create a root CA
req := &CreateRootCARequest{
Name: "Test Root CA",
Description: "Test Root CA for testing",
CommonName: "Test Root CA",
Organization: "Test Organization",
OrganizationalUnit: "IT Department",
Country: "DE",
State: "Bavaria",
Locality: "Munich",
Street: "Test Street 1",
Address: "Test Street 1, 80331 Munich",
PostalCode: "80331",
Email: "test@example.com",
OrganizationID: org.ID,
}
ca, err := service.CreateRootCA(req)
if err != nil {
t.Fatalf("Failed to create root CA: %v", err)
}
// Delete CA
if err := service.DeleteCA(ca.ID.String()); err != nil {
t.Fatalf("Failed to delete CA: %v", err)
}
// Verify CA was deleted
_, err = service.GetCA(ca.ID.String())
if err == nil {
t.Error("CA should have been deleted")
}
}
func TestValidateRootCARequest(t *testing.T) {
service, _, cleanup := setupTestService(t)
defer cleanup()
tests := []struct {
name string
req *CreateRootCARequest
wantErr bool
}{
{
name: "valid request",
req: &CreateRootCARequest{
Name: "Test CA",
CommonName: "Test CA",
Organization: "Test Org",
Country: "DE",
OrganizationID: uuid.New(),
},
wantErr: false,
},
{
name: "missing name",
req: &CreateRootCARequest{
CommonName: "Test CA",
Organization: "Test Org",
Country: "DE",
OrganizationID: uuid.New(),
},
wantErr: true,
},
{
name: "missing common name",
req: &CreateRootCARequest{
Name: "Test CA",
Organization: "Test Org",
Country: "DE",
OrganizationID: uuid.New(),
},
wantErr: true,
},
{
name: "missing organization",
req: &CreateRootCARequest{
Name: "Test CA",
CommonName: "Test CA",
Country: "DE",
OrganizationID: uuid.New(),
},
wantErr: true,
},
{
name: "missing country",
req: &CreateRootCARequest{
Name: "Test CA",
CommonName: "Test CA",
Organization: "Test Org",
OrganizationID: uuid.New(),
},
wantErr: true,
},
{
name: "missing organization ID",
req: &CreateRootCARequest{
Name: "Test CA",
CommonName: "Test CA",
Organization: "Test Org",
Country: "DE",
},
wantErr: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := service.validateRootCARequest(test.req)
if (err != nil) != test.wantErr {
t.Errorf("validateRootCARequest() error = %v, wantErr %v", err, test.wantErr)
}
})
}
}
func TestValidateIntermediateCARequest(t *testing.T) {
service, _, cleanup := setupTestService(t)
defer cleanup()
tests := []struct {
name string
req *CreateIntermediateCARequest
wantErr bool
}{
{
name: "valid request",
req: &CreateIntermediateCARequest{
Name: "Test CA",
CommonName: "Test CA",
Organization: "Test Org",
Country: "DE",
OrganizationID: uuid.New(),
ParentCAID: uuid.New(),
},
wantErr: false,
},
{
name: "missing parent CA ID",
req: &CreateIntermediateCARequest{
Name: "Test CA",
CommonName: "Test CA",
Organization: "Test Org",
Country: "DE",
OrganizationID: uuid.New(),
},
wantErr: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := service.validateIntermediateCARequest(test.req)
if (err != nil) != test.wantErr {
t.Errorf("validateIntermediateCARequest() error = %v, wantErr %v", err, test.wantErr)
}
})
}
}