sigstore-verify 0.13.0

Sigstore signature verification
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
//! Helper functions for verification
//!
//! This module contains extracted helper functions to break down the
//! large verification logic into manageable pieces.

use crate::error::{Error, Result};
use const_oid::db::rfc5912::ID_KP_CODE_SIGNING;
use rustls_pki_types::{CertificateDer, UnixTime};
use sigstore_crypto::CertificateInfo;
use sigstore_trust_root::{TrustedRoot, TsaAuthority};
use sigstore_types::bundle::VerificationMaterialContent;
use sigstore_types::{Bundle, DerPublicKey, KindVersion, SignatureBytes, SignatureContent};
use webpki::{EndEntityCert, KeyUsage, ALL_VERIFICATION_ALGS};

pub(crate) type FulcioAnchor = (
    rustls_pki_types::TrustAnchor<'static>,
    Option<sigstore_types::TimeRange>,
);

/// Extract signature from bundle content (needed for TSA verification).
///
/// `DsseEnvelope` holds exactly one signature by construction, so timestamp
/// verification necessarily authenticates the signature used by the bundle.
pub fn extract_signature(content: &SignatureContent) -> SignatureBytes {
    match content {
        SignatureContent::MessageSignature(msg_sig) => msg_sig.signature.clone(),
        SignatureContent::DsseEnvelope(envelope) => envelope.signature.sig.clone(),
    }
}

/// Extract and verify TSA RFC 3161 timestamps
///
/// Every timestamp present in the bundle must be cryptographically
/// authenticated by one of the configured timestamp authorities, and its
/// signed time must fall within *that* authority's `valid_for` window (see
/// [`verify_timestamp_against_authorities`]). Returns every verified
/// timestamp; any timestamp that fails verification is an error.
pub fn extract_tsa_timestamps(
    bundle: &Bundle,
    signature_bytes: &[u8],
    trusted_root: &TrustedRoot,
) -> Result<Vec<jiff::Timestamp>> {
    let rfc3161_timestamps = &bundle
        .verification_material
        .timestamp_verification_data
        .rfc3161_timestamps;

    if rfc3161_timestamps.is_empty() {
        return Ok(Vec::new());
    }

    let authorities = trusted_root.tsa_authorities();

    let mut timestamps = Vec::new();

    for ts in rfc3161_timestamps {
        timestamps.push(verify_timestamp_against_authorities(
            ts.signed_timestamp.as_bytes(),
            signature_bytes,
            &authorities,
        )?);
    }

    Ok(timestamps)
}

/// Verify a single RFC 3161 timestamp token against the configured timestamp
/// authorities.
///
/// Each authority is tried in isolation via
/// [`sigstore_tsa::verify_timestamp_for_authority`], whose success already
/// implies both cryptographic authentication by that authority AND temporal
/// authorization by that same authority's window (TOB-SIGSTORE-11), so the
/// first success wins. A token whose signing authority's window excludes the
/// signed time is NOT rescued by another authority's window.
fn verify_timestamp_against_authorities(
    ts_bytes: &[u8],
    signature_bytes: &[u8],
    authorities: &[TsaAuthority],
) -> Result<jiff::Timestamp> {
    // Signed time of a token that some authority authenticated but whose
    // window excludes it, kept for error reporting.
    let mut rejected_time = None;
    let mut last_error: Option<String> = None;

    for authority in authorities {
        match sigstore_tsa::verify_timestamp_for_authority(ts_bytes, signature_bytes, authority) {
            Ok(time) => return Ok(time),
            Err(sigstore_tsa::Error::TimestampOutsideValidity { time }) => {
                rejected_time = Some(time);
            }
            Err(e) => last_error = Some(e.to_string()),
        }
    }

    if let Some(time) = rejected_time {
        return Err(Error::Verification(format!(
            "TSA timestamp {} is outside the validity period of the timestamp authority that signed it",
            time
        )));
    }

    Err(Error::Verification(format!(
        "TSA timestamp verification failed: {}",
        last_error
            .unwrap_or_else(|| "no timestamp authorities configured in trusted root".to_string())
    )))
}

