hashicorp/packer · error

verify Fulcio certificate SCT: %w

Error message

verify Fulcio certificate SCT: %w

What it means

When no custom trusted root is configured, verifyKeylessCertificate additionally requires the Fulcio certificate to carry a valid Signed Certificate Timestamp (SCT) verifiable against at least 1 CT log in the public trusted material, via sigstore-go's VerifySignedCertificateTimestamp. This rejects certificates issued outside the public CT logs (e.g. by a private Fulcio or a rogue CA). The error means the SCT was missing, malformed, or failed log-signature verification.

Source

Thrown at internal/attestation/sign_keyless.go:62

	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)
	}
	if err := identity.Verify(summary); err != nil {
		return fmt.Errorf("verify keyless certificate identity: %w", err)
	}

	return nil
}

View on GitHub (pinned to eb36e3c3e4)

Solutions

  1. If the certificates genuinely come from a private Fulcio/CT setup, provide a custom trusted_root_path containing that deployment's CT log keys — the SCT check is then skipped by design.
  2. If using the public Sigstore infrastructure, refresh the trusted root (clear trusted_root_path) so the CT log that issued the SCT is present.
  3. Ensure attestations were produced via the public Fulcio (https://fulcio.sigstore.dev) which embeds/records SCTs; re-run signing so a new certificate with a valid SCT is issued.
  4. Inspect the wrapped inner error from VerifySignedCertificateTimestamp to see whether the SCT is absent or merely failed log verification.
  5. Check clock skew — SCT verification is time-sensitive.

Example fix

// before: private Fulcio cert verified against public root -> no matching SCT
// (no trusted_root_path set)
// after: supply the private deployment's trusted root
// trusted_root_path = "/etc/cosign/trusted_root_private.json"  // contains private CT log + Fulcio CA
Defensive patterns

Strategy: validation

Validate before calling

// confirm the verification context matches the certificate's origin before calling Verify
func sctCheckExpected(trustedRootPath string, cert *x509.Certificate) error {
	// public root -> SCT check runs; cert must have been logged in a public CT log
	if strings.TrimSpace(trustedRootPath) == "" {
		if issuedPrivately(cert) { // e.g. issuer CN/URL not the public Fulcio
			return fmt.Errorf("cert from private Fulcio but no trusted_root_path set; SCT check against public logs will fail")
		}
		return nil
	}
	return nil
}

Type guard

func isSCTError(err error) bool {
	return err != nil && strings.Contains(err.Error(), "verify Fulcio certificate SCT:")
}

Try / catch

err := keylessVerifier.Verify(ctx, payloadType, payload, sig)
if err != nil {
	if isSCTError(err) {
		return fmt.Errorf("no valid CT timestamp for certificate: either re-sign via public Fulcio, or set trusted_root_path to a root containing your private CT logs: %w", err)
	}
	return err
}

Prevention

When it happens

Trigger: keylessVerifier.Verify with empty trusted_root_path calls VerifySignedCertificateTimestamp(chains, 1, trustedMaterial) and it fails: the leaf certificate has no SCT extension/embedded proof, the SCT's log is absent from the trusted material, or the SCT signature does not verify.

Common situations: Verifying a certificate issued by a private/enterprise Fulcio deployment (no public CT log involvement) while still using the public trusted root; a stale trusted root that no longer contains the CT log that countersigned the cert; certificates issued by misconfigured Fulcio instances that skip CT inclusion.

Understand the failure class

Related errors


AI-assisted analysis of hashicorp/packer@eb36e3c3e4 (2026-09-05). Data as JSON: /api/errors/6d5da11e3b39571c. Report an issue: GitHub.