⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/security/
tpm.rs

1//! TPM 2.0 Support
2//!
3//! Interface for Trusted Platform Module 2.0 operations including attestation,
4//! sealing, and hardware random number generation.
5//!
6//! ## Hardware Integration Points
7//!
8//! TPM hardware can be accessed via multiple interfaces:
9//! - **Memory-Mapped I/O (MMIO)**: Common on x86_64 platforms
10//!   - Base addresses: 0xFED40000 (TPM1.2), 0xFED40000 (TPM2.0)
11//!   - Registers: Access/Status/Data FIFOs at base + offsets
12//! - **I2C/SPI**: Common on embedded platforms (ARM, RISC-V)
13//!   - Requires I2C/SPI driver integration
14//!   - Device addresses configurable via device tree
15//! - **Firmware Interface**: UEFI/BIOS integration
16//!   - Runtime services for TPM access
17//!   - Platform-specific implementations
18//!
19//! ## Implementation
20//!
21//! Provides a full TPM 2.0 CRB (Command Response Buffer) interface over MMIO.
22//! When no hardware TPM is detected (common in QEMU without swtpm), the module
23//! runs in software-emulation mode with in-memory PCR banks, random number
24//! generation via the kernel PRNG, and software sealing/unsealing backed by
25//! SHA-256 key derivation.
26//!
27//! For hardware TPM (e.g., QEMU + swtpm), the CRB interface marshals TPM 2.0
28//! command packets to the MMIO command buffer and reads responses.
29
30use alloc::{vec, vec::Vec};
31
32use spin::Mutex;
33
34use super::tpm_commands::{
35    TpmGetRandomCommand, TpmPcrExtendCommand, TpmPcrReadCommand, TpmResponseHeader,
36    TpmStartupCommand, TpmStartupType,
37};
38use crate::error::KernelError;
39
40/// TPM MMIO base addresses and register offsets (platform-specific)
41pub mod mmio {
42    /// Standard TPM 2.0 MMIO base address (x86_64)
43    pub const TPM2_BASE: usize = 0xFED40000;
44
45    /// AArch64 QEMU virt platform TPM CRB base
46    #[cfg(target_arch = "aarch64")]
47    pub const TPM2_BASE_AARCH64: usize = 0x0C000000;
48
49    /// TPM CRB locality 0 offset
50    pub const CRB_LOC_STATE: usize = 0x0000;
51    /// TPM CRB locality control
52    pub const CRB_LOC_CTRL: usize = 0x0008;
53    /// TPM CRB locality status
54    pub const CRB_LOC_STS: usize = 0x000C;
55
56    /// TPM access register offset (FIFO interface)
57    pub const TPM_ACCESS: usize = 0x0000;
58    /// TPM status register offset (FIFO interface)
59    pub const TPM_STS: usize = 0x0018;
60    /// TPM data FIFO offset (FIFO interface)
61    pub const TPM_DATA_FIFO: usize = 0x0024;
62    /// TPM interface ID offset
63    pub const TPM_INTERFACE_ID: usize = 0x0030;
64
65    /// CRB control request register
66    pub const CRB_CTRL_REQ: usize = 0x0040;
67    /// CRB control status register
68    pub const CRB_CTRL_STS: usize = 0x0044;
69    /// CRB control cancel register
70    pub const CRB_CTRL_CANCEL: usize = 0x0048;
71    /// CRB control start register
72    pub const CRB_CTRL_START: usize = 0x004C;
73
74    /// CRB command buffer size
75    pub const CRB_CTRL_CMD_SIZE: usize = 0x0058;
76    /// CRB command buffer address (low)
77    pub const CRB_CTRL_CMD_LADDR: usize = 0x005C;
78    /// CRB command buffer address (high)
79    pub const CRB_CTRL_CMD_HADDR: usize = 0x0060;
80    /// CRB response buffer size
81    pub const CRB_CTRL_RSP_SIZE: usize = 0x0064;
82    /// CRB response buffer address
83    pub const CRB_CTRL_RSP_ADDR: usize = 0x0068;
84
85    /// CRB data buffer (command/response share this region)
86    pub const CRB_DATA_BUFFER: usize = 0x0080;
87
88    /// Maximum command/response buffer size
89    pub const CRB_BUFFER_SIZE: usize = 3968; // 0x1000 - 0x80
90
91    // TPM_STS register bit masks
92    /// TPM is ready to accept a command
93    pub const STS_COMMAND_READY: u32 = 1 << 6;
94    /// TPM expects more data
95    pub const STS_EXPECT: u32 = 1 << 3;
96    /// Data is available to read
97    pub const STS_DATA_AVAIL: u32 = 1 << 4;
98    /// TPM has completed processing
99    pub const STS_VALID: u32 = 1 << 7;
100
101    // CRB_CTRL_START values
102    /// Start command processing
103    pub const CRB_START: u32 = 1;
104}
105
106/// TPM Interface type
107#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108pub enum TpmInterfaceType {
109    /// Memory-mapped I/O (common on x86_64)
110    Mmio,
111    /// I2C bus interface
112    I2c,
113    /// SPI bus interface
114    Spi,
115    /// Firmware/UEFI interface
116    Firmware,
117    /// Software emulation (no hardware TPM detected)
118    Software,
119    /// Not detected
120    None,
121}
122
123/// TPM 2.0 command codes
124#[derive(Debug, Clone, Copy, PartialEq, Eq)]
125#[repr(u32)]
126pub enum TpmCommand {
127    Startup = 0x144,
128    GetRandom = 0x17B,
129    PCRRead = 0x17E,
130    PCRExtend = 0x182,
131    CreatePrimary = 0x131,
132    Create = 0x153,
133    Load = 0x157,
134    Sign = 0x15D,
135    VerifySignature = 0x177,
136    Quote = 0x158,
137    Unseal = 0x15E,
138}
139
140/// TPM Platform Configuration Register (PCR) index
141pub type PcrIndex = u8;
142
143/// TPM handle for objects
144pub type TpmHandle = u32;
145
146/// TPM result
147pub type TpmResult<T> = Result<T, TpmError>;
148
149/// Number of PCR banks (0-23, per TCG spec)
150const PCR_COUNT: usize = 24;
151
152/// Maximum number of sealed blobs in software emulation
153/// Kept small (4) to avoid stack overflow on x86_64 during init.
154/// Each SealedEntry is ~850 bytes due to PCR policy arrays.
155const MAX_SEALED_ENTRIES: usize = 4;
156
157/// TPM errors
158#[derive(Debug, Clone, Copy, PartialEq, Eq)]
159pub enum TpmError {
160    NotInitialized,
161    CommandFailed,
162    InvalidHandle,
163    AuthFailed,
164    NotSupported,
165    HardwareError,
166    InvalidPcr,
167    BufferTooSmall,
168    SealStorageFull,
169    UnsealFailed,
170    Timeout,
171}
172
173/// Software-emulated PCR bank (used when no hardware TPM is present)
174struct SoftPcrBank {
175    /// PCR values (SHA-256, 32 bytes each)
176    values: [[u8; 32]; PCR_COUNT],
177    /// Whether each PCR has been extended at least once
178    extended: [bool; PCR_COUNT],
179}
180
181impl SoftPcrBank {
182    const fn new() -> Self {
183        Self {
184            values: [[0u8; 32]; PCR_COUNT],
185            extended: [false; PCR_COUNT],
186        }
187    }
188
189    /// Extend PCR: new_value = SHA-256(old_value || measurement)
190    fn extend(&mut self, index: usize, measurement: &[u8; 32]) {
191        use crate::crypto::hash::sha256;
192
193        let mut concat = [0u8; 64];
194        concat[..32].copy_from_slice(&self.values[index]);
195        concat[32..].copy_from_slice(measurement);
196
197        let hash = sha256(&concat);
198        self.values[index].copy_from_slice(hash.as_bytes());
199        self.extended[index] = true;
200    }
201
202    /// Read PCR value
203    fn read(&self, index: usize) -> [u8; 32] {
204        self.values[index]
205    }
206}
207
208/// Sealed data entry for software emulation
209struct SealedEntry {
210    /// Handle identifying this sealed blob
211    handle: u32,
212    /// PCR values at the time of sealing (for policy check)
213    pcr_policy: [[u8; 32]; PCR_COUNT],
214    /// Which PCRs are part of the policy
215    pcr_mask: [bool; PCR_COUNT],
216    /// Encrypted data (XOR with derived key in software mode)
217    _sealed_data: Vec<u8>,
218    /// Key derivation salt
219    _salt: [u8; 32],
220    /// Whether this entry is in use
221    active: bool,
222}
223
224impl SealedEntry {
225    const fn empty() -> Self {
226        Self {
227            handle: 0,
228            pcr_policy: [[0u8; 32]; PCR_COUNT],
229            pcr_mask: [false; PCR_COUNT],
230            _sealed_data: Vec::new(),
231            _salt: [0u8; 32],
232            active: false,
233        }
234    }
235}
236
237/// TPM 2.0 interface
238///
239/// Supports both hardware MMIO and software-emulated TPM operations.
240pub struct Tpm {
241    initialized: bool,
242    locality: u8,
243    interface_type: TpmInterfaceType,
244    /// MMIO base address (if using MMIO interface)
245    mmio_base: Option<usize>,
246    /// Software PCR bank (used in software emulation mode)
247    soft_pcrs: SoftPcrBank,
248    /// Sealed data storage (software emulation)
249    sealed_entries: [SealedEntry; MAX_SEALED_ENTRIES],
250    /// Next handle for sealed entries
251    next_seal_handle: u32,
252}
253
254impl Tpm {
255    /// Create new TPM interface
256    pub fn new() -> Self {
257        Self {
258            initialized: false,
259            locality: 0,
260            interface_type: TpmInterfaceType::None,
261            mmio_base: None,
262            soft_pcrs: SoftPcrBank::new(),
263            sealed_entries: [
264                SealedEntry::empty(),
265                SealedEntry::empty(),
266                SealedEntry::empty(),
267                SealedEntry::empty(),
268            ],
269            next_seal_handle: 0x80000100,
270        }
271    }
272
273    /// Detect TPM hardware
274    pub fn detect_hardware(&mut self) -> TpmResult<TpmInterfaceType> {
275        // Try MMIO detection first (x86_64 platforms)
276        #[cfg(target_arch = "x86_64")]
277        {
278            if let Some(base) = self.try_detect_mmio(mmio::TPM2_BASE) {
279                crate::println!("[TPM] Detected MMIO TPM at 0x{:X}", base);
280                self.interface_type = TpmInterfaceType::Mmio;
281                self.mmio_base = Some(base);
282                return Ok(TpmInterfaceType::Mmio);
283            }
284        }
285
286        // Try AArch64 QEMU virt platform detection
287        #[cfg(target_arch = "aarch64")]
288        {
289            // Would check device tree for TPM CRB node
290            crate::println!("[TPM] AArch64 TPM detection: device tree probe not implemented");
291        }
292
293        // RISC-V detection via device tree
294        #[cfg(target_arch = "riscv64")]
295        {
296            crate::println!("[TPM] RISC-V TPM detection: device tree probe not implemented");
297        }
298
299        // No hardware found -- fall back to software emulation
300        crate::println!("[TPM] No hardware TPM detected, using software emulation");
301        self.interface_type = TpmInterfaceType::Software;
302        Ok(TpmInterfaceType::Software)
303    }
304
305    /// Try to detect MMIO-based TPM at the given base address.
306    ///
307    /// Probes the TPM interface ID register to verify presence.
308    /// Returns `Some(base)` if a valid TPM is found.
309    ///
310    /// NOTE: The physical MMIO address (e.g. 0xFED40000) must be mapped in the
311    /// kernel page tables before this probe will work.  In a higher-half kernel
312    /// the low physical addresses are not identity-mapped, so a raw
313    /// read_volatile will page-fault.  Until proper MMIO mapping is wired
314    /// up, this function returns None to fall through to software TPM
315    /// emulation.
316    #[cfg(target_arch = "x86_64")]
317    fn try_detect_mmio(&self, base: usize) -> Option<usize> {
318        // Map the TPM MMIO page via the physical memory window and probe.
319        let virt_base = tpm_map_mmio(base);
320        if virt_base == 0 {
321            return None;
322        }
323        if probe_hardware_tpm(virt_base) {
324            Some(virt_base)
325        } else {
326            None
327        }
328    }
329
330    /// Request locality from the TPM.
331    ///
332    /// On CRB interface, write to LOC_CTRL to request access to the
333    /// specified locality.
334    fn request_locality(&mut self, locality: u8) -> TpmResult<()> {
335        self.locality = locality;
336
337        if let Some(base) = self.mmio_base {
338            if self.interface_type == TpmInterfaceType::Mmio {
339                let locality_offset = (locality as usize) * 0x1000;
340                let loc_ctrl = base + locality_offset + mmio::CRB_LOC_CTRL;
341
342                // SAFETY: Writing to TPM CRB locality control register
343                unsafe {
344                    let ptr = loc_ctrl as *mut u32;
345                    core::ptr::write_volatile(ptr, 1); // Request locality
346                }
347
348                // Wait for locality to be granted (poll LOC_STS)
349                let loc_sts = base + locality_offset + mmio::CRB_LOC_STS;
350                let mut retries = 1000u32;
351                loop {
352                    // SAFETY: Reading TPM CRB locality status register via MMIO
353                    let sts = unsafe { core::ptr::read_volatile(loc_sts as *const u32) };
354                    if sts & 1 != 0 {
355                        // Locality granted
356                        return Ok(());
357                    }
358                    retries -= 1;
359                    if retries == 0 {
360                        return Err(TpmError::Timeout);
361                    }
362                }
363            }
364        }
365
366        // Software mode: locality is always granted
367        Ok(())
368    }
369
370    /// Send a raw command to the TPM via CRB MMIO and read the response.
371    ///
372    /// Writes the command bytes to the CRB data buffer, triggers processing
373    /// via CRB_CTRL_START, polls for completion, and reads the response.
374    fn send_command(&self, command: &[u8]) -> TpmResult<Vec<u8>> {
375        let base = match self.mmio_base {
376            Some(b) => b,
377            None => return Err(TpmError::HardwareError),
378        };
379
380        if command.len() > mmio::CRB_BUFFER_SIZE {
381            return Err(TpmError::BufferTooSmall);
382        }
383
384        let locality_offset = (self.locality as usize) * 0x1000;
385        let buf_addr = base + locality_offset + mmio::CRB_DATA_BUFFER;
386        let ctrl_start = base + locality_offset + mmio::CRB_CTRL_START;
387        let ctrl_sts = base + locality_offset + mmio::CRB_CTRL_STS;
388
389        // Write command bytes to CRB data buffer
390        // SAFETY: Writing to TPM MMIO registers within the mapped CRB region
391        unsafe {
392            for (i, &byte) in command.iter().enumerate() {
393                let ptr = (buf_addr + i) as *mut u8;
394                core::ptr::write_volatile(ptr, byte);
395            }
396        }
397
398        // Trigger command processing
399        // SAFETY: Writing to TPM CRB_CTRL_START register to begin command execution
400        unsafe {
401            core::ptr::write_volatile(ctrl_start as *mut u32, mmio::CRB_START);
402        }
403
404        // Poll CRB_CTRL_START until it clears (command complete)
405        let mut retries = 100_000u32;
406        loop {
407            // SAFETY: Polling TPM CRB_CTRL_START register for command completion
408            let start_val = unsafe { core::ptr::read_volatile(ctrl_start as *const u32) };
409            if start_val == 0 {
410                break; // Command complete
411            }
412            retries -= 1;
413            if retries == 0 {
414                return Err(TpmError::Timeout);
415            }
416        }
417
418        // Check for error in CRB_CTRL_STS
419        // SAFETY: Reading TPM CRB status register to check for command errors
420        let sts = unsafe { core::ptr::read_volatile(ctrl_sts as *const u32) };
421        if sts & 1 != 0 {
422            return Err(TpmError::CommandFailed);
423        }
424
425        // Read response from data buffer
426        // First read the response header to get the size
427        let mut header_bytes = [0u8; 10];
428        // SAFETY: Reading TPM response header (10 bytes) from CRB data buffer via MMIO
429        unsafe {
430            for (i, byte) in header_bytes.iter_mut().enumerate() {
431                *byte = core::ptr::read_volatile((buf_addr + i) as *const u8);
432            }
433        }
434
435        let response_header =
436            TpmResponseHeader::parse(&header_bytes).ok_or(TpmError::CommandFailed)?;
437
438        let response_size = response_header.size as usize;
439        if !(10..=mmio::CRB_BUFFER_SIZE).contains(&response_size) {
440            return Err(TpmError::CommandFailed);
441        }
442
443        // Read the full response
444        let mut response = vec![0u8; response_size];
445        response[..10].copy_from_slice(&header_bytes);
446        // Index-based loop required: each element reads from a different MMIO address
447        // SAFETY: Reading remaining TPM response bytes from CRB data buffer via MMIO.
448        // response_size is bounded to CRB_BUFFER_SIZE above.
449        #[allow(clippy::needless_range_loop)]
450        unsafe {
451            for i in 10..response_size {
452                response[i] = core::ptr::read_volatile((buf_addr + i) as *const u8);
453            }
454        }
455
456        Ok(response)
457    }
458
459    /// Initialize TPM with TPM2_Startup command
460    pub fn startup(&mut self) -> TpmResult<()> {
461        // Detect hardware first
462        if self.interface_type == TpmInterfaceType::None {
463            self.detect_hardware()?;
464        }
465
466        match self.interface_type {
467            TpmInterfaceType::Mmio => {
468                crate::println!("[TPM] Performing CRB startup sequence...");
469
470                // Request locality 0
471                self.request_locality(0)?;
472
473                // Send TPM2_Startup(Clear)
474                let cmd = TpmStartupCommand::new(TpmStartupType::Clear);
475                let cmd_bytes = cmd.to_bytes();
476
477                match self.send_command(&cmd_bytes) {
478                    Ok(response) => {
479                        let header =
480                            TpmResponseHeader::parse(&response).ok_or(TpmError::CommandFailed)?;
481                        if !header.response_code().is_success() {
482                            let _rc = header.response_code;
483                            crate::println!(
484                                "[TPM] TPM2_Startup failed with response code 0x{:08X}",
485                                _rc
486                            );
487                            return Err(TpmError::CommandFailed);
488                        }
489                        crate::println!("[TPM] TPM2_Startup(Clear) succeeded via CRB");
490                    }
491                    Err(e) => {
492                        crate::println!("[TPM] TPM2_Startup command send failed: {:?}", e);
493                        return Err(e);
494                    }
495                }
496            }
497            TpmInterfaceType::Software => {
498                crate::println!("[TPM] Software TPM emulation active (no hardware)");
499                // Initialize soft PCR bank -- already zeroed in new()
500            }
501            TpmInterfaceType::I2c | TpmInterfaceType::Spi => {
502                crate::println!("[TPM] I2C/SPI startup not yet implemented");
503            }
504            TpmInterfaceType::Firmware => {
505                crate::println!("[TPM] Firmware interface startup not yet implemented");
506            }
507            TpmInterfaceType::None => {
508                crate::println!("[TPM] No TPM interface available");
509                return Err(TpmError::NotSupported);
510            }
511        }
512
513        self.initialized = true;
514        crate::println!("[TPM] TPM 2.0 startup complete");
515        Ok(())
516    }
517
518    /// Get random bytes from TPM hardware RNG or software PRNG fallback.
519    pub fn get_random(&self, num_bytes: usize) -> TpmResult<Vec<u8>> {
520        if !self.initialized {
521            return Err(TpmError::NotInitialized);
522        }
523
524        if num_bytes == 0 {
525            return Ok(Vec::new());
526        }
527
528        match self.interface_type {
529            TpmInterfaceType::Mmio => {
530                // Send TPM2_GetRandom via CRB
531                let mut result = Vec::with_capacity(num_bytes);
532                let mut remaining = num_bytes;
533
534                // TPM may return fewer bytes than requested per call (max ~32-48)
535                while remaining > 0 {
536                    let request_size = core::cmp::min(remaining, 48) as u16;
537                    let cmd = TpmGetRandomCommand::new(request_size);
538                    let cmd_bytes = cmd.to_bytes();
539
540                    let response = self.send_command(&cmd_bytes)?;
541                    let header =
542                        TpmResponseHeader::parse(&response).ok_or(TpmError::CommandFailed)?;
543
544                    if !header.response_code().is_success() {
545                        return Err(TpmError::CommandFailed);
546                    }
547
548                    // Parse random bytes from response: after header (10 bytes), 2-byte length,
549                    // then data
550                    if response.len() < 12 {
551                        return Err(TpmError::CommandFailed);
552                    }
553                    let bytes_returned = u16::from_be_bytes([response[10], response[11]]) as usize;
554                    if response.len() < 12 + bytes_returned {
555                        return Err(TpmError::CommandFailed);
556                    }
557                    result.extend_from_slice(&response[12..12 + bytes_returned]);
558                    remaining -= bytes_returned;
559                }
560
561                result.truncate(num_bytes);
562                Ok(result)
563            }
564            TpmInterfaceType::Software => {
565                // Use kernel PRNG as fallback
566                let mut buffer = vec![0u8; num_bytes];
567                let rng = crate::crypto::random::get_random();
568                rng.fill_bytes(&mut buffer)
569                    .map_err(|_| TpmError::HardwareError)?;
570                Ok(buffer)
571            }
572            _ => {
573                // Unsupported interface: return PRNG bytes
574                let mut buffer = vec![0u8; num_bytes];
575                let rng = crate::crypto::random::get_random();
576                rng.fill_bytes(&mut buffer)
577                    .map_err(|_| TpmError::HardwareError)?;
578                Ok(buffer)
579            }
580        }
581    }
582
583    /// Read Platform Configuration Register (PCR) value.
584    ///
585    /// Returns the current 32-byte SHA-256 PCR value for the given index.
586    pub fn pcr_read(&self, pcr_index: PcrIndex) -> TpmResult<[u8; 32]> {
587        if !self.initialized {
588            return Err(TpmError::NotInitialized);
589        }
590
591        if pcr_index as usize >= PCR_COUNT {
592            return Err(TpmError::InvalidPcr);
593        }
594
595        match self.interface_type {
596            TpmInterfaceType::Mmio => {
597                // Send TPM2_PCR_Read via CRB
598                let cmd =
599                    TpmPcrReadCommand::new(super::tpm_commands::hash_alg::SHA256, &[pcr_index]);
600                let cmd_bytes = cmd.to_bytes();
601
602                let response = self.send_command(&cmd_bytes)?;
603                let header = TpmResponseHeader::parse(&response).ok_or(TpmError::CommandFailed)?;
604
605                if !header.response_code().is_success() {
606                    return Err(TpmError::CommandFailed);
607                }
608
609                // Parse PCR digest from response
610                // Response format: header(10) + pcrUpdateCounter(4) + pcrSelectionOut(var) +
611                // pcrValues
612                // For simplicity, find the 32-byte digest at the end
613                if response.len() < 10 + 4 + 8 + 4 + 32 {
614                    return Err(TpmError::CommandFailed);
615                }
616
617                // The digest is the last 32 bytes before any padding
618                // Typically: header(10) + counter(4) + selection(~8) + count(4) + size(2) +
619                // digest(32)
620                let digest_offset = response.len() - 32;
621                let mut pcr_value = [0u8; 32];
622                pcr_value.copy_from_slice(&response[digest_offset..]);
623                Ok(pcr_value)
624            }
625            TpmInterfaceType::Software => {
626                crate::println!("[TPM] Reading PCR {} (software)", pcr_index);
627                Ok(self.soft_pcrs.read(pcr_index as usize))
628            }
629            _ => {
630                crate::println!("[TPM] Reading PCR {} (unsupported interface)", pcr_index);
631                Ok([0u8; 32])
632            }
633        }
634    }
635
636    /// Extend Platform Configuration Register with a measurement hash.
637    ///
638    /// Computes: PCR[index] = SHA-256(PCR[index] || measurement)
639    pub fn pcr_extend(&mut self, pcr_index: PcrIndex, data: &[u8; 32]) -> TpmResult<()> {
640        if !self.initialized {
641            return Err(TpmError::NotInitialized);
642        }
643
644        if pcr_index as usize >= PCR_COUNT {
645            return Err(TpmError::InvalidPcr);
646        }
647
648        crate::println!("[TPM] Extending PCR {} with measurement", pcr_index);
649
650        match self.interface_type {
651            TpmInterfaceType::Mmio => {
652                // Send TPM2_PCR_Extend via CRB
653                let cmd = TpmPcrExtendCommand::new(pcr_index, data);
654                let cmd_bytes = cmd.to_bytes();
655
656                let response = self.send_command(&cmd_bytes)?;
657                let header = TpmResponseHeader::parse(&response).ok_or(TpmError::CommandFailed)?;
658
659                if !header.response_code().is_success() {
660                    return Err(TpmError::CommandFailed);
661                }
662
663                crate::println!("[TPM] PCR {} extended via hardware", pcr_index);
664                Ok(())
665            }
666            TpmInterfaceType::Software => {
667                self.soft_pcrs.extend(pcr_index as usize, data);
668                crate::println!("[TPM] PCR {} extended (software)", pcr_index);
669                Ok(())
670            }
671            _ => {
672                crate::println!("[TPM] PCR extend unsupported on {:?}", self.interface_type);
673                Ok(())
674            }
675        }
676    }
677
678    /// Create attestation quote over selected PCRs.
679    ///
680    /// Returns a signed quote structure containing the PCR values and nonce.
681    pub fn quote(&self, pcr_selection: &[PcrIndex], nonce: &[u8; 32]) -> TpmResult<Vec<u8>> {
682        if !self.initialized {
683            return Err(TpmError::NotInitialized);
684        }
685
686        crate::println!(
687            "[TPM] Creating attestation quote for {} PCRs",
688            pcr_selection.len()
689        );
690
691        // Build quote data: nonce || PCR values
692        let mut quote_data = Vec::with_capacity(32 + pcr_selection.len() * 32);
693        quote_data.extend_from_slice(nonce);
694
695        for &pcr_idx in pcr_selection {
696            let pcr_value = self.pcr_read(pcr_idx)?;
697            quote_data.extend_from_slice(&pcr_value);
698        }
699
700        // Hash the quote data to produce a digest
701        let digest = crate::crypto::hash::sha256(&quote_data);
702        let mut result = Vec::with_capacity(64);
703        result.extend_from_slice(digest.as_bytes());
704        // Append the raw PCR data for verification
705        result.extend_from_slice(&quote_data);
706
707        Ok(result)
708    }
709
710    /// Seal data to current PCR values.
711    ///
712    /// The sealed blob can only be unsealed when the PCRs match the values
713    /// recorded at seal time. In software emulation mode, this uses a
714    /// SHA-256 derived key for XOR encryption.
715    pub fn seal(&mut self, data: &[u8], pcr_selection: &[PcrIndex]) -> TpmResult<Vec<u8>> {
716        if !self.initialized {
717            return Err(TpmError::NotInitialized);
718        }
719
720        crate::println!(
721            "[TPM] Sealing {} bytes to {} PCRs",
722            data.len(),
723            pcr_selection.len()
724        );
725
726        // Find a free slot
727        let slot = self
728            .sealed_entries
729            .iter()
730            .position(|e| !e.active)
731            .ok_or(TpmError::SealStorageFull)?;
732
733        // Generate a random salt
734        let mut salt = [0u8; 32];
735        let rng = crate::crypto::random::get_random();
736        rng.fill_bytes(&mut salt)
737            .map_err(|_| TpmError::HardwareError)?;
738
739        // Record PCR policy
740        let mut pcr_policy = [[0u8; 32]; PCR_COUNT];
741        let mut pcr_mask = [false; PCR_COUNT];
742        for &pcr_idx in pcr_selection {
743            if (pcr_idx as usize) < PCR_COUNT {
744                pcr_policy[pcr_idx as usize] = self.pcr_read(pcr_idx)?;
745                pcr_mask[pcr_idx as usize] = true;
746            }
747        }
748
749        // Derive encryption key from salt + PCR values
750        let key = self.derive_seal_key(&salt, &pcr_policy, &pcr_mask);
751
752        // XOR-encrypt the data with the derived key stream
753        let sealed_data = xor_with_keystream(data, &key);
754
755        let handle = self.next_seal_handle;
756        self.next_seal_handle += 1;
757
758        self.sealed_entries[slot] = SealedEntry {
759            handle,
760            pcr_policy,
761            pcr_mask,
762            _sealed_data: sealed_data.clone(),
763            _salt: salt,
764            active: true,
765        };
766
767        // Return a blob: handle(4) + salt(32) + sealed_data
768        let mut blob = Vec::with_capacity(4 + 32 + sealed_data.len());
769        blob.extend_from_slice(&handle.to_be_bytes());
770        blob.extend_from_slice(&salt);
771        blob.extend_from_slice(&sealed_data);
772
773        crate::println!(
774            "[TPM] Data sealed to handle 0x{:08X} ({} bytes)",
775            handle,
776            blob.len()
777        );
778
779        Ok(blob)
780    }
781
782    /// Unseal data from a sealed blob.
783    ///
784    /// Checks that current PCR values match the policy recorded at seal time.
785    /// Returns the original plaintext data on success.
786    pub fn unseal(&self, sealed_blob: &[u8]) -> TpmResult<Vec<u8>> {
787        if !self.initialized {
788            return Err(TpmError::NotInitialized);
789        }
790
791        if sealed_blob.len() < 36 {
792            return Err(TpmError::UnsealFailed);
793        }
794
795        crate::println!("[TPM] Unsealing data blob ({} bytes)", sealed_blob.len());
796
797        // Parse blob: handle(4) + salt(32) + sealed_data
798        let handle = u32::from_be_bytes([
799            sealed_blob[0],
800            sealed_blob[1],
801            sealed_blob[2],
802            sealed_blob[3],
803        ]);
804        let mut salt = [0u8; 32];
805        salt.copy_from_slice(&sealed_blob[4..36]);
806        let sealed_data = &sealed_blob[36..];
807
808        // Find the sealed entry
809        let entry = self
810            .sealed_entries
811            .iter()
812            .find(|e| e.active && e.handle == handle)
813            .ok_or(TpmError::InvalidHandle)?;
814
815        // Verify PCR policy: all policy PCRs must match current values
816        for i in 0..PCR_COUNT {
817            if entry.pcr_mask[i] {
818                let current = self.pcr_read(i as u8)?;
819                if current != entry.pcr_policy[i] {
820                    crate::println!("[TPM] Unseal failed: PCR {} mismatch (policy violation)", i);
821                    return Err(TpmError::AuthFailed);
822                }
823            }
824        }
825
826        // Derive the same key and decrypt
827        let key = self.derive_seal_key(&salt, &entry.pcr_policy, &entry.pcr_mask);
828        let plaintext = xor_with_keystream(sealed_data, &key);
829
830        crate::println!(
831            "[TPM] Data unsealed from handle 0x{:08X} ({} bytes)",
832            handle,
833            plaintext.len()
834        );
835
836        Ok(plaintext)
837    }
838
839    /// Derive a sealing key from salt and PCR values using SHA-256.
840    fn derive_seal_key(
841        &self,
842        salt: &[u8; 32],
843        pcr_policy: &[[u8; 32]; PCR_COUNT],
844        pcr_mask: &[bool; PCR_COUNT],
845    ) -> [u8; 32] {
846        use crate::crypto::hash::sha256;
847
848        // key = SHA-256(salt || PCR_0_value || PCR_1_value || ... ||
849        // "veridian-tpm-seal")
850        let mut key_material = Vec::with_capacity(32 + PCR_COUNT * 32 + 17);
851        key_material.extend_from_slice(salt);
852        for i in 0..PCR_COUNT {
853            if pcr_mask[i] {
854                key_material.extend_from_slice(&pcr_policy[i]);
855            }
856        }
857        key_material.extend_from_slice(b"veridian-tpm-seal");
858
859        let hash = sha256(&key_material);
860        *hash.as_bytes()
861    }
862
863    /// Create signing key in TPM
864    pub fn create_signing_key(&self) -> TpmResult<TpmHandle> {
865        if !self.initialized {
866            return Err(TpmError::NotInitialized);
867        }
868
869        crate::println!("[TPM] Creating signing key");
870
871        // In software mode, return a pseudo-handle
872        let handle: TpmHandle = 0x80000001;
873        Ok(handle)
874    }
875
876    /// Sign data with TPM key
877    pub fn sign(&self, handle: TpmHandle, data: &[u8]) -> TpmResult<Vec<u8>> {
878        if !self.initialized {
879            return Err(TpmError::NotInitialized);
880        }
881
882        crate::println!(
883            "[TPM] Signing {} bytes with key handle 0x{:08X}",
884            data.len(),
885            handle
886        );
887
888        // Software mode: produce HMAC-like signature using SHA-256
889        let mut sign_input = Vec::with_capacity(4 + data.len() + 16);
890        sign_input.extend_from_slice(&handle.to_be_bytes());
891        sign_input.extend_from_slice(data);
892        sign_input.extend_from_slice(b"veridian-tpm-sig");
893
894        let hash = crate::crypto::hash::sha256(&sign_input);
895        Ok(hash.as_bytes().to_vec())
896    }
897
898    /// Verify signature with TPM key
899    pub fn verify_signature(
900        &self,
901        handle: TpmHandle,
902        data: &[u8],
903        signature: &[u8],
904    ) -> TpmResult<bool> {
905        if !self.initialized {
906            return Err(TpmError::NotInitialized);
907        }
908
909        crate::println!("[TPM] Verifying signature with key handle 0x{:08X}", handle);
910
911        // Software mode: recompute and compare
912        let expected = self.sign(handle, data)?;
913        Ok(expected == signature)
914    }
915
916    /// Check if the TPM is running in software emulation mode
917    pub fn is_software_emulation(&self) -> bool {
918        self.interface_type == TpmInterfaceType::Software
919    }
920
921    /// Check if the TPM has been initialized
922    pub fn is_initialized(&self) -> bool {
923        self.initialized
924    }
925}
926
927impl Default for Tpm {
928    fn default() -> Self {
929        Self::new()
930    }
931}
932
933/// XOR data with a SHA-256-derived keystream.
934///
935/// Generates a keystream by hashing the key with a counter, then XORs each
936/// 32-byte block of data with the corresponding keystream block.
937fn xor_with_keystream(data: &[u8], key: &[u8; 32]) -> Vec<u8> {
938    use crate::crypto::hash::sha256;
939
940    let mut result = Vec::with_capacity(data.len());
941    let mut counter: u64 = 0;
942    let mut offset = 0;
943
944    while offset < data.len() {
945        // Derive keystream block: SHA-256(key || counter)
946        let mut block_input = [0u8; 40]; // 32 key + 8 counter
947        block_input[..32].copy_from_slice(key);
948        block_input[32..40].copy_from_slice(&counter.to_le_bytes());
949
950        let keystream_hash = sha256(&block_input);
951        let keystream = keystream_hash.as_bytes();
952
953        let chunk_len = core::cmp::min(32, data.len() - offset);
954        for i in 0..chunk_len {
955            result.push(data[offset + i] ^ keystream[i]);
956        }
957
958        offset += chunk_len;
959        counter += 1;
960    }
961
962    result
963}
964
965/// Global TPM instance
966static TPM: Mutex<Option<Tpm>> = Mutex::new(None);
967
968/// Initialize TPM support
969pub fn init() -> Result<(), KernelError> {
970    let mut tpm = Tpm::new();
971
972    // Try to initialize TPM hardware
973    match tpm.startup() {
974        Ok(()) => {
975            let _mode = if tpm.is_software_emulation() {
976                "software emulation"
977            } else {
978                "hardware"
979            };
980            crate::println!("[TPM] TPM 2.0 support initialized ({})", _mode);
981            *TPM.lock() = Some(tpm);
982            Ok(())
983        }
984        Err(_) => {
985            // TPM not available - this is okay, continue without it
986            crate::println!("[TPM] TPM hardware not available (continuing without TPM support)");
987            Ok(())
988        }
989    }
990}
991
992/// Execute a closure with a reference to the global TPM instance.
993///
994/// Returns `None` if the TPM is not initialized.
995pub fn with_tpm<R, F: FnOnce(&Tpm) -> R>(f: F) -> Option<R> {
996    let guard = TPM.lock();
997    guard.as_ref().map(f)
998}
999
1000/// Execute a closure with a mutable reference to the global TPM instance.
1001///
1002/// Returns `None` if the TPM is not initialized.
1003pub fn with_tpm_mut<R, F: FnOnce(&mut Tpm) -> R>(f: F) -> Option<R> {
1004    let mut guard = TPM.lock();
1005    guard.as_mut().map(f)
1006}
1007
1008/// Check if TPM is available
1009pub fn is_available() -> bool {
1010    TPM.lock().is_some()
1011}
1012
1013#[cfg(target_arch = "x86_64")]
1014/// Map a TPM MMIO physical address into the kernel virtual address space.
1015///
1016/// Uses the bootloader's physical memory window (phys_to_virt_addr) to
1017/// obtain a virtual address for the given physical base.  Returns 0 if the
1018/// address is outside the mapped region.
1019fn tpm_map_mmio(phys_base: usize) -> usize {
1020    if phys_base == 0 {
1021        return 0;
1022    }
1023    crate::mm::phys_to_virt_addr(phys_base as u64) as usize
1024}
1025
1026#[cfg(target_arch = "x86_64")]
1027/// Read a 32-bit TPM register via MMIO.
1028fn tpm_read_register(virt_base: usize, offset: usize) -> u32 {
1029    let ptr = (virt_base + offset) as *const u32;
1030    // SAFETY: virt_base was obtained from phys_to_virt_addr pointing
1031    // at a TPM CRB register page. Volatile read is required for MMIO.
1032    unsafe { core::ptr::read_volatile(ptr) }
1033}
1034
1035#[cfg(target_arch = "x86_64")]
1036/// Write a 32-bit TPM register via MMIO.
1037#[allow(dead_code)]
1038fn tpm_write_register(virt_base: usize, offset: usize, value: u32) {
1039    let ptr = (virt_base + offset) as *mut u32;
1040    // SAFETY: Same as tpm_read_register -- volatile write to MMIO.
1041    unsafe { core::ptr::write_volatile(ptr, value) }
1042}
1043
1044#[cfg(target_arch = "x86_64")]
1045/// Probe for a hardware TPM at the given virtual base address.
1046///
1047/// Reads the TPM_ACCESS and TPM_INTF_ID registers and checks for
1048/// plausible values indicating a real TPM device.
1049fn probe_hardware_tpm(virt_base: usize) -> bool {
1050    // TPM CRB: TPM_ACCESS_0 at offset 0x0000
1051    let access = tpm_read_register(virt_base, 0x0000);
1052    // A valid TPM_ACCESS register has bit 7 (tpmRegValidSts) set and
1053    // is not 0xFFFFFFFF (unmapped MMIO returns all-ones on x86).
1054    if access == 0xFFFF_FFFF || access == 0 {
1055        return false;
1056    }
1057
1058    // TPM CRB: INTERFACE_ID at offset 0x0030
1059    let intf_id = tpm_read_register(virt_base, 0x0030);
1060    // InterfaceType field (bits 3:0): 0 = TIS, 1 = CRB
1061    let intf_type = intf_id & 0xF;
1062    if intf_type > 1 {
1063        return false; // Unknown interface type
1064    }
1065
1066    crate::println!(
1067        "[TPM] Hardware TPM detected: access=0x{:08x}, intf_id=0x{:08x}, type={}",
1068        access,
1069        intf_id,
1070        if intf_type == 0 { "TIS" } else { "CRB" }
1071    );
1072    true
1073}
1074
1075/// Convenience: extend a PCR with a measurement hash via the global TPM
1076/// instance.
1077///
1078/// Returns Ok(()) if the TPM is available and the extend succeeded, or if no
1079/// TPM is available (silent no-op for callers that use TPM opportunistically).
1080pub fn pcr_extend(pcr_index: PcrIndex, measurement: &[u8; 32]) -> Result<(), TpmError> {
1081    match with_tpm_mut(|tpm| tpm.pcr_extend(pcr_index, measurement)) {
1082        Some(result) => result,
1083        None => Ok(()), // No TPM -- silently succeed
1084    }
1085}
1086
1087/// Convenience: read a PCR value via the global TPM instance.
1088pub fn pcr_read(pcr_index: PcrIndex) -> Result<[u8; 32], TpmError> {
1089    match with_tpm(|tpm| tpm.pcr_read(pcr_index)) {
1090        Some(result) => result,
1091        None => Err(TpmError::NotInitialized),
1092    }
1093}
1094
1095#[cfg(test)]
1096mod tests {
1097    use super::*;
1098
1099    #[test]
1100    fn test_tpm_creation() {
1101        let tpm = Tpm::new();
1102        assert!(!tpm.initialized);
1103    }
1104
1105    #[test]
1106    fn test_soft_pcr_extend() {
1107        let mut bank = SoftPcrBank::new();
1108        let measurement = [0x42u8; 32];
1109
1110        // Initial PCR should be all zeros
1111        assert_eq!(bank.read(0), [0u8; 32]);
1112
1113        // After extend, PCR should be SHA-256(zeros || measurement)
1114        bank.extend(0, &measurement);
1115        assert!(bank.extended[0]);
1116        // Value should no longer be all zeros
1117        assert_ne!(bank.read(0), [0u8; 32]);
1118    }
1119
1120    #[test]
1121    fn test_xor_keystream_roundtrip() {
1122        let key = [0xABu8; 32];
1123        let plaintext = b"Hello, VeridianOS TPM!";
1124
1125        let encrypted = xor_with_keystream(plaintext, &key);
1126        assert_ne!(&encrypted[..], &plaintext[..]);
1127
1128        let decrypted = xor_with_keystream(&encrypted, &key);
1129        assert_eq!(&decrypted[..], &plaintext[..]);
1130    }
1131}