/// Check whether the bundle contains a Rekor v2 entry.
///
/// Rekor v2 currently defines only `hashedrekord/0.0.2`. A `0.0.2` version on
/// another entry kind does not imply Rekor v2 semantics.
pub fn has_v2_tlog_entries(bundle: &Bundle) -> bool {
    bundle
        .verification_material
        .tlog_entries
        .iter()
        .any(|entry| entry.kind_version == KindVersion::HashedRekordV002)
}

/// Extract integrated time from V1 tlog entries that have inclusion promises.
///
/// Per sigstore-python, integrated_time is only valid as a timestamp source when:
/// 1. The entry has an inclusion_promise (SET) that cryptographically binds it
/// 2. The entry is a V1 type (hashedrekord/dsse v0.0.1)
/// 3. The entry carries an integrated time
///
/// The SET is verified here, before the integrated time is trusted: the SET
/// signature covers `integratedTime`, so this is what authenticates the
/// timestamp. Without it a tampered (e.g. backdated) `integratedTime` would
/// become the validation time whenever transparency log verification is
/// disabled or reordered. An entry that qualifies as a timestamp source but
/// whose SET does not verify is a hard error, not a skipped candidate.
///
/// Returns every authenticated integrated time.
fn extract_v1_integrated_times_with_promise(
    bundle: &Bundle,
    rekor_keys: &sigstore_crypto::Keyring,
) -> Result<Vec<jiff::Timestamp>> {
    let mut times = Vec::new();

    for entry in &bundle.verification_material.tlog_entries {
        // Rekor v1 uses 0.0.1 for hashedrekord/dsse and 0.0.2 for intoto.
        let is_v1 = matches!(
            entry.kind_version,
            KindVersion::HashedRekordV001 | KindVersion::DsseV001 | KindVersion::IntotoV002
        );

        if !is_v1 || entry.inclusion_promise.is_none() {
            continue;
        }

        if let Some(time) = entry.integrated_time {
            crate::verify_impl::tlog::verify_set(entry, rekor_keys)?;
            times.push(time);
        }
    }

    Ok(times)
}

/// Collect every verified timestamp for the signature.
///
/// At least one verified timestamp source is REQUIRED. This matches sigstore-python's
/// behavior which enforces `VERIFIED_TIME_THRESHOLD = 1`.
///
/// Timestamp sources:
/// - TSA timestamps (RFC 3161), verified against the trusted root's TSA certificates
/// - Integrated times from V1 tlog entries with inclusion promises, authenticated
///   via their SET
///
/// All verified timestamps are returned (never an empty vector), and the caller
/// must validate the signing certificate against **each** of them: checking only
/// one (e.g. the earliest) would let a single backdated-but-verifiable timestamp
/// mask another timestamp that falls outside the certificate's validity.
///
/// Note: There is NO fallback to current time. If no verified timestamp is found,
/// verification fails.
///
/// Both sources are always collected; there is no TSA-else-Rekor short circuit.
/// A timestamp source that is present but does not verify is a hard error, so a
/// bundle is rejected even when its *other* source would have been sufficient on
/// its own - an unverifiable SET is fatal despite a valid TSA timestamp, and vice
/// versa. This is deliberately stricter than sigstore-python, which discards
/// sources that fail to verify and then applies `VERIFIED_TIME_THRESHOLD = 1`.
/// It also applies when the caller passed
/// [`skip_tlog_unsafe`](crate::VerificationPolicy::skip_tlog_unsafe): that flag
/// skips inclusion proofs and checkpoints, not SET authentication.
pub fn determine_validation_times(
    bundle: &Bundle,
    signature: &SignatureBytes,
    trusted_root: &TrustedRoot,
    rekor_keys: &sigstore_crypto::Keyring,
) -> Result<Vec<jiff::Timestamp>> {
    let mut times = extract_tsa_timestamps(bundle, signature.as_bytes(), trusted_root)?;
    times.extend(extract_v1_integrated_times_with_promise(
        bundle, rekor_keys,
    )?);

    if !times.is_empty() {
        return Ok(times);
    }

    // No verified timestamp found - fail verification
    // This matches sigstore-python's behavior: "not enough sources of verified time"
    let is_v2 = has_v2_tlog_entries(bundle);
    if is_v2 {
        Err(Error::Verification(
            "V2 bundle requires RFC3161 timestamp but none could be verified. \
             V2 tlog entries have no integrated time by design. \
             Ensure TSA certificates are present in the trusted root."
                .to_string(),
        ))
    } else {
        Err(Error::Verification(
            "No verified timestamp found. V1 bundles require either an RFC3161 timestamp \
             or a tlog entry with both an integrated time and an inclusion_promise (SET)."
                .to_string(),
        ))
    }
}

