Skip to content

Commit e155f6e

Browse files
Merge pull request #27 from hyperfinitism/fix/policy-tests-and-policysigned-auth
fix: correct policy test flows and policysigned auth session handling
2 parents dab52d6 + 35c3fb2 commit e155f6e

8 files changed

Lines changed: 1293 additions & 355 deletions

File tree

src/cmd/policysigned.rs

Lines changed: 137 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,22 @@ use std::path::PathBuf;
55
use anyhow::Context;
66
use clap::Parser;
77
use log::info;
8-
use tss_esapi::constants::SessionType;
9-
use tss_esapi::handles::{ObjectHandle, SessionHandle};
10-
use tss_esapi::structures::{Digest, Nonce, Signature};
8+
use tss_esapi::structures::Signature;
119
use tss_esapi::traits::UnMarshall;
12-
use tss_esapi::tss2_esys::TPMT_TK_AUTH;
10+
use tss_esapi::tss2_esys::*;
1311

1412
use crate::cli::GlobalOpts;
15-
use crate::context::create_context;
16-
use crate::handle::{ContextSource, load_object_from_source};
17-
use crate::parse::parse_context_source;
18-
use crate::session::load_session_from_file;
13+
use crate::parse::{self, parse_context_source};
14+
use crate::raw_esys::RawEsysContext;
1915

