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.
561 lines
19 KiB
Go
561 lines
19 KiB
Go
// Package globalsign implements the issuer.Connector interface for GlobalSign Atlas HVCA.
|
|
//
|
|
// GlobalSign Atlas HVCA (Hosted Validation CA) is an enterprise certificate authority
|
|
// offering DV and OV certificates. Unlike synchronous issuers (Vault, step-ca), GlobalSign
|
|
// uses an asynchronous order model with serial number polling: submit a certificate order,
|
|
// receive a serial number immediately, then poll to check when the cert is available.
|
|
//
|
|
// This connector maps to certctl's existing job state machine:
|
|
// - IssueCertificate submits the order and returns the serial number. The cert PEM
|
|
// is typically available within seconds for DV certs.
|
|
// - GetOrderStatus polls via the serial number to retrieve the cert when ready.
|
|
//
|
|
// Authentication: mTLS client certificate (mutual TLS handshake) PLUS API key/secret
|
|
// headers on every request. This is a "double auth" pattern.
|
|
// - TLS client certificate: loaded from disk via tls.LoadX509KeyPair()
|
|
// - API key/secret: sent as custom HTTP headers (ApiKey, ApiSecret)
|
|
//
|
|
// GlobalSign Atlas HVCA API used:
|
|
//
|
|
// POST /v2/certificates - Submit certificate order, returns serial number
|
|
// GET /v2/certificates/{serial} - Get certificate PEM by serial number
|
|
// PUT /v2/certificates/{serial}/revoke - Revoke certificate (no reason code required)
|
|
// GET /v2/certificates - List certificates (for config validation)
|
|
package globalsign
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/json"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/shankar0123/certctl/internal/connector/issuer"
|
|
)
|
|
|
|
// Config represents the GlobalSign Atlas HVCA issuer connector configuration.
|
|
type Config struct {
|
|
// APIUrl is the GlobalSign Atlas HVCA API base URL (region-aware).
|
|
// Examples: https://emea.api.hvca.globalsign.com:8443/v2/ (EMEA region)
|
|
// Required. Set via CERTCTL_GLOBALSIGN_API_URL environment variable.
|
|
APIUrl string `json:"api_url"`
|
|
|
|
// APIKey is the GlobalSign API key for request authentication.
|
|
// Required. Set via CERTCTL_GLOBALSIGN_API_KEY environment variable.
|
|
APIKey string `json:"api_key"`
|
|
|
|
// APISecret is the GlobalSign API secret for request authentication.
|
|
// Required. Set via CERTCTL_GLOBALSIGN_API_SECRET environment variable.
|
|
APISecret string `json:"api_secret"`
|
|
|
|
// ClientCertPath is the filesystem path to the mTLS client certificate PEM file.
|
|
// The certificate must be signed by GlobalSign and loaded for TLS handshake.
|
|
// Required. Set via CERTCTL_GLOBALSIGN_CLIENT_CERT_PATH environment variable.
|
|
ClientCertPath string `json:"client_cert_path"`
|
|
|
|
// ClientKeyPath is the filesystem path to the mTLS client private key PEM file.
|
|
// Must match the certificate in ClientCertPath.
|
|
// Required. Set via CERTCTL_GLOBALSIGN_CLIENT_KEY_PATH environment variable.
|
|
ClientKeyPath string `json:"client_key_path"`
|
|
|
|
// ServerCAPath is the filesystem path to a PEM file containing the CA
|
|
// certificate(s) used to verify the GlobalSign Atlas HVCA API server certificate.
|
|
// Optional. If empty, the system trust store is used. This option exists for
|
|
// private/lab deployments of GlobalSign Atlas that terminate TLS with an
|
|
// internal CA not present in the host's default trust bundle.
|
|
// Set via CERTCTL_GLOBALSIGN_SERVER_CA_PATH environment variable.
|
|
ServerCAPath string `json:"server_ca_path,omitempty"`
|
|
}
|
|
|
|
// Connector implements the issuer.Connector interface for GlobalSign Atlas HVCA.
|
|
type Connector struct {
|
|
config *Config
|
|
logger *slog.Logger
|
|
httpClient *http.Client
|
|
}
|
|
|
|
// New creates a new GlobalSign Atlas HVCA connector with the given configuration and logger.
|
|
// The connector will load the mTLS client certificate from the config paths on each API call.
|
|
func New(config *Config, logger *slog.Logger) *Connector {
|
|
return &Connector{
|
|
config: config,
|
|
logger: logger,
|
|
httpClient: &http.Client{
|
|
Timeout: 30 * time.Second,
|
|
},
|
|
}
|
|
}
|
|
|
|
// NewWithHTTPClient creates a new GlobalSign connector with a custom HTTP client.
|
|
// Used for testing with mocked HTTP responses. The client is used directly instead of
|
|
// loading mTLS certificates, allowing tests to bypass TLS setup.
|
|
func NewWithHTTPClient(config *Config, logger *slog.Logger, client *http.Client) *Connector {
|
|
return &Connector{
|
|
config: config,
|
|
logger: logger,
|
|
httpClient: client,
|
|
}
|
|
}
|
|
|
|
// certificateRequest is the JSON body for GlobalSign certificate order submission.
|
|
type certificateRequest struct {
|
|
CSR string `json:"csr"`
|
|
SubjectDN subjectDNRequest `json:"subject_dn"`
|
|
SAN sanRequest `json:"san,omitempty"`
|
|
}
|
|
|
|
type subjectDNRequest struct {
|
|
CommonName string `json:"common_name"`
|
|
}
|
|
|
|
type sanRequest struct {
|
|
DNSNames []string `json:"dns_names,omitempty"`
|
|
}
|
|
|
|
// certificateResponse is the JSON response from a certificate order submission or retrieval.
|
|
type certificateResponse struct {
|
|
SerialNumber string `json:"serial_number"`
|
|
Status string `json:"status"`
|
|
Certificate string `json:"certificate,omitempty"`
|
|
Chain string `json:"chain,omitempty"`
|
|
IssuedAt string `json:"issued_at,omitempty"`
|
|
}
|
|
|
|
// ValidateConfig checks that the GlobalSign configuration is valid and mTLS connection works.
|
|
func (c *Connector) ValidateConfig(ctx context.Context, rawConfig json.RawMessage) error {
|
|
var cfg Config
|
|
if err := json.Unmarshal(rawConfig, &cfg); err != nil {
|
|
return fmt.Errorf("invalid GlobalSign config: %w", err)
|
|
}
|
|
|
|
if cfg.APIUrl == "" {
|
|
return fmt.Errorf("GlobalSign api_url is required")
|
|
}
|
|
|
|
if cfg.APIKey == "" {
|
|
return fmt.Errorf("GlobalSign api_key is required")
|
|
}
|
|
|
|
if cfg.APISecret == "" {
|
|
return fmt.Errorf("GlobalSign api_secret is required")
|
|
}
|
|
|
|
if cfg.ClientCertPath == "" {
|
|
return fmt.Errorf("GlobalSign client_cert_path is required")
|
|
}
|
|
|
|
if cfg.ClientKeyPath == "" {
|
|
return fmt.Errorf("GlobalSign client_key_path is required")
|
|
}
|
|
|
|
// Load the client certificate and key for mTLS validation
|
|
cert, err := tls.LoadX509KeyPair(cfg.ClientCertPath, cfg.ClientKeyPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load GlobalSign client certificate: %w", err)
|
|
}
|
|
|
|
// Build a verifying mTLS TLS config. If ServerCAPath is set, that PEM
|
|
// bundle is used as the trust anchor for the server certificate;
|
|
// otherwise the system trust store is used. TLS 1.2 is the minimum.
|
|
tlsConfig, err := buildServerTLSConfig(&cfg, cert)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to build GlobalSign TLS config: %w", err)
|
|
}
|
|
|
|
validationClient := &http.Client{
|
|
Transport: &http.Transport{
|
|
TLSClientConfig: tlsConfig,
|
|
},
|
|
Timeout: 10 * time.Second,
|
|
}
|
|
|
|
// Test API access via GET /v2/certificates (list, requires auth headers)
|
|
listURL := strings.TrimSuffix(cfg.APIUrl, "/") + "/v2/certificates"
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, listURL, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create API test request: %w", err)
|
|
}
|
|
|
|
// Add both authentication layers
|
|
req.Header.Set("ApiKey", cfg.APIKey)
|
|
req.Header.Set("ApiSecret", cfg.APISecret)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := validationClient.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("GlobalSign API not reachable at %s: %w", cfg.APIUrl, err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode == http.StatusForbidden || resp.StatusCode == http.StatusUnauthorized {
|
|
return fmt.Errorf("GlobalSign API credentials are invalid (status %d)", resp.StatusCode)
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusBadRequest {
|
|
respBody, _ := io.ReadAll(resp.Body)
|
|
return fmt.Errorf("GlobalSign API returned status %d: %s", resp.StatusCode, string(respBody))
|
|
}
|
|
|
|
c.config = &cfg
|
|
c.logger.Info("GlobalSign Atlas HVCA configuration validated",
|
|
"api_url", cfg.APIUrl)
|
|
|
|
return nil
|
|
}
|
|
|
|
// getHTTPClient returns the HTTP client to use, creating one with mTLS if needed.
|
|
// If the connector was created with NewWithHTTPClient (test mode), uses that client directly.
|
|
// Otherwise, creates a fresh mTLS client with the configured certificate.
|
|
func (c *Connector) getHTTPClient(ctx context.Context) (*http.Client, error) {
|
|
// Check if we're in test mode (httpClient was explicitly provided and has non-nil transport)
|
|
if c.httpClient != nil && c.httpClient.Transport != nil {
|
|
return c.httpClient, nil
|
|
}
|
|
|
|
// For tests with default client (nil or minimal), check if cert paths are available
|
|
if c.config.ClientCertPath == "" || c.config.ClientKeyPath == "" {
|
|
// Test mode: use httpClient as-is (won't load certs)
|
|
return c.httpClient, nil
|
|
}
|
|
|
|
// Production mode: load mTLS certificate
|
|
cert, err := tls.LoadX509KeyPair(c.config.ClientCertPath, c.config.ClientKeyPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load GlobalSign client certificate: %w", err)
|
|
}
|
|
|
|
tlsConfig, err := buildServerTLSConfig(c.config, cert)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to build GlobalSign TLS config: %w", err)
|
|
}
|
|
|
|
return &http.Client{
|
|
Transport: &http.Transport{
|
|
TLSClientConfig: tlsConfig,
|
|
},
|
|
Timeout: 30 * time.Second,
|
|
}, nil
|
|
}
|
|
|
|
// buildServerTLSConfig returns a TLS configuration for the GlobalSign Atlas
|
|
// HVCA API client. It always verifies the server certificate. When
|
|
// cfg.ServerCAPath is set, the PEM bundle at that path is used as the
|
|
// trust anchor (enables pinning a private/lab CA); otherwise the host's
|
|
// system trust store is used. TLS 1.2 is the minimum protocol version.
|
|
//
|
|
// This helper is the single source of truth for both the ValidateConfig
|
|
// probe client and the steady-state getHTTPClient production client, so
|
|
// any future TLS policy change applies uniformly.
|
|
func buildServerTLSConfig(cfg *Config, clientCert tls.Certificate) (*tls.Config, error) {
|
|
tlsConfig := &tls.Config{
|
|
Certificates: []tls.Certificate{clientCert},
|
|
MinVersion: tls.VersionTLS12,
|
|
}
|
|
|
|
if cfg.ServerCAPath != "" {
|
|
caPEM, err := os.ReadFile(cfg.ServerCAPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read server CA bundle at %s: %w", cfg.ServerCAPath, err)
|
|
}
|
|
|
|
pool := x509.NewCertPool()
|
|
if !pool.AppendCertsFromPEM(caPEM) {
|
|
return nil, fmt.Errorf("no valid PEM certificates found in server CA bundle at %s", cfg.ServerCAPath)
|
|
}
|
|
|
|
tlsConfig.RootCAs = pool
|
|
}
|
|
|
|
return tlsConfig, nil
|
|
}
|
|
|
|
// IssueCertificate submits a certificate order to GlobalSign Atlas HVCA.
|
|
// Returns the serial number immediately; typically the cert is available within seconds (DV) to minutes (OV).
|
|
func (c *Connector) IssueCertificate(ctx context.Context, request issuer.IssuanceRequest) (*issuer.IssuanceResult, error) {
|
|
c.logger.Info("processing GlobalSign issuance request",
|
|
"common_name", request.CommonName,
|
|
"san_count", len(request.SANs))
|
|
|
|
client, err := c.getHTTPClient(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
certReq := certificateRequest{
|
|
CSR: request.CSRPEM,
|
|
SubjectDN: subjectDNRequest{
|
|
CommonName: request.CommonName,
|
|
},
|
|
}
|
|
|
|
if len(request.SANs) > 0 {
|
|
certReq.SAN = sanRequest{
|
|
DNSNames: request.SANs,
|
|
}
|
|
}
|
|
|
|
body, err := json.Marshal(certReq)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal certificate request: %w", err)
|
|
}
|
|
|
|
certURL := strings.TrimSuffix(c.config.APIUrl, "/") + "/v2/certificates"
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, certURL, bytes.NewReader(body))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create certificate request: %w", err)
|
|
}
|
|
|
|
// Apply double auth: mTLS + headers
|
|
req.Header.Set("ApiKey", c.config.APIKey)
|
|
req.Header.Set("ApiSecret", c.config.APISecret)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("GlobalSign certificate request failed: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
respBody, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read certificate response: %w", err)
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
|
|
return nil, fmt.Errorf("GlobalSign certificate submission returned status %d: %s", resp.StatusCode, string(respBody))
|
|
}
|
|
|
|
var certResp certificateResponse
|
|
if err := json.Unmarshal(respBody, &certResp); err != nil {
|
|
return nil, fmt.Errorf("failed to parse certificate response: %w", err)
|
|
}
|
|
|
|
c.logger.Info("GlobalSign certificate order submitted",
|
|
"serial", certResp.SerialNumber,
|
|
"status", certResp.Status)
|
|
|
|
// If certificate is available immediately, return it.
|
|
// Otherwise, return just the serial number for polling via GetOrderStatus.
|
|
if certResp.Status == "issued" && certResp.Certificate != "" {
|
|
notBefore, notAfter, err := parseCertDates(certResp.Certificate)
|
|
if err != nil {
|
|
c.logger.Warn("failed to parse certificate dates", "error", err)
|
|
}
|
|
|
|
return &issuer.IssuanceResult{
|
|
CertPEM: certResp.Certificate,
|
|
ChainPEM: certResp.Chain,
|
|
Serial: certResp.SerialNumber,
|
|
NotBefore: notBefore,
|
|
NotAfter: notAfter,
|
|
OrderID: certResp.SerialNumber,
|
|
}, nil
|
|
}
|
|
|
|
// Pending — return serial number as OrderID for polling
|
|
c.logger.Info("GlobalSign certificate order pending",
|
|
"serial", certResp.SerialNumber,
|
|
"status", certResp.Status)
|
|
|
|
return &issuer.IssuanceResult{
|
|
OrderID: certResp.SerialNumber,
|
|
}, nil
|
|
}
|
|
|
|
// RenewCertificate renews a certificate by submitting a new order.
|
|
// GlobalSign uses serial number polling, so renewal is treated as a new issuance.
|
|
func (c *Connector) RenewCertificate(ctx context.Context, request issuer.RenewalRequest) (*issuer.IssuanceResult, error) {
|
|
c.logger.Info("processing GlobalSign renewal request",
|
|
"common_name", request.CommonName,
|
|
"san_count", len(request.SANs))
|
|
|
|
return c.IssueCertificate(ctx, issuer.IssuanceRequest{
|
|
CommonName: request.CommonName,
|
|
SANs: request.SANs,
|
|
CSRPEM: request.CSRPEM,
|
|
EKUs: request.EKUs,
|
|
})
|
|
}
|
|
|
|
// RevokeCertificate revokes a certificate at GlobalSign Atlas HVCA.
|
|
// GlobalSign revocation does not require a reason code.
|
|
func (c *Connector) RevokeCertificate(ctx context.Context, request issuer.RevocationRequest) error {
|
|
c.logger.Info("processing GlobalSign revocation request", "serial", request.Serial)
|
|
|
|
client, err := c.getHTTPClient(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// GlobalSign revocation endpoint: PUT /v2/certificates/{serial}/revoke
|
|
// No request body or reason code required.
|
|
revokeURL := strings.TrimSuffix(c.config.APIUrl, "/") + fmt.Sprintf("/v2/certificates/%s/revoke", request.Serial)
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPut, revokeURL, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create revoke request: %w", err)
|
|
}
|
|
|
|
req.Header.Set("ApiKey", c.config.APIKey)
|
|
req.Header.Set("ApiSecret", c.config.APISecret)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("GlobalSign revoke request failed: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// GlobalSign returns 200 OK on successful revocation
|
|
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
|
|
respBody, _ := io.ReadAll(resp.Body)
|
|
return fmt.Errorf("GlobalSign revoke returned status %d: %s", resp.StatusCode, string(respBody))
|
|
}
|
|
|
|
c.logger.Info("GlobalSign certificate revoked", "serial", request.Serial)
|
|
return nil
|
|
}
|
|
|
|
// GetOrderStatus checks the status of a GlobalSign certificate order by serial number.
|
|
// Polls the certificate endpoint; when status is "issued", downloads and returns the cert.
|
|
func (c *Connector) GetOrderStatus(ctx context.Context, orderID string) (*issuer.OrderStatus, error) {
|
|
c.logger.Debug("checking GlobalSign certificate status", "serial", orderID)
|
|
|
|
client, err := c.getHTTPClient(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// GlobalSign status endpoint: GET /v2/certificates/{serial}
|
|
statusURL := strings.TrimSuffix(c.config.APIUrl, "/") + fmt.Sprintf("/v2/certificates/%s", orderID)
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, statusURL, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create status request: %w", err)
|
|
}
|
|
|
|
req.Header.Set("ApiKey", c.config.APIKey)
|
|
req.Header.Set("ApiSecret", c.config.APISecret)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("GlobalSign status request failed: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
respBody, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read status response: %w", err)
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("GlobalSign certificate status returned %d: %s", resp.StatusCode, string(respBody))
|
|
}
|
|
|
|
var certResp certificateResponse
|
|
if err := json.Unmarshal(respBody, &certResp); err != nil {
|
|
return nil, fmt.Errorf("failed to parse status response: %w", err)
|
|
}
|
|
|
|
now := time.Now()
|
|
|
|
switch certResp.Status {
|
|
case "issued":
|
|
if certResp.Certificate == "" {
|
|
return nil, fmt.Errorf("certificate status is issued but certificate PEM is missing")
|
|
}
|
|
|
|
notBefore, notAfter, err := parseCertDates(certResp.Certificate)
|
|
if err != nil {
|
|
c.logger.Warn("failed to parse certificate dates", "error", err)
|
|
}
|
|
|
|
c.logger.Info("GlobalSign certificate ready",
|
|
"serial", orderID)
|
|
|
|
return &issuer.OrderStatus{
|
|
OrderID: orderID,
|
|
Status: "completed",
|
|
CertPEM: &certResp.Certificate,
|
|
ChainPEM: &certResp.Chain,
|
|
Serial: &certResp.SerialNumber,
|
|
NotBefore: ¬Before,
|
|
NotAfter: ¬After,
|
|
UpdatedAt: now,
|
|
}, nil
|
|
|
|
case "pending", "processing":
|
|
msg := fmt.Sprintf("certificate %s is %s", orderID, certResp.Status)
|
|
return &issuer.OrderStatus{
|
|
OrderID: orderID,
|
|
Status: "pending",
|
|
Message: &msg,
|
|
UpdatedAt: now,
|
|
}, nil
|
|
|
|
case "rejected", "denied", "failed":
|
|
msg := fmt.Sprintf("certificate %s was %s", orderID, certResp.Status)
|
|
return &issuer.OrderStatus{
|
|
OrderID: orderID,
|
|
Status: "failed",
|
|
Message: &msg,
|
|
UpdatedAt: now,
|
|
}, nil
|
|
|
|
default:
|
|
msg := fmt.Sprintf("unknown certificate status: %s", certResp.Status)
|
|
return &issuer.OrderStatus{
|
|
OrderID: orderID,
|
|
Status: "pending",
|
|
Message: &msg,
|
|
UpdatedAt: now,
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
// parseCertDates extracts NotBefore and NotAfter from a PEM-encoded certificate.
|
|
func parseCertDates(certPEM string) (time.Time, time.Time, error) {
|
|
block, _ := pem.Decode([]byte(certPEM))
|
|
if block == nil {
|
|
return time.Time{}, time.Time{}, fmt.Errorf("failed to decode certificate PEM")
|
|
}
|
|
|
|
cert, err := x509.ParseCertificate(block.Bytes)
|
|
if err != nil {
|
|
return time.Time{}, time.Time{}, fmt.Errorf("failed to parse certificate: %w", err)
|
|
}
|
|
|
|
return cert.NotBefore, cert.NotAfter, nil
|
|
}
|
|
|
|
// GenerateCRL is not supported because GlobalSign manages CRL distribution.
|
|
func (c *Connector) GenerateCRL(ctx context.Context, revokedCerts []issuer.RevokedCertEntry) ([]byte, error) {
|
|
return nil, fmt.Errorf("GlobalSign manages CRL distribution; use GlobalSign's CRL endpoints")
|
|
}
|
|
|
|
// SignOCSPResponse is not supported because GlobalSign manages OCSP.
|
|
func (c *Connector) SignOCSPResponse(ctx context.Context, req issuer.OCSPSignRequest) ([]byte, error) {
|
|
return nil, fmt.Errorf("GlobalSign manages OCSP; use GlobalSign's OCSP responder")
|
|
}
|
|
|
|
// GetCACertPEM is not directly supported. GlobalSign intermediate certificates
|
|
// come with each certificate issuance as part of the chain response.
|
|
func (c *Connector) GetCACertPEM(ctx context.Context) (string, error) {
|
|
return "", fmt.Errorf("GlobalSign intermediate certificates are included with each issued certificate")
|
|
}
|
|
|
|
// GetRenewalInfo returns nil, nil as GlobalSign does not support ACME Renewal Information (ARI).
|
|
func (c *Connector) GetRenewalInfo(ctx context.Context, certPEM string) (*issuer.RenewalInfoResult, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
// Ensure Connector implements the issuer.Connector interface.
|
|
var _ issuer.Connector = (*Connector)(nil)
|