/// Validate certificate is within validity period
pub fn validate_certificate_time(
    validation_time: jiff::Timestamp,
    cert_info: &CertificateInfo,
) -> Result<()> {
    if validation_time < cert_info.not_before {
        return Err(Error::Verification(format!(
            "certificate not yet valid: validation time {} is before not_before {}",
            validation_time, cert_info.not_before
        )));
    }

    if validation_time > cert_info.not_after {
        return Err(Error::Verification(format!(
            "certificate has expired: validation time {} is after not_after {}",
            validation_time, cert_info.not_after
        )));
    }

    Ok(())
}

/// Verify the certificate chain to the Fulcio root of trust
///
/// This function verifies that the signing certificate chains to a trusted
/// Fulcio root certificate whose authority's validity window covers the given
/// authenticated signing time. It also verifies that the certificate has the
/// CODE_SIGNING extended key usage.
///
/// On success, returns the SubjectPublicKeyInfo of the leaf's direct issuer
/// taken from the *verified* path. This is the canonical source for the issuer
/// used by SCT verification: it is the certificate webpki proved signed the
/// leaf, so it disambiguates Fulcio intermediates that share a subject name but
/// have different keys (as in Sigstore staging's multi-region deployment).
pub fn verify_certificate_chain(
    verification_material: &VerificationMaterialContent,
    validation_time: jiff::Timestamp,
    fulcio_anchors: &[FulcioAnchor],
) -> Result<DerPublicKey> {
    // Extract the end-entity certificate and any intermediates from the bundle
    let (ee_cert_der, intermediate_ders) = match verification_material {
        VerificationMaterialContent::Certificate(cert) => {
            (cert.raw_bytes.as_bytes().to_vec(), Vec::new())
        }
        VerificationMaterialContent::X509CertificateChain { certificates } => {
            if certificates.is_empty() {
                return Err(Error::Verification("no certificates in chain".to_string()));
            }
            let ee = certificates[0].raw_bytes.as_bytes().to_vec();
            let intermediates: Vec<Vec<u8>> = certificates[1..]
                .iter()
                .map(|c| c.raw_bytes.as_bytes().to_vec())
                .collect();
            (ee, intermediates)
        }
        VerificationMaterialContent::PublicKey { .. } => {
            return Err(Error::Verification(
                "public key verification not yet supported".to_string(),
            ));
        }
    };

    // Only anchors authorized at this authenticated signing time may terminate
    // the verified path. An unrelated authority cannot lend its validity window.
    let trust_anchors: Vec<_> = fulcio_anchors
        .iter()
        .filter(|(_, window)| window.is_none_or(|range| range.contains(validation_time)))
        .map(|(anchor, _)| anchor.clone())
        .collect();

    if trust_anchors.is_empty() {
        return Err(Error::Verification(format!(
            "no Fulcio trust anchor is valid at authenticated signing time {validation_time}"
        )));
    }

    // Convert intermediate certificates to CertificateDer
    let intermediate_certs: Vec<CertificateDer<'static>> = intermediate_ders
        .into_iter()
        .map(|der| CertificateDer::from(der).into_owned())
        .collect();

    // Parse the end-entity certificate for webpki
    let ee_cert_der_ref = CertificateDer::from(ee_cert_der.as_slice());
    let end_entity_cert = EndEntityCert::try_from(&ee_cert_der_ref).map_err(|e| {
        Error::Verification(format!("failed to parse end-entity certificate: {}", e))
    })?;

    // Convert validation time to webpki UnixTime
    let verification_time = UnixTime::since_unix_epoch(std::time::Duration::from_secs(
        validation_time.as_second() as u64,
    ));

    // Verify the certificate chain with CODE_SIGNING EKU
    // This performs:
    // - Chain building from end-entity to trust anchor
    // - Signature verification at each step
    // - Time validity checking
    // - Extended Key Usage validation (CODE_SIGNING)
    let path = end_entity_cert
        .verify_for_usage(
            ALL_VERIFICATION_ALGS,
            &trust_anchors,
            &intermediate_certs,
            verification_time,
            KeyUsage::required(ID_KP_CODE_SIGNING.as_bytes()),
            None, // No CRL/OCSP revocation checking (matches sigstore-python)
            None, // No custom path validation callback needed
        )
        .map_err(|e| Error::Verification(format!("certificate chain validation failed: {}", e)))?;

    tracing::debug!("Certificate chain validated successfully with CODE_SIGNING EKU");

    issuer_spki_from_path(&path)
}

