feat(m27): certificate export (PEM/PKCS#12) and S/MIME EKU support

Add certificate export in PEM (JSON or file download) and PKCS#12 formats.
Private keys are never included — they stay on agents. Add EKU-aware
issuance threading profile EKUs (serverAuth, clientAuth, codeSigning,
emailProtection, timeStamping) through the full issuance pipeline. Fix
agent CSR SAN splitting for email addresses, adaptive KeyUsage flags for
S/MIME vs TLS, and a pre-existing generateID collision bug in deployment
job creation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
shankar0123
2026-03-28 16:16:19 -04:00
parent 78c7bc16b0
commit a00bb349c4
26 changed files with 1354 additions and 53 deletions
+14 -1
View File
@@ -19,6 +19,7 @@ type AgentService struct {
certRepo repository.CertificateRepository
jobRepo repository.JobRepository
targetRepo repository.TargetRepository
profileRepo repository.CertificateProfileRepository
auditService *AuditService
issuerRegistry map[string]IssuerConnector
renewalService *RenewalService
@@ -45,6 +46,11 @@ func NewAgentService(
}
}
// SetProfileRepo sets the profile repository for EKU resolution during CSR signing.
func (s *AgentService) SetProfileRepo(repo repository.CertificateProfileRepository) {
s.profileRepo = repo
}
// Register creates a new agent and returns its API key (only once).
func (s *AgentService) Register(ctx context.Context, name string, hostname string) (*domain.Agent, string, error) {
if name == "" || hostname == "" {
@@ -159,7 +165,14 @@ func (s *AgentService) SubmitCSR(ctx context.Context, agentID string, certID str
// Fallback: direct issuer signing (no AwaitingCSR job — ad-hoc CSR submission)
connector, ok := s.issuerRegistry[cert.IssuerID]
if ok {
result, err := connector.IssueCertificate(ctx, cert.CommonName, cert.SANs, string(csrPEM))
// Resolve EKUs from the certificate profile if available
var ekus []string
if cert.CertificateProfileID != "" && s.profileRepo != nil {
if profile, profileErr := s.profileRepo.Get(ctx, cert.CertificateProfileID); profileErr == nil && profile != nil {
ekus = profile.AllowedEKUs
}
}
result, err := connector.IssueCertificate(ctx, cert.CommonName, cert.SANs, string(csrPEM), ekus)
if err != nil {
return fmt.Errorf("issuer signing failed: %w", err)
}