Skip to main content

exo_gatekeeper/
tee.rs

1// Copyright 2026 Exochain Foundation
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at:
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15// SPDX-License-Identifier: Apache-2.0
16
17//! Trusted Execution Environment (TEE) attestation.
18//!
19//! Provides attestation verification for hardware TEE platforms
20//! and a simulated platform for testing.
21
22use serde::{Deserialize, Serialize};
23
24use crate::error::GatekeeperError;
25
26// ---------------------------------------------------------------------------
27// TEE platform
28// ---------------------------------------------------------------------------
29
30/// Supported TEE platforms.
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
32pub enum TeePlatform {
33    /// Intel SGX.
34    Sgx,
35    /// ARM TrustZone.
36    TrustZone,
37    /// AMD SEV.
38    Sev,
39    /// Simulated TEE for testing.
40    Simulated,
41}
42
43// ---------------------------------------------------------------------------
44// TEE environment
45// ---------------------------------------------------------------------------
46
47/// The deployment environment for TEE policy enforcement.
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
49pub enum TeeEnvironment {
50    /// Production — simulated TEE fixtures are rejected.
51    Production,
52    /// Testing — all platforms including Simulated are permitted.
53    Testing,
54}
55
56// ---------------------------------------------------------------------------
57// TEE attestation
58// ---------------------------------------------------------------------------
59
60/// An attestation from a TEE.
61#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
62pub struct TeeAttestation {
63    /// The platform that produced this attestation.
64    pub platform: TeePlatform,
65    /// Blake3 hash of the enclave measurement.
66    pub measurement_hash: [u8; 32],
67    /// Timestamp (milliseconds since epoch).
68    pub timestamp: u64,
69    /// Signature over the measurement + timestamp.
70    pub signature: Vec<u8>,
71}
72
73impl std::fmt::Debug for TeeAttestation {
74    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75        f.debug_struct("TeeAttestation")
76            .field("platform", &self.platform)
77            .field("measurement_hash", &"[REDACTED]")
78            .field("timestamp", &self.timestamp)
79            .field("signature", &"[REDACTED]")
80            .finish()
81    }
82}
83
84// ---------------------------------------------------------------------------
85// TEE policy
86// ---------------------------------------------------------------------------
87
88/// Policy defining acceptable TEE attestations.
89#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct TeePolicy {
91    /// Accepted platforms.
92    pub accepted_platforms: Vec<TeePlatform>,
93    /// Required measurement hashes (if non-empty, attestation must match one).
94    pub required_measurements: Vec<[u8; 32]>,
95    /// Maximum attestation age in milliseconds (0 = no age limit).
96    pub max_age_ms: u64,
97    /// Current time in milliseconds (for age checking).
98    pub current_time_ms: u64,
99    /// Deployment environment (Production or Testing).
100    pub environment: TeeEnvironment,
101}
102
103impl Default for TeePolicy {
104    /// Secure by default: Production environment, hardware-only platforms.
105    fn default() -> Self {
106        Self::production()
107    }
108}
109
110impl TeePolicy {
111    /// Production policy — only hardware TEE platforms accepted.
112    #[must_use]
113    pub fn production() -> Self {
114        Self {
115            accepted_platforms: vec![TeePlatform::Sgx, TeePlatform::TrustZone, TeePlatform::Sev],
116            required_measurements: vec![],
117            max_age_ms: 0,
118            current_time_ms: 0,
119            environment: TeeEnvironment::Production,
120        }
121    }
122
123    /// Testing policy — all platforms including Simulated are accepted.
124    #[must_use]
125    pub fn testing() -> Self {
126        Self {
127            accepted_platforms: vec![
128                TeePlatform::Sgx,
129                TeePlatform::TrustZone,
130                TeePlatform::Sev,
131                TeePlatform::Simulated,
132            ],
133            required_measurements: vec![],
134            max_age_ms: 0,
135            current_time_ms: 0,
136            environment: TeeEnvironment::Testing,
137        }
138    }
139}
140
141// ---------------------------------------------------------------------------
142// Platform gating
143// ---------------------------------------------------------------------------
144
145/// Check whether a platform is allowed by policy, enforcing the production
146/// gate against simulated TEEs.
147fn is_platform_allowed(platform: &TeePlatform, policy: &TeePolicy) -> bool {
148    if *platform == TeePlatform::Simulated && policy.environment != TeeEnvironment::Testing {
149        return false;
150    }
151    policy.accepted_platforms.contains(platform)
152}
153
154// ---------------------------------------------------------------------------
155// Attestation generation
156// ---------------------------------------------------------------------------
157
158/// Generate an attestation for the given platform and measurement.
159///
160/// This creates deterministic simulated attestation fixtures. Hardware TEE
161/// platforms require platform quote verification and are not accepted by
162/// `verify_attestation` when carrying this synthetic signature.
163#[must_use]
164pub fn generate_attestation(
165    platform: &TeePlatform,
166    measurement: &[u8],
167    timestamp: u64,
168) -> TeeAttestation {
169    let measurement_hash = *blake3::hash(measurement).as_bytes();
170
171    TeeAttestation {
172        platform: *platform,
173        measurement_hash,
174        timestamp,
175        signature: synthetic_attestation_signature(platform, &measurement_hash, timestamp),
176    }
177}
178
179/// Deterministic signature used only for simulated TEE test fixtures.
180fn tee_platform_tag(platform: &TeePlatform) -> &'static [u8] {
181    match platform {
182        TeePlatform::Sgx => b"tee.platform.sgx.v1",
183        TeePlatform::Sev => b"tee.platform.sev.v1",
184        TeePlatform::TrustZone => b"tee.platform.trustzone.v1",
185        TeePlatform::Simulated => b"tee.platform.simulated.v1",
186    }
187}
188
189fn synthetic_attestation_signature(
190    platform: &TeePlatform,
191    measurement_hash: &[u8; 32],
192    timestamp: u64,
193) -> Vec<u8> {
194    let mut sig_input = Vec::new();
195    sig_input.extend_from_slice(measurement_hash);
196    sig_input.extend_from_slice(&timestamp.to_le_bytes());
197    sig_input.extend_from_slice(tee_platform_tag(platform));
198    blake3::hash(&sig_input).as_bytes().to_vec()
199}
200
201fn synthetic_signature_allowed(attestation: &TeeAttestation, policy: &TeePolicy) -> bool {
202    if attestation.platform != TeePlatform::Simulated {
203        return false;
204    }
205
206    policy.environment == TeeEnvironment::Testing
207}
208
209fn measurement_hash_eq_ct(left: &[u8; 32], right: &[u8; 32]) -> bool {
210    let mut diff = 0u8;
211    for (left_byte, right_byte) in left.iter().zip(right.iter()) {
212        diff |= left_byte ^ right_byte;
213    }
214    diff == 0
215}
216
217fn required_measurement_matches(
218    required_measurements: &[[u8; 32]],
219    measurement: &[u8; 32],
220) -> bool {
221    let mut matched = 0u8;
222    for required in required_measurements {
223        matched |= u8::from(measurement_hash_eq_ct(required, measurement));
224    }
225    matched != 0
226}
227
228// ---------------------------------------------------------------------------
229// Attestation verification
230// ---------------------------------------------------------------------------
231
232/// Platform-specific verifier for a hardware TEE quote.
233///
234/// The verifier is called only after deterministic policy checks pass and only
235/// for non-synthetic hardware attestations. Simulated TEEs keep using the local
236/// deterministic fixture signature path for tests.
237pub trait TeeQuoteVerifier {
238    /// Verify the platform quote carried in an attestation.
239    ///
240    /// Implementations should perform the platform-specific checks for SGX,
241    /// SEV, or TrustZone quote material and return a detailed [`GatekeeperError`]
242    /// on failure.
243    fn verify_quote(
244        &self,
245        attestation: &TeeAttestation,
246        policy: &TeePolicy,
247    ) -> Result<(), GatekeeperError>;
248}
249
250impl<F> TeeQuoteVerifier for F
251where
252    F: Fn(&TeeAttestation, &TeePolicy) -> Result<(), GatekeeperError>,
253{
254    fn verify_quote(
255        &self,
256        attestation: &TeeAttestation,
257        policy: &TeePolicy,
258    ) -> Result<(), GatekeeperError> {
259        self(attestation, policy)
260    }
261}
262
263enum AttestationSignatureKind {
264    SimulatedFixture,
265    HardwareQuote,
266}
267
268fn check_attestation_policy(
269    attestation: &TeeAttestation,
270    policy: &TeePolicy,
271) -> Result<AttestationSignatureKind, GatekeeperError> {
272    // Check platform (includes production gate for Simulated).
273    if !is_platform_allowed(&attestation.platform, policy) {
274        return Err(GatekeeperError::TeeError(format!(
275            "Platform {:?} is not accepted by policy",
276            attestation.platform
277        )));
278    }
279
280    // Check measurement (if policy specifies required measurements).
281    if !policy.required_measurements.is_empty()
282        && !required_measurement_matches(
283            &policy.required_measurements,
284            &attestation.measurement_hash,
285        )
286    {
287        return Err(GatekeeperError::TeeError(
288            "Measurement hash does not match any required measurement".into(),
289        ));
290    }
291
292    // Check signature is non-empty.
293    if attestation.signature.is_empty() {
294        return Err(GatekeeperError::TeeError(
295            "Attestation signature is empty".into(),
296        ));
297    }
298
299    // Check age.
300    if policy.max_age_ms > 0 {
301        let age = policy
302            .current_time_ms
303            .checked_sub(attestation.timestamp)
304            .ok_or_else(|| {
305                GatekeeperError::TeeError(format!(
306                    "Attestation timestamp {} is in the future relative to policy time {}",
307                    attestation.timestamp, policy.current_time_ms
308                ))
309            })?;
310        if age > policy.max_age_ms {
311            return Err(GatekeeperError::TeeError(format!(
312                "Attestation is too old: {} ms (max: {} ms)",
313                age, policy.max_age_ms
314            )));
315        }
316    }
317
318    let synthetic_sig = synthetic_attestation_signature(
319        &attestation.platform,
320        &attestation.measurement_hash,
321        attestation.timestamp,
322    );
323
324    if attestation.signature == synthetic_sig {
325        if synthetic_signature_allowed(attestation, policy) {
326            return Ok(AttestationSignatureKind::SimulatedFixture);
327        }
328        return Err(GatekeeperError::TeeError(
329            "synthetic TEE attestation signatures are only accepted for simulated TEEs in explicitly allowed environments".into(),
330        ));
331    }
332
333    if attestation.platform == TeePlatform::Simulated {
334        return Err(GatekeeperError::TeeError(
335            "simulated TEE attestation signature verification failed".into(),
336        ));
337    }
338
339    Ok(AttestationSignatureKind::HardwareQuote)
340}
341
342/// Verify a TEE attestation against a policy.
343pub fn verify_attestation(
344    attestation: &TeeAttestation,
345    policy: &TeePolicy,
346) -> Result<(), GatekeeperError> {
347    match check_attestation_policy(attestation, policy)? {
348        AttestationSignatureKind::SimulatedFixture => Ok(()),
349        AttestationSignatureKind::HardwareQuote => Err(GatekeeperError::TeeError(format!(
350            "Hardware TEE attestation for platform {:?} requires a platform quote verifier",
351            attestation.platform
352        ))),
353    }
354}
355
356/// Verify a TEE attestation with an explicit hardware quote verifier.
357///
358/// This preserves the fail-closed behavior of [`verify_attestation`] for
359/// callers that do not provide platform quote verification, while allowing
360/// production integrations to plug in audited SGX/SEV/TrustZone quote checks.
361pub fn verify_attestation_with_quote_verifier<V>(
362    attestation: &TeeAttestation,
363    policy: &TeePolicy,
364    verifier: &V,
365) -> Result<(), GatekeeperError>
366where
367    V: TeeQuoteVerifier + ?Sized,
368{
369    match check_attestation_policy(attestation, policy)? {
370        AttestationSignatureKind::SimulatedFixture => Ok(()),
371        AttestationSignatureKind::HardwareQuote => verifier.verify_quote(attestation, policy),
372    }
373}
374
375// ===========================================================================
376// Tests
377// ===========================================================================
378
379#[cfg(test)]
380mod tests {
381    use super::*;
382
383    const MEASUREMENT: &[u8] = b"enclave-binary-v1.0";
384    const TIMESTAMP: u64 = 1_700_000_000_000;
385
386    fn valid_attestation() -> TeeAttestation {
387        generate_attestation(&TeePlatform::Simulated, MEASUREMENT, TIMESTAMP)
388    }
389
390    fn permissive_policy() -> TeePolicy {
391        TeePolicy {
392            accepted_platforms: vec![
393                TeePlatform::Sgx,
394                TeePlatform::TrustZone,
395                TeePlatform::Sev,
396                TeePlatform::Simulated,
397            ],
398            required_measurements: vec![],
399            max_age_ms: 0,
400            current_time_ms: TIMESTAMP,
401            environment: TeeEnvironment::Testing,
402        }
403    }
404
405    fn accepting_sgx_quote_verifier(
406        verified_att: &TeeAttestation,
407        _policy: &TeePolicy,
408    ) -> Result<(), GatekeeperError> {
409        if verified_att.platform == TeePlatform::Sgx && verified_att.signature == vec![0xA5; 64] {
410            Ok(())
411        } else {
412            Err(GatekeeperError::TeeError(
413                "unexpected quote material".into(),
414            ))
415        }
416    }
417
418    fn revoked_quote_verifier(
419        _attestation: &TeeAttestation,
420        _policy: &TeePolicy,
421    ) -> Result<(), GatekeeperError> {
422        Err(GatekeeperError::TeeError("quote revoked".into()))
423    }
424
425    fn panic_quote_verifier(
426        _attestation: &TeeAttestation,
427        _policy: &TeePolicy,
428    ) -> Result<(), GatekeeperError> {
429        panic!("synthetic hardware attestations must not reach quote verifier")
430    }
431
432    // --- Generation ---
433
434    #[test]
435    fn generate_produces_valid_attestation() {
436        let att = valid_attestation();
437        assert_eq!(att.platform, TeePlatform::Simulated);
438        assert_eq!(att.timestamp, TIMESTAMP);
439        assert!(!att.signature.is_empty());
440        assert_eq!(att.measurement_hash, *blake3::hash(MEASUREMENT).as_bytes());
441    }
442
443    #[test]
444    fn tee_attestation_debug_redacts_sensitive_material() {
445        let att = valid_attestation();
446        let rendered = format!("{att:?}");
447
448        assert!(rendered.contains("TeeAttestation"));
449        assert!(rendered.contains("measurement_hash: \"[REDACTED]\""));
450        assert!(rendered.contains("signature: \"[REDACTED]\""));
451        assert!(!rendered.contains(&format!("{:?}", att.measurement_hash)));
452        assert!(!rendered.contains(&format!("{:?}", att.signature)));
453    }
454
455    #[test]
456    fn generate_is_deterministic() {
457        let att1 = generate_attestation(&TeePlatform::Sgx, MEASUREMENT, TIMESTAMP);
458        let att2 = generate_attestation(&TeePlatform::Sgx, MEASUREMENT, TIMESTAMP);
459        assert_eq!(att1, att2);
460    }
461
462    #[test]
463    fn generate_different_platforms_produce_different_signatures() {
464        let att_sgx = generate_attestation(&TeePlatform::Sgx, MEASUREMENT, TIMESTAMP);
465        let att_sev = generate_attestation(&TeePlatform::Sev, MEASUREMENT, TIMESTAMP);
466        assert_ne!(att_sgx.signature, att_sev.signature);
467    }
468
469    #[test]
470    fn generate_different_measurements_produce_different_hashes() {
471        let att1 = generate_attestation(&TeePlatform::Simulated, b"binary-v1", TIMESTAMP);
472        let att2 = generate_attestation(&TeePlatform::Simulated, b"binary-v2", TIMESTAMP);
473        assert_ne!(att1.measurement_hash, att2.measurement_hash);
474    }
475
476    // --- Verification: passing ---
477
478    #[test]
479    fn verify_passes_for_valid_attestation() {
480        let att = valid_attestation();
481        let policy = permissive_policy();
482        assert!(verify_attestation(&att, &policy).is_ok());
483    }
484
485    #[test]
486    fn verify_rejects_synthetic_sgx_attestation() {
487        let att = generate_attestation(&TeePlatform::Sgx, MEASUREMENT, TIMESTAMP);
488        let policy = permissive_policy();
489        let result = verify_attestation(&att, &policy);
490        assert!(result.is_err());
491        assert!(format!("{}", result.unwrap_err()).contains("synthetic"));
492    }
493
494    #[test]
495    fn verify_rejects_synthetic_trustzone_attestation() {
496        let att = generate_attestation(&TeePlatform::TrustZone, MEASUREMENT, TIMESTAMP);
497        let policy = permissive_policy();
498        let result = verify_attestation(&att, &policy);
499        assert!(result.is_err());
500        assert!(format!("{}", result.unwrap_err()).contains("synthetic"));
501    }
502
503    #[test]
504    fn verify_rejects_synthetic_sev_attestation() {
505        let att = generate_attestation(&TeePlatform::Sev, MEASUREMENT, TIMESTAMP);
506        let policy = permissive_policy();
507        let result = verify_attestation(&att, &policy);
508        assert!(result.is_err());
509        assert!(format!("{}", result.unwrap_err()).contains("synthetic"));
510    }
511
512    // --- Verification: platform rejection ---
513
514    #[test]
515    fn verify_rejects_unaccepted_platform() {
516        let att = valid_attestation(); // Simulated
517        let policy = TeePolicy {
518            accepted_platforms: vec![TeePlatform::Sgx], // only SGX
519            required_measurements: vec![],
520            max_age_ms: 0,
521            current_time_ms: TIMESTAMP,
522            environment: TeeEnvironment::Testing,
523        };
524        let result = verify_attestation(&att, &policy);
525        assert!(result.is_err());
526        assert!(format!("{}", result.unwrap_err()).contains("not accepted"));
527    }
528
529    // --- Verification: measurement mismatch ---
530
531    #[test]
532    fn verify_rejects_measurement_mismatch() {
533        let att = valid_attestation();
534        let policy = TeePolicy {
535            accepted_platforms: vec![TeePlatform::Simulated],
536            required_measurements: vec![[0u8; 32]], // wrong hash
537            max_age_ms: 0,
538            current_time_ms: TIMESTAMP,
539            environment: TeeEnvironment::Testing,
540        };
541        let result = verify_attestation(&att, &policy);
542        assert!(result.is_err());
543        assert!(format!("{}", result.unwrap_err()).contains("Measurement"));
544    }
545
546    #[test]
547    fn verify_passes_when_measurement_matches() {
548        let att = valid_attestation();
549        let policy = TeePolicy {
550            accepted_platforms: vec![TeePlatform::Simulated],
551            required_measurements: vec![att.measurement_hash],
552            max_age_ms: 0,
553            current_time_ms: TIMESTAMP,
554            environment: TeeEnvironment::Testing,
555        };
556        assert!(verify_attestation(&att, &policy).is_ok());
557    }
558
559    // --- Verification: empty signature ---
560
561    #[test]
562    fn verify_rejects_empty_signature() {
563        let mut att = valid_attestation();
564        att.signature = vec![]; // tamper
565        let policy = permissive_policy();
566        let result = verify_attestation(&att, &policy);
567        assert!(result.is_err());
568        assert!(format!("{}", result.unwrap_err()).contains("empty"));
569    }
570
571    // --- Verification: age ---
572
573    #[test]
574    fn verify_rejects_expired_attestation() {
575        let att = valid_attestation(); // timestamp = TIMESTAMP
576        let policy = TeePolicy {
577            accepted_platforms: vec![TeePlatform::Simulated],
578            required_measurements: vec![],
579            max_age_ms: 1000,                  // 1 second max
580            current_time_ms: TIMESTAMP + 5000, // 5 seconds later
581            environment: TeeEnvironment::Testing,
582        };
583        let result = verify_attestation(&att, &policy);
584        assert!(result.is_err());
585        assert!(format!("{}", result.unwrap_err()).contains("too old"));
586    }
587
588    #[test]
589    fn verify_passes_within_age_limit() {
590        let att = valid_attestation();
591        let policy = TeePolicy {
592            accepted_platforms: vec![TeePlatform::Simulated],
593            required_measurements: vec![],
594            max_age_ms: 10_000,
595            current_time_ms: TIMESTAMP + 5_000,
596            environment: TeeEnvironment::Testing,
597        };
598        assert!(verify_attestation(&att, &policy).is_ok());
599    }
600
601    #[test]
602    fn verify_rejects_future_dated_attestation_when_age_limit_is_enforced() {
603        let att = generate_attestation(&TeePlatform::Simulated, MEASUREMENT, TIMESTAMP + 5_000);
604        let policy = TeePolicy {
605            accepted_platforms: vec![TeePlatform::Simulated],
606            required_measurements: vec![],
607            max_age_ms: 1_000,
608            current_time_ms: TIMESTAMP,
609            environment: TeeEnvironment::Testing,
610        };
611
612        let result = verify_attestation(&att, &policy);
613
614        assert!(result.is_err());
615        assert!(format!("{}", result.unwrap_err()).contains("future"));
616    }
617
618    #[test]
619    fn verify_no_age_limit_passes_old_attestation() {
620        let att = valid_attestation();
621        let policy = TeePolicy {
622            accepted_platforms: vec![TeePlatform::Simulated],
623            required_measurements: vec![],
624            max_age_ms: 0, // no limit
625            current_time_ms: TIMESTAMP + 999_999_999,
626            environment: TeeEnvironment::Testing,
627        };
628        assert!(verify_attestation(&att, &policy).is_ok());
629    }
630
631    // --- Verification: tampered signature ---
632
633    #[test]
634    fn verify_rejects_tampered_signature() {
635        let mut att = valid_attestation();
636        att.signature = vec![0xDE, 0xAD, 0xBE, 0xEF]; // wrong
637        let policy = permissive_policy();
638        let result = verify_attestation(&att, &policy);
639        assert!(result.is_err());
640        assert!(format!("{}", result.unwrap_err()).contains("verification failed"));
641    }
642
643    #[test]
644    fn verify_rejects_tampered_timestamp() {
645        let mut att = valid_attestation();
646        att.timestamp += 1; // tamper — signature won't match
647        let policy = permissive_policy();
648        let result = verify_attestation(&att, &policy);
649        assert!(result.is_err());
650    }
651
652    #[test]
653    fn verify_rejects_tampered_measurement() {
654        let mut att = valid_attestation();
655        att.measurement_hash[0] ^= 0xFF; // tamper
656        let policy = permissive_policy();
657        let result = verify_attestation(&att, &policy);
658        assert!(result.is_err());
659    }
660
661    // --- Multiple required measurements: any match passes ---
662
663    #[test]
664    fn verify_passes_when_one_of_multiple_measurements_matches() {
665        let att = valid_attestation();
666        let policy = TeePolicy {
667            accepted_platforms: vec![TeePlatform::Simulated],
668            required_measurements: vec![[0u8; 32], att.measurement_hash, [1u8; 32]],
669            max_age_ms: 0,
670            current_time_ms: TIMESTAMP,
671            environment: TeeEnvironment::Testing,
672        };
673        assert!(verify_attestation(&att, &policy).is_ok());
674    }
675
676    #[test]
677    fn required_measurement_matcher_handles_empty_match_and_mismatch() {
678        let att = valid_attestation();
679
680        assert!(!required_measurement_matches(&[], &att.measurement_hash));
681        assert!(required_measurement_matches(
682            &[[0u8; 32], att.measurement_hash, [1u8; 32]],
683            &att.measurement_hash
684        ));
685        assert!(!required_measurement_matches(
686            &[[0u8; 32], [1u8; 32]],
687            &att.measurement_hash
688        ));
689    }
690
691    #[test]
692    fn measurement_policy_does_not_use_short_circuit_contains() {
693        let source = include_str!("tee.rs");
694        let start = source
695            .find("fn check_attestation_policy(")
696            .expect("check_attestation_policy source exists");
697        let end = source[start..]
698            .find("// Check signature is non-empty.")
699            .expect("signature check marker exists");
700        let measurement_check = &source[start..start + end];
701
702        assert!(
703            !measurement_check.contains(".contains(&attestation.measurement_hash)"),
704            "measurement hash matching must not use short-circuiting Vec::contains"
705        );
706    }
707
708    // --- Production TEE gate tests ---
709
710    #[test]
711    fn simulated_rejected_in_production() {
712        let att = generate_attestation(&TeePlatform::Simulated, MEASUREMENT, TIMESTAMP);
713        let mut policy = TeePolicy::production();
714        // Even if someone manually adds Simulated to accepted_platforms,
715        // the production gate must still reject it.
716        policy.accepted_platforms.push(TeePlatform::Simulated);
717        policy.current_time_ms = TIMESTAMP;
718        assert!(verify_attestation(&att, &policy).is_err());
719    }
720
721    #[test]
722    fn simulated_accepted_in_testing() {
723        let att = generate_attestation(&TeePlatform::Simulated, MEASUREMENT, TIMESTAMP);
724        let mut policy = TeePolicy::testing();
725        policy.current_time_ms = TIMESTAMP;
726        assert!(verify_attestation(&att, &policy).is_ok());
727    }
728
729    #[test]
730    fn synthetic_sgx_attestation_rejected_in_production() {
731        let att = generate_attestation(&TeePlatform::Sgx, MEASUREMENT, TIMESTAMP);
732        let mut policy = TeePolicy::production();
733        policy.current_time_ms = TIMESTAMP;
734        let result = verify_attestation(&att, &policy);
735        assert!(result.is_err());
736        assert!(format!("{}", result.unwrap_err()).contains("synthetic"));
737    }
738
739    #[test]
740    fn synthetic_trustzone_attestation_rejected_in_production() {
741        let att = generate_attestation(&TeePlatform::TrustZone, MEASUREMENT, TIMESTAMP);
742        let mut policy = TeePolicy::production();
743        policy.current_time_ms = TIMESTAMP;
744        let result = verify_attestation(&att, &policy);
745        assert!(result.is_err());
746        assert!(format!("{}", result.unwrap_err()).contains("synthetic"));
747    }
748
749    #[test]
750    fn synthetic_sev_attestation_rejected_in_production() {
751        let att = generate_attestation(&TeePlatform::Sev, MEASUREMENT, TIMESTAMP);
752        let mut policy = TeePolicy::production();
753        policy.current_time_ms = TIMESTAMP;
754        let result = verify_attestation(&att, &policy);
755        assert!(result.is_err());
756        assert!(format!("{}", result.unwrap_err()).contains("synthetic"));
757    }
758
759    #[test]
760    fn synthetic_hardware_attestation_rejected_in_testing() {
761        let att = generate_attestation(&TeePlatform::Sgx, MEASUREMENT, TIMESTAMP);
762        let mut policy = TeePolicy::testing();
763        policy.current_time_ms = TIMESTAMP;
764        let result = verify_attestation(&att, &policy);
765        assert!(result.is_err());
766        assert!(format!("{}", result.unwrap_err()).contains("synthetic"));
767    }
768
769    #[test]
770    fn sgx_non_synthetic_signature_rejected_without_quote_verifier_in_production() {
771        let mut att = generate_attestation(&TeePlatform::Sgx, MEASUREMENT, TIMESTAMP);
772        att.signature = vec![0xA5; 64];
773        let mut policy = TeePolicy::production();
774        policy.current_time_ms = TIMESTAMP;
775        let result = verify_attestation(&att, &policy);
776        assert!(result.is_err());
777        assert!(format!("{}", result.unwrap_err()).contains("quote verifier"));
778    }
779
780    #[test]
781    fn hardware_non_synthetic_signature_is_accepted_when_quote_verifier_accepts() {
782        let mut att = generate_attestation(&TeePlatform::Sgx, MEASUREMENT, TIMESTAMP);
783        att.signature = vec![0xA5; 64];
784        let mut policy = TeePolicy::production();
785        policy.current_time_ms = TIMESTAMP;
786
787        let result =
788            verify_attestation_with_quote_verifier(&att, &policy, &accepting_sgx_quote_verifier);
789
790        assert!(result.is_ok());
791    }
792
793    #[test]
794    fn hardware_quote_verifier_error_is_propagated() {
795        let mut att = generate_attestation(&TeePlatform::Sgx, MEASUREMENT, TIMESTAMP);
796        att.signature = vec![0xA5; 64];
797        let mut policy = TeePolicy::production();
798        policy.current_time_ms = TIMESTAMP;
799
800        let result = verify_attestation_with_quote_verifier(&att, &policy, &revoked_quote_verifier);
801
802        assert!(result.is_err());
803        assert!(format!("{}", result.unwrap_err()).contains("quote revoked"));
804    }
805
806    #[test]
807    fn synthetic_hardware_attestation_is_rejected_before_quote_verifier() {
808        let att = generate_attestation(&TeePlatform::Sgx, MEASUREMENT, TIMESTAMP);
809        let mut policy = TeePolicy::production();
810        policy.current_time_ms = TIMESTAMP;
811
812        let result = verify_attestation_with_quote_verifier(&att, &policy, &panic_quote_verifier);
813
814        assert!(result.is_err());
815        assert!(format!("{}", result.unwrap_err()).contains("synthetic"));
816    }
817
818    #[test]
819    fn sgx_non_synthetic_signature_rejected_without_quote_verifier_in_testing() {
820        let mut att = generate_attestation(&TeePlatform::Sgx, MEASUREMENT, TIMESTAMP);
821        att.signature = vec![0xA5; 64];
822        let mut policy = TeePolicy::testing();
823        policy.current_time_ms = TIMESTAMP;
824        let result = verify_attestation(&att, &policy);
825        assert!(result.is_err());
826        assert!(format!("{}", result.unwrap_err()).contains("quote verifier"));
827    }
828
829    #[test]
830    fn trustzone_non_synthetic_signature_rejected_without_quote_verifier() {
831        let mut att = generate_attestation(&TeePlatform::TrustZone, MEASUREMENT, TIMESTAMP);
832        att.signature = vec![0xA5; 64];
833        let mut policy = TeePolicy::production();
834        policy.current_time_ms = TIMESTAMP;
835        let result = verify_attestation(&att, &policy);
836        assert!(result.is_err());
837        assert!(format!("{}", result.unwrap_err()).contains("quote verifier"));
838    }
839
840    #[test]
841    fn sev_non_synthetic_signature_rejected_without_quote_verifier() {
842        let mut att = generate_attestation(&TeePlatform::Sev, MEASUREMENT, TIMESTAMP);
843        att.signature = vec![0xA5; 64];
844        let mut policy = TeePolicy::production();
845        policy.current_time_ms = TIMESTAMP;
846        let result = verify_attestation(&att, &policy);
847        assert!(result.is_err());
848        assert!(format!("{}", result.unwrap_err()).contains("quote verifier"));
849    }
850
851    #[test]
852    fn default_policy_is_production() {
853        let policy = TeePolicy::default();
854        assert_eq!(policy.environment, TeeEnvironment::Production);
855    }
856
857    #[test]
858    fn production_constructor_excludes_simulated() {
859        let policy = TeePolicy::production();
860        assert!(!policy.accepted_platforms.contains(&TeePlatform::Simulated));
861    }
862
863    #[test]
864    fn testing_constructor_includes_simulated() {
865        let policy = TeePolicy::testing();
866        assert!(policy.accepted_platforms.contains(&TeePlatform::Simulated));
867    }
868
869    #[test]
870    fn synthetic_attestation_signature_uses_stable_platform_tags() {
871        let source = include_str!("tee.rs");
872        let production = source
873            .split("// ===========================================================================\n// Tests")
874            .next()
875            .expect("tests marker present");
876
877        assert!(
878            !production.contains("format!(\"{:?}\", platform)"),
879            "synthetic attestation fixture signatures must not depend on Rust Debug output"
880        );
881    }
882}