/// Extract the leaf's direct-issuer SubjectPublicKeyInfo (full DER) from a
/// webpki-verified path.
///
/// The direct issuer is the leaf-proximal intermediate, or the trust anchor
/// itself when the leaf was signed directly by an anchor — which is the common
/// case for Sigstore, since Fulcio intermediates are shipped as trust anchors.
fn issuer_spki_from_path(path: &webpki::VerifiedPath) -> Result<DerPublicKey> {
    let der = match path.intermediate_certificates().next() {
        // `Cert::subject_public_key_info()` already returns the full SPKI SEQUENCE.
        Some(issuer) => issuer.subject_public_key_info().as_ref().to_vec(),
        None => {
            // webpki exposes the anchor SPKI as the SEQUENCE *contents* only, so
            // wrap it back into a SEQUENCE to get the full SubjectPublicKeyInfo.
            use x509_cert::der::{Any, Encode, Tag};
            let spki = path.anchor().subject_public_key_info.as_ref();
            Any::new(Tag::Sequence, spki)
                .and_then(|any| any.to_der())
                .map_err(|e| Error::Verification(format!("failed to encode issuer SPKI: {e}")))?
        }
    };
    Ok(DerPublicKey::new(der))
}

#[cfg(test)]
mod tests {
    use super::*;
    use sigstore_types::Bundle;

    /// The certificate must be validated against *every* verified timestamp
    /// in the bundle, so the timestamp collector must not collapse multiple
    /// sources into one. This bundle carries both a TSA timestamp and a
    /// SET-authenticated v1 integratedTime.
    #[test]
    fn determine_validation_times_returns_all_verified_sources() {
        let trusted_root =
            TrustedRoot::from_json(sigstore_trust_root::SIGSTORE_PRODUCTION_TRUSTED_ROOT).unwrap();
        let bundle = Bundle::from_json(include_str!(
            "../../test_data/bundles/cosign-v3-blob.sigstore.json"
        ))
        .unwrap();
        let signature = extract_signature(&bundle.content);

        let times = determine_validation_times(
            &bundle,
            &signature,
            &trusted_root,
            &trusted_root.rekor_keys().unwrap(),
        )
        .unwrap();

        assert_eq!(
            times.len(),
            2,
            "expected one TSA timestamp and one integratedTime, got {times:?}"
        );
        let integrated_time = bundle.verification_material.tlog_entries[0]
            .integrated_time
            .unwrap();
        assert!(times.contains(&integrated_time));
    }

