
- Implement CertificateService for end-to-end certificate management - Add support for various certificate types (web, client, email, etc.) - Include certificate generation, validation, and revocation - Add utility functions for certificate operations and validation - Implement comprehensive test coverage for certificate operations - Support SAN (Subject Alternative Name) and IP address extensions - Add proper error handling and validation for certificate requests
537 lines
14 KiB
Go
537 lines
14 KiB
Go
package utils
|
|
|
|
import (
|
|
"crypto/x509"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.secnex.io/secnex/certman/models"
|
|
)
|
|
|
|
func TestDefaultCertificateConfig(t *testing.T) {
|
|
tests := []struct {
|
|
certType models.CertificateType
|
|
expected bool
|
|
}{
|
|
{models.CertificateTypeCA, true},
|
|
{models.CertificateTypeWeb, true},
|
|
{models.CertificateTypeClient, true},
|
|
{models.CertificateTypeEmail, true},
|
|
{models.CertificateTypeCode, true},
|
|
{models.CertificateTypeServer, true},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
config := DefaultCertificateConfig(test.certType)
|
|
if config.CertificateType != test.certType {
|
|
t.Errorf("Expected certificate type %s, got %s", test.certType, config.CertificateType)
|
|
}
|
|
if config.SerialNumber == nil {
|
|
t.Error("Serial number should not be nil")
|
|
}
|
|
if config.NotBefore.After(config.NotAfter) {
|
|
t.Error("NotBefore should be before NotAfter")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCACertificateConfig(t *testing.T) {
|
|
// Test root CA
|
|
rootConfig := CACertificateConfig(true)
|
|
if !rootConfig.IsCA {
|
|
t.Error("Root CA should have IsCA = true")
|
|
}
|
|
if !rootConfig.KeyCertSign {
|
|
t.Error("Root CA should have KeyCertSign = true")
|
|
}
|
|
if !rootConfig.CRLSign {
|
|
t.Error("Root CA should have CRLSign = true")
|
|
}
|
|
if rootConfig.MaxPathLen != -1 {
|
|
t.Error("Root CA should have MaxPathLen = -1")
|
|
}
|
|
|
|
// Test intermediate CA
|
|
intermediateConfig := CACertificateConfig(false)
|
|
if !intermediateConfig.IsCA {
|
|
t.Error("Intermediate CA should have IsCA = true")
|
|
}
|
|
if !intermediateConfig.KeyCertSign {
|
|
t.Error("Intermediate CA should have KeyCertSign = true")
|
|
}
|
|
if !intermediateConfig.CRLSign {
|
|
t.Error("Intermediate CA should have CRLSign = true")
|
|
}
|
|
if intermediateConfig.MaxPathLen != 0 {
|
|
t.Error("Intermediate CA should have MaxPathLen = 0")
|
|
}
|
|
}
|
|
|
|
func TestWebServerCertificateConfig(t *testing.T) {
|
|
domains := []string{"example.com", "www.example.com"}
|
|
config := WebServerCertificateConfig(domains)
|
|
|
|
if config.CertificateType != models.CertificateTypeWeb {
|
|
t.Error("Should be web certificate type")
|
|
}
|
|
if !config.DigitalSignature {
|
|
t.Error("Should have digital signature")
|
|
}
|
|
if !config.KeyEncipherment {
|
|
t.Error("Should have key encipherment")
|
|
}
|
|
if !config.ServerAuth {
|
|
t.Error("Should have server auth")
|
|
}
|
|
if len(config.DNSNames) != len(domains) {
|
|
t.Error("DNS names should match input domains")
|
|
}
|
|
}
|
|
|
|
func TestClientCertificateConfig(t *testing.T) {
|
|
config := ClientCertificateConfig()
|
|
|
|
if config.CertificateType != models.CertificateTypeClient {
|
|
t.Error("Should be client certificate type")
|
|
}
|
|
if !config.DigitalSignature {
|
|
t.Error("Should have digital signature")
|
|
}
|
|
if !config.KeyEncipherment {
|
|
t.Error("Should have key encipherment")
|
|
}
|
|
if !config.ClientAuth {
|
|
t.Error("Should have client auth")
|
|
}
|
|
}
|
|
|
|
func TestEmailCertificateConfig(t *testing.T) {
|
|
email := "test@example.com"
|
|
config := EmailCertificateConfig(email)
|
|
|
|
if config.CertificateType != models.CertificateTypeEmail {
|
|
t.Error("Should be email certificate type")
|
|
}
|
|
if !config.DigitalSignature {
|
|
t.Error("Should have digital signature")
|
|
}
|
|
if !config.KeyEncipherment {
|
|
t.Error("Should have key encipherment")
|
|
}
|
|
if !config.EmailProtection {
|
|
t.Error("Should have email protection")
|
|
}
|
|
if len(config.EmailAddresses) != 1 || config.EmailAddresses[0] != email {
|
|
t.Error("Email addresses should match input")
|
|
}
|
|
}
|
|
|
|
func TestCodeSigningCertificateConfig(t *testing.T) {
|
|
config := CodeSigningCertificateConfig()
|
|
|
|
if config.CertificateType != models.CertificateTypeCode {
|
|
t.Error("Should be code signing certificate type")
|
|
}
|
|
if !config.DigitalSignature {
|
|
t.Error("Should have digital signature")
|
|
}
|
|
if !config.CodeSigning {
|
|
t.Error("Should have code signing")
|
|
}
|
|
}
|
|
|
|
func TestGenerateKeyPair(t *testing.T) {
|
|
tests := []struct {
|
|
keyType KeyType
|
|
keySize KeySize
|
|
curve Curve
|
|
}{
|
|
{KeyTypeRSA, KeySize2048, CurveP256},
|
|
{KeyTypeRSA, KeySize3072, CurveP256},
|
|
{KeyTypeRSA, KeySize4096, CurveP256},
|
|
{KeyTypeECDSA, KeySize2048, CurveP256},
|
|
{KeyTypeECDSA, KeySize2048, CurveP384},
|
|
{KeyTypeECDSA, KeySize2048, CurveP521},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
config := DefaultCertificateConfig(models.CertificateTypeWeb)
|
|
config.KeyType = test.keyType
|
|
config.KeySize = test.keySize
|
|
config.Curve = test.curve
|
|
|
|
generator := NewCertificateGenerator(config)
|
|
key, err := generator.GenerateKeyPair()
|
|
if err != nil {
|
|
t.Errorf("Failed to generate key pair for %s: %v", test.keyType, err)
|
|
}
|
|
if key == nil {
|
|
t.Errorf("Generated key should not be nil for %s", test.keyType)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGenerateSelfSignedCertificate(t *testing.T) {
|
|
config := CACertificateConfig(true)
|
|
config.CommonName = "Test Root CA"
|
|
config.Organization = "Test Org"
|
|
config.Country = "DE"
|
|
|
|
generator := NewCertificateGenerator(config)
|
|
cert, privateKey, err := generator.GenerateSelfSignedCertificate()
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate self-signed certificate: %v", err)
|
|
}
|
|
|
|
if cert == nil {
|
|
t.Error("Certificate should not be nil")
|
|
}
|
|
if privateKey == nil {
|
|
t.Error("Private key should not be nil")
|
|
}
|
|
if !cert.IsCA {
|
|
t.Error("CA certificate should have IsCA = true")
|
|
}
|
|
if cert.Subject.CommonName != config.CommonName {
|
|
t.Error("Certificate subject should match configuration")
|
|
}
|
|
}
|
|
|
|
func TestGenerateCertificate(t *testing.T) {
|
|
// Generate root CA first
|
|
rootConfig := CACertificateConfig(true)
|
|
rootConfig.CommonName = "Test Root CA"
|
|
rootConfig.Organization = "Test Org"
|
|
rootConfig.Country = "DE"
|
|
|
|
rootGenerator := NewCertificateGenerator(rootConfig)
|
|
rootCert, rootPrivateKey, err := rootGenerator.GenerateSelfSignedCertificate()
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate root CA: %v", err)
|
|
}
|
|
|
|
// Generate intermediate CA
|
|
intermediateConfig := CACertificateConfig(false)
|
|
intermediateConfig.CommonName = "Test Intermediate CA"
|
|
intermediateConfig.Organization = "Test Org"
|
|
intermediateConfig.Country = "DE"
|
|
|
|
intermediateGenerator := NewCertificateGenerator(intermediateConfig)
|
|
intermediateCert, intermediatePrivateKey, err := intermediateGenerator.GenerateCertificate(rootCert, rootPrivateKey)
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate intermediate CA: %v", err)
|
|
}
|
|
|
|
if intermediateCert == nil {
|
|
t.Error("Intermediate certificate should not be nil")
|
|
}
|
|
if intermediatePrivateKey == nil {
|
|
t.Error("Intermediate private key should not be nil")
|
|
}
|
|
if !intermediateCert.IsCA {
|
|
t.Error("Intermediate CA certificate should have IsCA = true")
|
|
}
|
|
if intermediateCert.Issuer.CommonName != rootCert.Subject.CommonName {
|
|
t.Error("Intermediate certificate issuer should match root certificate subject")
|
|
}
|
|
|
|
// Generate end entity certificate
|
|
endConfig := WebServerCertificateConfig([]string{"example.com"})
|
|
endConfig.CommonName = "example.com"
|
|
endConfig.Organization = "Test Org"
|
|
endConfig.Country = "DE"
|
|
|
|
endGenerator := NewCertificateGenerator(endConfig)
|
|
endCert, endPrivateKey, err := endGenerator.GenerateCertificate(intermediateCert, intermediatePrivateKey)
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate end entity certificate: %v", err)
|
|
}
|
|
|
|
if endCert == nil {
|
|
t.Error("End entity certificate should not be nil")
|
|
}
|
|
if endPrivateKey == nil {
|
|
t.Error("End entity private key should not be nil")
|
|
}
|
|
if endCert.IsCA {
|
|
t.Error("End entity certificate should not have IsCA = true")
|
|
}
|
|
if endCert.Issuer.CommonName != intermediateCert.Subject.CommonName {
|
|
t.Error("End entity certificate issuer should match intermediate certificate subject")
|
|
}
|
|
}
|
|
|
|
func TestValidateCertificateConfig(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
config *CertificateConfig
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "valid config",
|
|
config: &CertificateConfig{
|
|
CommonName: "example.com",
|
|
Organization: "Test Org",
|
|
Country: "DE",
|
|
NotBefore: time.Now(),
|
|
NotAfter: time.Now().AddDate(1, 0, 0),
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "missing common name",
|
|
config: &CertificateConfig{
|
|
Organization: "Test Org",
|
|
Country: "DE",
|
|
NotBefore: time.Now(),
|
|
NotAfter: time.Now().AddDate(1, 0, 0),
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "missing organization",
|
|
config: &CertificateConfig{
|
|
CommonName: "example.com",
|
|
Country: "DE",
|
|
NotBefore: time.Now(),
|
|
NotAfter: time.Now().AddDate(1, 0, 0),
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "missing country",
|
|
config: &CertificateConfig{
|
|
CommonName: "example.com",
|
|
Organization: "Test Org",
|
|
NotBefore: time.Now(),
|
|
NotAfter: time.Now().AddDate(1, 0, 0),
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "invalid date range",
|
|
config: &CertificateConfig{
|
|
CommonName: "example.com",
|
|
Organization: "Test Org",
|
|
Country: "DE",
|
|
NotBefore: time.Now().AddDate(1, 0, 0),
|
|
NotAfter: time.Now(),
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "CA without key cert sign",
|
|
config: &CertificateConfig{
|
|
CommonName: "example.com",
|
|
Organization: "Test Org",
|
|
Country: "DE",
|
|
NotBefore: time.Now(),
|
|
NotAfter: time.Now().AddDate(1, 0, 0),
|
|
IsCA: true,
|
|
KeyCertSign: false,
|
|
},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
err := ValidateCertificateConfig(test.config)
|
|
if (err != nil) != test.wantErr {
|
|
t.Errorf("ValidateCertificateConfig() error = %v, wantErr %v", err, test.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseSANs(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
expectedDNS []string
|
|
expectedIPs int
|
|
expectedEmails []string
|
|
expectedURIs []string
|
|
}{
|
|
{
|
|
input: "example.com,www.example.com",
|
|
expectedDNS: []string{"example.com", "www.example.com"},
|
|
expectedIPs: 0,
|
|
expectedEmails: []string{},
|
|
expectedURIs: []string{},
|
|
},
|
|
{
|
|
input: "example.com,192.168.1.1,user@example.com,https://example.com",
|
|
expectedDNS: []string{"example.com"},
|
|
expectedIPs: 1,
|
|
expectedEmails: []string{"user@example.com"},
|
|
expectedURIs: []string{"https://example.com"},
|
|
},
|
|
{
|
|
input: "",
|
|
expectedDNS: []string{},
|
|
expectedIPs: 0,
|
|
expectedEmails: []string{},
|
|
expectedURIs: []string{},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
dnsNames, ipAddresses, emailAddresses, uris := ParseSANs(test.input)
|
|
|
|
if len(dnsNames) != len(test.expectedDNS) {
|
|
t.Errorf("Expected %d DNS names, got %d", len(test.expectedDNS), len(dnsNames))
|
|
}
|
|
|
|
if len(ipAddresses) != test.expectedIPs {
|
|
t.Errorf("Expected %d IP addresses, got %d", test.expectedIPs, len(ipAddresses))
|
|
}
|
|
|
|
if len(emailAddresses) != len(test.expectedEmails) {
|
|
t.Errorf("Expected %d email addresses, got %d", len(test.expectedEmails), len(emailAddresses))
|
|
}
|
|
|
|
if len(uris) != len(test.expectedURIs) {
|
|
t.Errorf("Expected %d URIs, got %d", len(test.expectedURIs), len(uris))
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestExportCertificateToPEM(t *testing.T) {
|
|
// Generate a test certificate
|
|
config := CACertificateConfig(true)
|
|
config.CommonName = "Test CA"
|
|
config.Organization = "Test Org"
|
|
config.Country = "DE"
|
|
|
|
generator := NewCertificateGenerator(config)
|
|
cert, _, err := generator.GenerateSelfSignedCertificate()
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate certificate: %v", err)
|
|
}
|
|
|
|
exporter := NewCertificateExporter()
|
|
pemData, err := exporter.ExportCertificateToPEM(cert)
|
|
if err != nil {
|
|
t.Fatalf("Failed to export certificate to PEM: %v", err)
|
|
}
|
|
|
|
if len(pemData) == 0 {
|
|
t.Error("PEM data should not be empty")
|
|
}
|
|
|
|
// Verify PEM format
|
|
if !contains(pemData, []byte("-----BEGIN CERTIFICATE-----")) {
|
|
t.Error("PEM data should contain certificate header")
|
|
}
|
|
if !contains(pemData, []byte("-----END CERTIFICATE-----")) {
|
|
t.Error("PEM data should contain certificate footer")
|
|
}
|
|
}
|
|
|
|
func TestExportPrivateKeyToPEM(t *testing.T) {
|
|
// Generate a test certificate with private key
|
|
config := CACertificateConfig(true)
|
|
config.CommonName = "Test CA"
|
|
config.Organization = "Test Org"
|
|
config.Country = "DE"
|
|
|
|
generator := NewCertificateGenerator(config)
|
|
_, privateKey, err := generator.GenerateSelfSignedCertificate()
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate certificate: %v", err)
|
|
}
|
|
|
|
exporter := NewCertificateExporter()
|
|
pemData, err := exporter.ExportPrivateKeyToPEM(privateKey)
|
|
if err != nil {
|
|
t.Fatalf("Failed to export private key to PEM: %v", err)
|
|
}
|
|
|
|
if len(pemData) == 0 {
|
|
t.Error("PEM data should not be empty")
|
|
}
|
|
|
|
// Verify PEM format
|
|
if !contains(pemData, []byte("-----BEGIN")) {
|
|
t.Error("PEM data should contain private key header")
|
|
}
|
|
if !contains(pemData, []byte("-----END")) {
|
|
t.Error("PEM data should contain private key footer")
|
|
}
|
|
}
|
|
|
|
func TestExportCertificateToDER(t *testing.T) {
|
|
// Generate a test certificate
|
|
config := CACertificateConfig(true)
|
|
config.CommonName = "Test CA"
|
|
config.Organization = "Test Org"
|
|
config.Country = "DE"
|
|
|
|
generator := NewCertificateGenerator(config)
|
|
cert, _, err := generator.GenerateSelfSignedCertificate()
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate certificate: %v", err)
|
|
}
|
|
|
|
exporter := NewCertificateExporter()
|
|
derData := exporter.ExportCertificateToDER(cert)
|
|
|
|
if len(derData) == 0 {
|
|
t.Error("DER data should not be empty")
|
|
}
|
|
|
|
// Verify DER format by parsing it
|
|
_, err = x509.ParseCertificate(derData)
|
|
if err != nil {
|
|
t.Errorf("DER data should be valid certificate: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestExportPrivateKeyToDER(t *testing.T) {
|
|
// Generate a test certificate with private key
|
|
config := CACertificateConfig(true)
|
|
config.CommonName = "Test CA"
|
|
config.Organization = "Test Org"
|
|
config.Country = "DE"
|
|
|
|
generator := NewCertificateGenerator(config)
|
|
_, privateKey, err := generator.GenerateSelfSignedCertificate()
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate certificate: %v", err)
|
|
}
|
|
|
|
exporter := NewCertificateExporter()
|
|
derData, err := exporter.ExportPrivateKeyToDER(privateKey)
|
|
if err != nil {
|
|
t.Fatalf("Failed to export private key to DER: %v", err)
|
|
}
|
|
|
|
if len(derData) == 0 {
|
|
t.Error("DER data should not be empty")
|
|
}
|
|
}
|
|
|
|
// Helper function to check if a slice contains a subslice
|
|
func contains(s, subslice []byte) bool {
|
|
if len(subslice) > len(s) {
|
|
return false
|
|
}
|
|
for i := 0; i <= len(s)-len(subslice); i++ {
|
|
if bytesEqual(s[i:i+len(subslice)], subslice) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func bytesEqual(a, b []byte) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i := range a {
|
|
if a[i] != b[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
} |