1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
//! Seal a secret to the TPM and unseal it back (keyed-hash sealed objects).
use crate::device::Device;
use crate::key::{auth_ptr, Key, KeyBlob};
use crate::{check_rc, sys, Result, Secret, TpmError};
use alloc::vec;
use alloc::vec::Vec;
use core::ffi::c_int;
/// Number of PCRs a TPM 2.0 implementation exposes; valid indices are `0..24`.
const PCR_COUNT: u8 = 24;
/// Reject an empty or out-of-range PCR selection, which would otherwise bind a
/// sealed object to fewer PCRs than the caller asked for (silently dropped by
/// the TPM's selection builder).
fn check_pcr_indices(pcr_indices: &[u8]) -> Result<()> {
if pcr_indices.is_empty() || pcr_indices.iter().any(|&i| i >= PCR_COUNT) {
return Err(TpmError(crate::BUFFER_E));
}
Ok(())
}
impl Device {
/// Seal `data` under `parent`, optionally protected by `auth`. Returns the
/// sealed blob (persist it with [`KeyBlob::to_bytes`](crate::KeyBlob::to_bytes)).
pub fn seal(&self, parent: &Key<'_>, data: &[u8], auth: Option<&[u8]>) -> Result<KeyBlob<'_>> {
// SAFETY: TPMT_PUBLIC is a C POD struct; all-zero is a valid starting state for the seal template helper.
let mut tmpl: sys::TPMT_PUBLIC = unsafe { core::mem::zeroed() };
// SAFETY: &mut tmpl is a valid, exclusively-borrowed out-param for wolfTPM2_GetKeyTemplate_KeySeal.
let rc = unsafe {
sys::wolfTPM2_GetKeyTemplate_KeySeal(
&mut tmpl,
sys::TPM_ALG_ID_T_TPM_ALG_SHA256 as sys::TPM_ALG_ID,
)
};
check_rc(rc)?;
// SAFETY: WOLFTPM2_KEYBLOB is a C POD struct; all-zero is a valid starting state for CreateKeySeal to fill.
let data_len = crate::checked_c_int(data.len())?;
let mut blob: sys::WOLFTPM2_KEYBLOB = unsafe { core::mem::zeroed() };
let (authp, authsz) = auth_ptr(auth)?;
// SAFETY: self.ptr()/parent.handle_ptr() are live, &mut blob/&mut tmpl are valid out-params, authp/authsz match (ptr, len) or (null, 0), and data ptr+len bound its slice.
let rc = unsafe {
sys::wolfTPM2_CreateKeySeal(
self.ptr(),
&mut blob,
parent.handle_ptr(),
&mut tmpl,
authp,
authsz,
data.as_ptr(),
data_len,
)
};
check_rc(rc)?;
// Keep only the persistable pub/priv blob; drop any transient handle so
// unseal re-loads it fresh under the parent.
if blob.handle.hndl != 0 {
// SAFETY: self.ptr() is live and &mut blob.handle addresses the transient handle CreateKeySeal returned.
unsafe { sys::wolfTPM2_UnloadHandle(self.ptr(), &mut blob.handle) };
blob.handle.hndl = 0;
}
Ok(KeyBlob::from_parts(self.ptr(), blob))
}
/// Load a `sealed` blob under `parent` and release its secret. Requires the
/// same `auth` the blob was sealed with.
pub fn unseal(
&self,
sealed: KeyBlob<'_>,
parent: &Key<'_>,
auth: Option<&[u8]>,
) -> Result<Secret> {
let key = sealed.load(parent, None)?;
if let Some(a) = auth {
key.set_auth(a)?;
}
// SAFETY: self.ptr() is the heap-stable dev pointer and key.handle_ptr() remains valid for this call.
unsafe { sys::wolfTPM2_SetAuthHandle(self.ptr(), 0, key.handle_ptr()) };
// SAFETY: Unseal_In/Unseal_Out are C POD structs; all-zero is a valid starting state for TPM2_Unseal to fill.
let mut cmd_in: sys::Unseal_In = unsafe { core::mem::zeroed() };
cmd_in.itemHandle = key.handle();
// SAFETY: Unseal_Out is a C POD struct; all-zero is a valid starting state for TPM2_Unseal to fill.
let mut cmd_out: sys::Unseal_Out = unsafe { core::mem::zeroed() };
// SAFETY: &mut cmd_in/&mut cmd_out are valid, exclusively-borrowed in/out-params for TPM2_Unseal.
let rc = unsafe { sys::TPM2_Unseal(&mut cmd_in, &mut cmd_out) };
// SAFETY: self.ptr() is live; this clears the auth slot set above regardless of the unseal outcome.
unsafe { sys::wolfTPM2_UnsetAuth(self.ptr(), 0) };
let out = if rc == 0 {
let n = cmd_out.outData.size as usize;
cmd_out.outData.buffer[..n].to_vec()
} else {
Vec::new()
};
// SAFETY: cmd_out is a live, fully-owned local; zeroizing it after copying out scrubs the returned secret bytes.
unsafe { crate::zeroize_raw(&mut cmd_out) };
check_rc(rc)?;
Ok(Secret::new(out))
}
/// Seal `data` under `parent`, bound to the current values of the PCRs in
/// `pcr_indices` (SHA-256 bank). Unsealing later requires those PCRs to
/// still hold the same values (measured-boot binding).
///
/// Access is gated solely by the PCR policy: there is deliberately no auth
/// value, since a PCR-policy object clears `userWithAuth` and any auth would
/// not be enforced on unseal.
pub fn seal_pcr(
&self,
parent: &Key<'_>,
data: &[u8],
pcr_indices: &[u8],
) -> Result<KeyBlob<'_>> {
check_pcr_indices(pcr_indices)?;
let sha256 = sys::TPM_ALG_ID_T_TPM_ALG_SHA256 as sys::TPM_ALG_ID;
let mut pcrs = pcr_indices.to_vec();
// Compute the PCR policy digest exactly as unseal will, using a trial
// session, so the sealed authPolicy matches what PolicyPCR produces.
// SAFETY: WOLFTPM2_SESSION is a C POD struct; all-zero is a valid starting state for StartSession to fill.
let mut trial: sys::WOLFTPM2_SESSION = unsafe { core::mem::zeroed() };
// SAFETY: self.ptr() is live and &mut trial is a valid exclusive out-param.
let rc = unsafe {
sys::wolfTPM2_StartSession(
self.ptr(),
&mut trial,
core::ptr::null_mut(),
core::ptr::null_mut(),
sys::TPM_SE_T_TPM_SE_TRIAL as sys::TPM_SE,
sys::TPM_ALG_ID_T_TPM_ALG_NULL as c_int,
)
};
check_rc(rc)?;
// SAFETY: trial.handle.hndl is the session StartSession just opened, and pcrs ptr+len bound the live Vec.
let policy_rc = unsafe {
sys::wolfTPM2_PolicyPCR(
self.ptr(),
trial.handle.hndl,
sha256,
pcrs.as_mut_ptr(),
pcrs.len() as sys::word32,
)
};
let mut policy = vec![0u8; 64];
let mut policy_sz = policy.len() as sys::word32;
// SAFETY: policy.as_mut_ptr()/policy_sz describe the full policy Vec capacity, and trial.handle.hndl is still live.
let digest_rc = unsafe {
sys::wolfTPM2_GetPolicyDigest(
self.ptr(),
trial.handle.hndl,
policy.as_mut_ptr(),
&mut policy_sz,
)
};
// SAFETY: the trial session's policy digest has been read; this unloads it regardless of the calls' outcomes.
unsafe { sys::wolfTPM2_UnloadHandle(self.ptr(), &mut trial.handle) };
check_rc(policy_rc)?;
check_rc(digest_rc)?;
policy.truncate(policy_sz as usize);
// SAFETY: TPMT_PUBLIC is a C POD struct; all-zero is a valid starting state for the seal template helper.
let mut tmpl: sys::TPMT_PUBLIC = unsafe { core::mem::zeroed() };
// SAFETY: &mut tmpl is a valid, exclusively-borrowed out-param for wolfTPM2_GetKeyTemplate_KeySeal.
let rc = unsafe { sys::wolfTPM2_GetKeyTemplate_KeySeal(&mut tmpl, sha256) };
check_rc(rc)?;
// Policy-only access: clear userWithAuth so the PCR policy is required.
tmpl.objectAttributes &=
!(sys::TPMA_OBJECT_mask_TPMA_OBJECT_userWithAuth as sys::TPMA_OBJECT);
tmpl.authPolicy.size = policy.len() as u16;
tmpl.authPolicy.buffer[..policy.len()].copy_from_slice(&policy);
// SAFETY: WOLFTPM2_KEYBLOB is a C POD struct; all-zero is a valid starting state for CreateKeySeal_ex to fill.
let data_len = crate::checked_c_int(data.len())?;
let mut blob: sys::WOLFTPM2_KEYBLOB = unsafe { core::mem::zeroed() };
let (authp, authsz) = auth_ptr(None)?;
// SAFETY: self.ptr()/parent.handle_ptr() are live, &mut blob/&mut tmpl are valid out-params, authp/authsz is (null, 0), and data ptr+len bound its slice.
let rc = unsafe {
sys::wolfTPM2_CreateKeySeal_ex(
self.ptr(),
&mut blob,
parent.handle_ptr(),
&mut tmpl,
authp,
authsz,
sha256,
core::ptr::null_mut(),
0,
data.as_ptr(),
data_len,
)
};
check_rc(rc)?;
if blob.handle.hndl != 0 {
// SAFETY: self.ptr() is live and &mut blob.handle addresses the transient handle CreateKeySeal_ex returned.
unsafe { sys::wolfTPM2_UnloadHandle(self.ptr(), &mut blob.handle) };
blob.handle.hndl = 0;
}
Ok(KeyBlob::from_parts(self.ptr(), blob))
}
/// Unseal a PCR-bound blob. Succeeds only if the PCRs in `pcr_indices` still
/// hold the values they had when [`seal_pcr`](Device::seal_pcr) ran.
pub fn unseal_pcr(
&self,
sealed: KeyBlob<'_>,
parent: &Key<'_>,
pcr_indices: &[u8],
) -> Result<Secret> {
check_pcr_indices(pcr_indices)?;
let key = sealed.load(parent, None)?;
// Policy session, satisfy the PCR policy, register it, then bind the
// sealed object's name for the session HMAC before unsealing.
// SAFETY: WOLFTPM2_SESSION is a C POD struct; all-zero is a valid starting state for StartSession to fill.
let mut session: sys::WOLFTPM2_SESSION = unsafe { core::mem::zeroed() };
// SAFETY: self.ptr() is live and &mut session is a valid exclusive out-param.
let rc = unsafe {
sys::wolfTPM2_StartSession(
self.ptr(),
&mut session,
core::ptr::null_mut(),
core::ptr::null_mut(),
sys::TPM_SE_T_TPM_SE_POLICY as sys::TPM_SE,
sys::TPM_ALG_ID_T_TPM_ALG_NULL as c_int,
)
};
check_rc(rc)?;
let mut pcrs = pcr_indices.to_vec();
// SAFETY: session.handle.hndl is the session StartSession just opened, and pcrs ptr+len bound the live Vec.
let policy_rc = unsafe {
sys::wolfTPM2_PolicyPCR(
self.ptr(),
session.handle.hndl,
sys::TPM_ALG_ID_T_TPM_ALG_SHA256 as sys::TPM_ALG_ID,
pcrs.as_mut_ptr(),
pcrs.len() as sys::word32,
)
};
// SAFETY: &mut session still refers to the live session opened above.
let set_rc = unsafe {
sys::wolfTPM2_SetAuthSession(
self.ptr(),
0,
&mut session,
sys::TPMA_SESSION_mask_TPMA_SESSION_continueSession as sys::TPMA_SESSION,
)
};
// SAFETY: self.ptr() is live and key.handle_ptr() addresses the just-loaded key's own handle.
unsafe { sys::wolfTPM2_SetAuthHandleName(self.ptr(), 0, key.handle_ptr()) };
// SAFETY: Unseal_In/Unseal_Out are C POD structs; all-zero is a valid starting state for TPM2_Unseal to fill.
let mut cmd_in: sys::Unseal_In = unsafe { core::mem::zeroed() };
cmd_in.itemHandle = key.handle();
// SAFETY: Unseal_Out is a C POD struct; all-zero is a valid starting state for TPM2_Unseal to fill.
let mut cmd_out: sys::Unseal_Out = unsafe { core::mem::zeroed() };
let unseal_rc = if policy_rc != 0 {
policy_rc
} else if set_rc != 0 {
set_rc
} else {
// SAFETY: &mut cmd_in/&mut cmd_out are valid, exclusively-borrowed in/out-params, reached only once policy/session setup succeeded.
unsafe { sys::TPM2_Unseal(&mut cmd_in, &mut cmd_out) }
};
// SAFETY: self.ptr() is live; this clears the auth slot and unloads the policy session regardless of the unseal outcome.
unsafe {
sys::wolfTPM2_UnsetAuth(self.ptr(), 0);
sys::wolfTPM2_UnloadHandle(self.ptr(), &mut session.handle);
}
let out = if unseal_rc == 0 {
let n = cmd_out.outData.size as usize;
cmd_out.outData.buffer[..n].to_vec()
} else {
Vec::new()
};
// SAFETY: cmd_out is a live, fully-owned local; zeroizing it after copying out scrubs the returned secret bytes.
unsafe { crate::zeroize_raw(&mut cmd_out) };
check_rc(unseal_rc)?;
Ok(Secret::new(out))
}
}