// Copyright 2026 certctl LLC. All rights reserved. // SPDX-License-Identifier: BUSL-1.1 package issuer import ( "context" "encoding/json" "math/big" "time" ) // Connector defines the interface for certificate issuance operations. type Connector interface { // ValidateConfig validates the issuer configuration. ValidateConfig(ctx context.Context, config json.RawMessage) error // IssueCertificate issues a new certificate. IssueCertificate(ctx context.Context, request IssuanceRequest) (*IssuanceResult, error) // RenewCertificate renews an existing certificate. RenewCertificate(ctx context.Context, request RenewalRequest) (*IssuanceResult, error) // RevokeCertificate revokes a certificate. RevokeCertificate(ctx context.Context, request RevocationRequest) error // GetOrderStatus retrieves the status of an issuance or renewal order. GetOrderStatus(ctx context.Context, orderID string) (*OrderStatus, error) // GenerateCRL generates a DER-encoded X.509 CRL signed by this issuer. // Returns nil if the issuer does not support CRL generation (e.g., ACME). GenerateCRL(ctx context.Context, revokedCerts []RevokedCertEntry) ([]byte, error) // SignOCSPResponse signs an OCSP response for the given certificate serial. // Returns nil if the issuer does not support OCSP (e.g., ACME). SignOCSPResponse(ctx context.Context, req OCSPSignRequest) ([]byte, error) // GetCACertPEM returns the PEM-encoded CA certificate chain for this issuer. // Used by the EST /cacerts endpoint. Returns empty string if not available. GetCACertPEM(ctx context.Context) (string, error) // GetRenewalInfo retrieves ACME Renewal Information (ARI) per RFC 9773 for a certificate. // certPEM is the PEM-encoded certificate. Returns nil, nil if the CA does not support ARI. GetRenewalInfo(ctx context.Context, certPEM string) (*RenewalInfoResult, error) } // RenewalInfoResult holds the ACME ARI response from a CA. type RenewalInfoResult struct { SuggestedWindowStart time.Time SuggestedWindowEnd time.Time RetryAfter time.Time ExplanationURL string } // IssuanceRequest contains the parameters for issuing a new certificate. type IssuanceRequest struct { CommonName string `json:"common_name"` SANs []string `json:"sans"` CSRPEM string `json:"csr_pem"` EKUs []string `json:"ekus,omitempty"` // e.g., "serverAuth", "clientAuth", "emailProtection" MaxTTLSeconds int `json:"max_ttl_seconds,omitempty"` // 0 = no cap (use issuer default) // MustStaple, when true, instructs the issuer to add the RFC 7633 // must-staple extension (id-pe-tlsfeature) to the issued cert. // Plumbed from CertificateProfile.MustStaple at the service layer. // Issuers that don't support extension injection (Vault, EJBCA, etc.) // silently ignore this โ€” must-staple is a local-issuer-only feature // in V2 since upstream connectors enforce their own extension policy. MustStaple bool `json:"must_staple,omitempty"` } // IssuanceResult contains the result of a successful certificate issuance. type IssuanceResult struct { CertPEM string `json:"cert_pem"` ChainPEM string `json:"chain_pem"` Serial string `json:"serial"` NotBefore time.Time `json:"not_before"` NotAfter time.Time `json:"not_after"` OrderID string `json:"order_id"` } // RenewalRequest contains the parameters for renewing a certificate. type RenewalRequest struct { CommonName string `json:"common_name"` SANs []string `json:"sans"` CSRPEM string `json:"csr_pem"` EKUs []string `json:"ekus,omitempty"` // e.g., "serverAuth", "clientAuth", "emailProtection" MaxTTLSeconds int `json:"max_ttl_seconds,omitempty"` // 0 = no cap (use issuer default) OrderID *string `json:"order_id,omitempty"` // MustStaple โ€” same semantics as IssuanceRequest.MustStaple. The // renewal pipeline plumbs through the same CertificateProfile.MustStaple // field so renewed certs match their initial-issuance extension set. MustStaple bool `json:"must_staple,omitempty"` } // RevocationRequest contains the parameters for revoking a certificate. type RevocationRequest struct { Serial string `json:"serial"` Reason *string `json:"reason,omitempty"` } // OrderStatus contains the status of a pending issuance or renewal order. type OrderStatus struct { OrderID string `json:"order_id"` Status string `json:"status"` Message *string `json:"message,omitempty"` CertPEM *string `json:"cert_pem,omitempty"` ChainPEM *string `json:"chain_pem,omitempty"` Serial *string `json:"serial,omitempty"` NotBefore *time.Time `json:"not_before,omitempty"` NotAfter *time.Time `json:"not_after,omitempty"` UpdatedAt time.Time `json:"updated_at"` } // RevokedCertEntry represents a revoked certificate for CRL generation. type RevokedCertEntry struct { SerialNumber *big.Int RevokedAt time.Time ReasonCode int } // OCSPSignRequest contains the parameters for signing an OCSP response. type OCSPSignRequest struct { CertSerial *big.Int CertStatus int // 0=good, 1=revoked, 2=unknown RevokedAt time.Time RevocationReason int ThisUpdate time.Time NextUpdate time.Time // Nonce โ€” RFC 6960 ยง4.4.1 OCSP-nonce extension echo. When non-nil, // the responder MUST include this value in the response's // singleExtensions field. When nil, the response MUST NOT carry // a nonce extension (back-compat with relying parties that don't // understand it). Production hardening II Phase 1. Nonce []byte }