Skip to main content

a3s_box_runtime/tee/
verifier.rs

1//! SNP attestation report verifier.
2//!
3//! Verifies the cryptographic signature, certificate chain, and policy
4//! compliance of an AMD SEV-SNP attestation report. This is the core
5//! trust anchor — if verification passes, the report was genuinely
6//! produced by AMD hardware running the expected workload.
7
8use a3s_box_core::error::{BoxError, Result};
9
10use super::attestation::{parse_platform_info, AttestationReport, PlatformInfo, SNP_REPORT_SIZE};
11use super::policy::{AttestationPolicy, PolicyResult, PolicyViolation};
12use super::simulate::is_simulated_report;
13
14/// Result of a complete attestation verification.
15#[derive(Debug, Clone)]
16pub struct VerificationResult {
17    /// Whether the report passed all checks (signature + policy + age).
18    pub verified: bool,
19    /// Platform info extracted from the report.
20    pub platform: PlatformInfo,
21    /// Policy check result.
22    pub policy_result: PolicyResult,
23    /// Signature verification passed.
24    pub signature_valid: bool,
25    /// Certificate chain verification passed.
26    pub cert_chain_valid: bool,
27    /// Nonce in report matches the expected nonce.
28    pub nonce_valid: bool,
29    /// Report age is within the allowed threshold (or age check was skipped).
30    pub report_age_valid: bool,
31    /// Summary of any failures.
32    pub failures: Vec<String>,
33}
34
35/// Verify an SNP attestation report.
36///
37/// This performs the complete verification flow:
38/// 1. Parse and validate the report structure
39/// 2. Verify the nonce matches (anti-replay)
40/// 3. Verify the ECDSA-P384 signature using the VCEK public key
41/// 4. Verify the certificate chain (VCEK → ASK → ARK)
42/// 5. Check the report against the attestation policy
43/// 6. Check report age (if `nonce_issued_at` and `max_report_age_secs` are set)
44///
45/// If `allow_simulated` is true and the report has the simulated version
46/// marker (0xA3), signature and cert chain verification are skipped.
47/// Nonce and policy checks still apply.
48///
49/// # Arguments
50/// * `report` - The attestation report from the guest
51/// * `expected_nonce` - The nonce that was sent in the request
52/// * `policy` - The verification policy to check against
53/// * `allow_simulated` - Whether to accept simulated (non-hardware) reports
54pub fn verify_attestation(
55    report: &AttestationReport,
56    expected_nonce: &[u8],
57    policy: &AttestationPolicy,
58    allow_simulated: bool,
59) -> Result<VerificationResult> {
60    verify_attestation_with_time(report, expected_nonce, policy, allow_simulated, None)
61}
62
63/// Verify an SNP attestation report with optional replay protection.
64///
65/// Same as [`verify_attestation`], but accepts `nonce_issued_at` — the Unix
66/// timestamp (seconds) when the nonce was generated. When combined with
67/// `policy.max_report_age_secs`, this rejects stale reports that could be
68/// replayed by an attacker.
69///
70/// # Arguments
71/// * `report` - The attestation report from the guest
72/// * `expected_nonce` - The nonce that was sent in the request
73/// * `policy` - The verification policy to check against
74/// * `allow_simulated` - Whether to accept simulated (non-hardware) reports
75/// * `nonce_issued_at` - Unix timestamp (seconds) when the nonce was created.
76///   If `None`, report age checking is skipped even if `max_report_age_secs` is set.
77pub fn verify_attestation_with_time(
78    report: &AttestationReport,
79    expected_nonce: &[u8],
80    policy: &AttestationPolicy,
81    allow_simulated: bool,
82    nonce_issued_at: Option<u64>,
83) -> Result<VerificationResult> {
84    let mut failures = Vec::new();
85
86    // 1. Parse report structure
87    let platform = parse_platform_info(&report.report).ok_or_else(|| {
88        BoxError::AttestationError(format!(
89            "Invalid SNP report: expected {} bytes, got {}",
90            SNP_REPORT_SIZE,
91            report.report.len()
92        ))
93    })?;
94
95    // Check if this is a simulated report
96    let simulated = is_simulated_report(&report.report);
97    if simulated && !allow_simulated {
98        return Err(BoxError::AttestationError(
99            "Simulated report rejected: allow_simulated is false".to_string(),
100        ));
101    }
102    if simulated {
103        tracing::warn!("Accepting simulated TEE report (not hardware-attested)");
104    }
105
106    // 2. Verify nonce (report_data field at offset 0x50, 64 bytes)
107    let nonce_valid = verify_nonce(&report.report, expected_nonce);
108    if !nonce_valid {
109        failures.push("Nonce mismatch: report_data does not contain expected nonce".to_string());
110    }
111
112    // 3. Verify ECDSA-P384 signature (skip for simulated reports)
113    let signature_valid = if simulated {
114        true
115    } else {
116        let valid = verify_report_signature(&report.report, &report.cert_chain.vcek);
117        if !valid {
118            failures.push("Signature verification failed".to_string());
119        }
120        valid
121    };
122
123    // 4. Verify certificate chain (skip for simulated reports)
124    let cert_chain_valid = if simulated {
125        true
126    } else {
127        let valid = verify_cert_chain(
128            &report.cert_chain.vcek,
129            &report.cert_chain.ask,
130            &report.cert_chain.ark,
131        );
132        if !valid {
133            failures.push("Certificate chain verification failed".to_string());
134        }
135        valid
136    };
137
138    // 5. Check policy
139    let policy_result = check_policy(&platform, policy);
140    if !policy_result.passed {
141        for v in &policy_result.violations {
142            failures.push(v.to_string());
143        }
144    }
145
146    // 6. Check report age (replay protection)
147    let report_age_valid = check_report_age(policy, nonce_issued_at, &mut failures);
148
149    let verified = nonce_valid
150        && signature_valid
151        && cert_chain_valid
152        && policy_result.passed
153        && report_age_valid;
154
155    Ok(VerificationResult {
156        verified,
157        platform,
158        policy_result,
159        signature_valid,
160        cert_chain_valid,
161        nonce_valid,
162        report_age_valid,
163        failures,
164    })
165}
166
167/// Verify that the report's report_data field contains the expected nonce.
168///
169/// The report_data is at offset 0x50 in the SNP report, 64 bytes.
170/// The nonce is typically a SHA-512 hash of (verifier_nonce || optional_data).
171fn verify_nonce(report: &[u8], expected_nonce: &[u8]) -> bool {
172    if report.len() < 0x50 + 64 {
173        return false;
174    }
175
176    let report_data = &report[0x50..0x50 + 64];
177
178    // Compare the nonce portion. If expected_nonce is shorter than 64 bytes,
179    // only compare the prefix (remaining bytes may contain additional binding data).
180    let compare_len = expected_nonce.len().min(64);
181    report_data[..compare_len] == expected_nonce[..compare_len]
182}
183
184/// Verify the ECDSA-P384 signature on the SNP report using the VCEK public key.
185///
186/// The signature is the last 512 bytes of the report (offset 0x2A0).
187/// The signed data is everything before the signature (bytes 0x000..0x2A0).
188///
189/// The VCEK certificate contains the P-384 public key that signs the report.
190fn verify_report_signature(report: &[u8], vcek_der: &[u8]) -> bool {
191    if report.len() < SNP_REPORT_SIZE || vcek_der.is_empty() {
192        tracing::warn!(
193            report_len = report.len(),
194            vcek_len = vcek_der.len(),
195            "Cannot verify signature: invalid input sizes"
196        );
197        return false;
198    }
199
200    // The signed portion is bytes [0x00..0x2A0] (672 bytes)
201    let signed_data = &report[..0x2A0];
202
203    // The signature is at offset 0x2A0:
204    //   r: 72 bytes at 0x2A0
205    //   s: 72 bytes at 0x2E8
206    // Both are zero-padded big-endian integers (P-384 = 48 bytes each)
207    let r_bytes = &report[0x2A0..0x2A0 + 72];
208    let s_bytes = &report[0x2A0 + 72..0x2A0 + 144];
209
210    // Extract the actual 48-byte values (skip leading zero padding)
211    let r_trimmed = trim_leading_zeros(r_bytes, 48);
212    let s_trimmed = trim_leading_zeros(s_bytes, 48);
213
214    match verify_p384_signature(signed_data, r_trimmed, s_trimmed, vcek_der) {
215        Ok(valid) => valid,
216        Err(e) => {
217            tracing::warn!("Signature verification error: {}", e);
218            false
219        }
220    }
221}
222
223/// Trim leading zeros from a byte slice, keeping at least `min_len` bytes.
224fn trim_leading_zeros(bytes: &[u8], min_len: usize) -> &[u8] {
225    let start = bytes
226        .iter()
227        .position(|&b| b != 0)
228        .unwrap_or(bytes.len().saturating_sub(min_len));
229    let start = start.min(bytes.len().saturating_sub(min_len));
230    &bytes[start..]
231}
232
233/// Verify a P-384 ECDSA signature using the public key from a DER-encoded certificate.
234fn verify_p384_signature(
235    message: &[u8],
236    r: &[u8],
237    s: &[u8],
238    cert_der: &[u8],
239) -> std::result::Result<bool, String> {
240    use der::Decode;
241    use p384::ecdsa::{signature::Verifier, Signature, VerifyingKey};
242    use x509_cert::Certificate;
243
244    // Parse the X.509 certificate
245    let cert = Certificate::from_der(cert_der)
246        .map_err(|e| format!("Failed to parse VCEK certificate: {}", e))?;
247
248    // Extract the public key from the certificate
249    let spki = cert.tbs_certificate.subject_public_key_info;
250    let pub_key_bytes = spki
251        .subject_public_key
252        .as_bytes()
253        .ok_or("Failed to extract public key bytes from VCEK")?;
254
255    // Create the P-384 verifying key
256    let verifying_key = VerifyingKey::from_sec1_bytes(pub_key_bytes)
257        .map_err(|e| format!("Failed to create P-384 verifying key: {}", e))?;
258
259    // Build the signature from r and s components
260    // P-384 signature is 96 bytes (48 bytes r + 48 bytes s)
261    let mut sig_bytes = [0u8; 96];
262    // Right-align r and s in their 48-byte slots
263    let r_offset = 48usize.saturating_sub(r.len());
264    let s_offset = 48usize.saturating_sub(s.len());
265    sig_bytes[r_offset..48].copy_from_slice(&r[r.len().saturating_sub(48)..]);
266    sig_bytes[48 + s_offset..96].copy_from_slice(&s[s.len().saturating_sub(48)..]);
267
268    let signature = Signature::from_slice(&sig_bytes)
269        .map_err(|e| format!("Failed to parse ECDSA signature: {}", e))?;
270
271    // Verify: the SNP report is signed over raw bytes (not hashed first by us —
272    // the hardware uses SHA-384 internally before signing)
273    match verifying_key.verify(message, &signature) {
274        Ok(()) => Ok(true),
275        Err(_) => Ok(false),
276    }
277}
278
279/// Verify the certificate chain: VCEK → ASK → ARK.
280///
281/// Checks that:
282/// 1. Each certificate is a valid X.509 certificate
283/// 2. Issuer/subject names match across the chain
284/// 3. VCEK is signed by ASK, ASK is signed by ARK, ARK is self-signed
285///    (each verified with the issuer key's own algorithm — RSASSA-PSS/SHA-384
286///    for AMD's RSA ARK/ASK, ECDSA-P384 for an EC issuer)
287/// 4. **The ARK is a pinned genuine AMD root** ([`super::ark_roots`]) — without
288///    this, a self-minted but internally-consistent chain would pass.
289fn verify_cert_chain(vcek_der: &[u8], ask_der: &[u8], ark_der: &[u8]) -> bool {
290    use der::Decode;
291    use x509_cert::Certificate;
292
293    // A missing cert means the chain cannot be verified — fail closed. There is
294    // no KDS-fetch fallback in this path, so an absent VCEK/ASK/ARK is NOT a
295    // "skip and trust"; it is an unverifiable chain. (Returning true for the
296    // all-empty case was a latent fail-open, masked only by the separate report
297    // signature check rejecting an empty VCEK.)
298    if vcek_der.is_empty() || ask_der.is_empty() || ark_der.is_empty() {
299        tracing::warn!("Certificate chain incomplete — cannot verify, rejecting");
300        return false;
301    }
302
303    // Parse all three certificates
304    let vcek = match Certificate::from_der(vcek_der) {
305        Ok(c) => c,
306        Err(e) => {
307            tracing::warn!("Failed to parse VCEK certificate: {}", e);
308            return false;
309        }
310    };
311
312    let ask = match Certificate::from_der(ask_der) {
313        Ok(c) => c,
314        Err(e) => {
315            tracing::warn!("Failed to parse ASK certificate: {}", e);
316            return false;
317        }
318    };
319
320    let ark = match Certificate::from_der(ark_der) {
321        Ok(c) => c,
322        Err(e) => {
323            tracing::warn!("Failed to parse ARK certificate: {}", e);
324            return false;
325        }
326    };
327
328    // Verify issuer/subject chain:
329    // VCEK.issuer == ASK.subject
330    if vcek.tbs_certificate.issuer != ask.tbs_certificate.subject {
331        tracing::warn!("VCEK issuer does not match ASK subject");
332        return false;
333    }
334
335    // ASK.issuer == ARK.subject
336    if ask.tbs_certificate.issuer != ark.tbs_certificate.subject {
337        tracing::warn!("ASK issuer does not match ARK subject");
338        return false;
339    }
340
341    // ARK should be self-signed: ARK.issuer == ARK.subject
342    if ark.tbs_certificate.issuer != ark.tbs_certificate.subject {
343        tracing::warn!("ARK is not self-signed");
344        return false;
345    }
346
347    // Verify ECDSA-P384 signatures across the chain.
348    // Each certificate's tbsCertificate is signed by the issuer's private key.
349
350    // ARK is self-signed: verify ARK signature with ARK's own public key
351    if !verify_cert_signature(&ark, &ark) {
352        tracing::warn!("ARK self-signature verification failed");
353        return false;
354    }
355
356    // ASK is signed by ARK
357    if !verify_cert_signature(&ask, &ark) {
358        tracing::warn!("ASK signature verification failed (not signed by ARK)");
359        return false;
360    }
361
362    // VCEK is signed by ASK
363    if !verify_cert_signature(&vcek, &ask) {
364        tracing::warn!("VCEK signature verification failed (not signed by ASK)");
365        return false;
366    }
367
368    // Trust anchor: the chain must terminate at a GENUINE AMD ARK root, not a
369    // self-minted one. Internal self-consistency is necessary but NOT
370    // sufficient — without pinning, an attacker's own self-signed
371    // ARK → ASK → VCEK chain (signing a forged report) passes every check
372    // above. Pinning the ARK to AMD's published roots closes that fail-open.
373    if !super::ark_roots::is_trusted_ark(ark_der) {
374        tracing::warn!(
375            "ARK is not a pinned genuine AMD root key — rejecting (possible self-minted chain)"
376        );
377        return false;
378    }
379
380    true
381}
382
383/// Verify that `cert` was signed by `issuer`.
384///
385/// The verification primitive is chosen by the **issuer's** public-key
386/// algorithm: AMD's ARK and ASK are RSA-4096 (RSASSA-PSS / SHA-384), while only
387/// the leaf VCEK is ECDSA-P384. A verifier that only knew ECDSA would silently
388/// reject every genuine AMD chain — and "validate" a synthetic all-ECDSA one,
389/// which is exactly the footgun that lets a self-minted chain look legitimate.
390fn verify_cert_signature(cert: &x509_cert::Certificate, issuer: &x509_cert::Certificate) -> bool {
391    use der::Encode;
392
393    // Encode the tbsCertificate to DER (this is the signed data).
394    let tbs_der = match cert.tbs_certificate.to_der() {
395        Ok(d) => d,
396        Err(e) => {
397            tracing::warn!("Failed to encode tbsCertificate to DER: {}", e);
398            return false;
399        }
400    };
401
402    // Extract the signature bytes from the certificate.
403    let sig_bytes = match cert.signature.as_bytes() {
404        Some(b) => b,
405        None => {
406            tracing::warn!("Failed to extract signature bytes from certificate");
407            return false;
408        }
409    };
410
411    // Extract the issuer's public key bytes (DER RSAPublicKey for RSA, or the
412    // SEC1 point for EC).
413    let issuer_spki = &issuer.tbs_certificate.subject_public_key_info;
414    let issuer_pub_key_bytes = match issuer_spki.subject_public_key.as_bytes() {
415        Some(b) => b,
416        None => {
417            tracing::warn!("Failed to extract issuer public key bytes");
418            return false;
419        }
420    };
421
422    match issuer_spki.algorithm.oid.to_string().as_str() {
423        // rsaEncryption | rsassaPss → RSASSA-PSS with SHA-384 (AMD ARK/ASK).
424        "1.2.840.113549.1.1.1" | "1.2.840.113549.1.1.10" => {
425            verify_rsa_pss_sha384(&tbs_der, sig_bytes, issuer_pub_key_bytes)
426        }
427        // id-ecPublicKey → ECDSA-P384 (e.g. an EC-signed VCEK).
428        "1.2.840.10045.2.1" => verify_ecdsa_p384_cert(&tbs_der, sig_bytes, issuer_pub_key_bytes),
429        other => {
430            tracing::warn!("Unsupported issuer key algorithm OID: {other}");
431            false
432        }
433    }
434}
435
436/// Verify an RSASSA-PSS / SHA-384 signature (AMD ARK/ASK) over `message` using a
437/// DER `RSAPublicKey` (the SPKI subjectPublicKey contents for an RSA issuer).
438fn verify_rsa_pss_sha384(message: &[u8], signature: &[u8], rsa_pub_der: &[u8]) -> bool {
439    use ring::signature;
440    let key = signature::UnparsedPublicKey::new(&signature::RSA_PSS_2048_8192_SHA384, rsa_pub_der);
441    key.verify(message, signature).is_ok()
442}
443
444/// Verify an ECDSA-P384 certificate signature using a DER-encoded ECDSA
445/// signature and a SEC1-encoded EC public key.
446fn verify_ecdsa_p384_cert(message: &[u8], sig_der: &[u8], ec_pub_sec1: &[u8]) -> bool {
447    use p384::ecdsa::{signature::Verifier, DerSignature, VerifyingKey};
448
449    let signature = match DerSignature::from_bytes(sig_der) {
450        Ok(s) => s,
451        Err(e) => {
452            tracing::warn!("Failed to parse certificate ECDSA signature: {}", e);
453            return false;
454        }
455    };
456    let verifying_key = match VerifyingKey::from_sec1_bytes(ec_pub_sec1) {
457        Ok(k) => k,
458        Err(e) => {
459            tracing::warn!("Failed to create P-384 verifying key from issuer: {}", e);
460            return false;
461        }
462    };
463    verifying_key.verify(message, &signature).is_ok()
464}
465
466/// Check the attestation report against the verification policy.
467fn check_policy(platform: &PlatformInfo, policy: &AttestationPolicy) -> PolicyResult {
468    let mut violations = Vec::new();
469
470    // Check measurement
471    if let Some(ref expected) = policy.expected_measurement {
472        if platform.measurement != *expected {
473            violations.push(PolicyViolation {
474                check: "measurement".to_string(),
475                reason: format!(
476                    "Expected {}, got {}",
477                    &expected[..expected.len().min(16)],
478                    &platform.measurement[..platform.measurement.len().min(16)],
479                ),
480            });
481        }
482    }
483
484    // Check debug mode (bit 19 of guest policy = debug enabled)
485    if policy.require_no_debug {
486        let debug_enabled = (platform.policy >> 19) & 1 == 1;
487        if debug_enabled {
488            violations.push(PolicyViolation {
489                check: "debug".to_string(),
490                reason: "Debug mode is enabled (policy bit 19 set)".to_string(),
491            });
492        }
493    }
494
495    // Check SMT (bit 16 of guest policy = SMT allowed)
496    if policy.require_no_smt {
497        let smt_allowed = (platform.policy >> 16) & 1 == 1;
498        if smt_allowed {
499            violations.push(PolicyViolation {
500                check: "smt".to_string(),
501                reason: "SMT is enabled (policy bit 16 set)".to_string(),
502            });
503        }
504    }
505
506    // Check TCB version minimums
507    if let Some(ref min_tcb) = policy.min_tcb {
508        let tcb = &platform.tcb_version;
509
510        if let Some(min_bl) = min_tcb.boot_loader {
511            if tcb.boot_loader < min_bl {
512                violations.push(PolicyViolation {
513                    check: "tcb.boot_loader".to_string(),
514                    reason: format!("Boot loader SVN {} < minimum {}", tcb.boot_loader, min_bl),
515                });
516            }
517        }
518
519        if let Some(min_tee) = min_tcb.tee {
520            if tcb.tee < min_tee {
521                violations.push(PolicyViolation {
522                    check: "tcb.tee".to_string(),
523                    reason: format!("TEE SVN {} < minimum {}", tcb.tee, min_tee),
524                });
525            }
526        }
527
528        if let Some(min_snp) = min_tcb.snp {
529            if tcb.snp < min_snp {
530                violations.push(PolicyViolation {
531                    check: "tcb.snp".to_string(),
532                    reason: format!("SNP SVN {} < minimum {}", tcb.snp, min_snp),
533                });
534            }
535        }
536
537        if let Some(min_uc) = min_tcb.microcode {
538            if tcb.microcode < min_uc {
539                violations.push(PolicyViolation {
540                    check: "tcb.microcode".to_string(),
541                    reason: format!("Microcode SVN {} < minimum {}", tcb.microcode, min_uc),
542                });
543            }
544        }
545    }
546
547    // Check allowed policy mask
548    if let Some(mask) = policy.allowed_policy_mask {
549        if platform.policy & mask != mask {
550            violations.push(PolicyViolation {
551                check: "policy_mask".to_string(),
552                reason: format!(
553                    "Guest policy {:#x} does not satisfy mask {:#x}",
554                    platform.policy, mask,
555                ),
556            });
557        }
558    }
559
560    PolicyResult::from_violations(violations)
561}
562
563/// Check report age for replay protection.
564///
565/// SNP reports don't contain a hardware timestamp, so we rely on the
566/// application layer: the verifier records when the nonce was issued
567/// (`nonce_issued_at`) and checks that the current time minus that
568/// timestamp doesn't exceed `policy.max_report_age_secs`.
569///
570/// Returns `true` if the age check passes or is skipped.
571fn check_report_age(
572    policy: &AttestationPolicy,
573    nonce_issued_at: Option<u64>,
574    failures: &mut Vec<String>,
575) -> bool {
576    let max_age = match policy.max_report_age_secs {
577        Some(max) => max,
578        None => return true, // No age limit configured
579    };
580
581    let issued_at = match nonce_issued_at {
582        Some(t) => t,
583        None => {
584            // Policy requires age check but no timestamp was provided.
585            // This is a configuration issue, not a security failure —
586            // the caller should pass nonce_issued_at when using max_report_age_secs.
587            tracing::warn!(
588                "max_report_age_secs={} set but nonce_issued_at not provided, skipping age check",
589                max_age
590            );
591            return true;
592        }
593    };
594
595    let now = std::time::SystemTime::now()
596        .duration_since(std::time::UNIX_EPOCH)
597        .map(|d| d.as_secs())
598        .unwrap_or(0);
599
600    if now < issued_at {
601        // Clock skew — nonce_issued_at is in the future
602        failures.push(format!(
603            "Report age check failed: nonce_issued_at ({}) is in the future (now={})",
604            issued_at, now
605        ));
606        return false;
607    }
608
609    let age = now - issued_at;
610    if age > max_age {
611        failures.push(format!(
612            "Report too old: age {}s exceeds maximum {}s (replay protection)",
613            age, max_age
614        ));
615        return false;
616    }
617
618    true
619}
620
621#[cfg(test)]
622mod tests {
623    use super::*;
624    use crate::tee::attestation::{CertificateChain, TcbVersion, SNP_REPORT_SIZE};
625    use crate::tee::policy::MinTcbPolicy;
626
627    /// Helper: create a minimal valid-looking SNP report with given nonce.
628    fn make_test_report(nonce: &[u8]) -> Vec<u8> {
629        let mut report = vec![0u8; SNP_REPORT_SIZE];
630        // version = 2
631        report[0x00] = 2;
632        // guest_svn = 1
633        report[0x04] = 1;
634        // Set report_data at offset 0x50
635        let len = nonce.len().min(64);
636        report[0x50..0x50 + len].copy_from_slice(&nonce[..len]);
637        // Set some measurement at 0x90
638        report[0x90] = 0xAA;
639        report[0x91] = 0xBB;
640        // TCB at 0x38
641        report[0x38] = 3; // boot_loader
642        report[0x3E] = 8; // snp
643        report[0x3F] = 115; // microcode
644        report
645    }
646
647    #[test]
648    fn test_verify_nonce_match() {
649        let nonce = vec![1, 2, 3, 4, 5, 6, 7, 8];
650        let report = make_test_report(&nonce);
651        assert!(verify_nonce(&report, &nonce));
652    }
653
654    #[test]
655    fn test_verify_nonce_mismatch() {
656        let nonce = vec![1, 2, 3, 4];
657        let report = make_test_report(&nonce);
658        let wrong_nonce = vec![9, 9, 9, 9];
659        assert!(!verify_nonce(&report, &wrong_nonce));
660    }
661
662    #[test]
663    fn test_verify_nonce_report_too_short() {
664        let report = vec![0u8; 10];
665        assert!(!verify_nonce(&report, &[1, 2, 3]));
666    }
667
668    #[test]
669    fn test_trim_leading_zeros() {
670        assert_eq!(trim_leading_zeros(&[0, 0, 0, 1, 2, 3], 3), &[1, 2, 3]);
671        assert_eq!(trim_leading_zeros(&[0, 0, 0, 0], 2), &[0, 0]);
672        assert_eq!(trim_leading_zeros(&[1, 2, 3], 3), &[1, 2, 3]);
673        assert_eq!(trim_leading_zeros(&[0, 1], 1), &[1]);
674    }
675
676    #[test]
677    fn test_verify_report_signature_empty_vcek() {
678        let report = vec![0u8; SNP_REPORT_SIZE];
679        assert!(!verify_report_signature(&report, &[]));
680    }
681
682    #[test]
683    fn test_verify_report_signature_short_report() {
684        assert!(!verify_report_signature(&[0u8; 100], &[1, 2, 3]));
685    }
686
687    #[test]
688    fn test_verify_cert_chain_empty_fails_closed() {
689        // An empty chain is unverifiable, not "trusted by default" — fail closed
690        // (closes a latent fail-open where all-empty returned true).
691        assert!(!verify_cert_chain(&[], &[], &[]));
692    }
693
694    #[test]
695    fn test_verify_cert_chain_partially_empty() {
696        // Partially empty = inconsistent, should fail
697        assert!(!verify_cert_chain(&[1], &[], &[]));
698        assert!(!verify_cert_chain(&[], &[1], &[]));
699        assert!(!verify_cert_chain(&[], &[], &[1]));
700    }
701
702    // ========================================================================
703    // Certificate chain ECDSA signature verification tests
704    // ========================================================================
705
706    /// Generate a 3-level P-384 certificate chain: ARK (root) → ASK → VCEK.
707    /// Returns (vcek_der, ask_der, ark_der).
708    fn make_test_cert_chain() -> (Vec<u8>, Vec<u8>, Vec<u8>) {
709        use rcgen::{
710            BasicConstraints, CertificateParams, DistinguishedName, DnType, IsCa, KeyPair,
711            KeyUsagePurpose, PKCS_ECDSA_P384_SHA384,
712        };
713
714        // ARK (root CA, self-signed)
715        let ark_key = KeyPair::generate_for(&PKCS_ECDSA_P384_SHA384).unwrap();
716        let mut ark_params = CertificateParams::default();
717        let mut ark_dn = DistinguishedName::new();
718        ark_dn.push(DnType::CommonName, "AMD SEV ARK");
719        ark_dn.push(DnType::OrganizationName, "AMD");
720        ark_params.distinguished_name = ark_dn.clone();
721        ark_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
722        ark_params.key_usages = vec![KeyUsagePurpose::KeyCertSign];
723        let ark_cert = ark_params.self_signed(&ark_key).unwrap();
724
725        // ASK (intermediate CA, signed by ARK)
726        let ask_key = KeyPair::generate_for(&PKCS_ECDSA_P384_SHA384).unwrap();
727        let mut ask_params = CertificateParams::default();
728        let mut ask_dn = DistinguishedName::new();
729        ask_dn.push(DnType::CommonName, "AMD SEV ASK");
730        ask_dn.push(DnType::OrganizationName, "AMD");
731        ask_params.distinguished_name = ask_dn;
732        ask_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
733        ask_params.key_usages = vec![KeyUsagePurpose::KeyCertSign];
734        let ask_cert = ask_params.signed_by(&ask_key, &ark_cert, &ark_key).unwrap();
735
736        // VCEK (leaf, signed by ASK)
737        let vcek_key = KeyPair::generate_for(&PKCS_ECDSA_P384_SHA384).unwrap();
738        let mut vcek_params = CertificateParams::default();
739        let mut vcek_dn = DistinguishedName::new();
740        vcek_dn.push(DnType::CommonName, "AMD SEV VCEK");
741        vcek_dn.push(DnType::OrganizationName, "AMD");
742        vcek_params.distinguished_name = vcek_dn;
743        vcek_params.is_ca = IsCa::NoCa;
744        vcek_params.key_usages = vec![KeyUsagePurpose::DigitalSignature];
745        let vcek_cert = vcek_params
746            .signed_by(&vcek_key, &ask_cert, &ask_key)
747            .unwrap();
748
749        (
750            vcek_cert.der().to_vec(),
751            ask_cert.der().to_vec(),
752            ark_cert.der().to_vec(),
753        )
754    }
755
756    #[test]
757    fn test_verify_cert_chain_rejects_untrusted_synthetic_root() {
758        // The synthetic chain's signatures are all internally consistent (it is
759        // a genuine ECDSA chain), so it passes every signature/issuer check —
760        // but its ARK is self-minted, not a pinned AMD root, so it MUST be
761        // rejected. This is precisely the fail-open that pinning closes: an
762        // attacker who mints their own ARK → ASK → VCEK and signs a forged
763        // report can no longer pass verification.
764        let (vcek, ask, ark) = make_test_cert_chain();
765        assert!(
766            !crate::tee::ark_roots::is_trusted_ark(&ark),
767            "a self-minted ARK must not be trusted"
768        );
769        assert!(
770            !verify_cert_chain(&vcek, &ask, &ark),
771            "an internally-consistent but self-minted chain must be rejected"
772        );
773    }
774
775    #[test]
776    fn test_genuine_amd_ark_self_signature_verifies_via_rsa_pss() {
777        use crate::tee::ark_roots::AMD_ARK_ROOTS;
778        use der::Decode;
779        // The real AMD ARKs are RSA-4096 / RSASSA-PSS-SHA384. This exercises the
780        // RSA path of verify_cert_signature against genuine AMD certificates
781        // (the ECDSA-only predecessor would have rejected all of them).
782        for ark_der in AMD_ARK_ROOTS {
783            let ark = x509_cert::Certificate::from_der(ark_der).unwrap();
784            assert!(
785                verify_cert_signature(&ark, &ark),
786                "a genuine AMD ARK self-signature must verify via RSASSA-PSS/SHA-384"
787            );
788        }
789    }
790
791    #[test]
792    fn test_verify_cert_chain_wrong_ark_rejects() {
793        let (vcek, ask, _ark) = make_test_cert_chain();
794        // Generate a different ARK (different key pair)
795        let (_, _, wrong_ark) = make_test_cert_chain();
796        // ASK was signed by the original ARK, not this one
797        assert!(!verify_cert_chain(&vcek, &ask, &wrong_ark));
798    }
799
800    #[test]
801    fn test_verify_cert_chain_wrong_ask_rejects() {
802        let (vcek, _ask, ark) = make_test_cert_chain();
803        // Generate a different chain and use its ASK
804        let (_, wrong_ask, _) = make_test_cert_chain();
805        // VCEK was signed by the original ASK, not this one
806        assert!(!verify_cert_chain(&vcek, &wrong_ask, &ark));
807    }
808
809    #[test]
810    fn test_verify_cert_chain_swapped_ask_ark_rejects() {
811        let (vcek, ask, ark) = make_test_cert_chain();
812        // Swap ASK and ARK — should fail because ARK won't be self-signed
813        // and signatures won't match
814        assert!(!verify_cert_chain(&vcek, &ark, &ask));
815    }
816
817    #[test]
818    fn test_verify_cert_signature_self_signed() {
819        use der::Decode;
820        use rcgen::{
821            CertificateParams, DistinguishedName, DnType, KeyPair, PKCS_ECDSA_P384_SHA384,
822        };
823
824        let key = KeyPair::generate_for(&PKCS_ECDSA_P384_SHA384).unwrap();
825        let mut params = CertificateParams::default();
826        let mut dn = DistinguishedName::new();
827        dn.push(DnType::CommonName, "Test Self-Signed");
828        params.distinguished_name = dn;
829        let cert = params.self_signed(&key).unwrap();
830
831        let parsed = x509_cert::Certificate::from_der(cert.der()).unwrap();
832        assert!(verify_cert_signature(&parsed, &parsed));
833    }
834
835    #[test]
836    fn test_verify_cert_signature_wrong_issuer_rejects() {
837        use der::Decode;
838        use rcgen::{
839            CertificateParams, DistinguishedName, DnType, KeyPair, PKCS_ECDSA_P384_SHA384,
840        };
841
842        // Two independent self-signed certs
843        let key1 = KeyPair::generate_for(&PKCS_ECDSA_P384_SHA384).unwrap();
844        let mut params1 = CertificateParams::default();
845        let mut dn1 = DistinguishedName::new();
846        dn1.push(DnType::CommonName, "Cert A");
847        params1.distinguished_name = dn1;
848        let cert1 = params1.self_signed(&key1).unwrap();
849
850        let key2 = KeyPair::generate_for(&PKCS_ECDSA_P384_SHA384).unwrap();
851        let mut params2 = CertificateParams::default();
852        let mut dn2 = DistinguishedName::new();
853        dn2.push(DnType::CommonName, "Cert B");
854        params2.distinguished_name = dn2;
855        let cert2 = params2.self_signed(&key2).unwrap();
856
857        let parsed1 = x509_cert::Certificate::from_der(cert1.der()).unwrap();
858        let parsed2 = x509_cert::Certificate::from_der(cert2.der()).unwrap();
859
860        // cert1 was NOT signed by cert2's key
861        assert!(!verify_cert_signature(&parsed1, &parsed2));
862    }
863
864    #[test]
865    fn test_check_policy_pass_default() {
866        let platform = PlatformInfo {
867            version: 2,
868            guest_svn: 1,
869            policy: 0, // no debug, no SMT
870            measurement: "aabb".repeat(24),
871            tcb_version: TcbVersion {
872                boot_loader: 3,
873                tee: 0,
874                snp: 8,
875                microcode: 115,
876            },
877            chip_id: "00".repeat(64),
878        };
879        let policy = AttestationPolicy::default();
880        let result = check_policy(&platform, &policy);
881        assert!(result.passed);
882        assert!(result.violations.is_empty());
883    }
884
885    #[test]
886    fn test_check_policy_debug_violation() {
887        let platform = PlatformInfo {
888            policy: 1 << 19, // debug enabled
889            ..Default::default()
890        };
891        let policy = AttestationPolicy {
892            require_no_debug: true,
893            ..Default::default()
894        };
895        let result = check_policy(&platform, &policy);
896        assert!(!result.passed);
897        assert!(result.violations.iter().any(|v| v.check == "debug"));
898    }
899
900    #[test]
901    fn test_check_policy_smt_violation() {
902        let platform = PlatformInfo {
903            policy: 1 << 16, // SMT enabled
904            ..Default::default()
905        };
906        let policy = AttestationPolicy {
907            require_no_debug: false,
908            require_no_smt: true,
909            ..Default::default()
910        };
911        let result = check_policy(&platform, &policy);
912        assert!(!result.passed);
913        assert!(result.violations.iter().any(|v| v.check == "smt"));
914    }
915
916    #[test]
917    fn test_check_policy_measurement_mismatch() {
918        let platform = PlatformInfo {
919            measurement: "aa".repeat(48),
920            ..Default::default()
921        };
922        let policy = AttestationPolicy {
923            expected_measurement: Some("bb".repeat(48)),
924            require_no_debug: false,
925            ..Default::default()
926        };
927        let result = check_policy(&platform, &policy);
928        assert!(!result.passed);
929        assert!(result.violations.iter().any(|v| v.check == "measurement"));
930    }
931
932    #[test]
933    fn test_check_policy_measurement_match() {
934        let m = "aa".repeat(48);
935        let platform = PlatformInfo {
936            measurement: m.clone(),
937            ..Default::default()
938        };
939        let policy = AttestationPolicy {
940            expected_measurement: Some(m),
941            require_no_debug: false,
942            ..Default::default()
943        };
944        let result = check_policy(&platform, &policy);
945        assert!(result.passed);
946    }
947
948    #[test]
949    fn test_check_policy_tcb_violation() {
950        let platform = PlatformInfo {
951            tcb_version: TcbVersion {
952                boot_loader: 2,
953                tee: 0,
954                snp: 5,
955                microcode: 100,
956            },
957            ..Default::default()
958        };
959        let policy = AttestationPolicy {
960            require_no_debug: false,
961            min_tcb: Some(MinTcbPolicy {
962                snp: Some(8),        // requires 8, got 5
963                microcode: Some(93), // requires 93, got 100 (ok)
964                ..Default::default()
965            }),
966            ..Default::default()
967        };
968        let result = check_policy(&platform, &policy);
969        assert!(!result.passed);
970        assert!(result.violations.iter().any(|v| v.check == "tcb.snp"));
971        assert!(!result.violations.iter().any(|v| v.check == "tcb.microcode"));
972    }
973
974    #[test]
975    fn test_check_policy_mask_violation() {
976        let platform = PlatformInfo {
977            policy: 0x30, // bits 4,5 set
978            ..Default::default()
979        };
980        let policy = AttestationPolicy {
981            require_no_debug: false,
982            allowed_policy_mask: Some(0x70), // requires bits 4,5,6
983            ..Default::default()
984        };
985        let result = check_policy(&platform, &policy);
986        assert!(!result.passed);
987        assert!(result.violations.iter().any(|v| v.check == "policy_mask"));
988    }
989
990    #[test]
991    fn test_verify_attestation_nonce_mismatch() {
992        let nonce = vec![1, 2, 3, 4];
993        let report_bytes = make_test_report(&nonce);
994        let report = AttestationReport {
995            report: report_bytes,
996            cert_chain: CertificateChain::default(),
997            platform: PlatformInfo::default(),
998        };
999        let wrong_nonce = vec![9, 9, 9, 9];
1000        let policy = AttestationPolicy {
1001            require_no_debug: false,
1002            ..Default::default()
1003        };
1004        let result = verify_attestation(&report, &wrong_nonce, &policy, false).unwrap();
1005        assert!(!result.verified);
1006        assert!(!result.nonce_valid);
1007    }
1008
1009    #[test]
1010    fn test_verify_attestation_invalid_report_size() {
1011        let report = AttestationReport {
1012            report: vec![0u8; 100], // too short
1013            cert_chain: CertificateChain::default(),
1014            platform: PlatformInfo::default(),
1015        };
1016        let result = verify_attestation(&report, &[1, 2, 3], &AttestationPolicy::default(), false);
1017        assert!(result.is_err());
1018    }
1019
1020    #[test]
1021    fn test_verify_simulated_report_accepted() {
1022        let nonce = vec![1, 2, 3, 4];
1023        let mut report_data = [0u8; 64];
1024        report_data[..4].copy_from_slice(&nonce);
1025        let report_bytes = crate::tee::simulate::build_simulated_report(&report_data);
1026        let report = AttestationReport {
1027            report: report_bytes,
1028            cert_chain: CertificateChain::default(),
1029            platform: PlatformInfo::default(),
1030        };
1031        let policy = AttestationPolicy {
1032            require_no_debug: false,
1033            ..Default::default()
1034        };
1035        let result = verify_attestation(&report, &nonce, &policy, true).unwrap();
1036        assert!(result.verified);
1037        assert!(result.signature_valid);
1038        assert!(result.cert_chain_valid);
1039        assert!(result.nonce_valid);
1040    }
1041
1042    #[test]
1043    fn test_verify_simulated_report_rejected_when_not_allowed() {
1044        let nonce = vec![1, 2, 3, 4];
1045        let mut report_data = [0u8; 64];
1046        report_data[..4].copy_from_slice(&nonce);
1047        let report_bytes = crate::tee::simulate::build_simulated_report(&report_data);
1048        let report = AttestationReport {
1049            report: report_bytes,
1050            cert_chain: CertificateChain::default(),
1051            platform: PlatformInfo::default(),
1052        };
1053        let policy = AttestationPolicy::default();
1054        let result = verify_attestation(&report, &nonce, &policy, false);
1055        assert!(result.is_err());
1056    }
1057
1058    #[test]
1059    fn test_verify_simulated_report_nonce_still_checked() {
1060        let nonce = vec![1, 2, 3, 4];
1061        let mut report_data = [0u8; 64];
1062        report_data[..4].copy_from_slice(&nonce);
1063        let report_bytes = crate::tee::simulate::build_simulated_report(&report_data);
1064        let report = AttestationReport {
1065            report: report_bytes,
1066            cert_chain: CertificateChain::default(),
1067            platform: PlatformInfo::default(),
1068        };
1069        let wrong_nonce = vec![9, 9, 9, 9];
1070        let policy = AttestationPolicy {
1071            require_no_debug: false,
1072            ..Default::default()
1073        };
1074        let result = verify_attestation(&report, &wrong_nonce, &policy, true).unwrap();
1075        assert!(!result.verified);
1076        assert!(!result.nonce_valid);
1077    }
1078
1079    // ========================================================================
1080    // Report age checking tests
1081    // ========================================================================
1082
1083    fn now_secs() -> u64 {
1084        std::time::SystemTime::now()
1085            .duration_since(std::time::UNIX_EPOCH)
1086            .unwrap()
1087            .as_secs()
1088    }
1089
1090    #[test]
1091    fn test_check_report_age_no_policy() {
1092        // No max_report_age_secs → always passes
1093        let policy = AttestationPolicy {
1094            require_no_debug: false,
1095            ..Default::default()
1096        };
1097        let mut failures = Vec::new();
1098        assert!(check_report_age(
1099            &policy,
1100            Some(now_secs() - 9999),
1101            &mut failures
1102        ));
1103        assert!(failures.is_empty());
1104    }
1105
1106    #[test]
1107    fn test_check_report_age_no_timestamp() {
1108        // max_report_age_secs set but no timestamp → skip (warn, pass)
1109        let policy = AttestationPolicy {
1110            require_no_debug: false,
1111            max_report_age_secs: Some(60),
1112            ..Default::default()
1113        };
1114        let mut failures = Vec::new();
1115        assert!(check_report_age(&policy, None, &mut failures));
1116        assert!(failures.is_empty());
1117    }
1118
1119    #[test]
1120    fn test_check_report_age_fresh_report() {
1121        let policy = AttestationPolicy {
1122            require_no_debug: false,
1123            max_report_age_secs: Some(60),
1124            ..Default::default()
1125        };
1126        let mut failures = Vec::new();
1127        // Issued 5 seconds ago
1128        assert!(check_report_age(
1129            &policy,
1130            Some(now_secs() - 5),
1131            &mut failures
1132        ));
1133        assert!(failures.is_empty());
1134    }
1135
1136    #[test]
1137    fn test_check_report_age_stale_report() {
1138        let policy = AttestationPolicy {
1139            require_no_debug: false,
1140            max_report_age_secs: Some(60),
1141            ..Default::default()
1142        };
1143        let mut failures = Vec::new();
1144        // Issued 120 seconds ago, max is 60
1145        assert!(!check_report_age(
1146            &policy,
1147            Some(now_secs() - 120),
1148            &mut failures
1149        ));
1150        assert!(failures.len() == 1);
1151        assert!(failures[0].contains("too old"));
1152    }
1153
1154    #[test]
1155    fn test_check_report_age_future_timestamp() {
1156        let policy = AttestationPolicy {
1157            require_no_debug: false,
1158            max_report_age_secs: Some(60),
1159            ..Default::default()
1160        };
1161        let mut failures = Vec::new();
1162        // Issued in the future (clock skew)
1163        assert!(!check_report_age(
1164            &policy,
1165            Some(now_secs() + 3600),
1166            &mut failures
1167        ));
1168        assert!(failures[0].contains("future"));
1169    }
1170
1171    #[test]
1172    fn test_check_report_age_exact_boundary() {
1173        let policy = AttestationPolicy {
1174            require_no_debug: false,
1175            max_report_age_secs: Some(60),
1176            ..Default::default()
1177        };
1178        let mut failures = Vec::new();
1179        // Issued exactly at the boundary — age == max, should pass (not strictly greater)
1180        assert!(check_report_age(
1181            &policy,
1182            Some(now_secs() - 60),
1183            &mut failures
1184        ));
1185        assert!(failures.is_empty());
1186    }
1187
1188    #[test]
1189    fn test_verify_attestation_with_time_fresh() {
1190        let nonce = vec![1, 2, 3, 4];
1191        let mut report_data = [0u8; 64];
1192        report_data[..4].copy_from_slice(&nonce);
1193        let report_bytes = crate::tee::simulate::build_simulated_report(&report_data);
1194        let report = AttestationReport {
1195            report: report_bytes,
1196            cert_chain: CertificateChain::default(),
1197            platform: PlatformInfo::default(),
1198        };
1199        let policy = AttestationPolicy {
1200            require_no_debug: false,
1201            max_report_age_secs: Some(60),
1202            ..Default::default()
1203        };
1204        let result =
1205            verify_attestation_with_time(&report, &nonce, &policy, true, Some(now_secs() - 5))
1206                .unwrap();
1207        assert!(result.verified);
1208        assert!(result.report_age_valid);
1209    }
1210
1211    #[test]
1212    fn test_verify_attestation_with_time_stale() {
1213        let nonce = vec![1, 2, 3, 4];
1214        let mut report_data = [0u8; 64];
1215        report_data[..4].copy_from_slice(&nonce);
1216        let report_bytes = crate::tee::simulate::build_simulated_report(&report_data);
1217        let report = AttestationReport {
1218            report: report_bytes,
1219            cert_chain: CertificateChain::default(),
1220            platform: PlatformInfo::default(),
1221        };
1222        let policy = AttestationPolicy {
1223            require_no_debug: false,
1224            max_report_age_secs: Some(30),
1225            ..Default::default()
1226        };
1227        let result =
1228            verify_attestation_with_time(&report, &nonce, &policy, true, Some(now_secs() - 120))
1229                .unwrap();
1230        assert!(!result.verified);
1231        assert!(!result.report_age_valid);
1232    }
1233}