    /// Regression test for the Sigstore staging multi-region rollout (July 2026).
    ///
    /// Staging began issuing certificates from a second Fulcio intermediate that
    /// shares the subject `CN=sigstore-intermediate` with the pre-existing 2022
    /// intermediate but has a different key. SCT verification used to resolve the
    /// issuer from the trusted root by subject *name* and picked the first (wrong)
    /// intermediate, so the reconstructed `issuer_key_hash` was wrong and SCT
    /// verification failed with a spurious "ECDSA P-256 SHA-256 signature invalid"
    /// error. Sourcing the issuer from the webpki-verified chain fixes it, because
    /// the verified path identifies the certificate that actually signed the leaf.
    ///
    /// The trusted root and bundle fixtures are the real staging artifacts from
    /// the failing tuf-on-ci smoke test run.
    #[test]
    fn sct_verifies_with_multiple_same_named_intermediates() {
        // The leaf's SCT timestamp / notBefore; used as the chain validation time.
        let validation_time = jiff::Timestamp::from_second(1_783_488_311).unwrap();

        let trusted_root = TrustedRoot::from_json(include_str!(
            "../../test_data/sct-multi-intermediate/staging_trusted_root.json"
        ))
        .expect("failed to load staging trusted root");
        let bundle = Bundle::from_json(include_str!(
            "../../test_data/sct-multi-intermediate/staging_bundle.sigstore.json"
        ))
        .expect("failed to parse staging bundle");
        let material = &bundle.verification_material.content;

        // Sanity check: the trusted root really does contain two Fulcio
        // intermediates that share the same subject name, which is the
        // condition that triggered the bug.
        use x509_cert::der::Decode;
        use x509_cert::Certificate;
        let same_named_intermediates = trusted_root
            .fulcio_certs()
            .iter()
            .filter_map(|der| Certificate::from_der(der).ok())
            .filter(|c| {
                c.tbs_certificate
                    .subject
                    .to_string()
                    .contains("sigstore-intermediate")
            })
            .count();
        assert!(
            same_named_intermediates >= 2,
            "fixture must contain multiple sigstore-intermediate CAs to exercise the bug, found {same_named_intermediates}"
        );

        // The canonical flow: the issuer comes from the verified chain, then SCT
        // verification uses it. Before the fix, SCT verification returned
        // Err("SCT signature verification failed: ... signature invalid").
        let verifier = crate::Verifier::new(&trusted_root).unwrap();
        let issuer_spki =
            verify_certificate_chain(material, validation_time, &verifier.fulcio_anchors)
                .expect("certificate chain should verify against the staging root");
        let cert = match material {
            VerificationMaterialContent::Certificate(cert) => &cert.raw_bytes,
            VerificationMaterialContent::X509CertificateChain { certificates } => {
                &certificates[0].raw_bytes
            }
            _ => panic!("fixture must have a signing certificate"),
        };
        super::super::sct::verify_sct(cert.as_bytes(), issuer_spki.as_bytes(), &verifier.ct_keys)
            .expect("SCT verification should succeed once the correct issuer is selected");
    }

    /// Tests for TOB-SIGSTORE-11: an RFC 3161 timestamp must be temporally
    /// authorized by the `valid_for` window of the exact timestamp authority
    /// that signed it — never by an unrelated authority's window.
    mod tsa_authority_binding {
        use super::*;
        use serde_json::{json, Value};

