hashicorp/packer · error
verify Fulcio certificate chain: %w
Error message
verify Fulcio certificate chain: %w
What it means
verifyKeylessCertificate wraps sigstore-go's VerifyLeafCertificate, which validates that the Fulcio-issued short-lived certificate in a keyless attestation chains to a trusted Fulcio root within the configured TrustedMaterial. Failure means the leaf certificate's chain, validity window, or issuing root does not match the trusted root (the embedded public Sigstore root by default, or a custom trusted_root.json).
Source
Thrown at internal/attestation/sign_keyless.go:55
var newKeylessBundle = sigstoregosign.Bundle
var newKeylessRekor = func(baseURL string) sigstoregosign.Transparency {
return sigstoregosign.NewRekor(&sigstoregosign.RekorOptions{BaseURL: baseURL})
}
var loadKeylessTrustedMaterial = func(cfg BackendConfig) (sigstoreroot.TrustedMaterial, error) {
trustedRootPath := strings.TrimSpace(cfg.TrustedRootPath)
if trustedRootPath == "" {
return sigstoreroot.FetchTrustedRoot()
}
return sigstoreroot.NewTrustedRootFromPath(trustedRootPath)
}
var verifyKeylessCertificate = func(certificate *x509.Certificate, trustedMaterial sigstoreroot.TrustedMaterial, expectedIdentity, expectedOIDCIssuer, trustedRootPath string) error {
chains, err := sigstoreverify.VerifyLeafCertificate(time.Now().UTC(), certificate, trustedMaterial)
if err != nil {
return fmt.Errorf("verify Fulcio certificate chain: %w", err)
}
// When using the public Sigstore root (no custom trusted root configured),
// require a valid SCT so certificates issued outside a public CT log are rejected.
if strings.TrimSpace(trustedRootPath) == "" {
if err := sigstoreverify.VerifySignedCertificateTimestamp(chains, 1, trustedMaterial); err != nil {
return fmt.Errorf("verify Fulcio certificate SCT: %w", err)
}
}
summary, err := fulciocertificate.SummarizeCertificate(certificate)
if err != nil {
return fmt.Errorf("summarize Fulcio certificate: %w", err)
}
identity, err := sigstoreverify.NewShortCertificateIdentity(expectedOIDCIssuer, "", expectedIdentity, "")
if err != nil {
return fmt.Errorf("build keyless identity policy: %w", err)View on GitHub (pinned to eb36e3c3e4)
Solutions
- Update to a current trusted root: clear trusted_root_path so FetchTrustedRoot() pulls the latest public Sigstore root, or re-download trusted_root.json ('sigstore fetch-trusted-root' / TUF client).
- If using a custom trusted_root_path, ensure it contains the Fulcio CA (and intermediates) that issued the signer's certificate.
- Check clock skew on the verifying machine (NTP); certificates are short-lived and 'now' must fall inside the validity window or the timestamp/chain checks fail.
- Confirm the attestation bundle is intact and was produced against the same Sigstore ecosystem (public Fulcio vs. a private/enterprise instance — roots must match).
- Read the wrapped inner error from sigstore-go; it distinguishes expired cert, unknown issuer, and chain-building failures, which point to different fixes.
Example fix
// before: stale custom trusted root missing current Fulcio CA // trusted_root_path = "old_trusted_root.json" // after: refresh or omit to use the fetched public root // trusted_root_path = "" // fetches current Sigstore trusted root via TUF
Defensive patterns
Strategy: try-catch
Validate before calling
// pre-check: refresh trusted root and confirm clock sanity before verifying
func precheckKeylessEnv(trustedRootPath string) error {
if strings.TrimSpace(trustedRootPath) == "" {
if _, err := sigstoreroot.FetchTrustedRoot(); err != nil {
return fmt.Errorf("cannot fetch current Sigstore trusted root: %w", err)
}
} else if _, err := sigstoreroot.NewTrustedRootFromPath(trustedRootPath); err != nil {
return fmt.Errorf("cannot load trusted root %s: %w", trustedRootPath, err)
}
if sk := time.Until(time.Now().UTC().Truncate(time.Hour)); sk > time.Hour || sk < -time.Hour {
return fmt.Errorf("system clock appears skewed; sync NTP before verifying")
}
return nil
} Type guard
func isChainError(err error) bool {
return err != nil && strings.Contains(err.Error(), "verify Fulcio certificate chain:")
} Try / catch
err := keylessVerifier.Verify(ctx, payloadType, payload, sig)
if err != nil {
if strings.Contains(err.Error(), "verify Fulcio certificate chain") {
// refresh trusted root once, then surface actionable guidance
return fmt.Errorf("keyless verification failed: Fulcio cert does not chain to configured trusted root; refresh trusted_root or re-sign the artifact: %w", err)
}
return err
} Prevention
- Do not pin a stale trusted_root.json; refresh it regularly (TUF) or omit the path to auto-fetch the public root.
- Keep verifying hosts NTP-synced — Fulcio certs live only minutes and chain checks are time-sensitive.
- Match ecosystems: verify private-Fulcio attestations with that deployment's trusted root, public ones with the public root.
- Log the wrapped inner error from sigstore-go; it names the exact chain failure (expired, unknown issuer, no chain).
When it happens
Trigger: keylessVerifier.Verify is invoked (via newKeylessVerifier/newKeylessVerifierForEnvelope) and sigstoreverify.VerifyLeafCertificate(time.Now(), cert, trustedMaterial) rejects the certificate: untrusted root, expired/not-yet-valid cert, wrong chain, or the trusted root omits the Fulcio CA that issued the cert.
Common situations: Verifying an old attestation whose Fulcio cert has expired (typically valid ~10 minutes) — chain trust relies on the recorded Rekor entry/trusted root; using a custom trusted_root.json that lacks the CA which issued the certificate; a stale embedded trusted root that predates a Fulcio CA rotation; system clock skew making the cert appear not-yet-valid or expired.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- summarize Fulcio certificate: %w
- verify Fulcio certificate SCT: %w
- verifier overrides are not supported for keyless attestation
- bundle-based Rekor or timestamp verification currently requi
- bundle-based Rekor or timestamp verification requires keyles
AI-assisted analysis of hashicorp/packer@eb36e3c3e4 (2026-09-05).
Data as JSON: /api/errors/15f5489f262626bb.
Report an issue: GitHub.