Skip to main content

strat9_kernel/
crypto.rs

1//! Cryptographic verification for CMOD modules.
2//!
3//! Provides Ed25519 signature verification to ensure module integrity.
4//! The kernel maintains a key store of trusted public keys; modules must
5//! be signed by one of these keys to be loaded.
6//!
7//! # Security Design
8//!
9//! The signed payload is **code + data only** (everything after the header).
10//! The header (containing key_id and signature) is NOT part of the signed
11//! payload. This prevents an attacker from using a known key_id to trick
12//! the kernel into looking up a legitimate key while verifying a malicious
13//! signature.
14//!
15//! Verification flow:
16//! 1. Extract key_id (8 bytes) and signature (64 bytes) from header
17//! 2. Look up the public key by key_id
18//! 3. Verify signature over: module_data[HEADER_SIZE..]
19//!    (code + data, excluding the header)
20
21/* TODO: re-enable once LLVM backend crash is fixed
22use ed25519_dalek::{Signature, Verifier, VerifyingKey};
23*/
24use spin::Mutex;
25
26/// Size of an Ed25519 public key in bytes.
27pub const ED25519_PUBLIC_KEY_SIZE: usize = 32;
28/// Size of an Ed25519 signature in bytes.
29pub const ED25519_SIGNATURE_SIZE: usize = 64;
30/// Size of a key ID.
31pub const KEY_ID_SIZE: usize = 8;
32
33/// Result of a signature verification attempt.
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum VerifyResult {
36    /// Signature is valid.
37    Valid,
38    /// Signature is invalid (tampered module or wrong key).
39    InvalidSignature,
40    /// No key found matching the key_id in the module header.
41    KeyNotFound,
42    /// The signature field is all zeros (unsigned module).
43    Unsigned,
44}
45
46impl core::fmt::Display for VerifyResult {
47    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
48        match self {
49            VerifyResult::Valid => write!(f, "valid"),
50            VerifyResult::InvalidSignature => write!(f, "invalid signature"),
51            VerifyResult::KeyNotFound => write!(f, "key not found"),
52            VerifyResult::Unsigned => write!(f, "unsigned module"),
53        }
54    }
55}
56
57// ============================================================================
58// Trusted Key Store
59// ============================================================================
60
61/// Global trusted key store protected by a spinlock.
62static TRUSTED_KEYS: Mutex<alloc::vec::Vec<TrustedKey>> = Mutex::new(alloc::vec::Vec::new());
63
64/// A trusted signing key with its identifier.
65#[derive(Debug, Clone)]
66pub struct TrustedKey {
67    /// 8-byte key identifier (matches `key_id` in CMOD header).
68    pub id: [u8; KEY_ID_SIZE],
69    /// Ed25519 public key (32 bytes).
70    pub public_key: [u8; ED25519_PUBLIC_KEY_SIZE],
71    /// Human-readable label for this key.
72    pub label: &'static str,
73}
74
75/// Register a trusted signing key.
76///
77/// Called during boot to populate the key store. In production, these
78/// keys would be provisioned via a secure channel or hardware root of trust.
79pub fn register_trusted_key(
80    id: [u8; KEY_ID_SIZE],
81    public_key: [u8; ED25519_PUBLIC_KEY_SIZE],
82    label: &'static str,
83) {
84    let mut keys = TRUSTED_KEYS.lock();
85    if keys.iter().any(|k| k.id == id) {
86        log::warn!("[crypto] duplicate key id {:02x?}, skipping", id);
87        return;
88    }
89    keys.push(TrustedKey {
90        id,
91        public_key,
92        label,
93    });
94    log::info!(
95        "[crypto] registered trusted key: {} (id={:02x?})",
96        label,
97        id
98    );
99}
100
101/// Remove a trusted signing key by ID.
102pub fn remove_trusted_key(id: [u8; KEY_ID_SIZE]) -> bool {
103    let mut keys = TRUSTED_KEYS.lock();
104    let len_before = keys.len();
105    keys.retain(|k| k.id != id);
106    keys.len() < len_before
107}
108
109/// Number of registered trusted keys.
110pub fn trusted_key_count() -> usize {
111    TRUSTED_KEYS.lock().len()
112}
113
114/// Check if a key ID is registered as trusted.
115pub fn is_key_trusted(id: &[u8; KEY_ID_SIZE]) -> bool {
116    TRUSTED_KEYS.lock().iter().any(|k| k.id == *id)
117}
118
119/// Lookup a trusted key by its ID.
120fn find_trusted_key(id: &[u8; KEY_ID_SIZE]) -> Option<TrustedKey> {
121    TRUSTED_KEYS.lock().iter().find(|k| k.id == *id).cloned()
122}
123
124// ============================================================================
125// Ed25519 Verification
126// ============================================================================
127
128/// Verify an Ed25519 signature over data.
129///
130/// # Arguments
131/// * `key_id` - 8-byte identifier for the signing key
132/// * `signature` - 64-byte Ed25519 signature
133/// * `data` - The data that was signed (code + data sections)
134///
135/// # Returns
136/// * `VerifyResult::Valid` if signature is valid
137/// * `VerifyResult::KeyNotFound` if no trusted key matches `key_id`
138/// * `VerifyResult::InvalidSignature` if verification fails
139/// * `VerifyResult::Unsigned` if signature is all zeros
140pub fn verify_signature(
141    key_id: &[u8; KEY_ID_SIZE],
142    signature: &[u8; ED25519_SIGNATURE_SIZE],
143    data: &[u8],
144) -> VerifyResult {
145    if signature.iter().all(|&b| b == 0) {
146        return VerifyResult::Unsigned;
147    }
148
149    let key = match find_trusted_key(key_id) {
150        Some(k) => k,
151        None => return VerifyResult::KeyNotFound,
152    };
153
154    /* TODO: re-enable ed25519-dalek verification once LLVM backend crash is fixed.
155    let verifying_key = match VerifyingKey::from_bytes(&key.public_key) {
156        Ok(vk) => vk,
157        Err(_) => {
158            log::error!("[crypto] corrupt public key for id {:02x?}", key_id);
159            return VerifyResult::InvalidSignature;
160        }
161    };
162
163    let sig = Signature::from_bytes(signature);
164
165    let ok = verifying_key.verify(data, &sig).is_ok();
166    if ok {
167        log::debug!(
168            "[crypto] signature OK for key {} (id={:02x?})",
169            key.label,
170            key_id
171        );
172        VerifyResult::Valid
173    } else {
174        log::warn!("[crypto] signature FAILED for key id {:02x?}", key_id);
175        VerifyResult::InvalidSignature
176    }
177    */
178
179    // Stub: accept all signatures from trusted keys
180    let _ = data;
181    log::warn!(
182        "[crypto] ed25519 verification DISABLED (LLVM backend bug) : accepting key {} (id={:02x?})",
183        key.label,
184        key_id
185    );
186    VerifyResult::Valid
187}
188
189/// Verify a CMOD module's signature.
190///
191/// Extracts key_id and signature from the raw header, then verifies
192/// the signature over `payload` (which must be code+data, NOT the header).
193///
194/// # Arguments
195/// * `header_bytes` - The raw CMOD header
196/// * `key_id_offset` - Byte offset of key_id in header
197/// * `sig_offset` - Byte offset of signature in header
198/// * `payload` - The data that was signed (code + data sections)
199pub fn verify_cmod_signature(
200    header_bytes: &[u8],
201    key_id_offset: usize,
202    sig_offset: usize,
203    payload: &[u8],
204) -> VerifyResult {
205    // Bounds: key_id must fit before signature, signature must fit in header.
206    if key_id_offset + KEY_ID_SIZE > sig_offset {
207        return VerifyResult::InvalidSignature;
208    }
209    if sig_offset + ED25519_SIGNATURE_SIZE > header_bytes.len() {
210        return VerifyResult::InvalidSignature;
211    }
212
213    let mut key_id = [0u8; KEY_ID_SIZE];
214    key_id.copy_from_slice(&header_bytes[key_id_offset..key_id_offset + KEY_ID_SIZE]);
215
216    let mut signature = [0u8; ED25519_SIGNATURE_SIZE];
217    signature.copy_from_slice(&header_bytes[sig_offset..sig_offset + ED25519_SIGNATURE_SIZE]);
218
219    verify_signature(&key_id, &signature, payload)
220}
221
222// ============================================================================
223// Key Provisioning (Boot-time initialization)
224// ============================================================================
225
226/// Initialize the crypto subsystem.
227///
228/// Called during kernel boot. In production, keys are provisioned via
229/// secure boot chain or hardware root of trust. No default keys are
230/// embedded : unsigned or unverifiable modules are rejected.
231pub fn init() {
232    log::info!("[init] Crypto subsystem...");
233    let count = trusted_key_count();
234    if count == 0 {
235        log::warn!("[init] No trusted keys : module signing verification disabled");
236    } else {
237        log::info!("[init] Crypto subsystem ready ({} trusted key(s))", count);
238    }
239}
240
241// ============================================================================
242// Unit tests
243// ============================================================================
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248
249    #[test]
250    fn test_unsigned_module_detection() {
251        let sig = [0u8; ED25519_SIGNATURE_SIZE];
252        let key_id = [0u8; KEY_ID_SIZE];
253        assert_eq!(verify_signature(&key_id, &sig, &[]), VerifyResult::Unsigned);
254    }
255
256    #[test]
257    fn test_key_not_found() {
258        let sig = [1u8; ED25519_SIGNATURE_SIZE];
259        let key_id = [0xff; KEY_ID_SIZE];
260        assert_eq!(
261            verify_signature(&key_id, &sig, &[]),
262            VerifyResult::KeyNotFound
263        );
264    }
265}