        // Conformance fixtures shared with `sigstore-tsa`: a bundle carrying a
        // single RFC 3161 timestamp and the trusted root of the TSA that
        // issued it (leaf + self-signed root, `validFor.start` only).
        const TSA_BUNDLE: &str = include_str!("../../test_data/timestamps/valid_bundle.json");
        const TSA_TRUSTED_ROOT: &str =
            include_str!("../../test_data/timestamps/valid_trusted_root.json");
        // GitHub's TSA trust root (five authorities with overlapping windows)
        // and a real GitHub-issued attestation bundle.
        const GITHUB_TSA_BUNDLE: &str =
            include_str!("../../test_data/timestamps/github_sha384_bundle.json");
        const GITHUB_TRUSTED_ROOT: &str =
            include_str!("../../test_data/timestamps/github_trusted_root.json");

        fn verify_bundle_timestamp(root: &TrustedRoot) -> Result<Option<i64>> {
            let bundle = Bundle::from_json(TSA_BUNDLE).unwrap();
            let signature = extract_signature(&bundle.content);
            let timestamps = extract_tsa_timestamps(&bundle, signature.as_bytes(), root)?;
            Ok(timestamps.first().map(|t| t.as_second()))
        }

        /// The token's signed time (whole seconds; the fixture's genTime has
        /// no fractional part), derived by verifying against the pristine
        /// trusted root.
        fn token_time() -> i64 {
            let root = TrustedRoot::from_json(TSA_TRUSTED_ROOT).unwrap();
            verify_bundle_timestamp(&root)
                .expect("fixture bundle should verify against its own trusted root")
                .expect("fixture bundle carries a timestamp")
        }

        fn iso(seconds: i64) -> String {
            jiff::Timestamp::from_second(seconds).unwrap().to_string()
        }

        /// Rebuild the fixture trusted root with the signing authority's
        /// `validFor` window replaced (or removed), optionally appending
        /// extra timestamp authorities.
        fn root_with(valid_for: Option<Value>, extra_authorities: Vec<Value>) -> TrustedRoot {
            let mut root: Value = serde_json::from_str(TSA_TRUSTED_ROOT).unwrap();
            let tsas = root["timestampAuthorities"].as_array_mut().unwrap();
            match valid_for {
                Some(window) => tsas[0]["validFor"] = window,
                None => {
                    tsas[0].as_object_mut().unwrap().remove("validFor");
                }
            }
            tsas.extend(extra_authorities);
            TrustedRoot::from_json(&root.to_string()).unwrap()
        }

        /// A real but unrelated timestamp authority (GitHub's TSA) with the
        /// given `validFor` window. Its chain verifies nothing signed by the
        /// fixture TSA.
        fn unrelated_authority(valid_for: Value) -> Value {
            let github: Value = serde_json::from_str(GITHUB_TRUSTED_ROOT).unwrap();
            let mut tsa = github["timestampAuthorities"][0].clone();
            tsa["validFor"] = valid_for;
            tsa
        }

        #[test]
        fn accepts_token_within_signing_authoritys_window() {
            let time = token_time();
            let covering = json!({ "start": iso(time - 3600), "end": iso(time + 3600) });
            // The unrelated authority's presence must not disturb acceptance.
            let root = root_with(Some(covering.clone()), vec![unrelated_authority(covering)]);
            assert_eq!(verify_bundle_timestamp(&root).unwrap(), Some(time));
        }

        #[test]
        fn accepts_token_from_authority_without_validity_window() {
            // An absent `valid_for` window means the authority is unrestricted.
            let root = root_with(None, vec![]);
            assert_eq!(verify_bundle_timestamp(&root).unwrap(), Some(token_time()));
        }

        #[test]
        fn accepts_token_exactly_at_window_boundaries() {
            // `ValidityPeriod::contains` is inclusive on both ends.
            let time = token_time();
            let root = root_with(
                Some(json!({ "start": iso(time), "end": iso(time) })),
                vec![],
            );
            assert_eq!(verify_bundle_timestamp(&root).unwrap(), Some(time));
        }

