
- 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
691 lines
17 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|
|
}
|