20-
/// Authorize a policy with a signed authorization.
16+
use crate::handle::ContextSource;
17+
18+
/// Extend a policy with PolicySigned.
2119
///
22-
/// Wraps TPM2_PolicySigned.
20+
/// Wraps TPM2_PolicySigned (raw FFI). PolicySigned uses no
21+
/// authorization sessions because the command has no authIndex.
22+
/// Instead, the TPM validates the provided signature against
23+
/// authObject's public key as part of the policy assertion.
2324
#[derive(Parser)]
2425
pub struct PolicySignedCmd {
2526
/// Policy session file
@@ -43,8 +44,8 @@ pub struct PolicySignedCmd {
4344
pub cphash_input: Option<PathBuf>,
4445

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

4950
/// Output file for the timeout
5051
#[arg(short = 't', long = "timeout")]
@@ -61,84 +62,160 @@ pub struct PolicySignedCmd {
6162

6263
impl PolicySignedCmd {
6364
pub fn execute(&self, global: &GlobalOpts) -> anyhow::Result<()> {
64-
let mut ctx = create_context(global.tcti.as_deref())?;
65+
let mut raw = RawEsysContext::new(global.tcti.as_deref())?;
6566

66-
let session = load_session_from_file(&mut ctx, &self.session, SessionType::Policy)?;
67-
let policy_session = session
68-
.try_into()
69-
.map_err(|_| anyhow::anyhow!("expected a policy session"))?;
67+
let session_handle = raw.context_load(
68+
self.session
69+
.to_str()
70+
.ok_or_else(|| anyhow::anyhow!("invalid session path"))?,
71+
)?;
7072

71-
let auth_object = load_object_from_source(&mut ctx, &self.key_context)?;
73+
let auth_object = raw.resolve_handle_from_source(&self.key_context)?;
7274

7375
let sig_data = std::fs::read(&self.signature)
7476
.with_context(|| format!("reading signature from {}", self.signature.display()))?;
7577
let signature = Signature::unmarshall(&sig_data)
7678
.map_err(|e| anyhow::anyhow!("invalid signature: {e}"))?;
79+
let tpmt_sig: TPMT_SIGNATURE = signature
80+
.try_into()
81+
.map_err(|e| anyhow::anyhow!("signature conversion: {e:?}"))?;
82+
83+
let nonce_tpm = TPM2B_NONCE::default();
7784

7885
let cp_hash = match &self.cphash_input {
7986
Some(path) => {
8087
let data = std::fs::read(path)?;
81-
Digest::try_from(data).map_err(|e| anyhow::anyhow!("invalid cpHash: {e}"))?
88+
let mut buf = TPM2B_DIGEST::default();
89+
if data.len() > buf.buffer.len() {
90+
anyhow::bail!(
91+
"cpHash from {} is too large: {} bytes (maximum {} bytes)",
92+
path.display(),
93+
data.len(),
94+
buf.buffer.len()
95+
);
96+
}
97+
buf.size = data.len() as u16;
98+
buf.buffer[..data.len()].copy_from_slice(&data);
99+
buf
82100
}
83-
None => Digest::default(),
101+
None => TPM2B_DIGEST::default(),
84102
};
85103

86104
let policy_ref = match &self.qualification {
87-
Some(bytes) => Nonce::try_from(bytes.as_slice().to_vec())
88-
.map_err(|e| anyhow::anyhow!("qualifying data: {e}"))?,
89-
None => Nonce::default(),
105+
Some(q) => {
106+
let data = q.as_slice();
107+
let mut buf = TPM2B_NONCE::default();
108+
if data.len() > buf.buffer.len() {
109+
anyhow::bail!(
110+
"qualification is too large: {} bytes (maximum {} bytes)",
111+
data.len(),
112+
buf.buffer.len()
113+
);
114+
}
115+
buf.size = data.len() as u16;
116+
buf.buffer[..data.len()].copy_from_slice(data);
117+
buf
118+
}
119+
None => TPM2B_NONCE::default(),
90120
};
91121

92-
let expiration = if self.expiration == 0 {
93-
None
94-
} else {
95-
Some(std::time::Duration::from_secs(self.expiration as u64))
96-
};
122+
// Extract data from ESYS-allocated pointers immediately, then free
123+
// them before performing any I/O that could fail and leak memory.
124+
let (timeout_data, ticket_data) = unsafe {
125+
let mut timeout_ptr: *mut TPM2B_TIMEOUT = std::ptr::null_mut();
126+
let mut ticket_ptr: *mut TPMT_TK_AUTH = std::ptr::null_mut();
97127

98-
let (timeout, ticket) = ctx
99-
.policy_signed(
100-
policy_session,
128+
// PolicySigned has Auth Index: None for both handles,
129+
// so all session handles are ESYS_TR_NONE.
130+
let rc = Esys_PolicySigned(
131+
raw.ptr(),
101132
auth_object,
102-
Nonce::default(), // nonce_tpm
103-
cp_hash,
104-
policy_ref,
105-
expiration,
106-
signature,
107-
)
108-
.context("TPM2_PolicySigned failed")?;
133+
session_handle,
134+
ESYS_TR_NONE,
135+
ESYS_TR_NONE,
136+
ESYS_TR_NONE,
137+
&nonce_tpm,
138+
&cp_hash,
139+
&policy_ref,
140+
self.expiration,
141+
&tpmt_sig,
142+
&mut timeout_ptr,
143+
&mut ticket_ptr,
144+
);
145+
if rc != 0 {
146+
anyhow::bail!("Esys_PolicySigned failed: 0x{rc:08x}");
147+
}
109148

110-
info!("policy signed succeeded");
149+
let timeout_data = if !timeout_ptr.is_null() {
150+
let t = &*timeout_ptr;
151+
Some(t.buffer[..t.size as usize].to_vec())
152+
} else {
153+
None
154+
};
155+
156+
let ticket_data = if !ticket_ptr.is_null() {
157+
let ticket = &*ticket_ptr;
158+
let bytes = std::slice::from_raw_parts(
159+
ticket as *const TPMT_TK_AUTH as *const u8,
160+
std::mem::size_of::<TPMT_TK_AUTH>(),
161+
);
162+
Some(bytes.to_vec())
163+
} else {
164+
None
165+
};
111166

112-
if let Some(ref path) = self.timeout_out {
113-
std::fs::write(path, timeout.as_bytes())
167+
if !timeout_ptr.is_null() {
168+
Esys_Free(timeout_ptr as *mut _);
169+
}
170+
if !ticket_ptr.is_null() {
171+
Esys_Free(ticket_ptr as *mut _);
172+
}
173+
174+
(timeout_data, ticket_data)
175+
};
176+
177+
if let (Some(path), Some(data)) = (&self.timeout_out, &timeout_data) {
178+
std::fs::write(path, data)
114179
.with_context(|| format!("writing timeout to {}", path.display()))?;
115180
}
116181

117-
if let Some(ref path) = self.ticket_out {
118-
let tss_ticket: TPMT_TK_AUTH = ticket
119-
.try_into()
120-
.map_err(|e| anyhow::anyhow!("failed to convert ticket: {e:?}"))?;
121-
let bytes = unsafe {
122-
std::slice::from_raw_parts(
123-
&tss_ticket as *const TPMT_TK_AUTH as *const u8,
124-
std::mem::size_of::<TPMT_TK_AUTH>(),
125-
)
126-
};
127-
std::fs::write(path, bytes)
182+
if let (Some(path), Some(data)) = (&self.ticket_out, &ticket_data) {
183+
std::fs::write(path, data)
128184
.with_context(|| format!("writing ticket to {}", path.display()))?;
129185
}
130186

131187
if let Some(ref path) = self.policy {
132-
let digest = ctx
133-
.policy_get_digest(policy_session)
134-
.context("TPM2_PolicyGetDigest failed")?;
135-
std::fs::write(path, digest.as_bytes())
136-
.with_context(|| format!("writing policy digest to {}", path.display()))?;
188+
let digest_data = unsafe {
189+
let mut digest_ptr: *mut TPM2B_DIGEST = std::ptr::null_mut();
190+
let rc = Esys_PolicyGetDigest(
191+
raw.ptr(),
192+
session_handle,
193+
ESYS_TR_NONE,
194+
ESYS_TR_NONE,
195+
ESYS_TR_NONE,
196+
&mut digest_ptr,
197+
);
198+
if rc != 0 {
199+
anyhow::bail!("Esys_PolicyGetDigest failed: 0x{rc:08x}");
200+
}
201+
202+
if !digest_ptr.is_null() {
203+
let d = &*digest_ptr;
204+
let v = d.buffer[..d.size as usize].to_vec();
205+
Esys_Free(digest_ptr as *mut _);
206+
Some(v)
207+
} else {
208+
None
209+
}
210+
};
211+
if let Some(ref data) = digest_data {
212+
std::fs::write(path, data)
213+
.with_context(|| format!("writing policy digest to {}", path.display()))?;
214+
}
137215
}
138216

139-
let handle: ObjectHandle = SessionHandle::from(policy_session).into();
140-
crate::session::save_session_and_forget(ctx, handle, &self.session)?;
141-
217+
raw.context_save_to_file(session_handle, &self.session)?;
218+
info!("policy signed succeeded");
142219
Ok(())
143220
}
144221
}

tests/context.rs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
//! Context management tests: contextsave, contextload.
3+
4+
mod common;
5+
6+
use common::SwtpmSession;
7+
8+
#[test]
9+
fn contextsave_and_contextload_roundtrip() {
10+
let s = SwtpmSession::new();
11+
let primary = s.create_primary_rsa("primary");
12+
13+
// Save the context to a file.
14+
let saved = s.tmp().path().join("saved.json");
15+
s.cmd("contextsave")
16+
.arg("-c")
17+
.arg(SwtpmSession::file_ref(&primary))
18+
.arg("-o")
19+
.arg(&saved)
20+
.assert()
21+
.success();
22+
assert!(std::fs::metadata(&saved).unwrap().len() > 0);
23+
24+
// Flush the object so the handle is freed.
25+
s.flush_transient();
26+
27+
// Reload the context from the saved file.
28+
let restored = s.tmp().path().join("restored.json");
29+
s.cmd("contextload")
30+
.arg("-c")
31+
.arg(&saved)
32+
.arg("-o")
33+
.arg(&restored)
34+
.assert()
35+
.success();
36+
assert!(std::fs::metadata(&restored).unwrap().len() > 0);
37+
}
38+
39+
#[test]
40+
fn contextsave_ecc_key() {
41+
let s = SwtpmSession::new();
42+
let primary = s.create_primary_ecc("ecc_primary");
43+
44+
let saved = s.tmp().path().join("ecc_saved.json");
45+
s.cmd("contextsave")
46+
.arg("-c")
47+
.arg(SwtpmSession::file_ref(&primary))
48+
.arg("-o")
49+
.arg(&saved)
50+
.assert()
51+
.success();
52+
assert!(std::fs::metadata(&saved).unwrap().len() > 0);
53+
}
54+
55+
#[test]
56+
fn contextload_then_readpublic() {
57+
let s = SwtpmSession::new();
58+
let primary = s.create_primary_rsa("primary");
59+
60+
// Read public from original handle.
61+
let pub_orig = s.tmp().path().join("pub_orig.bin");
62+
s.cmd("readpublic")
63+
.arg("-c")
64+
.arg(SwtpmSession::file_ref(&primary))
65+
.arg("-o")
66+
.arg(&pub_orig)
67+
.assert()
68+
.success();
69+
70+
// Save and reload.
71+
let saved = s.tmp().path().join("saved.json");
72+
s.cmd("contextsave")
73+
.arg("-c")
74+
.arg(SwtpmSession::file_ref(&primary))
75+
.arg("-o")
76+
.arg(&saved)
77+
.assert()
78+
.success();
79+
80+
s.flush_transient();
81+
82+
let restored = s.tmp().path().join("restored.json");
83+
s.cmd("contextload")
84+
.arg("-c")
85+
.arg(&saved)
86+
.arg("-o")
87+
.arg(&restored)
88+
.assert()
89+
.success();
90+
91+
// Read public from restored context — should match.
92+
let pub_restored = s.tmp().path().join("pub_restored.bin");
93+
s.cmd("readpublic")
94+
.arg("-c")
95+
.arg(SwtpmSession::file_ref(&restored))
96+
.arg("-o")
97+
.arg(&pub_restored)
98+
.assert()
99+
.success();
100+
101+
assert_eq!(
102+
std::fs::read(&pub_orig).unwrap(),
103+
std::fs::read(&pub_restored).unwrap()
104+
);
105+
}

0 commit comments

Comments
 (0)