diff --git a/Cargo.lock b/Cargo.lock index ba0a275..ed37e04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1103,8 +1103,7 @@ dependencies = [ [[package]] name = "tss-esapi" version = "8.0.0-alpha.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1751ea94b699404cd8c52fe2f1cb6ba811b8a7d26151298b946b3b8424468e" +source = "git+https://github.com/parallaxsecond/rust-tss-esapi?rev=2c36f103edb30320ec03e5d4b3ebe3eb58a2291f#2c36f103edb30320ec03e5d4b3ebe3eb58a2291f" dependencies = [ "bitfield", "cfg-if", @@ -1132,8 +1131,7 @@ dependencies = [ [[package]] name = "tss-esapi-sys" version = "0.6.0-alpha.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705c6ac50dbeca81dfd5ddc678d25673023547d3bd8345e61a997ec163797c9a" +source = "git+https://github.com/parallaxsecond/rust-tss-esapi?rev=2c36f103edb30320ec03e5d4b3ebe3eb58a2291f#2c36f103edb30320ec03e5d4b3ebe3eb58a2291f" dependencies = [ "cfg-if", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 2720ddc..1e3b0fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ log = "0.4.29" serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" thiserror = "2.0.18" -tss-esapi = { version = "8.0.0-alpha.2", features = ["serde"] } +tss-esapi = { git = "https://github.com/parallaxsecond/rust-tss-esapi", rev = "2c36f103edb30320ec03e5d4b3ebe3eb58a2291f", features = ["serde"] } [dev-dependencies] assert_cmd = "2.2.0" diff --git a/src/cmd/policysigned.rs b/src/cmd/policysigned.rs index b6b806e..b0089a3 100644 --- a/src/cmd/policysigned.rs +++ b/src/cmd/policysigned.rs @@ -1,26 +1,26 @@ // SPDX-License-Identifier: Apache-2.0 use std::path::PathBuf; +use std::time::Duration; use anyhow::Context; use clap::Parser; use log::info; -use tss_esapi::structures::Signature; +use tss_esapi::constants::SessionType; +use tss_esapi::handles::{ObjectHandle, SessionHandle}; +use tss_esapi::structures::{Digest, Nonce, Signature}; use tss_esapi::traits::UnMarshall; -use tss_esapi::tss2_esys::*; +use tss_esapi::tss2_esys::TPMT_TK_AUTH; use crate::cli::GlobalOpts; -use crate::parse::{self, parse_context_source}; -use crate::raw_esys::RawEsysContext; +use crate::context::create_context; +use crate::handle::{ContextSource, load_object_from_source}; +use crate::parse::{parse_context_source, parse_duration}; +use crate::session::load_session_from_file; -use crate::handle::ContextSource; - -/// Extend a policy with PolicySigned. +/// Authorize a policy with a signed authorization. /// -/// 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. +/// Wraps TPM2_PolicySigned. #[derive(Parser)] pub struct PolicySignedCmd { /// Policy session file @@ -36,16 +36,16 @@ pub struct PolicySignedCmd { pub signature: PathBuf, /// Expiration time in seconds (0 = no expiration) - #[arg(short = 'x', long = "expiration", default_value = "0")] - pub expiration: i32, + #[arg(short = 'x', long = "expiration", value_parser = parse_duration, default_value = None)] + pub expiration: Option, /// cpHash file (optional) #[arg(long = "cphash-input")] pub cphash_input: Option, /// Policy reference (digest) (hex: or file:) - #[arg(short = 'q', long = "qualification", value_parser = parse::parse_qualification)] - pub qualification: Option, + #[arg(short = 'q', long = "qualification", value_parser = crate::parse::parse_qualification)] + pub qualification: Option, /// Output file for the timeout #[arg(short = 't', long = "timeout")] @@ -62,160 +62,78 @@ pub struct PolicySignedCmd { impl PolicySignedCmd { pub fn execute(&self, global: &GlobalOpts) -> anyhow::Result<()> { - let mut raw = RawEsysContext::new(global.tcti.as_deref())?; + let mut ctx = create_context(global.tcti.as_deref())?; - let session_handle = raw.context_load( - self.session - .to_str() - .ok_or_else(|| anyhow::anyhow!("invalid session path"))?, - )?; + 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 auth_object = raw.resolve_handle_from_source(&self.key_context)?; + let auth_object = load_object_from_source(&mut ctx, &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)?; - 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 + Digest::try_from(data).map_err(|e| anyhow::anyhow!("invalid cpHash: {e}"))? } - None => TPM2B_DIGEST::default(), + None => Digest::default(), }; let policy_ref = match &self.qualification { - 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(), + Some(bytes) => Nonce::try_from(bytes.as_slice().to_vec()) + .map_err(|e| anyhow::anyhow!("qualifying data: {e}"))?, + None => Nonce::default(), }; - // 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(); - - // PolicySigned has Auth Index: None for both handles, - // so all session handles are ESYS_TR_NONE. - let rc = Esys_PolicySigned( - raw.ptr(), + let (timeout, ticket) = ctx + .policy_signed( + policy_session, auth_object, - session_handle, - ESYS_TR_NONE, - ESYS_TR_NONE, - ESYS_TR_NONE, - &nonce_tpm, - &cp_hash, - &policy_ref, + Nonce::default(), // 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}"); - } - - 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 - }; + signature, + ) + .context("TPM2_PolicySigned failed")?; - 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) - }; + info!("policy signed succeeded"); - if let (Some(path), Some(data)) = (&self.timeout_out, &timeout_data) { - std::fs::write(path, data) + if let Some(ref path) = self.timeout_out { + std::fs::write(path, timeout.as_bytes()) .with_context(|| format!("writing timeout to {}", path.display()))?; } - if let (Some(path), Some(data)) = (&self.ticket_out, &ticket_data) { - std::fs::write(path, data) + 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) .with_context(|| format!("writing ticket to {}", path.display()))?; } if let Some(ref path) = self.policy { - 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 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()))?; } - raw.context_save_to_file(session_handle, &self.session)?; - info!("policy signed succeeded"); + let handle: ObjectHandle = SessionHandle::from(policy_session).into(); + crate::session::save_session_and_forget(ctx, handle, &self.session)?; + Ok(()) } } diff --git a/src/parse.rs b/src/parse.rs index a663361..e0ce42f 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -46,6 +46,21 @@ pub fn parse_hex_u32(s: &str) -> Result { .map_err(|_| format!("expected a hex value (e.g. 0x01400001), got: '{s}'")) } +// --------------------------------------------------------------------------- +// Duration +// --------------------------------------------------------------------------- + +pub fn parse_duration(s: &str) -> Result, String> { + let secs: u64 = s + .parse() + .map_err(|_| format!("expected a u64 value, got: '{s}'"))?; + let duration = match secs { + 0 => None, + _ => Some(std::time::Duration::from_secs(secs)), + }; + Ok(duration) +} + // --------------------------------------------------------------------------- // Context source // ---------------------------------------------------------------------------