Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 137 additions & 60 deletions src/cmd/policysigned.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")]
Expand All @@ -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;

Check failure

Code scanning / CodeQL

Access of invalid pointer High

This operation dereferences a pointer that may be
invalid
.

Copilot Autofix

AI 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.

Some(t.buffer[..t.size as usize].to_vec())
} else {
None
};

let ticket_data = if !ticket_ptr.is_null() {
let ticket = &*ticket_ptr;

Check failure

Code scanning / CodeQL

Access of invalid pointer High

This operation dereferences a pointer that may be
invalid
.

Copilot Autofix

AI 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;

Check failure

Code scanning / CodeQL

Access of invalid pointer High

This operation dereferences a pointer that may be
invalid
.

Copilot Autofix

AI 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 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(())
}
}
105 changes: 105 additions & 0 deletions tests/context.rs
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()
);
}
Loading
Loading