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.
562 lines
19 KiB
Go
562 lines
19 KiB
Go
// Package stepca implements the issuer.Connector interface for Smallstep step-ca
|
|
// private certificate authority.
|
|
//
|
|
// step-ca is a popular open-source private CA that provides both ACME and native
|
|
// provisioner-based certificate issuance. This connector uses the native /sign API
|
|
// with JWK provisioner authentication, which is simpler than ACME for internal PKI:
|
|
// no challenge solving, no domain validation — just CSR + auth token → signed cert.
|
|
//
|
|
// For teams already using step-ca, this connector integrates certctl's lifecycle
|
|
// management (renewal policies, deployment, audit) with step-ca's certificate signing.
|
|
//
|
|
// Authentication: JWK provisioner with a shared provisioner password.
|
|
// The connector generates a short-lived token for each signing request using the
|
|
// provisioner key (loaded from disk or provided inline).
|
|
//
|
|
// step-ca API used:
|
|
//
|
|
// POST /sign — submit CSR with provisioner token, receive signed certificate
|
|
// POST /revoke — revoke a certificate by serial
|
|
// GET /health — check CA availability
|
|
package stepca
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/shankar0123/certctl/internal/connector/issuer"
|
|
)
|
|
|
|
// Config represents the step-ca issuer connector configuration.
|
|
type Config struct {
|
|
// CAURL is the base URL of the step-ca server (e.g., "https://ca.internal:9000").
|
|
CAURL string `json:"ca_url"`
|
|
|
|
// RootCertPath is the path to the step-ca root certificate PEM (for TLS verification).
|
|
// If empty, the system trust store is used.
|
|
RootCertPath string `json:"root_cert_path,omitempty"`
|
|
|
|
// ProvisionerName is the name of the JWK provisioner to use for signing.
|
|
ProvisionerName string `json:"provisioner_name"`
|
|
|
|
// ProvisionerKeyPath is the path to the provisioner's encrypted private key (JWK JSON).
|
|
// This is the key file generated by `step ca provisioner add`.
|
|
ProvisionerKeyPath string `json:"provisioner_key_path,omitempty"`
|
|
|
|
// ProvisionerPassword is the password to decrypt the provisioner key.
|
|
// Can also be set via CERTCTL_STEPCA_PROVISIONER_PASSWORD env var.
|
|
ProvisionerPassword string `json:"provisioner_password,omitempty"`
|
|
|
|
// ValidityDays is the requested certificate validity (step-ca may enforce a maximum).
|
|
// Defaults to 90.
|
|
ValidityDays int `json:"validity_days,omitempty"`
|
|
}
|
|
|
|
// Connector implements the issuer.Connector interface for step-ca.
|
|
type Connector struct {
|
|
config *Config
|
|
logger *slog.Logger
|
|
httpClient *http.Client
|
|
}
|
|
|
|
// New creates a new step-ca connector with the given configuration and logger.
|
|
// If RootCertPath is set, the HTTP client will trust that CA certificate for TLS connections.
|
|
// Otherwise, the system trust store is used (which works if setup-trust.sh has run).
|
|
func New(config *Config, logger *slog.Logger) *Connector {
|
|
// Don't default ValidityDays — let step-ca use its own default duration.
|
|
// Operators can explicitly set ValidityDays if their step-ca is configured
|
|
// with longer max durations. A zero value means "omit from sign request."
|
|
|
|
httpClient := &http.Client{Timeout: 30 * time.Second}
|
|
|
|
// Load custom root CA cert if provided
|
|
if config != nil && config.RootCertPath != "" {
|
|
rootPEM, err := os.ReadFile(config.RootCertPath)
|
|
if err == nil {
|
|
pool := x509.NewCertPool()
|
|
if pool.AppendCertsFromPEM(rootPEM) {
|
|
httpClient.Transport = &http.Transport{
|
|
TLSClientConfig: &tls.Config{
|
|
RootCAs: pool,
|
|
},
|
|
}
|
|
logger.Info("step-ca custom root CA loaded", "path", config.RootCertPath)
|
|
}
|
|
} else {
|
|
logger.Warn("failed to read step-ca root cert, using system trust store", "path", config.RootCertPath, "error", err)
|
|
}
|
|
}
|
|
|
|
return &Connector{
|
|
config: config,
|
|
logger: logger,
|
|
httpClient: httpClient,
|
|
}
|
|
}
|
|
|
|
// ValidateConfig checks that the step-ca configuration is valid and the CA is reachable.
|
|
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 step-ca config: %w", err)
|
|
}
|
|
|
|
if cfg.CAURL == "" {
|
|
return fmt.Errorf("step-ca ca_url is required")
|
|
}
|
|
|
|
if cfg.ProvisionerName == "" {
|
|
return fmt.Errorf("step-ca provisioner_name is required")
|
|
}
|
|
|
|
// Don't default ValidityDays — 0 means "let step-ca use its own default duration"
|
|
|
|
// Check CA health
|
|
healthURL := cfg.CAURL + "/health"
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, healthURL, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create health check request: %w", err)
|
|
}
|
|
|
|
resp, err := c.httpClient.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("step-ca not reachable at %s: %w", cfg.CAURL, err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("step-ca health check returned status %d", resp.StatusCode)
|
|
}
|
|
|
|
// Validate provisioner key path exists if provided
|
|
if cfg.ProvisionerKeyPath != "" {
|
|
if _, err := os.Stat(cfg.ProvisionerKeyPath); err != nil {
|
|
return fmt.Errorf("provisioner key not accessible: %w", err)
|
|
}
|
|
}
|
|
|
|
c.config = &cfg
|
|
c.logger.Info("step-ca configuration validated",
|
|
"ca_url", cfg.CAURL,
|
|
"provisioner", cfg.ProvisionerName)
|
|
|
|
return nil
|
|
}
|
|
|
|
// signRequest is the JSON body for the step-ca /sign endpoint.
|
|
type signRequest struct {
|
|
CsrPEM string `json:"csr"`
|
|
OTT string `json:"ott"` // One-Time Token (provisioner JWT)
|
|
NotBefore time.Time `json:"notBefore,omitempty"`
|
|
NotAfter time.Time `json:"notAfter,omitempty"`
|
|
}
|
|
|
|
// signResponse is the JSON response from the step-ca /sign endpoint.
|
|
type signResponse struct {
|
|
ServerPEM certificateChain `json:"serverPEM,omitempty"`
|
|
CaPEM certificateChain `json:"caPEM,omitempty"`
|
|
CertChainPEM []certBlock `json:"certChainPEM,omitempty"`
|
|
}
|
|
|
|
type certificateChain struct {
|
|
Certificate string `json:"certificate"`
|
|
}
|
|
|
|
type certBlock struct {
|
|
Certificate string `json:"certificate"`
|
|
}
|
|
|
|
// IssueCertificate submits a CSR to step-ca for signing.
|
|
func (c *Connector) IssueCertificate(ctx context.Context, request issuer.IssuanceRequest) (*issuer.IssuanceResult, error) {
|
|
c.logger.Info("processing step-ca issuance request",
|
|
"common_name", request.CommonName,
|
|
"san_count", len(request.SANs))
|
|
|
|
// Generate a provisioner token (OTT) for this request
|
|
ott, err := c.generateProvisionerToken(request.CommonName, request.SANs)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to generate provisioner token: %w", err)
|
|
}
|
|
|
|
// Build the sign request.
|
|
// When ValidityDays is 0 (default), omit NotBefore/NotAfter so step-ca uses its
|
|
// own default duration (typically 24h). The signRequest struct has omitempty on
|
|
// both time fields, so zero-value time.Time{} gets stripped from the JSON.
|
|
signReq := signRequest{
|
|
CsrPEM: request.CSRPEM,
|
|
OTT: ott,
|
|
}
|
|
if c.config.ValidityDays > 0 || request.MaxTTLSeconds > 0 {
|
|
now := time.Now()
|
|
signReq.NotBefore = now
|
|
if c.config.ValidityDays > 0 {
|
|
signReq.NotAfter = now.AddDate(0, 0, c.config.ValidityDays)
|
|
}
|
|
// Cap validity to MaxTTLSeconds if profile specifies a maximum
|
|
if request.MaxTTLSeconds > 0 {
|
|
maxNotAfter := now.Add(time.Duration(request.MaxTTLSeconds) * time.Second)
|
|
if signReq.NotAfter.IsZero() || maxNotAfter.Before(signReq.NotAfter) {
|
|
signReq.NotAfter = maxNotAfter
|
|
}
|
|
}
|
|
}
|
|
|
|
body, err := json.Marshal(signReq)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal sign request: %w", err)
|
|
}
|
|
|
|
// POST /sign
|
|
signURL := c.config.CAURL + "/sign"
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, signURL, bytes.NewReader(body))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create sign request: %w", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := c.httpClient.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("step-ca sign request failed: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
respBody, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read sign response: %w", err)
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("step-ca sign returned status %d: %s", resp.StatusCode, string(respBody))
|
|
}
|
|
|
|
// Parse response — step-ca returns the cert chain
|
|
certPEM, chainPEM, serial, certNotBefore, certNotAfter, err := parseSignResponse(respBody)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse sign response: %w", err)
|
|
}
|
|
|
|
orderID := fmt.Sprintf("stepca-%s", serial)
|
|
|
|
c.logger.Info("step-ca certificate issued",
|
|
"common_name", request.CommonName,
|
|
"serial", serial,
|
|
"not_after", certNotAfter)
|
|
|
|
return &issuer.IssuanceResult{
|
|
CertPEM: certPEM,
|
|
ChainPEM: chainPEM,
|
|
Serial: serial,
|
|
NotBefore: certNotBefore,
|
|
NotAfter: certNotAfter,
|
|
OrderID: orderID,
|
|
}, nil
|
|
}
|
|
|
|
// RenewCertificate renews a certificate by creating a new signing request.
|
|
// For step-ca, renewal is functionally identical to issuance.
|
|
func (c *Connector) RenewCertificate(ctx context.Context, request issuer.RenewalRequest) (*issuer.IssuanceResult, error) {
|
|
c.logger.Info("processing step-ca 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,
|
|
MaxTTLSeconds: request.MaxTTLSeconds,
|
|
})
|
|
}
|
|
|
|
// revokeRequest is the JSON body for the step-ca /revoke endpoint.
|
|
type revokeRequest struct {
|
|
Serial string `json:"serial"`
|
|
ReasonCode int `json:"reasonCode,omitempty"`
|
|
Reason string `json:"reason,omitempty"`
|
|
OTT string `json:"ott"`
|
|
Passive bool `json:"passive"` // true = don't propagate to OCSP (just mark revoked)
|
|
}
|
|
|
|
// RevokeCertificate revokes a certificate at step-ca.
|
|
func (c *Connector) RevokeCertificate(ctx context.Context, request issuer.RevocationRequest) error {
|
|
c.logger.Info("processing step-ca revocation request", "serial", request.Serial)
|
|
|
|
ott, err := c.generateProvisionerToken(request.Serial, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to generate revocation token: %w", err)
|
|
}
|
|
|
|
reason := "unspecified"
|
|
if request.Reason != nil {
|
|
reason = *request.Reason
|
|
}
|
|
|
|
revokeReq := revokeRequest{
|
|
Serial: request.Serial,
|
|
Reason: reason,
|
|
OTT: ott,
|
|
Passive: true,
|
|
}
|
|
|
|
body, err := json.Marshal(revokeReq)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal revoke request: %w", err)
|
|
}
|
|
|
|
revokeURL := c.config.CAURL + "/revoke"
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, revokeURL, bytes.NewReader(body))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create revoke request: %w", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := c.httpClient.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("step-ca revoke request failed: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
respBody, _ := io.ReadAll(resp.Body)
|
|
return fmt.Errorf("step-ca revoke returned status %d: %s", resp.StatusCode, string(respBody))
|
|
}
|
|
|
|
c.logger.Info("step-ca certificate revoked", "serial", request.Serial, "reason", reason)
|
|
return nil
|
|
}
|
|
|
|
// GetOrderStatus returns the status of a step-ca order.
|
|
// step-ca signs synchronously, so orders are always "completed" immediately.
|
|
func (c *Connector) GetOrderStatus(ctx context.Context, orderID string) (*issuer.OrderStatus, error) {
|
|
return &issuer.OrderStatus{
|
|
OrderID: orderID,
|
|
Status: "completed",
|
|
UpdatedAt: time.Now(),
|
|
}, nil
|
|
}
|
|
|
|
// generateProvisionerToken creates a short-lived JWT (One-Time Token) for step-ca API calls.
|
|
// The JWT is signed with the provisioner's private key (loaded from the encrypted JWE file
|
|
// at ProvisionerKeyPath and decrypted with ProvisionerPassword).
|
|
func (c *Connector) generateProvisionerToken(subject string, sans []string) (string, error) {
|
|
var key *ecdsa.PrivateKey
|
|
var kid string
|
|
|
|
if c.config.ProvisionerKeyPath != "" {
|
|
// Production: load and decrypt the real provisioner key from disk
|
|
var err error
|
|
key, kid, err = c.loadProvisionerKey()
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to load provisioner key: %w", err)
|
|
}
|
|
} else {
|
|
// Fallback: generate an ephemeral key (for testing or when key path not configured).
|
|
// This won't authenticate with a real step-ca server, but allows the connector
|
|
// to function against mock servers in tests.
|
|
c.logger.Warn("no provisioner key path configured, using ephemeral key (will not work with real step-ca)")
|
|
var err error
|
|
key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to generate ephemeral key: %w", err)
|
|
}
|
|
kid = "ephemeral"
|
|
}
|
|
|
|
now := time.Now()
|
|
|
|
// step-ca expects: aud = <ca-url>/1.0/sign (the sign endpoint audience)
|
|
claims := map[string]interface{}{
|
|
"sub": subject,
|
|
"iss": c.config.ProvisionerName,
|
|
"aud": c.config.CAURL + "/1.0/sign",
|
|
"nbf": now.Unix(),
|
|
"iat": now.Unix(),
|
|
"exp": now.Add(5 * time.Minute).Unix(),
|
|
"jti": generateJTI(),
|
|
"sha": kid, // step-ca uses this to look up the provisioner by key fingerprint
|
|
}
|
|
|
|
if len(sans) > 0 {
|
|
claims["sans"] = sans
|
|
}
|
|
|
|
return signJWTWithKID(claims, key, kid)
|
|
}
|
|
|
|
// loadProvisionerKey loads and decrypts the step-ca provisioner key from disk.
|
|
// Returns the ECDSA private key and the key ID (JWK thumbprint).
|
|
func (c *Connector) loadProvisionerKey() (*ecdsa.PrivateKey, string, error) {
|
|
if c.config.ProvisionerKeyPath == "" {
|
|
return nil, "", fmt.Errorf("provisioner_key_path is required for step-ca JWK authentication")
|
|
}
|
|
|
|
jweData, err := os.ReadFile(c.config.ProvisionerKeyPath)
|
|
if err != nil {
|
|
return nil, "", fmt.Errorf("failed to read provisioner key file %s: %w", c.config.ProvisionerKeyPath, err)
|
|
}
|
|
|
|
password := c.config.ProvisionerPassword
|
|
if password == "" {
|
|
return nil, "", fmt.Errorf("provisioner_password is required to decrypt the provisioner key")
|
|
}
|
|
|
|
key, kid, err := decryptProvisionerKey(jweData, password)
|
|
if err != nil {
|
|
return nil, "", fmt.Errorf("failed to decrypt provisioner key: %w", err)
|
|
}
|
|
|
|
c.logger.Info("provisioner key loaded and decrypted",
|
|
"key_path", c.config.ProvisionerKeyPath,
|
|
"kid", kid)
|
|
|
|
return key, kid, nil
|
|
}
|
|
|
|
// generateJTI creates a unique JWT ID.
|
|
func generateJTI() string {
|
|
b := make([]byte, 16)
|
|
_, _ = rand.Read(b)
|
|
return base64.RawURLEncoding.EncodeToString(b)
|
|
}
|
|
|
|
// signJWTWithKID creates an ES256 JWT with a key ID in the header.
|
|
func signJWTWithKID(claims map[string]interface{}, key *ecdsa.PrivateKey, kid string) (string, error) {
|
|
// Header with kid so step-ca can look up the provisioner
|
|
header := map[string]string{
|
|
"alg": "ES256",
|
|
"typ": "JWT",
|
|
"kid": kid,
|
|
}
|
|
|
|
return signJWTRaw(claims, key, header)
|
|
}
|
|
|
|
// signJWTRaw creates an ES256 JWT from the given claims and header.
|
|
func signJWTRaw(claims map[string]interface{}, key *ecdsa.PrivateKey, header map[string]string) (string, error) {
|
|
|
|
headerJSON, err := json.Marshal(header)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
claimsJSON, err := json.Marshal(claims)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
headerB64 := base64.RawURLEncoding.EncodeToString(headerJSON)
|
|
claimsB64 := base64.RawURLEncoding.EncodeToString(claimsJSON)
|
|
signingInput := headerB64 + "." + claimsB64
|
|
|
|
// Sign with ES256
|
|
hash := sha256.Sum256([]byte(signingInput))
|
|
r, s, err := ecdsa.Sign(rand.Reader, key, hash[:])
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to sign JWT: %w", err)
|
|
}
|
|
|
|
// Encode signature as fixed-size concatenation (r || s, 32 bytes each for P-256)
|
|
sig := make([]byte, 64)
|
|
rBytes := r.Bytes()
|
|
sBytes := s.Bytes()
|
|
copy(sig[32-len(rBytes):32], rBytes)
|
|
copy(sig[64-len(sBytes):64], sBytes)
|
|
|
|
sigB64 := base64.RawURLEncoding.EncodeToString(sig)
|
|
return signingInput + "." + sigB64, nil
|
|
}
|
|
|
|
// parseSignResponse extracts the certificate and chain from step-ca's /sign response.
|
|
func parseSignResponse(respBody []byte) (certPEM string, chainPEM string, serial string, notBefore time.Time, notAfter time.Time, err error) {
|
|
// step-ca /sign response format:
|
|
// { "crt": "-----BEGIN CERTIFICATE-----\n...", "ca": "-----BEGIN CERTIFICATE-----\n..." }
|
|
// or
|
|
// { "serverPEM": { "certificate": "..." }, "caPEM": { "certificate": "..." } }
|
|
// or
|
|
// { "certChainPEM": [ { "certificate": "..." }, ... ] }
|
|
|
|
// Try the simple format first (crt/ca)
|
|
var simpleResp struct {
|
|
Crt string `json:"crt"`
|
|
Ca string `json:"ca"`
|
|
}
|
|
if err = json.Unmarshal(respBody, &simpleResp); err == nil && simpleResp.Crt != "" {
|
|
certPEM = simpleResp.Crt
|
|
chainPEM = simpleResp.Ca
|
|
} else {
|
|
// Try the structured format
|
|
var structResp signResponse
|
|
if err = json.Unmarshal(respBody, &structResp); err != nil {
|
|
return "", "", "", time.Time{}, time.Time{}, fmt.Errorf("failed to parse sign response: %w", err)
|
|
}
|
|
|
|
if structResp.ServerPEM.Certificate != "" {
|
|
certPEM = structResp.ServerPEM.Certificate
|
|
chainPEM = structResp.CaPEM.Certificate
|
|
} else if len(structResp.CertChainPEM) > 0 {
|
|
certPEM = structResp.CertChainPEM[0].Certificate
|
|
for i := 1; i < len(structResp.CertChainPEM); i++ {
|
|
chainPEM += structResp.CertChainPEM[i].Certificate
|
|
}
|
|
}
|
|
}
|
|
|
|
if certPEM == "" {
|
|
return "", "", "", time.Time{}, time.Time{}, fmt.Errorf("no certificate in sign response")
|
|
}
|
|
|
|
// Parse the leaf cert to extract metadata
|
|
block, _ := pem.Decode([]byte(certPEM))
|
|
if block == nil {
|
|
return "", "", "", time.Time{}, time.Time{}, fmt.Errorf("failed to decode certificate PEM")
|
|
}
|
|
|
|
cert, parseErr := x509.ParseCertificate(block.Bytes)
|
|
if parseErr != nil {
|
|
return "", "", "", time.Time{}, time.Time{}, fmt.Errorf("failed to parse certificate: %w", parseErr)
|
|
}
|
|
|
|
serial = cert.SerialNumber.String()
|
|
notBefore = cert.NotBefore
|
|
notAfter = cert.NotAfter
|
|
|
|
return certPEM, chainPEM, serial, notBefore, notAfter, nil
|
|
}
|
|
|
|
// GenerateCRL is not supported by step-ca as step-ca provides its own CRL endpoint.
|
|
func (c *Connector) GenerateCRL(ctx context.Context, revokedCerts []issuer.RevokedCertEntry) ([]byte, error) {
|
|
return nil, fmt.Errorf("step-ca provides its own CRL endpoint; use step-ca's /crl directly")
|
|
}
|
|
|
|
// SignOCSPResponse is not supported by step-ca as step-ca provides its own OCSP responder.
|
|
func (c *Connector) SignOCSPResponse(ctx context.Context, req issuer.OCSPSignRequest) ([]byte, error) {
|
|
return nil, fmt.Errorf("step-ca provides its own OCSP responder; use step-ca's /ocsp directly")
|
|
}
|
|
|
|
// GetCACertPEM is not directly supported; step-ca serves its own /root endpoint.
|
|
func (c *Connector) GetCACertPEM(ctx context.Context) (string, error) {
|
|
return "", fmt.Errorf("step-ca serves its own CA certificate at /root; use step-ca's endpoint directly")
|
|
}
|
|
|
|
// GetRenewalInfo returns nil, nil as step-ca 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)
|