Surfaced by STPA-Sec analysis recorded in `docs/security/stpa-keyless-2026-05-25.md`, section 4 — UCA-3. PR #136 (v0.9.1) closed UCA-2 from that report; this is a remaining adjacent gap.
What's wrong
`KeylessSignature::verify_cert_chain` (`format.rs:396–415`) parses `integrated_time` from `self.rekor_entry.integrated_time` and uses it as the `verification_time` passed to WebPKI's `verify_for_usage` via `CertificatePool::verify_pem_cert` (`cert_verifier.rs:194–238`).
After PR #136 closed UCA-2, the Rekor entry's body must match the bundle, so the attacker can no longer borrow an arbitrary Rekor entry. But `integrated_time` still flows from the entry that the attacker constructed, and the cert-validity window is checked at that moment in time — not at the moment the artifact was signed.
Concrete attack (post-PR-#136 world)
- Attacker's Fulcio cert was valid for 10 minutes on 2025-03-01 (Fulcio short-lived certs).
- Cert is now expired in wall-clock terms.
- Attacker generates a new malicious module M' and signs it with the expired cert's still-held private key.
- Attacker uploads M' to Rekor today (Rekor will accept and log it; Rekor does not validate cert expiry, only that the signature verifies under the public key). The newly-logged entry has a current `integrated_time`.
- `wsc verify --keyless` checks cert validity against today's `integrated_time` → fails (cert expired).
So this scenario fails closed today if the attacker uses a current Rekor entry. The actual exploit needs the attacker to splice in an old Rekor entry whose `integrated_time` falls within the cert's old validity window — but UCA-2 now requires that old entry's body to match this bundle, which it won't.
Why it still matters
This UCA was rated High-conditional on UCA-2 in the STPA report. With UCA-2 closed, the severity drops substantially. The residual risk is:
- The verifier reads time from a field that is structurally controlled by the signer rather than from an artifact-bound source (e.g., the signing time recorded in the cert's notBefore, the time at which the Rekor body was actually signed, or a verifier-side wall clock with a tight bound).
- This affects forward-security reasoning: a compromised long-ago private key whose cert is now expired remains a valid signer if the attacker can find a Rekor entry from the cert's validity window whose body happens to match the bundle they want to sign. This is hard to engineer in practice but not provably impossible.
Suggested fix
Either:
- Use a verifier-side wall clock plus a configurable allowable skew, and require `integrated_time` to fall within `now ± skew`; or
- Verify the cert was valid at the body's actual signing time (which is the same as `integrated_time` in current Rekor unless Rekor lags); or
- Cross-check `integrated_time` against the cert's notBefore/notAfter and refuse if it's not within the cert's validity window and the validity window has ended in wall-clock time.
Acceptance
- Decide whether to address with verifier-side clock or with stricter source-binding.
- Test: a bundle whose `integrated_time` is outside both the cert's validity and the verifier's clock window must reject.
- Test: a bundle whose `integrated_time` is well within the cert's validity but far from wall-clock present must reject with the new policy.
Cross-ref
Adjacent to the prior audit's H-3 ("Time-of-check Fulcio cert validity, no CRL/OCSP") but a different shape: H-3 is about clock manipulation, this is about which clock the verifier reads.
Surfaced by STPA-Sec analysis recorded in `docs/security/stpa-keyless-2026-05-25.md`, section 4 — UCA-3. PR #136 (v0.9.1) closed UCA-2 from that report; this is a remaining adjacent gap.
What's wrong
`KeylessSignature::verify_cert_chain` (`format.rs:396–415`) parses `integrated_time` from `self.rekor_entry.integrated_time` and uses it as the `verification_time` passed to WebPKI's `verify_for_usage` via `CertificatePool::verify_pem_cert` (`cert_verifier.rs:194–238`).
After PR #136 closed UCA-2, the Rekor entry's body must match the bundle, so the attacker can no longer borrow an arbitrary Rekor entry. But `integrated_time` still flows from the entry that the attacker constructed, and the cert-validity window is checked at that moment in time — not at the moment the artifact was signed.
Concrete attack (post-PR-#136 world)
So this scenario fails closed today if the attacker uses a current Rekor entry. The actual exploit needs the attacker to splice in an old Rekor entry whose `integrated_time` falls within the cert's old validity window — but UCA-2 now requires that old entry's body to match this bundle, which it won't.
Why it still matters
This UCA was rated High-conditional on UCA-2 in the STPA report. With UCA-2 closed, the severity drops substantially. The residual risk is:
Suggested fix
Either:
Acceptance
Cross-ref
Adjacent to the prior audit's H-3 ("Time-of-check Fulcio cert validity, no CRL/OCSP") but a different shape: H-3 is about clock manipulation, this is about which clock the verifier reads.