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

veridian_kernel/security/
boot.rs

1//! Secure Boot Verification
2//!
3//! Verifies the integrity of the boot chain using cryptographic measurements,
4//! signature verification, and TPM PCR extensions.
5//!
6//! ## Boot Measurement Flow
7//!
8//! 1. Compute SHA-256 hash of the kernel image in memory
9//! 2. Verify kernel signature (if a signature is provided)
10//! 3. Record measurement in the boot measurement log
11//! 4. Extend TPM PCR 0 with the kernel measurement
12//! 5. Return verification status
13//!
14//! ## PCR Allocation
15//!
16//! - PCR 0: Kernel image measurement
17//! - PCR 1: Kernel configuration / command line
18//! - PCR 2: Boot stage measurements (bootloader, early init)
19
20use spin::Mutex;
21
22use crate::{crypto::hash::sha256, error::KernelError};
23
24/// Maximum number of measurements in the boot log
25const MAX_BOOT_MEASUREMENTS: usize = 32;
26
27/// Boot verification status
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum BootStatus {
30    /// Secure boot verified -- kernel hash and signature checked
31    Verified,
32    /// Secure boot not supported on this platform
33    NotSupported,
34    /// Secure boot verification failed (hash or signature mismatch)
35    Failed,
36    /// Secure boot disabled by configuration
37    Disabled,
38    /// Verified hash only (no signature available)
39    HashOnly,
40}
41
42/// Secure boot configuration
43pub struct SecureBootConfig {
44    /// Enable secure boot verification
45    pub enabled: bool,
46    /// Enforce verification (fail hard on mismatch)
47    pub enforce: bool,
48    /// Expected kernel hash (for verification against a known-good image)
49    pub kernel_hash: Option<[u8; 32]>,
50    /// Boot signature for kernel verification
51    pub signature: Option<BootSignature>,
52    /// Signing public key (Ed25519 verifying key, 32 bytes)
53    pub signer_public_key: Option<[u8; 32]>,
54}
55
56impl SecureBootConfig {
57    /// Create default configuration (secure boot disabled)
58    pub const fn default() -> Self {
59        Self {
60            enabled: false,
61            enforce: false,
62            kernel_hash: None,
63            signature: None,
64            signer_public_key: None,
65        }
66    }
67}
68
69/// Boot signature for kernel image verification
70#[derive(Clone)]
71pub struct BootSignature {
72    /// Ed25519 signature bytes (64 bytes)
73    pub signature_bytes: [u8; 64],
74    /// Signer identity (e.g., "VeridianOS Release Signing Key")
75    pub signer: &'static str,
76    /// Signing algorithm identifier
77    pub algorithm: SignatureAlgorithm,
78}
79
80/// Supported signature algorithms
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub enum SignatureAlgorithm {
83    /// Ed25519 (RFC 8032)
84    Ed25519,
85}
86
87/// A single boot measurement entry in the measurement log
88#[derive(Clone)]
89pub struct BootMeasurement {
90    /// Human-readable description of what was measured
91    pub stage: &'static str,
92    /// SHA-256 hash of the measured data
93    pub hash: [u8; 32],
94    /// Monotonic timestamp (kernel tick counter or similar)
95    pub timestamp: u64,
96    /// PCR index this measurement was extended into (if any)
97    pub pcr_index: Option<u8>,
98}
99
100/// Boot measurement log recording all measurements taken during boot.
101///
102/// This log is analogous to the TCG event log -- it records what was measured
103/// and extended into PCRs, allowing later attestation to reconstruct the
104/// expected PCR values.
105pub struct BootMeasurementLog {
106    entries: [Option<BootMeasurement>; MAX_BOOT_MEASUREMENTS],
107    count: usize,
108}
109
110impl BootMeasurementLog {
111    const fn new() -> Self {
112        // Use const initialization compatible with no_std
113        const NONE: Option<BootMeasurement> = None;
114        Self {
115            entries: [NONE; MAX_BOOT_MEASUREMENTS],
116            count: 0,
117        }
118    }
119
120    /// Record a new measurement in the log.
121    ///
122    /// Returns the index of the new entry, or None if the log is full.
123    fn record(
124        &mut self,
125        stage: &'static str,
126        hash: [u8; 32],
127        timestamp: u64,
128        pcr_index: Option<u8>,
129    ) -> Option<usize> {
130        if self.count >= MAX_BOOT_MEASUREMENTS {
131            return None;
132        }
133        let idx = self.count;
134        self.entries[idx] = Some(BootMeasurement {
135            stage,
136            hash,
137            timestamp,
138            pcr_index,
139        });
140        self.count += 1;
141        Some(idx)
142    }
143
144    /// Get the number of measurements recorded
145    pub fn len(&self) -> usize {
146        self.count
147    }
148
149    /// Check if the log is empty
150    pub fn is_empty(&self) -> bool {
151        self.count == 0
152    }
153
154    /// Get a measurement by index
155    pub fn get(&self, index: usize) -> Option<&BootMeasurement> {
156        if index < self.count {
157            self.entries[index].as_ref()
158        } else {
159            None
160        }
161    }
162
163    /// Get all recorded measurements as a slice-like iterator
164    pub fn measurements(&self) -> &[Option<BootMeasurement>] {
165        &self.entries[..self.count]
166    }
167}
168
169/// Global state: configuration + measurement log
170struct SecureBootState {
171    config: SecureBootConfig,
172    measurement_log: BootMeasurementLog,
173    last_status: BootStatus,
174}
175
176impl SecureBootState {
177    const fn new() -> Self {
178        Self {
179            config: SecureBootConfig::default(),
180            measurement_log: BootMeasurementLog::new(),
181            last_status: BootStatus::Disabled,
182        }
183    }
184}
185
186static STATE: Mutex<SecureBootState> = Mutex::new(SecureBootState::new());
187
188// ============================================================================
189// Kernel image hashing
190// ============================================================================
191
192/// Compute a SHA-256 hash of the kernel image in memory.
193///
194/// Uses the `__kernel_end` linker symbol (defined in all three architecture
195/// linker scripts) to determine the extent of the kernel image.  The start
196/// address is derived from the architecture-specific load address.
197///
198/// If linker symbols are not available, falls back to hashing the first 64 KiB
199/// of the kernel text section.
200pub fn compute_kernel_hash() -> Result<[u8; 32], KernelError> {
201    // Try to use linker-provided kernel extent
202    let (start, size) = get_kernel_extent();
203
204    println!(
205        "[SECBOOT] Hashing kernel image: start=0x{:X}, size={} bytes",
206        start, size
207    );
208
209    // Read kernel memory and hash it
210    // SAFETY: We are reading the kernel's own mapped text/data/bss sections.
211    // These addresses come from the linker script and are always valid while
212    // the kernel is running.
213    let kernel_bytes = unsafe { core::slice::from_raw_parts(start as *const u8, size) };
214
215    let hash = sha256(kernel_bytes);
216    let result = *hash.as_bytes();
217
218    println!(
219        "[SECBOOT] Kernel SHA-256: {:02X}{:02X}{:02X}{:02X}...{:02X}{:02X}{:02X}{:02X}",
220        result[0], result[1], result[2], result[3], result[28], result[29], result[30], result[31],
221    );
222
223    Ok(result)
224}
225
226/// Get the kernel image start address and size.
227///
228/// Uses the architecture-specific kernel load address as the start and
229/// hashes a fixed extent of the kernel text.  A linker-symbol-based
230/// approach (`_kernel_end`) would be more precise but requires custom
231/// target specs; standard bare-metal targets (used by CI) do not
232/// provide these symbols, so we use a conservative 64 KiB extent that
233/// covers the critical kernel text section.
234fn get_kernel_extent() -> (usize, usize) {
235    // Architecture-specific kernel start addresses (from linker scripts)
236    #[cfg(target_arch = "x86_64")]
237    let kernel_start: usize = 0xFFFFFFFF80100000;
238
239    #[cfg(target_arch = "aarch64")]
240    let kernel_start: usize = 0x40080000; // QEMU virt machine load address
241
242    #[cfg(target_arch = "riscv64")]
243    let kernel_start: usize = 0x80200000; // OpenSBI jump address
244
245    // Hash the first 64 KiB of the kernel text section.
246    // This covers the entry point, interrupt vectors, and core dispatch
247    // routines -- the most security-critical code.
248    (kernel_start, 64 * 1024)
249}
250
251// ============================================================================
252// Signature verification
253// ============================================================================
254
255/// Verify the kernel image signature using Ed25519.
256///
257/// If no signature or public key is configured, returns `Ok(false)` (not an
258/// error, just unsigned).  If the crypto module's Ed25519 verifier is
259/// available, delegates to it; otherwise falls back to hash comparison against
260/// a known-good hash.
261fn verify_kernel_signature(
262    kernel_hash: &[u8; 32],
263    config: &SecureBootConfig,
264) -> Result<bool, KernelError> {
265    // Check if we have a signature and public key
266    let signature = match &config.signature {
267        Some(sig) => sig,
268        None => {
269            println!("[SECBOOT] No boot signature configured -- skipping signature check");
270            return Ok(false);
271        }
272    };
273
274    let public_key = match &config.signer_public_key {
275        Some(pk) => pk,
276        None => {
277            println!("[SECBOOT] No signer public key configured -- skipping signature check");
278            return Ok(false);
279        }
280    };
281
282    println!(
283        "[SECBOOT] Verifying {:?} signature from '{}'",
284        signature.algorithm, signature.signer
285    );
286
287    // Try Ed25519 verification via the crypto module
288    match signature.algorithm {
289        SignatureAlgorithm::Ed25519 => {
290            use crate::crypto::asymmetric::{Signature, VerifyingKey};
291
292            let verifying_key = match VerifyingKey::from_bytes(public_key) {
293                Ok(vk) => vk,
294                Err(_e) => {
295                    println!("[SECBOOT] Invalid verifying key: {:?}", _e);
296                    return Ok(false);
297                }
298            };
299
300            let sig = match Signature::from_bytes(&signature.signature_bytes) {
301                Ok(s) => s,
302                Err(_e) => {
303                    println!("[SECBOOT] Invalid signature format: {:?}", _e);
304                    return Ok(false);
305                }
306            };
307
308            // Verify the signature over the kernel hash
309            match verifying_key.verify(kernel_hash, &sig) {
310                Ok(valid) => {
311                    // Use a variable with underscore prefix so it's not
312                    // flagged as unused on architectures where println!
313                    // is a no-op (AArch64).
314                    let _status = if valid { "VALID" } else { "INVALID" };
315                    println!("[SECBOOT] Ed25519 signature {}", _status);
316                    Ok(valid)
317                }
318                Err(_e) => {
319                    println!("[SECBOOT] Signature verification error: {:?}", _e);
320                    Ok(false)
321                }
322            }
323        }
324    }
325}
326
327/// Fall back to comparing the computed hash against a known-good hash.
328fn verify_hash_only(computed_hash: &[u8; 32], config: &SecureBootConfig) -> bool {
329    match &config.kernel_hash {
330        Some(expected) => {
331            let matches = computed_hash == expected;
332            if matches {
333                println!("[SECBOOT] Kernel hash matches expected value");
334            } else {
335                println!("[SECBOOT] Kernel hash MISMATCH");
336                println!(
337                    "[SECBOOT]   Expected: {:02X}{:02X}{:02X}{:02X}...",
338                    expected[0], expected[1], expected[2], expected[3]
339                );
340                println!(
341                    "[SECBOOT]   Computed: {:02X}{:02X}{:02X}{:02X}...",
342                    computed_hash[0], computed_hash[1], computed_hash[2], computed_hash[3]
343                );
344            }
345            matches
346        }
347        None => {
348            println!("[SECBOOT] No expected kernel hash configured");
349            false
350        }
351    }
352}
353
354// ============================================================================
355// TPM PCR extension
356// ============================================================================
357
358/// Extend TPM PCR with a measurement hash.
359///
360/// Silently succeeds if no TPM is available (the measurement is still recorded
361/// in the boot log regardless).
362fn extend_pcr(pcr_index: u8, measurement: &[u8; 32]) {
363    match super::tpm::pcr_extend(pcr_index, measurement) {
364        Ok(()) => {
365            println!("[SECBOOT] Extended TPM PCR {} with measurement", pcr_index);
366        }
367        Err(_e) => {
368            println!(
369                "[SECBOOT] TPM PCR extend failed for PCR {}: {:?} (continuing)",
370                pcr_index, _e
371            );
372        }
373    }
374}
375
376// ============================================================================
377// Measured boot
378// ============================================================================
379
380/// Record a boot stage measurement.
381///
382/// Computes SHA-256 of the given data, records it in the measurement log,
383/// and optionally extends a TPM PCR.
384pub fn measure_boot_stage(
385    stage: &'static str,
386    data: &[u8],
387    pcr_index: Option<u8>,
388) -> Result<[u8; 32], KernelError> {
389    let hash = sha256(data);
390    let hash_bytes = *hash.as_bytes();
391
392    // Get a timestamp (use a simple counter since we may not have a timer yet)
393    let timestamp = get_boot_timestamp();
394
395    let mut state = STATE.lock();
396    if let Some(_idx) = state
397        .measurement_log
398        .record(stage, hash_bytes, timestamp, pcr_index)
399    {
400        println!(
401            "[SECBOOT] Measurement #{}: '{}' hash={:02X}{:02X}{:02X}{:02X}...",
402            _idx, stage, hash_bytes[0], hash_bytes[1], hash_bytes[2], hash_bytes[3]
403        );
404    } else {
405        println!(
406            "[SECBOOT] Warning: measurement log full, could not record '{}'",
407            stage
408        );
409    }
410    drop(state);
411
412    // Extend TPM PCR if requested
413    if let Some(pcr) = pcr_index {
414        extend_pcr(pcr, &hash_bytes);
415    }
416
417    Ok(hash_bytes)
418}
419
420/// Get a monotonic boot timestamp.
421///
422/// Uses architecture-specific cycle counter or falls back to a simple counter.
423fn get_boot_timestamp() -> u64 {
424    use core::sync::atomic::{AtomicU64, Ordering};
425    static COUNTER: AtomicU64 = AtomicU64::new(0);
426    COUNTER.fetch_add(1, Ordering::Relaxed)
427}
428
429// ============================================================================
430// Main verification entry point
431// ============================================================================
432
433/// Verify secure boot chain.
434///
435/// This is the main entry point called during kernel initialization.
436/// It performs the following steps:
437///
438/// 1. Check if secure boot is enabled
439/// 2. Hash the kernel image
440/// 3. Record the measurement in the boot log
441/// 4. Extend TPM PCR 0 with the kernel hash
442/// 5. Verify the kernel signature (if configured)
443/// 6. Fall back to hash comparison (if no signature)
444/// 7. Return overall verification status
445pub fn verify() -> Result<(), KernelError> {
446    let state = STATE.lock();
447    let enabled = state.config.enabled;
448    let enforce = state.config.enforce;
449    drop(state);
450
451    if !enabled {
452        println!("[SECBOOT] Secure boot disabled");
453        // Skip kernel hashing when disabled - it's too slow on emulated platforms
454        // (11MB+ in software SHA-256 takes minutes on QEMU).
455        // Hash will be computed on-demand when secure boot is enabled.
456        let mut state = STATE.lock();
457        state.last_status = BootStatus::Disabled;
458        drop(state);
459        return Ok(());
460    }
461
462    println!("[SECBOOT] Verifying secure boot chain...");
463
464    // Step 1: Hash the kernel image
465    let kernel_hash = compute_kernel_hash()?;
466
467    // Step 2: Record measurement and extend PCR 0
468    {
469        let mut state = STATE.lock();
470        let ts = get_boot_timestamp();
471        state
472            .measurement_log
473            .record("kernel_image", kernel_hash, ts, Some(0));
474    }
475    extend_pcr(0, &kernel_hash);
476
477    // Step 3: Verify signature
478    let state_guard = STATE.lock();
479    let sig_valid = verify_kernel_signature(&kernel_hash, &state_guard.config)?;
480    let hash_valid = verify_hash_only(&kernel_hash, &state_guard.config);
481    let has_signature = state_guard.config.signature.is_some();
482    drop(state_guard);
483
484    // Step 4: Determine overall status
485    let status = if has_signature && sig_valid {
486        BootStatus::Verified
487    } else if hash_valid {
488        BootStatus::HashOnly
489    } else if !has_signature && !state_guard_has_expected_hash() {
490        // No signature and no expected hash configured -- can't verify
491        println!("[SECBOOT] No verification material configured");
492        BootStatus::NotSupported
493    } else {
494        BootStatus::Failed
495    };
496
497    // Step 5: Record status and enforce
498    {
499        let mut state = STATE.lock();
500        state.last_status = status;
501    }
502
503    match status {
504        BootStatus::Verified => {
505            println!("[SECBOOT] Secure boot verification PASSED (signature valid)");
506            Ok(())
507        }
508        BootStatus::HashOnly => {
509            println!("[SECBOOT] Secure boot verification PASSED (hash match, no signature)");
510            Ok(())
511        }
512        BootStatus::NotSupported => {
513            println!("[SECBOOT] Secure boot: no verification material configured");
514            if enforce {
515                Err(KernelError::NotImplemented {
516                    feature: "secure boot verification material",
517                })
518            } else {
519                Ok(())
520            }
521        }
522        BootStatus::Failed => {
523            println!("[SECBOOT] Secure boot verification FAILED");
524            if enforce {
525                Err(KernelError::PermissionDenied {
526                    operation: "secure boot enforcement",
527                })
528            } else {
529                println!("[SECBOOT] Non-enforcing mode -- continuing despite failure");
530                Ok(())
531            }
532        }
533        BootStatus::Disabled => {
534            // Should not reach here (we checked enabled above)
535            Ok(())
536        }
537    }
538}
539
540/// Helper to check if expected hash is configured (avoids holding lock across
541/// verify calls)
542fn state_guard_has_expected_hash() -> bool {
543    let state = STATE.lock();
544    state.config.kernel_hash.is_some()
545}
546
547// ============================================================================
548// Configuration API
549// ============================================================================
550
551/// Enable secure boot with optional enforcement.
552pub fn enable(enforce: bool) {
553    let mut state = STATE.lock();
554    state.config.enabled = true;
555    state.config.enforce = enforce;
556    println!("[SECBOOT] Secure boot enabled (enforce={})", enforce);
557}
558
559/// Disable secure boot.
560pub fn disable() {
561    let mut state = STATE.lock();
562    state.config.enabled = false;
563    state.config.enforce = false;
564    state.last_status = BootStatus::Disabled;
565    println!("[SECBOOT] Secure boot disabled");
566}
567
568/// Set the expected kernel hash for verification.
569pub fn set_expected_hash(hash: [u8; 32]) {
570    let mut state = STATE.lock();
571    state.config.kernel_hash = Some(hash);
572}
573
574/// Set the boot signature and signer public key.
575pub fn set_signature(signature: BootSignature, public_key: [u8; 32]) {
576    let mut state = STATE.lock();
577    state.config.signature = Some(signature);
578    state.config.signer_public_key = Some(public_key);
579}
580
581/// Get the current boot verification status.
582pub fn get_status() -> BootStatus {
583    let state = STATE.lock();
584    state.last_status
585}
586
587/// Get the number of recorded boot measurements.
588pub fn measurement_count() -> usize {
589    let state = STATE.lock();
590    state.measurement_log.len()
591}
592
593/// Get a recorded boot measurement by index.
594///
595/// Returns a copy of the measurement entry (stage name, hash, timestamp, PCR
596/// index).
597pub fn get_measurement(index: usize) -> Option<(&'static str, [u8; 32], u64, Option<u8>)> {
598    let state = STATE.lock();
599    state
600        .measurement_log
601        .get(index)
602        .map(|m| (m.stage, m.hash, m.timestamp, m.pcr_index))
603}
604
605/// Print all boot measurements to the kernel console.
606pub fn print_measurement_log() {
607    let state = STATE.lock();
608    println!(
609        "[SECBOOT] Boot Measurement Log ({} entries):",
610        state.measurement_log.len()
611    );
612    for i in 0..state.measurement_log.len() {
613        if let Some(_m) = state.measurement_log.get(i) {
614            println!(
615                "[SECBOOT]   #{}: stage='{}' hash={:02X}{:02X}{:02X}{:02X}... pcr={:?} ts={}",
616                i,
617                _m.stage,
618                _m.hash[0],
619                _m.hash[1],
620                _m.hash[2],
621                _m.hash[3],
622                _m.pcr_index,
623                _m.timestamp
624            );
625        }
626    }
627}
628
629#[cfg(test)]
630mod tests {
631    use super::*;
632
633    // Requires valid kernel address space (linker-defined addresses only map on
634    // bare-metal; reading them on the host causes SIGSEGV).
635    #[cfg(target_os = "none")]
636    #[test]
637    fn test_kernel_hash() {
638        let hash = compute_kernel_hash();
639        assert!(hash.is_ok());
640        let h = hash.unwrap();
641        // Hash should be non-zero (kernel image exists)
642        assert_ne!(h, [0u8; 32]);
643    }
644
645    #[test]
646    fn test_verify_disabled() {
647        // Should succeed when disabled (default)
648        assert!(verify().is_ok());
649    }
650
651    #[test]
652    fn test_boot_measurement_log() {
653        let mut log = BootMeasurementLog::new();
654        assert!(log.is_empty());
655
656        let hash = [0x42u8; 32];
657        let idx = log.record("test_stage", hash, 100, Some(0));
658        assert_eq!(idx, Some(0));
659        assert_eq!(log.len(), 1);
660        assert!(!log.is_empty());
661
662        let m = log.get(0).unwrap();
663        assert_eq!(m.stage, "test_stage");
664        assert_eq!(m.hash, hash);
665        assert_eq!(m.timestamp, 100);
666        assert_eq!(m.pcr_index, Some(0));
667    }
668
669    #[test]
670    fn test_measure_boot_stage() {
671        let data = b"test boot stage data";
672        let result = measure_boot_stage("test_boot", data, None);
673        assert!(result.is_ok());
674        let hash = result.unwrap();
675        // Hash should match SHA-256 of the input
676        let expected = sha256(data);
677        assert_eq!(&hash, expected.as_bytes());
678    }
679}