        #[test]
        fn rejects_token_just_outside_window_boundary() {
            let time = token_time();
            let root = root_with(
                Some(json!({ "start": iso(time - 3600), "end": iso(time - 1) })),
                vec![],
            );
            let err = verify_bundle_timestamp(&root).unwrap_err();
            assert!(
                err.to_string().contains("outside the validity period"),
                "unexpected error: {err}"
            );
        }

        /// The finding's core scenario: the signing authority's window
        /// excludes the token, and an unrelated authority's covering window
        /// must NOT lend it temporal authorization.
        #[test]
        fn rejects_token_outside_its_authoritys_window_despite_unrelated_covering_authority() {
            let time = token_time();
            let excluding = json!({ "start": iso(time - 7200), "end": iso(time - 3600) });
            let covering = json!({ "start": iso(time - 3600), "end": iso(time + 3600) });
            let root = root_with(Some(excluding), vec![unrelated_authority(covering)]);
            let err = verify_bundle_timestamp(&root).unwrap_err();
            assert!(
                err.to_string().contains("outside the validity period"),
                "unexpected error: {err}"
            );
        }

        /// Trail of Bits' exact reproducer: an authority entry with an EMPTY
        /// certificate chain and a covering window used to authorize a token
        /// that was rejected under its actual signer's window.
        #[test]
        fn rejects_empty_chain_authority_lending_its_window() {
            let time = token_time();
            let excluding = json!({ "start": iso(time - 7200), "end": iso(time - 3600) });
            let empty_chain_authority = json!({
                "certChain": { "certificates": [] },
                "validFor": { "start": iso(time - 3600), "end": iso(time + 3600) },
            });
            let root = root_with(Some(excluding), vec![empty_chain_authority]);
            let err = verify_bundle_timestamp(&root).unwrap_err();
            assert!(
                err.to_string().contains("outside the validity period"),
                "unexpected error: {err}"
            );
        }

        #[test]
        fn rejects_token_signed_by_no_configured_authority() {
            let time = token_time();
            let covering = json!({ "start": iso(time - 3600), "end": iso(time + 3600) });
            // Replace the signing authority entirely with an unrelated one
            // whose window covers the token.
            let mut root: Value = serde_json::from_str(TSA_TRUSTED_ROOT).unwrap();
            root["timestampAuthorities"] = json!([unrelated_authority(covering)]);
            let root = TrustedRoot::from_json(&root.to_string()).unwrap();
            let err = verify_bundle_timestamp(&root).unwrap_err();
            assert!(
                err.to_string()
                    .contains("TSA timestamp verification failed"),
                "unexpected error: {err}"
            );
        }

        #[test]
        fn rejects_token_when_no_authorities_configured() {
            let mut root: Value = serde_json::from_str(TSA_TRUSTED_ROOT).unwrap();
            root["timestampAuthorities"] = json!([]);
            let root = TrustedRoot::from_json(&root.to_string()).unwrap();
            let err = verify_bundle_timestamp(&root).unwrap_err();
            assert!(
                err.to_string()
                    .contains("no timestamp authorities configured"),
                "unexpected error: {err}"
            );
        }

        /// Multi-authority selection against real data: GitHub's trust root
        /// configures five TSA entries with overlapping epochs, and the token
        /// must be matched to (and authorized by) the one that signed it.
        #[test]
        fn selects_signing_authority_among_many_real_authorities() {
            let bundle = Bundle::from_json(GITHUB_TSA_BUNDLE).unwrap();
            let signature = extract_signature(&bundle.content);
            let root = TrustedRoot::from_json(GITHUB_TRUSTED_ROOT).unwrap();
            let timestamps = extract_tsa_timestamps(&bundle, signature.as_bytes(), &root)
                .expect("GitHub bundle timestamp should verify");
            let time = timestamps
                .first()
                .expect("GitHub bundle carries a timestamp");
            assert!(time.as_second() > 0);
        }
    }
}