|
| 1 | +// SPDX-License-Identifier: MIT |
| 2 | +// |
| 3 | +// TPM internal-state extraction and injection. |
| 4 | +// |
| 5 | +// Provides `extract_vtpm_state()` / `inject_vtpm_state()` via FFI into the |
| 6 | +// `state_accessor.{c,h}` getter/setter functions, which directly access |
| 7 | +// the TPM 2.0 Reference Implementation's internal globals (gp, gc, gr). |
| 8 | +// A command-based fallback (`extract_vtpm_state_via_commands`) is sketched |
| 9 | +// for environments where the direct accessors are unavailable. |
| 10 | + |
| 11 | +extern crate alloc; |
| 12 | + |
| 13 | +use alloc::vec; |
| 14 | +use alloc::vec::Vec; |
| 15 | + |
| 16 | +use crate::protocols::errors::SvsmReqError; |
| 17 | + |
| 18 | +// ============================================================ |
| 19 | +// FFI Declarations — direct extern "C" (no bindgen needed) |
| 20 | +// ============================================================ |
| 21 | + |
| 22 | +unsafe extern "C" { |
| 23 | + // Seed accessors |
| 24 | + fn get_ep_seed(out: *mut u8); |
| 25 | + fn set_ep_seed(in_: *const u8); |
| 26 | + fn get_sp_seed(out: *mut u8); |
| 27 | + fn set_sp_seed(in_: *const u8); |
| 28 | + fn get_pp_seed(out: *mut u8); |
| 29 | + fn set_pp_seed(in_: *const u8); |
| 30 | + |
| 31 | + // Auth value accessors (return actual size written) |
| 32 | + fn get_owner_auth(out_buf: *mut u8, buf_size: usize) -> usize; |
| 33 | + fn set_owner_auth(in_: *const u8, len: usize); |
| 34 | + fn get_endorsement_auth(out_buf: *mut u8, buf_size: usize) -> usize; |
| 35 | + fn set_endorsement_auth(in_: *const u8, len: usize); |
| 36 | + fn get_lockout_auth(out_buf: *mut u8, buf_size: usize) -> usize; |
| 37 | + fn set_lockout_auth(in_: *const u8, len: usize); |
| 38 | + fn get_platform_auth(out_buf: *mut u8, buf_size: usize) -> usize; |
| 39 | + fn set_platform_auth(in_: *const u8, len: usize); |
| 40 | + |
| 41 | + // Counter accessors |
| 42 | + fn get_total_reset_count() -> u64; |
| 43 | + fn set_total_reset_count(val: u64); |
| 44 | + fn get_reset_count() -> u32; |
| 45 | + fn set_reset_count(val: u32); |
| 46 | + fn get_clear_count() -> u32; |
| 47 | + fn set_clear_count(val: u32); |
| 48 | + #[allow(dead_code)] |
| 49 | + fn get_object_context_id() -> u64; |
| 50 | + #[allow(dead_code)] |
| 51 | + fn set_object_context_id(val: u64); |
| 52 | + |
| 53 | + // PCR save accessors |
| 54 | + fn get_pcr_save(out_buf: *mut u8, buf_size: usize) -> usize; |
| 55 | + fn set_pcr_save(in_: *const u8, len: usize); |
| 56 | + |
| 57 | + // Bulk serialization |
| 58 | + fn serialize_vtpm_state(out_buf: *mut u8, buf_size: usize) -> usize; |
| 59 | + fn deserialize_vtpm_state(in_: *const u8, len: usize) -> i32; |
| 60 | +} |
| 61 | + |
| 62 | +// ============================================================ |
| 63 | +// Constants |
| 64 | +// ============================================================ |
| 65 | + |
| 66 | +/// Primary seed size in bytes (SHA-256 → 32 bytes) |
| 67 | +pub const PRIMARY_SEED_SIZE: usize = 32; |
| 68 | + |
| 69 | +/// Maximum auth value buffer size |
| 70 | +const MAX_AUTH_SIZE: usize = 64; |
| 71 | + |
| 72 | +/// Maximum PCR save area size (worst case: SHA-256 + SHA-384 + SHA-512 banks) |
| 73 | +const MAX_PCR_SAVE_SIZE: usize = 4096; |
| 74 | + |
| 75 | +/// Maximum serialized state buffer size |
| 76 | +const MAX_SERIALIZED_SIZE: usize = 8192; |
| 77 | + |
| 78 | +// ============================================================ |
| 79 | +// VtpmInternalState — Mirror of TPM Internal Globals |
| 80 | +// ============================================================ |
| 81 | + |
| 82 | +/// Complete dump of seal-relevant TPM internal state. |
| 83 | +/// |
| 84 | +/// Covers Persistent (gp), State Clear (gc), and State Reset (gr) data. |
| 85 | +#[derive(Debug, Clone)] |
| 86 | +pub struct VtpmInternalState { |
| 87 | + /// Endorsement Primary Seed (32 bytes) |
| 88 | + pub ep_seed: [u8; 32], |
| 89 | + /// Storage Primary Seed (32 bytes) — the SRK seed |
| 90 | + pub sp_seed: [u8; 32], |
| 91 | + /// Platform Primary Seed (32 bytes) |
| 92 | + pub pp_seed: [u8; 32], |
| 93 | + /// Owner hierarchy auth value |
| 94 | + pub owner_auth: Vec<u8>, |
| 95 | + /// Endorsement hierarchy auth value |
| 96 | + pub endorsement_auth: Vec<u8>, |
| 97 | + /// Lockout auth value |
| 98 | + pub lockout_auth: Vec<u8>, |
| 99 | + /// Platform auth value |
| 100 | + pub platform_auth: Vec<u8>, |
| 101 | + /// PCR save area (raw binary dump of PCR_SAVE struct) |
| 102 | + pub pcr_save: Vec<u8>, |
| 103 | + /// Total reset counter (monotonic across TPM lifetime) |
| 104 | + pub total_reset_count: u64, |
| 105 | + /// Reset counter (reset by TPM2_Clear) |
| 106 | + pub reset_count: u32, |
| 107 | + /// Clear count (incremented on TPM Resume) |
| 108 | + pub clear_count: u32, |
| 109 | +} |
| 110 | + |
| 111 | +impl VtpmInternalState { |
| 112 | + /// Create an empty (all-zeros) state for bootstrap. |
| 113 | + pub fn empty() -> Self { |
| 114 | + Self { |
| 115 | + ep_seed: [0u8; 32], |
| 116 | + sp_seed: [0u8; 32], |
| 117 | + pp_seed: [0u8; 32], |
| 118 | + owner_auth: Vec::new(), |
| 119 | + endorsement_auth: Vec::new(), |
| 120 | + lockout_auth: Vec::new(), |
| 121 | + platform_auth: Vec::new(), |
| 122 | + pcr_save: Vec::new(), |
| 123 | + total_reset_count: 0, |
| 124 | + reset_count: 0, |
| 125 | + clear_count: 0, |
| 126 | + } |
| 127 | + } |
| 128 | +} |
| 129 | + |
| 130 | +// ============================================================ |
| 131 | +// Direct global access via libtcgtpm accessors (FFI) |
| 132 | +// ============================================================ |
| 133 | + |
| 134 | +/// Extract all seal-relevant TPM internal state via direct C global access. |
| 135 | +/// |
| 136 | +/// Calls the state_accessor.c getter functions, which read gp, gc, gr |
| 137 | +/// directly. This is the fastest path but requires libtcgtpm to be |
| 138 | +/// compiled with state_accessor.c. |
| 139 | +pub fn extract_vtpm_state() -> Result<VtpmInternalState, SvsmReqError> { |
| 140 | + let mut ep_seed = [0u8; 32]; |
| 141 | + let mut sp_seed = [0u8; 32]; |
| 142 | + let mut pp_seed = [0u8; 32]; |
| 143 | + |
| 144 | + // SAFETY: FFI calls into state_accessor.c. Buffer pointers and sizes are correct. |
| 145 | + unsafe { |
| 146 | + get_ep_seed(ep_seed.as_mut_ptr()); |
| 147 | + get_sp_seed(sp_seed.as_mut_ptr()); |
| 148 | + get_pp_seed(pp_seed.as_mut_ptr()); |
| 149 | + } |
| 150 | + |
| 151 | + let owner_auth = get_auth_value(get_owner_auth)?; |
| 152 | + let endorsement_auth = get_auth_value(get_endorsement_auth)?; |
| 153 | + let lockout_auth = get_auth_value(get_lockout_auth)?; |
| 154 | + let platform_auth = get_auth_value(get_platform_auth)?; |
| 155 | + |
| 156 | + let mut pcr_buf = vec![0u8; MAX_PCR_SAVE_SIZE]; |
| 157 | + let pcr_len; |
| 158 | + // SAFETY: FFI call with correct buffer size. |
| 159 | + unsafe { |
| 160 | + pcr_len = get_pcr_save(pcr_buf.as_mut_ptr(), pcr_buf.len()); |
| 161 | + } |
| 162 | + pcr_buf.truncate(pcr_len); |
| 163 | + |
| 164 | + let total_reset_count; |
| 165 | + let reset_count; |
| 166 | + let clear_count; |
| 167 | + // SAFETY: FFI calls returning scalar values. |
| 168 | + unsafe { |
| 169 | + total_reset_count = get_total_reset_count(); |
| 170 | + reset_count = get_reset_count(); |
| 171 | + clear_count = get_clear_count(); |
| 172 | + } |
| 173 | + |
| 174 | + Ok(VtpmInternalState { |
| 175 | + ep_seed, |
| 176 | + sp_seed, |
| 177 | + pp_seed, |
| 178 | + owner_auth, |
| 179 | + endorsement_auth, |
| 180 | + lockout_auth, |
| 181 | + platform_auth, |
| 182 | + pcr_save: pcr_buf, |
| 183 | + total_reset_count, |
| 184 | + reset_count, |
| 185 | + clear_count, |
| 186 | + }) |
| 187 | +} |
| 188 | + |
| 189 | +/// Inject TPM internal state via direct C global write. |
| 190 | +/// |
| 191 | +/// Writes all seal-relevant fields back into gp, gc, gr. This must be |
| 192 | +/// called before any TPM operation that depends on the restored state |
| 193 | +/// (e.g., before creating the SRK or loading persistent objects). |
| 194 | +pub fn inject_vtpm_state(state: &VtpmInternalState) -> Result<(), SvsmReqError> { |
| 195 | + // SAFETY: FFI calls into state_accessor.c. All pointers and lengths are valid. |
| 196 | + unsafe { |
| 197 | + set_ep_seed(state.ep_seed.as_ptr()); |
| 198 | + set_sp_seed(state.sp_seed.as_ptr()); |
| 199 | + set_pp_seed(state.pp_seed.as_ptr()); |
| 200 | + |
| 201 | + set_owner_auth(state.owner_auth.as_ptr(), state.owner_auth.len()); |
| 202 | + set_endorsement_auth( |
| 203 | + state.endorsement_auth.as_ptr(), |
| 204 | + state.endorsement_auth.len(), |
| 205 | + ); |
| 206 | + set_lockout_auth(state.lockout_auth.as_ptr(), state.lockout_auth.len()); |
| 207 | + set_platform_auth(state.platform_auth.as_ptr(), state.platform_auth.len()); |
| 208 | + |
| 209 | + set_pcr_save(state.pcr_save.as_ptr(), state.pcr_save.len()); |
| 210 | + |
| 211 | + set_total_reset_count(state.total_reset_count); |
| 212 | + set_reset_count(state.reset_count); |
| 213 | + set_clear_count(state.clear_count); |
| 214 | + } |
| 215 | + |
| 216 | + Ok(()) |
| 217 | +} |
| 218 | + |
| 219 | +// ============================================================ |
| 220 | +// Bulk Serialization (C-backed) |
| 221 | +// ============================================================ |
| 222 | + |
| 223 | +/// Serialize all seal-relevant state into a flat binary buffer using |
| 224 | +/// the C `serialize_vtpm_state()` function. |
| 225 | +/// |
| 226 | +/// Returns the serialized bytes. This is the recommended path for |
| 227 | +/// state extraction before sealing, as the C function knows the exact |
| 228 | +/// struct layouts. |
| 229 | +pub fn extract_serialized_state() -> Result<Vec<u8>, SvsmReqError> { |
| 230 | + let mut buf = vec![0u8; MAX_SERIALIZED_SIZE]; |
| 231 | + let written; |
| 232 | + // SAFETY: FFI call with correctly sized buffer. |
| 233 | + unsafe { |
| 234 | + written = serialize_vtpm_state(buf.as_mut_ptr(), buf.len()); |
| 235 | + } |
| 236 | + if written == 0 || written > buf.len() { |
| 237 | + log::error!("serialize_vtpm_state failed: buffer too small or error"); |
| 238 | + return Err(SvsmReqError::invalid_request()); |
| 239 | + } |
| 240 | + buf.truncate(written); |
| 241 | + Ok(buf) |
| 242 | +} |
| 243 | + |
| 244 | +/// Deserialize state from the C `serialize_vtpm_state()` format back |
| 245 | +/// into TPM globals. |
| 246 | +pub fn inject_serialized_state(data: &[u8]) -> Result<(), SvsmReqError> { |
| 247 | + let ret; |
| 248 | + // SAFETY: FFI call with valid buffer pointer and length. |
| 249 | + unsafe { |
| 250 | + ret = deserialize_vtpm_state(data.as_ptr(), data.len()); |
| 251 | + } |
| 252 | + if ret != 0 { |
| 253 | + log::error!("deserialize_vtpm_state failed: error code {ret}"); |
| 254 | + return Err(SvsmReqError::invalid_request()); |
| 255 | + } |
| 256 | + Ok(()) |
| 257 | +} |
| 258 | + |
| 259 | +// ============================================================ |
| 260 | +// Command-based fallback path (skeleton) |
| 261 | +// ============================================================ |
| 262 | + |
| 263 | +/// Extract vTPM state using standard TPM 2.0 commands. |
| 264 | +/// |
| 265 | +/// This path uses: |
| 266 | +/// - TPM2_NV_Read for persistent NV indices (auth values, counters) |
| 267 | +/// - TPM2_ContextSave for loaded objects (EK, SRK) |
| 268 | +/// - TPM2_GetCapability for PCR allocation and algorithm info |
| 269 | +/// |
| 270 | +/// This is specification-compliant and works with any TPM (real or |
| 271 | +/// simulated), but requires the TPM to be fully initialized and the |
| 272 | +/// target objects to be loaded. |
| 273 | +/// |
| 274 | +/// NOT YET IMPLEMENTED — placeholder for environments where the direct |
| 275 | +/// libtcgtpm accessors are unavailable. |
| 276 | +#[allow(dead_code)] |
| 277 | +pub fn extract_vtpm_state_via_commands() -> Result<Vec<u8>, SvsmReqError> { |
| 278 | + // A command-based extraction path would: |
| 279 | + // 1. TPM2_HierarchyChangeAuth(TPM_RH_OWNER) — read ownerAuth |
| 280 | + // (can't actually read it; would need prior knowledge or use Policy) |
| 281 | + // 2. TPM2_NV_Read for NV indices 0x01c00000–0x01c00003 (EK, SRK templates) |
| 282 | + // 3. TPM2_ContextSave for the SRK handle (if loaded) |
| 283 | + // 4. TPM2_ContextSave for the EK handle (if loaded) |
| 284 | + // 5. TPM2_GetCapability(TPM_CAP_TPM_PROPERTIES, TPM_PT_RESET_COUNT, ...) |
| 285 | + // 6. TPM2_GetCapability(TPM_CAP_PCR, ...) |
| 286 | + // |
| 287 | + // Limitation: auth values cannot be read via TPM commands by design. |
| 288 | + // This path therefore requires the provisioner to have set known auth |
| 289 | + // values during manufacturing, or to use policy-based access. |
| 290 | + log::warn!("Command-based state extraction not yet implemented"); |
| 291 | + Err(SvsmReqError::invalid_request()) |
| 292 | +} |
| 293 | + |
| 294 | +// ============================================================ |
| 295 | +// Helpers |
| 296 | +// ============================================================ |
| 297 | + |
| 298 | +/// Read an auth value via the C getter, which returns the actual |
| 299 | +/// length and copies data into a caller-provided buffer. |
| 300 | +fn get_auth_value( |
| 301 | + getter: unsafe extern "C" fn(*mut u8, usize) -> usize, |
| 302 | +) -> Result<Vec<u8>, SvsmReqError> { |
| 303 | + let mut buf = vec![0u8; MAX_AUTH_SIZE]; |
| 304 | + let len; |
| 305 | + // SAFETY: FFI call into state_accessor.c. Buffer pointer and size are valid. |
| 306 | + unsafe { |
| 307 | + len = getter(buf.as_mut_ptr(), buf.len()); |
| 308 | + } |
| 309 | + if len > buf.len() { |
| 310 | + log::error!( |
| 311 | + "Auth value getter returned length {} > buffer {}", |
| 312 | + len, |
| 313 | + buf.len() |
| 314 | + ); |
| 315 | + return Err(SvsmReqError::invalid_request()); |
| 316 | + } |
| 317 | + buf.truncate(len); |
| 318 | + Ok(buf) |
| 319 | +} |
0 commit comments