{"record":{"id":"6d5da11e3b39571c","repo":"hashicorp/packer","slug":"verify-fulcio-certificate-sct-w","errorCode":null,"errorMessage":"verify Fulcio certificate SCT: %w","messagePattern":"verify Fulcio certificate SCT: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/attestation/sign_keyless.go","lineNumber":62,"sourceCode":"\ttrustedRootPath := strings.TrimSpace(cfg.TrustedRootPath)\n\tif trustedRootPath == \"\" {\n\t\treturn sigstoreroot.FetchTrustedRoot()\n\t}\n\n\treturn sigstoreroot.NewTrustedRootFromPath(trustedRootPath)\n}\n\nvar verifyKeylessCertificate = func(certificate *x509.Certificate, trustedMaterial sigstoreroot.TrustedMaterial, expectedIdentity, expectedOIDCIssuer, trustedRootPath string) error {\n\tchains, err := sigstoreverify.VerifyLeafCertificate(time.Now().UTC(), certificate, trustedMaterial)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"verify Fulcio certificate chain: %w\", err)\n\t}\n\n\t// When using the public Sigstore root (no custom trusted root configured),\n\t// require a valid SCT so certificates issued outside a public CT log are rejected.\n\tif strings.TrimSpace(trustedRootPath) == \"\" {\n\t\tif err := sigstoreverify.VerifySignedCertificateTimestamp(chains, 1, trustedMaterial); err != nil {\n\t\t\treturn fmt.Errorf(\"verify Fulcio certificate SCT: %w\", err)\n\t\t}\n\t}\n\n\tsummary, err := fulciocertificate.SummarizeCertificate(certificate)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"summarize Fulcio certificate: %w\", err)\n\t}\n\n\tidentity, err := sigstoreverify.NewShortCertificateIdentity(expectedOIDCIssuer, \"\", expectedIdentity, \"\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"build keyless identity policy: %w\", err)\n\t}\n\tif err := identity.Verify(summary); err != nil {\n\t\treturn fmt.Errorf(\"verify keyless certificate identity: %w\", err)\n\t}\n\n\treturn nil\n}","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/hashicorp/packer/blob/eb36e3c3e48a036f3e8cc94087636ee72e1303c9/internal/attestation/sign_keyless.go#L44-L80","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","If using the public Sigstore infrastructure, refresh the trusted root (clear trusted_root_path) so the CT log that issued the SCT is present.","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.","Inspect the wrapped inner error from VerifySignedCertificateTimestamp to see whether the SCT is absent or merely failed log verification.","Check clock skew — SCT verification is time-sensitive."],"exampleFix":"// before: private Fulcio cert verified against public root -> no matching SCT\n// (no trusted_root_path set)\n// after: supply the private deployment's trusted root\n// trusted_root_path = \"/etc/cosign/trusted_root_private.json\"  // contains private CT log + Fulcio CA","handlingStrategy":"validation","validationCode":"// confirm the verification context matches the certificate's origin before calling Verify\nfunc sctCheckExpected(trustedRootPath string, cert *x509.Certificate) error {\n\t// public root -> SCT check runs; cert must have been logged in a public CT log\n\tif strings.TrimSpace(trustedRootPath) == \"\" {\n\t\tif issuedPrivately(cert) { // e.g. issuer CN/URL not the public Fulcio\n\t\t\treturn fmt.Errorf(\"cert from private Fulcio but no trusted_root_path set; SCT check against public logs will fail\")\n\t\t}\n\t\treturn nil\n\t}\n\treturn nil\n}","typeGuard":"func isSCTError(err error) bool {\n\treturn err != nil && strings.Contains(err.Error(), \"verify Fulcio certificate SCT:\")\n}","tryCatchPattern":"err := keylessVerifier.Verify(ctx, payloadType, payload, sig)\nif err != nil {\n\tif isSCTError(err) {\n\t\treturn 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)\n\t}\n\treturn err\n}","preventionTips":["Only sign keyless attestations through a Fulcio/CT configuration your verifiers' trusted root actually covers.","If operating a private Sigstore instance, always distribute its trusted_root.json (CT logs + Fulcio CAs) to verifiers.","Refresh the public trusted root periodically so current CT log keys remain available for SCT verification.","Document that omitting trusted_root_path intentionally enforces the public-CT SCT requirement."],"tags":["sigstore","certificate-transparency","sct","keyless"],"backgroundTag":"missing-certificate-transparency-sct","analyzedSha":"eb36e3c3e48a036f3e8cc94087636ee72e1303c9","analyzedAt":"2026-09-05T13:20:43.127Z","contentChangedAt":"2026-09-05T13:20:43.127Z","schemaVersion":2},"datasetVersion":"2026-09-12T17:17:11.597Z"}