diff --git a/src/cmd/policysigned.rs b/src/cmd/policysigned.rs index 6c1ffea..b6b806e 100644 --- a/src/cmd/policysigned.rs +++ b/src/cmd/policysigned.rs @@ -5,21 +5,22 @@ use std::path::PathBuf; use anyhow::Context; use clap::Parser; use log::info; -use tss_esapi::constants::SessionType; -use tss_esapi::handles::{ObjectHandle, SessionHandle}; -use tss_esapi::structures::{Digest, Nonce, Signature}; +use tss_esapi::structures::Signature; use tss_esapi::traits::UnMarshall; -use tss_esapi::tss2_esys::TPMT_TK_AUTH; +use tss_esapi::tss2_esys::*; use crate::cli::GlobalOpts; -use crate::context::create_context; -use crate::handle::{ContextSource, load_object_from_source}; -use crate::parse::parse_context_source; -use crate::session::load_session_from_file; +use crate::parse::{self, parse_context_source}; +use crate::raw_esys::RawEsysContext; -/// Authorize a policy with a signed authorization. +use crate::handle::ContextSource; + +/// Extend a policy with PolicySigned. /// -/// Wraps TPM2_PolicySigned. +/// Wraps TPM2_PolicySigned (raw FFI). PolicySigned uses no +/// authorization sessions because the command has no authIndex. +/// Instead, the TPM validates the provided signature against +/// authObject's public key as part of the policy assertion. #[derive(Parser)] pub struct PolicySignedCmd { /// Policy session file @@ -43,8 +44,8 @@ pub struct PolicySignedCmd { pub cphash_input: Option, /// Policy reference (digest) (hex: or file:) - #[arg(short = 'q', long = "qualification", value_parser = crate::parse::parse_qualification)] - pub qualification: Option, + #[arg(short = 'q', long = "qualification", value_parser = parse::parse_qualification)] + pub qualification: Option, /// Output file for the timeout #[arg(short = 't', long = "timeout")] @@ -61,84 +62,160 @@ pub struct PolicySignedCmd { impl PolicySignedCmd { pub fn execute(&self, global: &GlobalOpts) -> anyhow::Result<()> { - let mut ctx = create_context(global.tcti.as_deref())?; + let mut raw = RawEsysContext::new(global.tcti.as_deref())?; - let session = load_session_from_file(&mut ctx, &self.session, SessionType::Policy)?; - let policy_session = session - .try_into() - .map_err(|_| anyhow::anyhow!("expected a policy session"))?; + let session_handle = raw.context_load( + self.session + .to_str() + .ok_or_else(|| anyhow::anyhow!("invalid session path"))?, + )?; - let auth_object = load_object_from_source(&mut ctx, &self.key_context)?; + let auth_object = raw.resolve_handle_from_source(&self.key_context)?; let sig_data = std::fs::read(&self.signature) .with_context(|| format!("reading signature from {}", self.signature.display()))?; let signature = Signature::unmarshall(&sig_data) .map_err(|e| anyhow::anyhow!("invalid signature: {e}"))?; + let tpmt_sig: TPMT_SIGNATURE = signature + .try_into() + .map_err(|e| anyhow::anyhow!("signature conversion: {e:?}"))?; + + let nonce_tpm = TPM2B_NONCE::default(); let cp_hash = match &self.cphash_input { Some(path) => { let data = std::fs::read(path)?; - Digest::try_from(data).map_err(|e| anyhow::anyhow!("invalid cpHash: {e}"))? + let mut buf = TPM2B_DIGEST::default(); + if data.len() > buf.buffer.len() { + anyhow::bail!( + "cpHash from {} is too large: {} bytes (maximum {} bytes)", + path.display(), + data.len(), + buf.buffer.len() + ); + } + buf.size = data.len() as u16; + buf.buffer[..data.len()].copy_from_slice(&data); + buf } - None => Digest::default(), + None => TPM2B_DIGEST::default(), }; let policy_ref = match &self.qualification { - Some(bytes) => Nonce::try_from(bytes.as_slice().to_vec()) - .map_err(|e| anyhow::anyhow!("qualifying data: {e}"))?, - None => Nonce::default(), + Some(q) => { + let data = q.as_slice(); + let mut buf = TPM2B_NONCE::default(); + if data.len() > buf.buffer.len() { + anyhow::bail!( + "qualification is too large: {} bytes (maximum {} bytes)", + data.len(), + buf.buffer.len() + ); + } + buf.size = data.len() as u16; + buf.buffer[..data.len()].copy_from_slice(data); + buf + } + None => TPM2B_NONCE::default(), }; - let expiration = if self.expiration == 0 { - None - } else { - Some(std::time::Duration::from_secs(self.expiration as u64)) - }; + // Extract data from ESYS-allocated pointers immediately, then free + // them before performing any I/O that could fail and leak memory. + let (timeout_data, ticket_data) = unsafe { + let mut timeout_ptr: *mut TPM2B_TIMEOUT = std::ptr::null_mut(); + let mut ticket_ptr: *mut TPMT_TK_AUTH = std::ptr::null_mut(); - let (timeout, ticket) = ctx - .policy_signed( - policy_session, + // PolicySigned has Auth Index: None for both handles, + // so all session handles are ESYS_TR_NONE. + let rc = Esys_PolicySigned( + raw.ptr(), auth_object, - Nonce::default(), // nonce_tpm - cp_hash, - policy_ref, - expiration, - signature, - ) - .context("TPM2_PolicySigned failed")?; + session_handle, + ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, + &nonce_tpm, + &cp_hash, + &policy_ref, + self.expiration, + &tpmt_sig, + &mut timeout_ptr, + &mut ticket_ptr, + ); + if rc != 0 { + anyhow::bail!("Esys_PolicySigned failed: 0x{rc:08x}"); + } - info!("policy signed succeeded"); + let timeout_data = if !timeout_ptr.is_null() { + let t = &*timeout_ptr; + Some(t.buffer[..t.size as usize].to_vec()) + } else { + None + }; + + let ticket_data = if !ticket_ptr.is_null() { + let ticket = &*ticket_ptr; + let bytes = std::slice::from_raw_parts( + ticket as *const TPMT_TK_AUTH as *const u8, + std::mem::size_of::(), + ); + Some(bytes.to_vec()) + } else { + None + }; - if let Some(ref path) = self.timeout_out { - std::fs::write(path, timeout.as_bytes()) + if !timeout_ptr.is_null() { + Esys_Free(timeout_ptr as *mut _); + } + if !ticket_ptr.is_null() { + Esys_Free(ticket_ptr as *mut _); + } + + (timeout_data, ticket_data) + }; + + if let (Some(path), Some(data)) = (&self.timeout_out, &timeout_data) { + std::fs::write(path, data) .with_context(|| format!("writing timeout to {}", path.display()))?; } - if let Some(ref path) = self.ticket_out { - let tss_ticket: TPMT_TK_AUTH = ticket - .try_into() - .map_err(|e| anyhow::anyhow!("failed to convert ticket: {e:?}"))?; - let bytes = unsafe { - std::slice::from_raw_parts( - &tss_ticket as *const TPMT_TK_AUTH as *const u8, - std::mem::size_of::(), - ) - }; - std::fs::write(path, bytes) + if let (Some(path), Some(data)) = (&self.ticket_out, &ticket_data) { + std::fs::write(path, data) .with_context(|| format!("writing ticket to {}", path.display()))?; } if let Some(ref path) = self.policy { - let digest = ctx - .policy_get_digest(policy_session) - .context("TPM2_PolicyGetDigest failed")?; - std::fs::write(path, digest.as_bytes()) - .with_context(|| format!("writing policy digest to {}", path.display()))?; + let digest_data = unsafe { + let mut digest_ptr: *mut TPM2B_DIGEST = std::ptr::null_mut(); + let rc = Esys_PolicyGetDigest( + raw.ptr(), + session_handle, + ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, + &mut digest_ptr, + ); + if rc != 0 { + anyhow::bail!("Esys_PolicyGetDigest failed: 0x{rc:08x}"); + } + + if !digest_ptr.is_null() { + let d = &*digest_ptr; + let v = d.buffer[..d.size as usize].to_vec(); + Esys_Free(digest_ptr as *mut _); + Some(v) + } else { + None + } + }; + if let Some(ref data) = digest_data { + std::fs::write(path, data) + .with_context(|| format!("writing policy digest to {}", path.display()))?; + } } - let handle: ObjectHandle = SessionHandle::from(policy_session).into(); - crate::session::save_session_and_forget(ctx, handle, &self.session)?; - + raw.context_save_to_file(session_handle, &self.session)?; + info!("policy signed succeeded"); Ok(()) } } diff --git a/tests/context.rs b/tests/context.rs new file mode 100644 index 0000000..106e463 --- /dev/null +++ b/tests/context.rs @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: Apache-2.0 +//! Context management tests: contextsave, contextload. + +mod common; + +use common::SwtpmSession; + +#[test] +fn contextsave_and_contextload_roundtrip() { + let s = SwtpmSession::new(); + let primary = s.create_primary_rsa("primary"); + + // Save the context to a file. + let saved = s.tmp().path().join("saved.json"); + s.cmd("contextsave") + .arg("-c") + .arg(SwtpmSession::file_ref(&primary)) + .arg("-o") + .arg(&saved) + .assert() + .success(); + assert!(std::fs::metadata(&saved).unwrap().len() > 0); + + // Flush the object so the handle is freed. + s.flush_transient(); + + // Reload the context from the saved file. + let restored = s.tmp().path().join("restored.json"); + s.cmd("contextload") + .arg("-c") + .arg(&saved) + .arg("-o") + .arg(&restored) + .assert() + .success(); + assert!(std::fs::metadata(&restored).unwrap().len() > 0); +} + +#[test] +fn contextsave_ecc_key() { + let s = SwtpmSession::new(); + let primary = s.create_primary_ecc("ecc_primary"); + + let saved = s.tmp().path().join("ecc_saved.json"); + s.cmd("contextsave") + .arg("-c") + .arg(SwtpmSession::file_ref(&primary)) + .arg("-o") + .arg(&saved) + .assert() + .success(); + assert!(std::fs::metadata(&saved).unwrap().len() > 0); +} + +#[test] +fn contextload_then_readpublic() { + let s = SwtpmSession::new(); + let primary = s.create_primary_rsa("primary"); + + // Read public from original handle. + let pub_orig = s.tmp().path().join("pub_orig.bin"); + s.cmd("readpublic") + .arg("-c") + .arg(SwtpmSession::file_ref(&primary)) + .arg("-o") + .arg(&pub_orig) + .assert() + .success(); + + // Save and reload. + let saved = s.tmp().path().join("saved.json"); + s.cmd("contextsave") + .arg("-c") + .arg(SwtpmSession::file_ref(&primary)) + .arg("-o") + .arg(&saved) + .assert() + .success(); + + s.flush_transient(); + + let restored = s.tmp().path().join("restored.json"); + s.cmd("contextload") + .arg("-c") + .arg(&saved) + .arg("-o") + .arg(&restored) + .assert() + .success(); + + // Read public from restored context — should match. + let pub_restored = s.tmp().path().join("pub_restored.bin"); + s.cmd("readpublic") + .arg("-c") + .arg(SwtpmSession::file_ref(&restored)) + .arg("-o") + .arg(&pub_restored) + .assert() + .success(); + + assert_eq!( + std::fs::read(&pub_orig).unwrap(), + std::fs::read(&pub_restored).unwrap() + ); +} diff --git a/tests/duplicate_import.rs b/tests/duplicate_import.rs new file mode 100644 index 0000000..bbbe760 --- /dev/null +++ b/tests/duplicate_import.rs @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 +//! Duplicate/import tests: verify duplicate fails on non-duplicatable keys +//! (the CLI creates keys with fixedTPM|fixedParent by default). + +mod common; + +use common::SwtpmSession; + +#[test] +fn duplicate_fixed_key_fails() { + let s = SwtpmSession::new(); + + let parent = s.create_primary_rsa("parent"); + let (child_ctx, _, _) = s.create_and_load_signing_key(&parent, "rsa", "child"); + + // Keys created via `create` have fixedTPM|fixedParent, so duplicate + // must fail with TPM_RC_ATTRIBUTES. + let dup_priv = s.tmp().path().join("dup.priv"); + let dup_seed = s.tmp().path().join("dup.seed"); + s.cmd("duplicate") + .arg("-c") + .arg(SwtpmSession::file_ref(&child_ctx)) + .arg("--parent-context-null") + .arg("-G") + .arg("null") + .arg("-r") + .arg(&dup_priv) + .arg("-s") + .arg(&dup_seed) + .assert() + .failure(); +} + +#[test] +fn duplicate_primary_key_fails() { + let s = SwtpmSession::new(); + let primary = s.create_primary_rsa("primary"); + + let dup_priv = s.tmp().path().join("dup.priv"); + let dup_seed = s.tmp().path().join("dup.seed"); + s.cmd("duplicate") + .arg("-c") + .arg(SwtpmSession::file_ref(&primary)) + .arg("--parent-context-null") + .arg("-G") + .arg("null") + .arg("-r") + .arg(&dup_priv) + .arg("-s") + .arg(&dup_seed) + .assert() + .failure(); +} diff --git a/tests/hierarchy.rs b/tests/hierarchy.rs index 5b43d00..47af246 100644 --- a/tests/hierarchy.rs +++ b/tests/hierarchy.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 //! Hierarchy & admin tests: clear, clearcontrol, dictionarylockout, -//! setprimarypolicy, clockrateadjust, setclock, changeeps, changepps. +//! setprimarypolicy, clockrateadjust, setclock, changeeps, changepps, +//! hierarchycontrol, setcommandauditstatus, pcrallocate. mod common; @@ -121,3 +122,40 @@ fn hierarchycontrol_enable() { .success(); assert!(ek_ctx.exists()); } + +// ── setcommandauditstatus ────────────────────────────────────────── + +#[test] +fn setcommandauditstatus_set_and_clear() { + let s = SwtpmSession::new(); + // TCG TPM 2.0 Spec requires the audit hash algorithm to be configured before + // commands can be added to the audit list. Initialize it first. + s.cmd("setcommandauditstatus") + .args(["-C", "o", "-g", "sha256"]) + .assert() + .success(); + + // Set getrandom (0x0000017B) for audit. + s.cmd("setcommandauditstatus") + .args(["-C", "o", "-g", "sha256", "--set-list", "0x17B"]) + .assert() + .success(); + + // Clear it. + s.cmd("setcommandauditstatus") + .args(["-C", "o", "-g", "sha256", "--clear-list", "0x17B"]) + .assert() + .success(); +} + +// ── pcrallocate ──────────────────────────────────────────────────── + +#[test] +fn pcrallocate_sha256() { + let s = SwtpmSession::new(); + // Allocate SHA-256 for PCRs 0-7 (platform auth). + s.cmd("pcrallocate") + .args(["-C", "p", "sha256:0,1,2,3,4,5,6,7"]) + .assert() + .success(); +} diff --git a/tests/parameter_encryption.rs b/tests/parameter_encryption.rs new file mode 100644 index 0000000..19cf9b0 --- /dev/null +++ b/tests/parameter_encryption.rs @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: Apache-2.0 +//! Parameter encryption tests: encrypted HMAC sessions for getrandom and +//! nvwrite/nvread roundtrips. + +mod common; + +use common::SwtpmSession; + +#[test] +fn parameter_encryption_getrandom() { + let s = SwtpmSession::new(); + + // Start HMAC session with encryption enabled. + let enc_session = s.tmp().path().join("enc_session.ctx"); + s.cmd("startauthsession") + .arg("-S") + .arg(&enc_session) + .args(["--hmac-session", "-g", "sha256"]) + .assert() + .success(); + + s.cmd("sessionconfig") + .arg("-S") + .arg(&enc_session) + .arg("--enable-encrypt") + .assert() + .success(); + + // Use the encrypted session for getrandom. + let out = s.tmp().path().join("rand.bin"); + s.cmd("getrandom") + .args(["32", "-o"]) + .arg(&out) + .arg("-S") + .arg(&enc_session) + .assert() + .success(); + + let data = std::fs::read(&out).unwrap(); + assert_eq!(data.len(), 32); + assert_ne!(data, vec![0u8; 32]); +} + +#[test] +fn parameter_encryption_nvwrite_nvread_roundtrip() { + let s = SwtpmSession::new(); + let nv_idx = "0x01000060"; + + s.cmd("nvdefine") + .args(["-C", "o", "-s", "16", "-a", "ownerwrite|ownerread", nv_idx]) + .assert() + .success(); + + // Start HMAC session with encryption for writing. + let enc_session = s.tmp().path().join("enc_session.ctx"); + s.cmd("startauthsession") + .arg("-S") + .arg(&enc_session) + .args(["--hmac-session", "-g", "sha256"]) + .assert() + .success(); + s.cmd("sessionconfig") + .arg("-S") + .arg(&enc_session) + .arg("--enable-encrypt") + .assert() + .success(); + + let data = s.write_tmp_file("data.bin", b"encrypted-param!"); + s.cmd("nvwrite") + .args(["-C", "o", "-i"]) + .arg(&data) + .arg(nv_idx) + .arg("-S") + .arg(&enc_session) + .assert() + .success(); + + s.flush_sessions(); + + // Read back without encryption — data should still match. + let out = s.tmp().path().join("read.bin"); + s.cmd("nvread") + .args(["-C", "o", "-o"]) + .arg(&out) + .arg(nv_idx) + .assert() + .success(); + + assert_eq!(std::fs::read(&out).unwrap(), b"encrypted-param!"); + + let _ = s.cmd("nvundefine").args(["-C", "o", nv_idx]).ok(); +} diff --git a/tests/policy_workflows.rs b/tests/policy_workflows.rs deleted file mode 100644 index 96a85a1..0000000 --- a/tests/policy_workflows.rs +++ /dev/null @@ -1,291 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -//! Advanced policy workflow tests: -//! - policynv: policy bound to NV index contents -//! - Parameter encryption via encrypted sessions -//! - getsessionauditdigest: audit trail for session operations - -mod common; - -use common::SwtpmSession; - -// ════════════════════════════════════════════════════════════════════ -// Policy bound to NV index contents (policynv) -// ════════════════════════════════════════════════════════════════════ - -#[test] -fn policynv_eq_trial() { - let s = SwtpmSession::new(); - - // Define NV index with known value. - let nv_idx = "0x01000050"; - s.cmd("nvdefine") - .args(["-C", "o", "-s", "8", "-a", "ownerwrite|ownerread", nv_idx]) - .assert() - .success(); - - let data = s.write_tmp_file("nv_data.bin", &[0x42u8; 8]); - s.cmd("nvwrite") - .args(["-C", "o", "-i"]) - .arg(&data) - .arg(nv_idx) - .assert() - .success(); - - // Trial session: policynv with eq succeeds when values match. - let trial = s.tmp().path().join("trial.ctx"); - s.cmd("startauthsession") - .arg("-S") - .arg(&trial) - .args(["-g", "sha256"]) - .assert() - .success(); - - s.cmd("policynv") - .arg("-S") - .arg(&trial) - .args(["-i", nv_idx, "-C", "o"]) - .args([ - "--operand-b", - "4242424242424242", - "--offset", - "0", - "--operation", - "eq", - ]) - .assert() - .success(); - - let _ = s.cmd("nvundefine").args(["-C", "o", nv_idx]).ok(); -} - -#[test] -fn policynv_neq_fails_when_equal() { - let s = SwtpmSession::new(); - - let nv_idx = "0x01000051"; - s.cmd("nvdefine") - .args(["-C", "o", "-s", "8", "-a", "ownerwrite|ownerread", nv_idx]) - .assert() - .success(); - - let data = s.write_tmp_file("nv_data.bin", &[0x42u8; 8]); - s.cmd("nvwrite") - .args(["-C", "o", "-i"]) - .arg(&data) - .arg(nv_idx) - .assert() - .success(); - - // Policy session: policynv with "neq" should fail - // because the NV contents are equal to operand-b. - let policy_session = s.tmp().path().join("policy_session.ctx"); - s.cmd("startauthsession") - .arg("-S") - .arg(&policy_session) - .args(["--policy-session", "-g", "sha256"]) - .assert() - .success(); - - s.cmd("policynv") - .arg("-S") - .arg(&policy_session) - .args(["-i", nv_idx, "-C", "o"]) - .args([ - "--operand-b", - "4242424242424242", - "--offset", - "0", - "--operation", - "neq", - ]) - .assert() - .failure(); - - let _ = s.cmd("nvundefine").args(["-C", "o", nv_idx]).ok(); -} - -#[test] -fn policynv_ult_unsigned_less_than() { - let s = SwtpmSession::new(); - - let nv_idx = "0x01000052"; - s.cmd("nvdefine") - .args(["-C", "o", "-s", "8", "-a", "ownerwrite|ownerread", nv_idx]) - .assert() - .success(); - - // Write value 0x0000000000000005. - let data = s.write_tmp_file("nv_data.bin", &[0, 0, 0, 0, 0, 0, 0, 5]); - s.cmd("nvwrite") - .args(["-C", "o", "-i"]) - .arg(&data) - .arg(nv_idx) - .assert() - .success(); - - // Trial: policynv with "ult" — NV(5) < operand(10) → should succeed. - let trial = s.tmp().path().join("trial.ctx"); - s.cmd("startauthsession") - .arg("-S") - .arg(&trial) - .args(["-g", "sha256"]) - .assert() - .success(); - - s.cmd("policynv") - .arg("-S") - .arg(&trial) - .args(["-i", nv_idx, "-C", "o"]) - .args([ - "--operand-b", - "000000000000000A", - "--offset", - "0", - "--operation", - "ult", - ]) - .assert() - .success(); - - let _ = s.cmd("nvundefine").args(["-C", "o", nv_idx]).ok(); -} - -#[test] -fn policynv_ult_fails_when_greater() { - let s = SwtpmSession::new(); - - let nv_idx = "0x01000053"; - s.cmd("nvdefine") - .args(["-C", "o", "-s", "8", "-a", "ownerwrite|ownerread", nv_idx]) - .assert() - .success(); - - // Write value 0x0000000000000010 (16). - let data = s.write_tmp_file("nv_data.bin", &[0, 0, 0, 0, 0, 0, 0, 0x10]); - s.cmd("nvwrite") - .args(["-C", "o", "-i"]) - .arg(&data) - .arg(nv_idx) - .assert() - .success(); - - // Policy session: policynv with "ult" — NV(16) < operand(5) → should fail. - let session = s.tmp().path().join("session.ctx"); - s.cmd("startauthsession") - .arg("-S") - .arg(&session) - .args(["--policy-session", "-g", "sha256"]) - .assert() - .success(); - - s.cmd("policynv") - .arg("-S") - .arg(&session) - .args(["-i", nv_idx, "-C", "o"]) - .args([ - "--operand-b", - "0000000000000005", - "--offset", - "0", - "--operation", - "ult", - ]) - .assert() - .failure(); - - let _ = s.cmd("nvundefine").args(["-C", "o", nv_idx]).ok(); -} - -// ════════════════════════════════════════════════════════════════════ -// Parameter encryption via encrypted session -// ════════════════════════════════════════════════════════════════════ - -#[test] -fn parameter_encryption_getrandom() { - let s = SwtpmSession::new(); - - // Start HMAC session with encryption enabled. - let enc_session = s.tmp().path().join("enc_session.ctx"); - s.cmd("startauthsession") - .arg("-S") - .arg(&enc_session) - .args(["--hmac-session", "-g", "sha256"]) - .assert() - .success(); - - s.cmd("sessionconfig") - .arg("-S") - .arg(&enc_session) - .arg("--enable-encrypt") - .assert() - .success(); - - // Use the encrypted session for getrandom. - let out = s.tmp().path().join("rand.bin"); - s.cmd("getrandom") - .args(["32", "-o"]) - .arg(&out) - .arg("-S") - .arg(&enc_session) - .assert() - .success(); - - let data = std::fs::read(&out).unwrap(); - assert_eq!(data.len(), 32); - assert_ne!(data, vec![0u8; 32]); -} - -#[test] -fn parameter_encryption_nvwrite_nvread_roundtrip() { - let s = SwtpmSession::new(); - let nv_idx = "0x01000060"; - - s.cmd("nvdefine") - .args(["-C", "o", "-s", "16", "-a", "ownerwrite|ownerread", nv_idx]) - .assert() - .success(); - - // Start HMAC session with encryption for writing. - let enc_session = s.tmp().path().join("enc_session.ctx"); - s.cmd("startauthsession") - .arg("-S") - .arg(&enc_session) - .args(["--hmac-session", "-g", "sha256"]) - .assert() - .success(); - s.cmd("sessionconfig") - .arg("-S") - .arg(&enc_session) - .arg("--enable-encrypt") - .assert() - .success(); - - let data = s.write_tmp_file("data.bin", b"encrypted-param!"); - s.cmd("nvwrite") - .args(["-C", "o", "-i"]) - .arg(&data) - .arg(nv_idx) - .arg("-S") - .arg(&enc_session) - .assert() - .success(); - - s.flush_sessions(); - - // Read back without encryption — data should still match. - let out = s.tmp().path().join("read.bin"); - s.cmd("nvread") - .args(["-C", "o", "-o"]) - .arg(&out) - .arg(nv_idx) - .assert() - .success(); - - assert_eq!(std::fs::read(&out).unwrap(), b"encrypted-param!"); - - let _ = s.cmd("nvundefine").args(["-C", "o", nv_idx]).ok(); -} - -// Note: getsessionauditdigest is not tested here because the -// --audit-session flag doesn't properly set session attributes -// for TPM2_GetSessionAuditDigest (TPM returns RC_ATTRIBUTES). diff --git a/tests/sequence.rs b/tests/sequence.rs new file mode 100644 index 0000000..e400c55 --- /dev/null +++ b/tests/sequence.rs @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: Apache-2.0 +//! Sequence operation tests: hashsequencestart, hmacsequencestart, +//! sequenceupdate, sequencecomplete. + +mod common; + +use common::SwtpmSession; + +// ── Hash sequence ────────────────────────────────────────────────── + +#[test] +fn hash_sequence_sha256_matches_single_hash() { + let s = SwtpmSession::new(); + let data = s.write_tmp_file("data.bin", b"hello world"); + + // Compute hash in one shot for reference. + let expected = s.tmp().path().join("expected.bin"); + s.cmd("hash") + .args(["-g", "sha256", "-o"]) + .arg(&expected) + .arg(&data) + .assert() + .success(); + + // Compute the same hash via the sequence API. + let seq_ctx = s.tmp().path().join("seq.ctx"); + s.cmd("hashsequencestart") + .args(["-g", "sha256", "-o"]) + .arg(&seq_ctx) + .assert() + .success(); + + s.cmd("sequenceupdate") + .arg("-c") + .arg(&seq_ctx) + .arg("-i") + .arg(&data) + .assert() + .success(); + + let result = s.tmp().path().join("result.bin"); + s.cmd("sequencecomplete") + .arg("-c") + .arg(&seq_ctx) + .arg("-o") + .arg(&result) + .args(["-C", "n"]) + .assert() + .success(); + + assert_eq!( + std::fs::read(&expected).unwrap(), + std::fs::read(&result).unwrap() + ); +} + +#[test] +fn hash_sequence_multiple_updates() { + let s = SwtpmSession::new(); + + // Split "hello world" across two updates. + let part1 = s.write_tmp_file("part1.bin", b"hello "); + let part2 = s.write_tmp_file("part2.bin", b"world"); + + // Reference hash of the full data. + let full = s.write_tmp_file("full.bin", b"hello world"); + let expected = s.tmp().path().join("expected.bin"); + s.cmd("hash") + .args(["-g", "sha256", "-o"]) + .arg(&expected) + .arg(&full) + .assert() + .success(); + + // Sequence with two updates. + let seq_ctx = s.tmp().path().join("seq.ctx"); + s.cmd("hashsequencestart") + .args(["-g", "sha256", "-o"]) + .arg(&seq_ctx) + .assert() + .success(); + + s.cmd("sequenceupdate") + .arg("-c") + .arg(&seq_ctx) + .arg("-i") + .arg(&part1) + .assert() + .success(); + + s.cmd("sequenceupdate") + .arg("-c") + .arg(&seq_ctx) + .arg("-i") + .arg(&part2) + .assert() + .success(); + + let result = s.tmp().path().join("result.bin"); + s.cmd("sequencecomplete") + .arg("-c") + .arg(&seq_ctx) + .arg("-o") + .arg(&result) + .args(["-C", "n"]) + .assert() + .success(); + + assert_eq!( + std::fs::read(&expected).unwrap(), + std::fs::read(&result).unwrap() + ); +} + +#[test] +fn hash_sequence_with_final_data_in_complete() { + let s = SwtpmSession::new(); + + let part1 = s.write_tmp_file("part1.bin", b"hello "); + let part2 = s.write_tmp_file("part2.bin", b"world"); + + let full = s.write_tmp_file("full.bin", b"hello world"); + let expected = s.tmp().path().join("expected.bin"); + s.cmd("hash") + .args(["-g", "sha256", "-o"]) + .arg(&expected) + .arg(&full) + .assert() + .success(); + + let seq_ctx = s.tmp().path().join("seq.ctx"); + s.cmd("hashsequencestart") + .args(["-g", "sha256", "-o"]) + .arg(&seq_ctx) + .assert() + .success(); + + s.cmd("sequenceupdate") + .arg("-c") + .arg(&seq_ctx) + .arg("-i") + .arg(&part1) + .assert() + .success(); + + // Provide the remaining data via sequencecomplete's -i flag. + let result = s.tmp().path().join("result.bin"); + s.cmd("sequencecomplete") + .arg("-c") + .arg(&seq_ctx) + .arg("-i") + .arg(&part2) + .arg("-o") + .arg(&result) + .args(["-C", "n"]) + .assert() + .success(); + + assert_eq!( + std::fs::read(&expected).unwrap(), + std::fs::read(&result).unwrap() + ); +} + +#[test] +fn hash_sequence_produces_ticket() { + let s = SwtpmSession::new(); + let data = s.write_tmp_file("data.bin", b"ticket test"); + + let seq_ctx = s.tmp().path().join("seq.ctx"); + s.cmd("hashsequencestart") + .args(["-g", "sha256", "-o"]) + .arg(&seq_ctx) + .assert() + .success(); + + s.cmd("sequenceupdate") + .arg("-c") + .arg(&seq_ctx) + .arg("-i") + .arg(&data) + .assert() + .success(); + + let result = s.tmp().path().join("result.bin"); + let ticket = s.tmp().path().join("ticket.bin"); + s.cmd("sequencecomplete") + .arg("-c") + .arg(&seq_ctx) + .arg("-o") + .arg(&result) + .arg("-t") + .arg(&ticket) + .args(["-C", "o"]) + .assert() + .success(); + + assert!(std::fs::metadata(&result).unwrap().len() > 0); + assert!(std::fs::metadata(&ticket).unwrap().len() > 0); +} + +// ── HMAC sequence ────────────────────────────────────────────────── + +#[test] +fn hmac_sequence_matches_single_hmac() { + let s = SwtpmSession::new(); + let data = s.write_tmp_file("data.bin", b"hmac sequence test"); + + // Create an HMAC key. + let primary = s.create_primary_rsa("primary"); + let hmac_priv = s.tmp().path().join("hmac.priv"); + let hmac_pub = s.tmp().path().join("hmac.pub"); + let hmac_ctx = s.tmp().path().join("hmac.ctx"); + s.cmd("create") + .arg("-C") + .arg(SwtpmSession::file_ref(&primary)) + .args(["-G", "hmac", "-g", "sha256", "-r"]) + .arg(&hmac_priv) + .arg("-u") + .arg(&hmac_pub) + .assert() + .success(); + s.cmd("load") + .arg("-C") + .arg(SwtpmSession::file_ref(&primary)) + .arg("-r") + .arg(&hmac_priv) + .arg("-u") + .arg(&hmac_pub) + .arg("-c") + .arg(&hmac_ctx) + .assert() + .success(); + + // Compute HMAC in one shot for reference. + let expected = s.tmp().path().join("expected.bin"); + s.cmd("hmac") + .arg("-c") + .arg(SwtpmSession::file_ref(&hmac_ctx)) + .args(["-g", "sha256", "-o"]) + .arg(&expected) + .arg("-i") + .arg(&data) + .assert() + .success(); + + // Compute the same HMAC via the sequence API. + let seq_ctx = s.tmp().path().join("hmac_seq.ctx"); + s.cmd("hmacsequencestart") + .arg("-c") + .arg(SwtpmSession::file_ref(&hmac_ctx)) + .args(["-g", "sha256", "-o"]) + .arg(&seq_ctx) + .assert() + .success(); + + s.cmd("sequenceupdate") + .arg("-c") + .arg(&seq_ctx) + .arg("-i") + .arg(&data) + .assert() + .success(); + + let result = s.tmp().path().join("hmac_result.bin"); + s.cmd("sequencecomplete") + .arg("-c") + .arg(&seq_ctx) + .arg("-o") + .arg(&result) + .args(["-C", "n"]) + .assert() + .success(); + + assert_eq!( + std::fs::read(&expected).unwrap(), + std::fs::read(&result).unwrap() + ); +} diff --git a/tests/session_policy.rs b/tests/session_policy.rs index de78849..cc8717c 100644 --- a/tests/session_policy.rs +++ b/tests/session_policy.rs @@ -1,7 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 -//! Session & policy tests: startauthsession, sessionconfig, policyrestart, -//! policypcr, policycommandcode, policyauthvalue, policypassword, policyor, -//! policylocality, policynvwritten, createpolicy, policycountertimer, policysecret. +//! Session & policy tests covering all policy subcommands: +//! startauthsession, sessionconfig, createpolicy, policyrestart, +//! policyauthvalue, policyauthorize, policyauthorizenv, policycommandcode, +//! policycountertimer, policycphash, policyduplicationselect, policylocality, +//! policynamehash, policynv, policynvwritten, policyor, policypassword, +//! policypcr, policysecret, policysigned, policytemplate. mod common; @@ -381,3 +384,584 @@ fn different_pcr_selection_different_policy() { std::fs::read(&pol_b).unwrap() ); } + +// ════════════════════════════════════════════════════════════════════ +// policycphash +// ════════════════════════════════════════════════════════════════════ + +#[test] +fn policycphash_trial() { + let s = SwtpmSession::new(); + let trial_ctx = s.tmp().path().join("trial.ctx"); + s.cmd("startauthsession") + .arg("-S") + .arg(&trial_ctx) + .args(["-g", "sha256"]) + .assert() + .success(); + + // Use a 32-byte digest as cpHash. + let cphash = s.write_tmp_file("cphash.bin", &[0xAAu8; 32]); + let policy_file = s.tmp().path().join("cphash_policy.bin"); + s.cmd("policycphash") + .arg("-S") + .arg(&trial_ctx) + .arg("--cphash") + .arg(&cphash) + .arg("-L") + .arg(&policy_file) + .assert() + .success(); + assert!(std::fs::metadata(&policy_file).unwrap().len() > 0); +} + +// ════════════════════════════════════════════════════════════════════ +// policynamehash +// ════════════════════════════════════════════════════════════════════ + +#[test] +fn policynamehash_trial() { + let s = SwtpmSession::new(); + let trial_ctx = s.tmp().path().join("trial.ctx"); + s.cmd("startauthsession") + .arg("-S") + .arg(&trial_ctx) + .args(["-g", "sha256"]) + .assert() + .success(); + + // Use a 32-byte digest as nameHash. + let namehash = s.write_tmp_file("namehash.bin", &[0xBBu8; 32]); + let policy_file = s.tmp().path().join("namehash_policy.bin"); + s.cmd("policynamehash") + .arg("-S") + .arg(&trial_ctx) + .arg("--namehash") + .arg(&namehash) + .arg("-L") + .arg(&policy_file) + .assert() + .success(); + assert!(std::fs::metadata(&policy_file).unwrap().len() > 0); +} + +// ════════════════════════════════════════════════════════════════════ +// policytemplate +// ════════════════════════════════════════════════════════════════════ + +#[test] +fn policytemplate_trial() { + let s = SwtpmSession::new(); + let trial_ctx = s.tmp().path().join("trial.ctx"); + s.cmd("startauthsession") + .arg("-S") + .arg(&trial_ctx) + .args(["-g", "sha256"]) + .assert() + .success(); + + // Use a 32-byte digest as templateHash. + let template_hash = s.write_tmp_file("template_hash.bin", &[0xCCu8; 32]); + let policy_file = s.tmp().path().join("template_policy.bin"); + s.cmd("policytemplate") + .arg("-S") + .arg(&trial_ctx) + .arg("--template-hash") + .arg(&template_hash) + .arg("-L") + .arg(&policy_file) + .assert() + .success(); + assert!(std::fs::metadata(&policy_file).unwrap().len() > 0); +} + +// ════════════════════════════════════════════════════════════════════ +// policyduplicationselect +// ════════════════════════════════════════════════════════════════════ + +#[test] +fn policyduplicationselect_trial() { + let s = SwtpmSession::new(); + let trial_ctx = s.tmp().path().join("trial.ctx"); + s.cmd("startauthsession") + .arg("-S") + .arg(&trial_ctx) + .args(["-g", "sha256"]) + .assert() + .success(); + + // In trial mode the TPM computes the policy hash from the names + // without verifying they refer to real objects. + // TPM name format: 2-byte algorithm ID (00 0B = SHA-256) + 32-byte hash. + let mut obj_name = vec![0x00, 0x0B]; + obj_name.extend_from_slice(&[0x11u8; 32]); + let obj_name_file = s.write_tmp_file("obj_name.bin", &obj_name); + + let mut parent_name = vec![0x00, 0x0B]; + parent_name.extend_from_slice(&[0x22u8; 32]); + let parent_name_file = s.write_tmp_file("parent_name.bin", &parent_name); + + let policy_file = s.tmp().path().join("dupsel_policy.bin"); + s.cmd("policyduplicationselect") + .arg("-S") + .arg(&trial_ctx) + .arg("-n") + .arg(&obj_name_file) + .arg("-N") + .arg(&parent_name_file) + .arg("-L") + .arg(&policy_file) + .assert() + .success(); + assert!(std::fs::metadata(&policy_file).unwrap().len() > 0); +} + +// ════════════════════════════════════════════════════════════════════ +// policynv +// ════════════════════════════════════════════════════════════════════ + +#[test] +fn policynv_eq_trial() { + let s = SwtpmSession::new(); + + // Define NV index with known value. + let nv_idx = "0x01000050"; + s.cmd("nvdefine") + .args(["-C", "o", "-s", "8", "-a", "ownerwrite|ownerread", nv_idx]) + .assert() + .success(); + + let data = s.write_tmp_file("nv_data.bin", &[0x42u8; 8]); + s.cmd("nvwrite") + .args(["-C", "o", "-i"]) + .arg(&data) + .arg(nv_idx) + .assert() + .success(); + + // Trial session: policynv with eq succeeds when values match. + let trial = s.tmp().path().join("trial.ctx"); + s.cmd("startauthsession") + .arg("-S") + .arg(&trial) + .args(["-g", "sha256"]) + .assert() + .success(); + + s.cmd("policynv") + .arg("-S") + .arg(&trial) + .args(["-i", nv_idx, "-C", "o"]) + .args([ + "--operand-b", + "4242424242424242", + "--offset", + "0", + "--operation", + "eq", + ]) + .assert() + .success(); + + let _ = s.cmd("nvundefine").args(["-C", "o", nv_idx]).ok(); +} + +#[test] +fn policynv_neq_fails_when_equal() { + let s = SwtpmSession::new(); + + let nv_idx = "0x01000051"; + s.cmd("nvdefine") + .args(["-C", "o", "-s", "8", "-a", "ownerwrite|ownerread", nv_idx]) + .assert() + .success(); + + let data = s.write_tmp_file("nv_data.bin", &[0x42u8; 8]); + s.cmd("nvwrite") + .args(["-C", "o", "-i"]) + .arg(&data) + .arg(nv_idx) + .assert() + .success(); + + // Policy session: policynv with "neq" should fail + // because the NV contents are equal to operand-b. + let policy_session = s.tmp().path().join("policy_session.ctx"); + s.cmd("startauthsession") + .arg("-S") + .arg(&policy_session) + .args(["--policy-session", "-g", "sha256"]) + .assert() + .success(); + + s.cmd("policynv") + .arg("-S") + .arg(&policy_session) + .args(["-i", nv_idx, "-C", "o"]) + .args([ + "--operand-b", + "4242424242424242", + "--offset", + "0", + "--operation", + "neq", + ]) + .assert() + .failure(); + + let _ = s.cmd("nvundefine").args(["-C", "o", nv_idx]).ok(); +} + +#[test] +fn policynv_ult_unsigned_less_than() { + let s = SwtpmSession::new(); + + let nv_idx = "0x01000052"; + s.cmd("nvdefine") + .args(["-C", "o", "-s", "8", "-a", "ownerwrite|ownerread", nv_idx]) + .assert() + .success(); + + // Write value 0x0000000000000005. + let data = s.write_tmp_file("nv_data.bin", &[0, 0, 0, 0, 0, 0, 0, 5]); + s.cmd("nvwrite") + .args(["-C", "o", "-i"]) + .arg(&data) + .arg(nv_idx) + .assert() + .success(); + + // Trial: policynv with "ult" — NV(5) < operand(10) → should succeed. + let trial = s.tmp().path().join("trial.ctx"); + s.cmd("startauthsession") + .arg("-S") + .arg(&trial) + .args(["-g", "sha256"]) + .assert() + .success(); + + s.cmd("policynv") + .arg("-S") + .arg(&trial) + .args(["-i", nv_idx, "-C", "o"]) + .args([ + "--operand-b", + "000000000000000A", + "--offset", + "0", + "--operation", + "ult", + ]) + .assert() + .success(); + + let _ = s.cmd("nvundefine").args(["-C", "o", nv_idx]).ok(); +} + +#[test] +fn policynv_ult_fails_when_greater() { + let s = SwtpmSession::new(); + + let nv_idx = "0x01000053"; + s.cmd("nvdefine") + .args(["-C", "o", "-s", "8", "-a", "ownerwrite|ownerread", nv_idx]) + .assert() + .success(); + + // Write value 0x0000000000000010 (16). + let data = s.write_tmp_file("nv_data.bin", &[0, 0, 0, 0, 0, 0, 0, 0x10]); + s.cmd("nvwrite") + .args(["-C", "o", "-i"]) + .arg(&data) + .arg(nv_idx) + .assert() + .success(); + + // Policy session: policynv with "ult" — NV(16) < operand(5) → should fail. + let session = s.tmp().path().join("session.ctx"); + s.cmd("startauthsession") + .arg("-S") + .arg(&session) + .args(["--policy-session", "-g", "sha256"]) + .assert() + .success(); + + s.cmd("policynv") + .arg("-S") + .arg(&session) + .args(["-i", nv_idx, "-C", "o"]) + .args([ + "--operand-b", + "0000000000000005", + "--offset", + "0", + "--operation", + "ult", + ]) + .assert() + .failure(); + + let _ = s.cmd("nvundefine").args(["-C", "o", nv_idx]).ok(); +} + +// ════════════════════════════════════════════════════════════════════ +// policyauthorizenv +// ════════════════════════════════════════════════════════════════════ + +#[test] +fn policyauthorizenv_trial() { + let s = SwtpmSession::new(); + + // Step 1: compute a trial policy (policyauthvalue) to store in NV. + let trial1 = s.tmp().path().join("trial1.ctx"); + s.cmd("startauthsession") + .arg("-S") + .arg(&trial1) + .args(["-g", "sha256"]) + .assert() + .success(); + + let stored_policy = s.tmp().path().join("stored_policy.bin"); + s.cmd("policyauthvalue") + .arg("-S") + .arg(&trial1) + .arg("-L") + .arg(&stored_policy) + .assert() + .success(); + + let policy_bytes = std::fs::read(&stored_policy).unwrap(); + s.flush_sessions(); + + // Step 2: define NV index and write the policy digest as a marshaled + // TPMT_HA (2-byte big-endian hashAlg + digest). PolicyAuthorizeNV + // reads the NV data as TPMT_HA and checks that its hashAlg matches + // the policy session's hash algorithm. + let nv_idx = "0x01000070"; + let mut tpmt_ha = Vec::with_capacity(2 + policy_bytes.len()); + tpmt_ha.extend_from_slice(&0x000Bu16.to_be_bytes()); // TPM2_ALG_SHA256 + tpmt_ha.extend_from_slice(&policy_bytes); + let nv_data = s.write_tmp_file("nv_policy.bin", &tpmt_ha); + + s.cmd("nvdefine") + .args([ + "-C", + "o", + "-s", + &tpmt_ha.len().to_string(), + "-a", + "ownerwrite|ownerread", + nv_idx, + ]) + .assert() + .success(); + + s.cmd("nvwrite") + .args(["-C", "o", "-i"]) + .arg(&nv_data) + .arg(nv_idx) + .assert() + .success(); + + // Step 3: start a policy session, replay the approved policy so that + // policyDigest matches the NV data, then call policyauthorizenv. + let policy_session = s.tmp().path().join("policy_session.ctx"); + s.cmd("startauthsession") + .arg("-S") + .arg(&policy_session) + .args(["--policy-session", "-g", "sha256"]) + .assert() + .success(); + + s.cmd("policyauthvalue") + .arg("-S") + .arg(&policy_session) + .assert() + .success(); + + s.cmd("policyauthorizenv") + .arg("-S") + .arg(&policy_session) + .args(["-i", nv_idx, "-C", "o"]) + .assert() + .success(); + + let _ = s.cmd("nvundefine").args(["-C", "o", nv_idx]).ok(); +} + +// ════════════════════════════════════════════════════════════════════ +// policyauthorize +// ════════════════════════════════════════════════════════════════════ + +#[test] +fn policyauthorize_with_signed_policy() { + let s = SwtpmSession::new(); + + // Step 1: create a signing key for policy authorization. + let primary = s.create_primary_rsa("primary"); + let (signing_key, signing_pub, _) = + s.create_and_load_signing_key(&primary, "rsa", "auth_signer"); + + // Step 2: compute a trial policy to be authorized. + let trial1 = s.tmp().path().join("trial1.ctx"); + s.cmd("startauthsession") + .arg("-S") + .arg(&trial1) + .args(["-g", "sha256"]) + .assert() + .success(); + + let approved_policy = s.tmp().path().join("approved_policy.bin"); + s.cmd("policypassword") + .arg("-S") + .arg(&trial1) + .arg("-L") + .arg(&approved_policy) + .assert() + .success(); + s.flush_sessions(); + + // Step 3: hash the approved policy digest for signing. + let policy_hash = s.tmp().path().join("policy_hash.bin"); + let hash_ticket = s.tmp().path().join("hash_ticket.bin"); + s.cmd("hash") + .arg("-g") + .arg("sha256") + .arg("-C") + .arg("o") + .arg("-o") + .arg(&policy_hash) + .arg("-t") + .arg(&hash_ticket) + .arg(&approved_policy) + .assert() + .success(); + + // Step 4: sign the policy hash. + let signature = s.tmp().path().join("policy_sig.bin"); + s.cmd("sign") + .arg("-c") + .arg(SwtpmSession::file_ref(&signing_key)) + .arg("-g") + .arg("sha256") + .arg("-s") + .arg("rsassa") + .arg("-o") + .arg(&signature) + .arg("-d") + .arg(&policy_hash) + .assert() + .success(); + + // Step 5: verify the signature to get a verification ticket. + let verify_ticket = s.tmp().path().join("verify_ticket.bin"); + s.cmd("verifysignature") + .arg("-c") + .arg(SwtpmSession::file_ref(&signing_key)) + .arg("-s") + .arg(&signature) + .arg("-t") + .arg(&verify_ticket) + .arg("-d") + .arg(&policy_hash) + .assert() + .success(); + + // Step 6: get the signing key name via loadexternal. + let key_name = s.tmp().path().join("signer_name.bin"); + let ext_ctx = s.tmp().path().join("ext_signer.ctx"); + s.cmd("loadexternal") + .arg("-u") + .arg(&signing_pub) + .arg("-c") + .arg(&ext_ctx) + .arg("-n") + .arg(&key_name) + .assert() + .success(); + + // Step 7: start a policy session and call policyauthorize. + // PolicyAuthorize requires that policyDigest == approvedPolicy, + // so we must replay the approved policy (policypassword) on the + // policy session before calling policyauthorize. + let policy_session = s.tmp().path().join("pa_session.ctx"); + s.cmd("startauthsession") + .arg("-S") + .arg(&policy_session) + .args(["--policy-session", "-g", "sha256"]) + .assert() + .success(); + + s.cmd("policypassword") + .arg("-S") + .arg(&policy_session) + .assert() + .success(); + + s.cmd("policyauthorize") + .arg("-S") + .arg(&policy_session) + .arg("-i") + .arg(&approved_policy) + .arg("-n") + .arg(&key_name) + .arg("-t") + .arg(&verify_ticket) + .assert() + .success(); +} + +// ════════════════════════════════════════════════════════════════════ +// policysigned +// ════════════════════════════════════════════════════════════════════ + +#[test] +fn policysigned_trial() { + let s = SwtpmSession::new(); + + // Create a signing key. + let primary = s.create_primary_rsa("primary"); + let (signing_key, _, _) = s.create_and_load_signing_key(&primary, "rsa", "signer"); + + // In trial mode, the TPM does not verify the signature — it only + // updates the policy digest. We still need a structurally valid + // TPMT_SIGNATURE. Sign some dummy data to obtain one. + let dummy_data = s.write_tmp_file("dummy.bin", &[0u8; 32]); + let signature = s.tmp().path().join("sig.bin"); + s.cmd("sign") + .arg("-c") + .arg(SwtpmSession::file_ref(&signing_key)) + .arg("-g") + .arg("sha256") + .arg("-s") + .arg("rsassa") + .arg("-o") + .arg(&signature) + .arg("-d") + .arg(&dummy_data) + .assert() + .success(); + + // Start trial session and call policysigned. + let trial_ctx = s.tmp().path().join("trial.ctx"); + s.cmd("startauthsession") + .arg("-S") + .arg(&trial_ctx) + .args(["-g", "sha256"]) + .assert() + .success(); + + let policy_file = s.tmp().path().join("signed_policy.bin"); + s.cmd("policysigned") + .arg("-S") + .arg(&trial_ctx) + .arg("-c") + .arg(SwtpmSession::file_ref(&signing_key)) + .arg("-s") + .arg(&signature) + .arg("-L") + .arg(&policy_file) + .assert() + .success(); + assert!(std::fs::metadata(&policy_file).unwrap().len() > 0); +}