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
6 changes: 2 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
196 changes: 57 additions & 139 deletions src/cmd/policysigned.rs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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<Duration>,

/// cpHash file (optional)
#[arg(long = "cphash-input")]
pub cphash_input: Option<PathBuf>,

/// Policy reference (digest) (hex:<hex_bytes> or file:<path>)
#[arg(short = 'q', long = "qualification", value_parser = parse::parse_qualification)]
pub qualification: Option<parse::Qualification>,
#[arg(short = 'q', long = "qualification", value_parser = crate::parse::parse_qualification)]
pub qualification: Option<crate::parse::Qualification>,

/// Output file for the timeout
#[arg(short = 't', long = "timeout")]
Expand All @@ -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::<TPMT_TK_AUTH>(),
);
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::<TPMT_TK_AUTH>(),
)
};
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(())
}
}
15 changes: 15 additions & 0 deletions src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,21 @@ pub fn parse_hex_u32(s: &str) -> Result<u32, String> {
.map_err(|_| format!("expected a hex value (e.g. 0x01400001), got: '{s}'"))
}

// ---------------------------------------------------------------------------
// Duration
// ---------------------------------------------------------------------------

pub fn parse_duration(s: &str) -> Result<Option<std::time::Duration>, 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
// ---------------------------------------------------------------------------
Expand Down
Loading