mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 15:01:32 +00:00
7cb453a336
Mechanical reformat. The new 'gofmt drift' CI step (added in
ci-pipeline-cleanup Phase 4, commit 0f205a8) surfaced 111 files
with accumulated gofmt drift across cmd/, internal/, and deploy/test/.
Each file's diff is gofmt-standard: whitespace adjustments, intra-
group import sorting (alphabetical by import path within blank-line-
separated groups), and struct-tag column alignment. No semantic
changes — verified via 'git diff --ignore-all-space' which shows only
the line-position deltas from import reordering.
The gate stays in place after this commit. Going forward it catches
gofmt drift at PR time.
759 lines
21 KiB
Go
759 lines
21 KiB
Go
package openssl_test
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/json"
|
|
"encoding/pem"
|
|
"log/slog"
|
|
"math/big"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/shankar0123/certctl/internal/connector/issuer"
|
|
"github.com/shankar0123/certctl/internal/connector/issuer/openssl"
|
|
)
|
|
|
|
func TestOpenSSLConnector(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}))
|
|
ctx := context.Background()
|
|
|
|
// Test 1: ValidateConfig with valid config
|
|
t.Run("ValidateConfig_Success", func(t *testing.T) {
|
|
// Create a temporary directory for script files
|
|
tmpDir := t.TempDir()
|
|
|
|
// Create a minimal sign script
|
|
signScript := filepath.Join(tmpDir, "sign.sh")
|
|
if err := os.WriteFile(signScript, []byte("#!/bin/sh\nexit 0"), 0755); err != nil {
|
|
t.Fatalf("Failed to create sign script: %v", err)
|
|
}
|
|
|
|
config := &openssl.Config{
|
|
SignScript: signScript,
|
|
TimeoutSeconds: 30,
|
|
}
|
|
connector := openssl.New(config, logger)
|
|
|
|
rawConfig, _ := json.Marshal(config)
|
|
err := connector.ValidateConfig(ctx, rawConfig)
|
|
if err != nil {
|
|
t.Fatalf("ValidateConfig failed: %v", err)
|
|
}
|
|
})
|
|
|
|
// Test 2: ValidateConfig with missing sign_script
|
|
t.Run("ValidateConfig_MissingSignScript", func(t *testing.T) {
|
|
config := &openssl.Config{
|
|
SignScript: "",
|
|
}
|
|
connector := openssl.New(config, logger)
|
|
|
|
rawConfig, _ := json.Marshal(config)
|
|
err := connector.ValidateConfig(ctx, rawConfig)
|
|
if err == nil {
|
|
t.Fatal("Expected error for missing sign_script, got nil")
|
|
}
|
|
})
|
|
|
|
// Test 3: ValidateConfig with nonexistent script path
|
|
t.Run("ValidateConfig_NonexistentScript", func(t *testing.T) {
|
|
config := &openssl.Config{
|
|
SignScript: "/nonexistent/path/to/sign.sh",
|
|
}
|
|
connector := openssl.New(config, logger)
|
|
|
|
rawConfig, _ := json.Marshal(config)
|
|
err := connector.ValidateConfig(ctx, rawConfig)
|
|
if err == nil {
|
|
t.Fatal("Expected error for nonexistent script, got nil")
|
|
}
|
|
})
|
|
|
|
// Test 4: IssueCertificate with a real test CSR and mock sign script
|
|
t.Run("IssueCertificate_Success", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
// Create a mock sign script that creates a self-signed cert from CSR
|
|
signScript := filepath.Join(tmpDir, "sign.sh")
|
|
mockCertPEM := generateMockCertPEM()
|
|
scriptContent := "#!/bin/sh\n" +
|
|
"CSR_FILE=\"$1\"\n" +
|
|
"CERT_FILE=\"$2\"\n" +
|
|
"cat > \"$CERT_FILE\" << 'EOF'\n" + mockCertPEM + "\nEOF\n" +
|
|
"exit 0\n"
|
|
if err := os.WriteFile(signScript, []byte(scriptContent), 0755); err != nil {
|
|
t.Fatalf("Failed to create sign script: %v", err)
|
|
}
|
|
|
|
config := &openssl.Config{
|
|
SignScript: signScript,
|
|
TimeoutSeconds: 30,
|
|
}
|
|
connector := openssl.New(config, logger)
|
|
|
|
// Validate config first
|
|
rawConfig, _ := json.Marshal(config)
|
|
if err := connector.ValidateConfig(ctx, rawConfig); err != nil {
|
|
t.Fatalf("ValidateConfig failed: %v", err)
|
|
}
|
|
|
|
// Generate test CSR
|
|
csr, csrPEM, err := generateTestCSR("test.example.com")
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate CSR: %v", err)
|
|
}
|
|
|
|
req := issuer.IssuanceRequest{
|
|
CommonName: csr.Subject.CommonName,
|
|
SANs: []string{"www.test.example.com"},
|
|
CSRPEM: csrPEM,
|
|
}
|
|
|
|
result, err := connector.IssueCertificate(ctx, req)
|
|
if err != nil {
|
|
t.Fatalf("IssueCertificate failed: %v", err)
|
|
}
|
|
|
|
if result.Serial == "" {
|
|
t.Error("Serial is empty")
|
|
}
|
|
if result.CertPEM == "" {
|
|
t.Error("CertPEM is empty")
|
|
}
|
|
if result.OrderID == "" {
|
|
t.Error("OrderID is empty")
|
|
}
|
|
if result.NotAfter.IsZero() {
|
|
t.Error("NotAfter is zero")
|
|
}
|
|
|
|
t.Logf("Certificate issued: serial=%s, orderID=%s", result.Serial, result.OrderID)
|
|
})
|
|
|
|
// Test 5: IssueCertificate with sign script failure
|
|
t.Run("IssueCertificate_SignScriptFailure", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
// Create a sign script that fails
|
|
signScript := filepath.Join(tmpDir, "sign.sh")
|
|
if err := os.WriteFile(signScript, []byte("#!/bin/sh\nexit 1"), 0755); err != nil {
|
|
t.Fatalf("Failed to create sign script: %v", err)
|
|
}
|
|
|
|
config := &openssl.Config{
|
|
SignScript: signScript,
|
|
TimeoutSeconds: 30,
|
|
}
|
|
connector := openssl.New(config, logger)
|
|
|
|
rawConfig, _ := json.Marshal(config)
|
|
if err := connector.ValidateConfig(ctx, rawConfig); err != nil {
|
|
t.Fatalf("ValidateConfig failed: %v", err)
|
|
}
|
|
|
|
csr, csrPEM, err := generateTestCSR("test.example.com")
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate CSR: %v", err)
|
|
}
|
|
|
|
req := issuer.IssuanceRequest{
|
|
CommonName: csr.Subject.CommonName,
|
|
SANs: []string{"www.test.example.com"},
|
|
CSRPEM: csrPEM,
|
|
}
|
|
|
|
result, err := connector.IssueCertificate(ctx, req)
|
|
if err == nil {
|
|
t.Fatal("Expected error from failing sign script, got nil")
|
|
}
|
|
if result != nil {
|
|
t.Error("Expected result to be nil on error")
|
|
}
|
|
})
|
|
|
|
// Test 6: IssueCertificate with timeout
|
|
t.Run("IssueCertificate_SignScriptTimeout", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
// Create a sign script that takes too long
|
|
signScript := filepath.Join(tmpDir, "sign.sh")
|
|
if err := os.WriteFile(signScript, []byte("#!/bin/sh\nsleep 10\nexit 0"), 0755); err != nil {
|
|
t.Fatalf("Failed to create sign script: %v", err)
|
|
}
|
|
|
|
config := &openssl.Config{
|
|
SignScript: signScript,
|
|
TimeoutSeconds: 1, // 1 second timeout
|
|
}
|
|
connector := openssl.New(config, logger)
|
|
|
|
rawConfig, _ := json.Marshal(config)
|
|
if err := connector.ValidateConfig(ctx, rawConfig); err != nil {
|
|
t.Fatalf("ValidateConfig failed: %v", err)
|
|
}
|
|
|
|
csr, csrPEM, err := generateTestCSR("test.example.com")
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate CSR: %v", err)
|
|
}
|
|
|
|
req := issuer.IssuanceRequest{
|
|
CommonName: csr.Subject.CommonName,
|
|
SANs: []string{"www.test.example.com"},
|
|
CSRPEM: csrPEM,
|
|
}
|
|
|
|
result, err := connector.IssueCertificate(ctx, req)
|
|
if err == nil {
|
|
t.Fatal("Expected timeout error, got nil")
|
|
}
|
|
if result != nil {
|
|
t.Error("Expected result to be nil on timeout")
|
|
}
|
|
})
|
|
|
|
// Test 7: RenewCertificate delegates to IssueCertificate
|
|
t.Run("RenewCertificate_Success", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
// Create a mock sign script
|
|
signScript := filepath.Join(tmpDir, "sign.sh")
|
|
mockCertPEM := generateMockCertPEM()
|
|
scriptContent := "#!/bin/sh\n" +
|
|
"CSR_FILE=\"$1\"\n" +
|
|
"CERT_FILE=\"$2\"\n" +
|
|
"cat > \"$CERT_FILE\" << 'EOF'\n" + mockCertPEM + "\nEOF\n" +
|
|
"exit 0\n"
|
|
if err := os.WriteFile(signScript, []byte(scriptContent), 0755); err != nil {
|
|
t.Fatalf("Failed to create sign script: %v", err)
|
|
}
|
|
|
|
config := &openssl.Config{
|
|
SignScript: signScript,
|
|
TimeoutSeconds: 30,
|
|
}
|
|
connector := openssl.New(config, logger)
|
|
|
|
rawConfig, _ := json.Marshal(config)
|
|
if err := connector.ValidateConfig(ctx, rawConfig); err != nil {
|
|
t.Fatalf("ValidateConfig failed: %v", err)
|
|
}
|
|
|
|
csr, csrPEM, err := generateTestCSR("test.example.com")
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate CSR: %v", err)
|
|
}
|
|
|
|
renewReq := issuer.RenewalRequest{
|
|
CommonName: csr.Subject.CommonName,
|
|
SANs: []string{"www.test.example.com"},
|
|
CSRPEM: csrPEM,
|
|
}
|
|
|
|
result, err := connector.RenewCertificate(ctx, renewReq)
|
|
if err != nil {
|
|
t.Fatalf("RenewCertificate failed: %v", err)
|
|
}
|
|
|
|
if result.Serial == "" {
|
|
t.Error("Serial is empty")
|
|
}
|
|
|
|
t.Logf("Certificate renewed: serial=%s", result.Serial)
|
|
})
|
|
|
|
// Test 8: RevokeCertificate without revoke script configured
|
|
t.Run("RevokeCertificate_NoScript", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
signScript := filepath.Join(tmpDir, "sign.sh")
|
|
if err := os.WriteFile(signScript, []byte("#!/bin/sh\nexit 0"), 0755); err != nil {
|
|
t.Fatalf("Failed to create sign script: %v", err)
|
|
}
|
|
|
|
config := &openssl.Config{
|
|
SignScript: signScript,
|
|
// RevokeScript not set
|
|
}
|
|
connector := openssl.New(config, logger)
|
|
|
|
rawConfig, _ := json.Marshal(config)
|
|
if err := connector.ValidateConfig(ctx, rawConfig); err != nil {
|
|
t.Fatalf("ValidateConfig failed: %v", err)
|
|
}
|
|
|
|
revokeReq := issuer.RevocationRequest{
|
|
Serial: "ABCDEF1234567890",
|
|
}
|
|
|
|
// Should return nil (no-op) when revoke script not configured
|
|
err := connector.RevokeCertificate(ctx, revokeReq)
|
|
if err != nil {
|
|
t.Fatalf("RevokeCertificate failed: %v", err)
|
|
}
|
|
})
|
|
|
|
// Test 9: RevokeCertificate with revoke script
|
|
t.Run("RevokeCertificate_WithScript", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
signScript := filepath.Join(tmpDir, "sign.sh")
|
|
if err := os.WriteFile(signScript, []byte("#!/bin/sh\nexit 0"), 0755); err != nil {
|
|
t.Fatalf("Failed to create sign script: %v", err)
|
|
}
|
|
|
|
revokeScript := filepath.Join(tmpDir, "revoke.sh")
|
|
if err := os.WriteFile(revokeScript, []byte("#!/bin/sh\nexit 0"), 0755); err != nil {
|
|
t.Fatalf("Failed to create revoke script: %v", err)
|
|
}
|
|
|
|
config := &openssl.Config{
|
|
SignScript: signScript,
|
|
RevokeScript: revokeScript,
|
|
}
|
|
connector := openssl.New(config, logger)
|
|
|
|
rawConfig, _ := json.Marshal(config)
|
|
if err := connector.ValidateConfig(ctx, rawConfig); err != nil {
|
|
t.Fatalf("ValidateConfig failed: %v", err)
|
|
}
|
|
|
|
reason := "keyCompromise"
|
|
revokeReq := issuer.RevocationRequest{
|
|
Serial: "ABCDEF1234567890",
|
|
Reason: &reason,
|
|
}
|
|
|
|
err := connector.RevokeCertificate(ctx, revokeReq)
|
|
if err != nil {
|
|
t.Fatalf("RevokeCertificate failed: %v", err)
|
|
}
|
|
})
|
|
|
|
// Test 15: RevokeCertificate rejects injection payloads in serial number
|
|
t.Run("RevokeCertificate_InjectionSerial", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
signScript := filepath.Join(tmpDir, "sign.sh")
|
|
if err := os.WriteFile(signScript, []byte("#!/bin/sh\nexit 0"), 0755); err != nil {
|
|
t.Fatalf("Failed to create sign script: %v", err)
|
|
}
|
|
revokeScript := filepath.Join(tmpDir, "revoke.sh")
|
|
if err := os.WriteFile(revokeScript, []byte("#!/bin/sh\nexit 0"), 0755); err != nil {
|
|
t.Fatalf("Failed to create revoke script: %v", err)
|
|
}
|
|
|
|
config := &openssl.Config{
|
|
SignScript: signScript,
|
|
RevokeScript: revokeScript,
|
|
}
|
|
connector := openssl.New(config, logger)
|
|
rawConfig, _ := json.Marshal(config)
|
|
if err := connector.ValidateConfig(ctx, rawConfig); err != nil {
|
|
t.Fatalf("ValidateConfig failed: %v", err)
|
|
}
|
|
|
|
injectionPayloads := []string{
|
|
"1234;rm -rf /",
|
|
"1234|cat /etc/passwd",
|
|
"1234&whoami",
|
|
"$(id)",
|
|
"`id`",
|
|
"1234\nid",
|
|
"../../../etc/passwd",
|
|
"test-serial-12345", // hyphens not allowed (not hex)
|
|
}
|
|
|
|
for _, payload := range injectionPayloads {
|
|
t.Run(payload, func(t *testing.T) {
|
|
req := issuer.RevocationRequest{Serial: payload}
|
|
err := connector.RevokeCertificate(ctx, req)
|
|
if err == nil {
|
|
t.Errorf("Expected injection payload %q to be rejected, but it was accepted", payload)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
// Test 16: RevokeCertificate rejects invalid reason codes
|
|
t.Run("RevokeCertificate_InvalidReason", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
signScript := filepath.Join(tmpDir, "sign.sh")
|
|
if err := os.WriteFile(signScript, []byte("#!/bin/sh\nexit 0"), 0755); err != nil {
|
|
t.Fatalf("Failed to create sign script: %v", err)
|
|
}
|
|
revokeScript := filepath.Join(tmpDir, "revoke.sh")
|
|
if err := os.WriteFile(revokeScript, []byte("#!/bin/sh\nexit 0"), 0755); err != nil {
|
|
t.Fatalf("Failed to create revoke script: %v", err)
|
|
}
|
|
|
|
config := &openssl.Config{
|
|
SignScript: signScript,
|
|
RevokeScript: revokeScript,
|
|
}
|
|
connector := openssl.New(config, logger)
|
|
rawConfig, _ := json.Marshal(config)
|
|
if err := connector.ValidateConfig(ctx, rawConfig); err != nil {
|
|
t.Fatalf("ValidateConfig failed: %v", err)
|
|
}
|
|
|
|
invalidReasons := []string{
|
|
"notARealReason",
|
|
"keyCompromise;rm -rf /",
|
|
"$(whoami)",
|
|
"`id`",
|
|
}
|
|
|
|
for _, reason := range invalidReasons {
|
|
t.Run(reason, func(t *testing.T) {
|
|
r := reason
|
|
req := issuer.RevocationRequest{
|
|
Serial: "ABCDEF1234567890",
|
|
Reason: &r,
|
|
}
|
|
err := connector.RevokeCertificate(ctx, req)
|
|
if err == nil {
|
|
t.Errorf("Expected invalid reason %q to be rejected, but it was accepted", reason)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
// Test 17: RevokeCertificate accepts all valid RFC 5280 reason codes
|
|
t.Run("RevokeCertificate_ValidReasons", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
signScript := filepath.Join(tmpDir, "sign.sh")
|
|
if err := os.WriteFile(signScript, []byte("#!/bin/sh\nexit 0"), 0755); err != nil {
|
|
t.Fatalf("Failed to create sign script: %v", err)
|
|
}
|
|
revokeScript := filepath.Join(tmpDir, "revoke.sh")
|
|
if err := os.WriteFile(revokeScript, []byte("#!/bin/sh\nexit 0"), 0755); err != nil {
|
|
t.Fatalf("Failed to create revoke script: %v", err)
|
|
}
|
|
|
|
config := &openssl.Config{
|
|
SignScript: signScript,
|
|
RevokeScript: revokeScript,
|
|
}
|
|
connector := openssl.New(config, logger)
|
|
rawConfig, _ := json.Marshal(config)
|
|
if err := connector.ValidateConfig(ctx, rawConfig); err != nil {
|
|
t.Fatalf("ValidateConfig failed: %v", err)
|
|
}
|
|
|
|
validReasons := []string{
|
|
"unspecified", "keyCompromise", "caCompromise", "affiliationChanged",
|
|
"superseded", "cessationOfOperation", "certificateHold", "privilegeWithdrawn",
|
|
}
|
|
|
|
for _, reason := range validReasons {
|
|
t.Run(reason, func(t *testing.T) {
|
|
r := reason
|
|
req := issuer.RevocationRequest{
|
|
Serial: "ABCDEF1234567890",
|
|
Reason: &r,
|
|
}
|
|
err := connector.RevokeCertificate(ctx, req)
|
|
if err != nil {
|
|
t.Errorf("Expected valid reason %q to be accepted, got error: %v", reason, err)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
// Test 10: GetOrderStatus always returns "completed"
|
|
t.Run("GetOrderStatus", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
signScript := filepath.Join(tmpDir, "sign.sh")
|
|
if err := os.WriteFile(signScript, []byte("#!/bin/sh\nexit 0"), 0755); err != nil {
|
|
t.Fatalf("Failed to create sign script: %v", err)
|
|
}
|
|
|
|
config := &openssl.Config{
|
|
SignScript: signScript,
|
|
}
|
|
connector := openssl.New(config, logger)
|
|
|
|
rawConfig, _ := json.Marshal(config)
|
|
if err := connector.ValidateConfig(ctx, rawConfig); err != nil {
|
|
t.Fatalf("ValidateConfig failed: %v", err)
|
|
}
|
|
|
|
status, err := connector.GetOrderStatus(ctx, "openssl-12345")
|
|
if err != nil {
|
|
t.Fatalf("GetOrderStatus failed: %v", err)
|
|
}
|
|
|
|
if status.Status != "completed" {
|
|
t.Errorf("Expected status 'completed', got '%s'", status.Status)
|
|
}
|
|
|
|
t.Logf("Order status: %s", status.Status)
|
|
})
|
|
|
|
// Test 11: GenerateCRL without CRL script configured
|
|
t.Run("GenerateCRL_NoScript", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
signScript := filepath.Join(tmpDir, "sign.sh")
|
|
if err := os.WriteFile(signScript, []byte("#!/bin/sh\nexit 0"), 0755); err != nil {
|
|
t.Fatalf("Failed to create sign script: %v", err)
|
|
}
|
|
|
|
config := &openssl.Config{
|
|
SignScript: signScript,
|
|
// CRLScript not set
|
|
}
|
|
connector := openssl.New(config, logger)
|
|
|
|
rawConfig, _ := json.Marshal(config)
|
|
if err := connector.ValidateConfig(ctx, rawConfig); err != nil {
|
|
t.Fatalf("ValidateConfig failed: %v", err)
|
|
}
|
|
|
|
crl, err := connector.GenerateCRL(ctx, []issuer.RevokedCertEntry{})
|
|
if err != nil {
|
|
t.Fatalf("GenerateCRL failed: %v", err)
|
|
}
|
|
|
|
// Should return nil when CRL script not configured
|
|
if crl != nil {
|
|
t.Error("Expected nil CRL when CRL script not configured")
|
|
}
|
|
})
|
|
|
|
// Test 12: GenerateCRL with CRL script
|
|
t.Run("GenerateCRL_WithScript", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
signScript := filepath.Join(tmpDir, "sign.sh")
|
|
if err := os.WriteFile(signScript, []byte("#!/bin/sh\nexit 0"), 0755); err != nil {
|
|
t.Fatalf("Failed to create sign script: %v", err)
|
|
}
|
|
|
|
crlScript := filepath.Join(tmpDir, "crl.sh")
|
|
scriptContent := "#!/bin/sh\n" +
|
|
"SERIALS_FILE=\"$1\"\n" +
|
|
"CRL_FILE=\"$2\"\n" +
|
|
"echo 'test-crl-content' > \"$CRL_FILE\"\n" +
|
|
"exit 0\n"
|
|
if err := os.WriteFile(crlScript, []byte(scriptContent), 0755); err != nil {
|
|
t.Fatalf("Failed to create CRL script: %v", err)
|
|
}
|
|
|
|
config := &openssl.Config{
|
|
SignScript: signScript,
|
|
CRLScript: crlScript,
|
|
}
|
|
connector := openssl.New(config, logger)
|
|
|
|
rawConfig, _ := json.Marshal(config)
|
|
if err := connector.ValidateConfig(ctx, rawConfig); err != nil {
|
|
t.Fatalf("ValidateConfig failed: %v", err)
|
|
}
|
|
|
|
crl, err := connector.GenerateCRL(ctx, []issuer.RevokedCertEntry{})
|
|
if err != nil {
|
|
t.Fatalf("GenerateCRL failed: %v", err)
|
|
}
|
|
|
|
if crl == nil {
|
|
t.Error("Expected CRL, got nil")
|
|
}
|
|
if len(crl) == 0 {
|
|
t.Error("Expected non-empty CRL")
|
|
}
|
|
})
|
|
|
|
// Test 13: SignOCSPResponse returns nil (not supported)
|
|
t.Run("SignOCSPResponse_NotSupported", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
signScript := filepath.Join(tmpDir, "sign.sh")
|
|
if err := os.WriteFile(signScript, []byte("#!/bin/sh\nexit 0"), 0755); err != nil {
|
|
t.Fatalf("Failed to create sign script: %v", err)
|
|
}
|
|
|
|
config := &openssl.Config{
|
|
SignScript: signScript,
|
|
}
|
|
connector := openssl.New(config, logger)
|
|
|
|
rawConfig, _ := json.Marshal(config)
|
|
if err := connector.ValidateConfig(ctx, rawConfig); err != nil {
|
|
t.Fatalf("ValidateConfig failed: %v", err)
|
|
}
|
|
|
|
resp, err := connector.SignOCSPResponse(ctx, issuer.OCSPSignRequest{})
|
|
if err != nil {
|
|
t.Fatalf("SignOCSPResponse failed: %v", err)
|
|
}
|
|
|
|
if resp != nil {
|
|
t.Error("Expected nil OCSP response (not supported)")
|
|
}
|
|
})
|
|
|
|
// Test 14: Default timeout
|
|
t.Run("DefaultTimeout", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
signScript := filepath.Join(tmpDir, "sign.sh")
|
|
if err := os.WriteFile(signScript, []byte("#!/bin/sh\nexit 0"), 0755); err != nil {
|
|
t.Fatalf("Failed to create sign script: %v", err)
|
|
}
|
|
|
|
config := &openssl.Config{
|
|
SignScript: signScript,
|
|
TimeoutSeconds: 0, // Should default to 30
|
|
}
|
|
connector := openssl.New(config, logger)
|
|
|
|
rawConfig, _ := json.Marshal(config)
|
|
if err := connector.ValidateConfig(ctx, rawConfig); err != nil {
|
|
t.Fatalf("ValidateConfig failed: %v", err)
|
|
}
|
|
|
|
// If timeout is 30 seconds, the config should validate without errors
|
|
// (we can't easily test the actual timeout value without accessing private fields)
|
|
t.Log("Default timeout configured (should be 30 seconds)")
|
|
})
|
|
}
|
|
|
|
// --- Test Helpers ---
|
|
|
|
// generateTestCSR creates a test Certificate Signing Request.
|
|
func generateTestCSR(cn string) (*x509.CertificateRequest, string, error) {
|
|
privKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
subject := pkix.Name{
|
|
CommonName: cn,
|
|
}
|
|
|
|
csrTemplate := x509.CertificateRequest{
|
|
Subject: subject,
|
|
DNSNames: []string{cn, "www." + cn},
|
|
}
|
|
|
|
csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &csrTemplate, privKey)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
csr, err := x509.ParseCertificateRequest(csrBytes)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
csrPEM := pem.EncodeToMemory(&pem.Block{
|
|
Type: "CERTIFICATE REQUEST",
|
|
Bytes: csrBytes,
|
|
})
|
|
|
|
return csr, string(csrPEM), nil
|
|
}
|
|
|
|
// generateMockCertPEM creates a self-signed certificate for testing.
|
|
func generateMockCertPEM() string {
|
|
privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
|
|
|
|
serialNumber := big.NewInt(1234567890)
|
|
subject := pkix.Name{
|
|
CommonName: "test.example.com",
|
|
}
|
|
|
|
template := &x509.Certificate{
|
|
SerialNumber: serialNumber,
|
|
Subject: subject,
|
|
NotBefore: time.Now(),
|
|
NotAfter: time.Now().AddDate(0, 0, 90),
|
|
KeyUsage: x509.KeyUsageDigitalSignature,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
|
DNSNames: []string{"test.example.com", "www.test.example.com"},
|
|
}
|
|
|
|
certBytes, _ := x509.CreateCertificate(rand.Reader, template, template, privKey.Public(), privKey)
|
|
|
|
return string(pem.EncodeToMemory(&pem.Block{
|
|
Type: "CERTIFICATE",
|
|
Bytes: certBytes,
|
|
}))
|
|
}
|
|
|
|
// Security tests for script path validation
|
|
|
|
func TestOpenSSLConnector_ValidateConfig_RejectNonRegularFile(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}))
|
|
ctx := context.Background()
|
|
|
|
// Try to use a directory as a script path
|
|
tmpDir := t.TempDir()
|
|
|
|
config := &openssl.Config{
|
|
SignScript: tmpDir, // This is a directory, not a regular file
|
|
}
|
|
connector := openssl.New(config, logger)
|
|
|
|
rawConfig, _ := json.Marshal(config)
|
|
err := connector.ValidateConfig(ctx, rawConfig)
|
|
if err == nil {
|
|
t.Fatal("Expected error when sign_script is not a regular file")
|
|
}
|
|
}
|
|
|
|
func TestOpenSSLConnector_ValidateConfig_ValidateRevokeScriptPath(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}))
|
|
ctx := context.Background()
|
|
|
|
tmpDir := t.TempDir()
|
|
signScript := filepath.Join(tmpDir, "sign.sh")
|
|
os.WriteFile(signScript, []byte("#!/bin/sh\nexit 0"), 0755)
|
|
|
|
// Try to use a nonexistent file as revoke_script
|
|
config := &openssl.Config{
|
|
SignScript: signScript,
|
|
RevokeScript: "/nonexistent/revoke.sh",
|
|
}
|
|
connector := openssl.New(config, logger)
|
|
|
|
rawConfig, _ := json.Marshal(config)
|
|
err := connector.ValidateConfig(ctx, rawConfig)
|
|
if err == nil {
|
|
t.Fatal("Expected error when revoke_script is nonexistent")
|
|
}
|
|
}
|
|
|
|
func TestOpenSSLConnector_ValidateConfig_ValidateCRLScriptPath(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}))
|
|
ctx := context.Background()
|
|
|
|
tmpDir := t.TempDir()
|
|
signScript := filepath.Join(tmpDir, "sign.sh")
|
|
os.WriteFile(signScript, []byte("#!/bin/sh\nexit 0"), 0755)
|
|
|
|
// Try to use a directory as crl_script
|
|
config := &openssl.Config{
|
|
SignScript: signScript,
|
|
CRLScript: tmpDir, // This is a directory, not a regular file
|
|
}
|
|
connector := openssl.New(config, logger)
|
|
|
|
rawConfig, _ := json.Marshal(config)
|
|
err := connector.ValidateConfig(ctx, rawConfig)
|
|
if err == nil {
|
|
t.Fatal("Expected error when crl_script is not a regular file")
|
|
}
|
|
}
|