-
Notifications
You must be signed in to change notification settings - Fork 0
fix: correct policy test flows and policysigned auth session handling #27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,21 +5,22 @@ | |
| 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 cphash_input: Option<PathBuf>, | ||
|
|
||
| /// Policy reference (digest) (hex:<hex_bytes> or file:<path>) | ||
| #[arg(short = 'q', long = "qualification", value_parser = crate::parse::parse_qualification)] | ||
| pub qualification: Option<crate::parse::Qualification>, | ||
| #[arg(short = 'q', long = "qualification", value_parser = parse::parse_qualification)] | ||
| pub qualification: Option<parse::Qualification>, | ||
|
|
||
| /// Output file for the timeout | ||
| #[arg(short = 't', long = "timeout")] | ||
|
|
@@ -61,84 +62,160 @@ | |
|
|
||
| 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; | ||
Check failureCode scanning / CodeQL Access of invalid pointer High
This operation dereferences a pointer that may be
invalid Error loading related location Loading Copilot AutofixAI 6 days ago Copilot could not generate an autofix suggestion Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support. |
||
| let bytes = std::slice::from_raw_parts( | ||
| ticket as *const TPMT_TK_AUTH as *const u8, | ||
| std::mem::size_of::<TPMT_TK_AUTH>(), | ||
| ); | ||
| 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::<TPMT_TK_AUTH>(), | ||
| ) | ||
| }; | ||
| 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(()) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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() | ||
| ); | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.