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

veridian_kernel/security/
auth.rs

1//! Authentication Framework
2//!
3//! Provides user authentication, password hashing, and multi-factor
4//! authentication.
5//!
6//! # Features
7//!
8//! - PBKDF2-HMAC-SHA256 password hashing with configurable iterations
9//! - Password complexity enforcement via configurable policy
10//! - Password history tracking (prevent reuse of last N passwords)
11//! - Account expiration with timestamp-based checks
12//! - Multi-factor authentication (TOTP-like)
13//! - Account lockout after configurable failed attempts
14//!
15//! # No-Heap Design
16//!
17//! All data structures use fixed-size stack/static buffers to avoid heap
18//! allocations during boot. This prevents corruption of the bump allocator
19//! on architectures (e.g., RISC-V) where the heap is not yet fully
20//! initialized when the auth module runs.
21
22use spin::RwLock;
23
24use crate::{
25    crypto::hash::{sha256, Hash256},
26    error::KernelError,
27    sync::once_lock::OnceLock,
28};
29
30/// User identifier
31pub type UserId = u32;
32
33/// Maximum number of user accounts.
34///
35/// Kept small (16) to avoid stack overflow during init on x86_64 where the
36/// kernel stack is limited. The AccountDatabase ([Option<UserAccount>; N])
37/// is constructed on the stack before being moved into the OnceLock static.
38/// At ~320 bytes per UserAccount, 16 entries = ~5KB which fits safely.
39const MAX_ACCOUNTS: usize = 16;
40
41/// Maximum number of previous passwords to remember per account.
42const MAX_PASSWORD_HISTORY: usize = 5;
43
44// ---------------------------------------------------------------------------
45// Authentication Result
46// ---------------------------------------------------------------------------
47
48/// Authentication result
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum AuthResult {
51    Success,
52    InvalidCredentials,
53    AccountLocked,
54    PasswordExpired,
55    MfaRequired,
56    AccountExpired,
57    Denied,
58}
59
60// ---------------------------------------------------------------------------
61// Password Policy
62// ---------------------------------------------------------------------------
63
64/// Password complexity enforcement policy.
65#[derive(Debug, Clone, Copy)]
66pub struct PasswordPolicy {
67    /// Minimum password length
68    pub min_length: usize,
69    /// Require at least one uppercase letter
70    pub require_uppercase: bool,
71    /// Require at least one lowercase letter
72    pub require_lowercase: bool,
73    /// Require at least one digit
74    pub require_digit: bool,
75    /// Require at least one special character
76    pub require_special: bool,
77    /// Maximum number of previous passwords to remember
78    pub history_size: usize,
79}
80
81impl PasswordPolicy {
82    /// Default password policy: 8 chars, upper+lower+digit required.
83    pub const fn default_policy() -> Self {
84        Self {
85            min_length: 8,
86            require_uppercase: true,
87            require_lowercase: true,
88            require_digit: true,
89            require_special: false,
90            history_size: 5,
91        }
92    }
93
94    /// Relaxed policy (for testing or early boot).
95    pub const fn relaxed() -> Self {
96        Self {
97            min_length: 1,
98            require_uppercase: false,
99            require_lowercase: false,
100            require_digit: false,
101            require_special: false,
102            history_size: 0,
103        }
104    }
105
106    /// Validate a password against this policy.
107    ///
108    /// Returns `Ok(())` if the password meets all requirements, or
109    /// `Err` with a description of the first failing requirement.
110    pub fn validate_password(&self, password: &str) -> Result<(), KernelError> {
111        if password.len() < self.min_length {
112            return Err(KernelError::InvalidArgument {
113                name: "password",
114                value: "too short",
115            });
116        }
117
118        if self.require_uppercase && !password.bytes().any(|b| b.is_ascii_uppercase()) {
119            return Err(KernelError::InvalidArgument {
120                name: "password",
121                value: "must contain an uppercase letter",
122            });
123        }
124
125        if self.require_lowercase && !password.bytes().any(|b| b.is_ascii_lowercase()) {
126            return Err(KernelError::InvalidArgument {
127                name: "password",
128                value: "must contain a lowercase letter",
129            });
130        }
131
132        if self.require_digit && !password.bytes().any(|b| b.is_ascii_digit()) {
133            return Err(KernelError::InvalidArgument {
134                name: "password",
135                value: "must contain a digit",
136            });
137        }
138
139        if self.require_special
140            && !password
141                .bytes()
142                .any(|b| b.is_ascii_punctuation() || b == b' ')
143        {
144            return Err(KernelError::InvalidArgument {
145                name: "password",
146                value: "must contain a special character",
147            });
148        }
149
150        Ok(())
151    }
152}
153
154// ---------------------------------------------------------------------------
155// PBKDF2-HMAC-SHA256 (zero-heap implementation)
156// ---------------------------------------------------------------------------
157
158/// SHA-256 block size in bytes.
159const BLOCK_SIZE: usize = 64;
160
161/// Maximum message size for HMAC inner hash.
162///
163/// The largest message passed to `hmac_sha256` is either:
164/// - In `pbkdf2_hmac_sha256`: salt (up to 32 bytes) + 4-byte counter = 36 bytes
165/// - In `check_totp_window`: 8-byte counter
166/// - In `change_password` history check: 32-byte hash used as salt + 4 = 36
167///   bytes
168///
169/// Total inner buffer: BLOCK_SIZE (64) + max_message (36) = 100.
170/// We use 192 to leave headroom for future callers.
171const HMAC_INNER_BUF_SIZE: usize = 192;
172
173/// HMAC-SHA256 implementation for PBKDF2 (no heap allocation).
174///
175/// Computes HMAC(key, message) = SHA256((key XOR opad) || SHA256((key XOR ipad)
176/// || message))
177///
178/// # Panics
179///
180/// Panics if `message.len() > HMAC_INNER_BUF_SIZE - BLOCK_SIZE` (128 bytes).
181/// All internal callers stay well within this limit.
182fn hmac_sha256(key: &[u8], message: &[u8]) -> Hash256 {
183    const IPAD: u8 = 0x36;
184    const OPAD: u8 = 0x5c;
185
186    let max_msg = HMAC_INNER_BUF_SIZE - BLOCK_SIZE;
187    assert!(
188        message.len() <= max_msg,
189        "hmac_sha256: message too large for stack buffer"
190    );
191
192    // If key is longer than block size, hash it first
193    let key_hash;
194    let actual_key = if key.len() > BLOCK_SIZE {
195        key_hash = sha256(key);
196        key_hash.as_bytes().as_slice()
197    } else {
198        key
199    };
200
201    // Pad key to block size
202    let mut padded_key = [0u8; BLOCK_SIZE];
203    padded_key[..actual_key.len()].copy_from_slice(actual_key);
204
205    // Inner hash: SHA256((key XOR ipad) || message) -- stack buffer
206    let mut inner_buf = [0u8; HMAC_INNER_BUF_SIZE];
207    for (i, byte) in padded_key.iter().enumerate() {
208        inner_buf[i] = byte ^ IPAD;
209    }
210    let inner_len = BLOCK_SIZE + message.len();
211    inner_buf[BLOCK_SIZE..inner_len].copy_from_slice(message);
212    let inner_hash = sha256(&inner_buf[..inner_len]);
213
214    // Outer hash: SHA256((key XOR opad) || inner_hash) -- stack buffer
215    let mut outer_buf = [0u8; BLOCK_SIZE + 32];
216    for (i, byte) in padded_key.iter().enumerate() {
217        outer_buf[i] = byte ^ OPAD;
218    }
219    outer_buf[BLOCK_SIZE..BLOCK_SIZE + 32].copy_from_slice(inner_hash.as_bytes());
220
221    sha256(&outer_buf[..BLOCK_SIZE + 32])
222}
223
224/// PBKDF2-HMAC-SHA256 key derivation (no heap allocation).
225///
226/// Derives a 256-bit key from `password` and `salt` using `iterations`
227/// rounds of HMAC-SHA256 with XOR accumulation (RFC 8018, Section 5.2).
228///
229/// # Panics
230///
231/// Panics if `salt.len() > 128` (extremely unlikely for real usage).
232fn pbkdf2_hmac_sha256(password: &[u8], salt: &[u8], iterations: u32) -> Hash256 {
233    // For a single 32-byte block (which is all we need for Hash256):
234    // U1 = HMAC(password, salt || INT(1))
235    // U2 = HMAC(password, U1)
236    // ...
237    // result = U1 XOR U2 XOR ... XOR Uc
238
239    // Build salt || counter on stack (salt up to 128 bytes + 4 bytes counter)
240    let mut salt_with_counter = [0u8; 128 + 4];
241    assert!(salt.len() <= 128, "pbkdf2: salt too large for stack buffer");
242    salt_with_counter[..salt.len()].copy_from_slice(salt);
243    salt_with_counter[salt.len()..salt.len() + 4].copy_from_slice(&1u32.to_be_bytes());
244    let salt_counter_len = salt.len() + 4;
245
246    let u1 = hmac_sha256(password, &salt_with_counter[..salt_counter_len]);
247    let mut result = *u1.as_bytes();
248    let mut prev = u1;
249
250    for _ in 1..iterations {
251        let u_next = hmac_sha256(password, prev.as_bytes());
252        // XOR accumulate
253        for (r, u) in result.iter_mut().zip(u_next.as_bytes().iter()) {
254            *r ^= u;
255        }
256        prev = u_next;
257    }
258
259    Hash256(result)
260}
261
262// ---------------------------------------------------------------------------
263// User Credential (legacy compat)
264// ---------------------------------------------------------------------------
265
266/// User credential
267#[derive(Debug, Clone)]
268#[allow(dead_code)]
269pub struct Credential {
270    pub username: &'static str,
271    pub(crate) password_hash: Hash256,
272    pub(crate) salt: [u8; 32],
273}
274
275// ---------------------------------------------------------------------------
276// User Account
277// ---------------------------------------------------------------------------
278
279/// User account information (fixed-size, no heap allocation).
280#[derive(Debug, Clone)]
281pub struct UserAccount {
282    pub user_id: UserId,
283    pub username: &'static str,
284    pub(crate) password_hash: Hash256,
285    pub salt: [u8; 32],
286    pub locked: bool,
287    pub failed_attempts: u32,
288    pub mfa_enabled: bool,
289    pub mfa_secret: Option<[u8; 32]>,
290    /// Account expiration timestamp (seconds since boot). `None` = never
291    /// expires.
292    pub expires_at: Option<u64>,
293    /// Password history: stores (hash, salt) pairs of previous passwords.
294    pub(crate) password_history: [Option<(Hash256, [u8; 32])>; MAX_PASSWORD_HISTORY],
295    /// Number of valid entries in `password_history`.
296    pub password_history_len: usize,
297}
298
299impl UserAccount {
300    /// PBKDF2 iteration count for password hashing.
301    /// Reduced in debug builds because QEMU is slow.
302    #[cfg(debug_assertions)]
303    const PBKDF2_ITERATIONS: u32 = 10;
304    #[cfg(not(debug_assertions))]
305    const PBKDF2_ITERATIONS: u32 = 10_000;
306
307    /// Create new user account
308    pub fn new(user_id: UserId, username: &'static str, password: &str) -> Self {
309        let (password_hash, salt) = Self::hash_password(password);
310
311        Self {
312            user_id,
313            username,
314            password_hash,
315            salt,
316            locked: false,
317            failed_attempts: 0,
318            mfa_enabled: false,
319            mfa_secret: None,
320            expires_at: None,
321            password_history: [None; MAX_PASSWORD_HISTORY],
322            password_history_len: 0,
323        }
324    }
325
326    /// Hash password with PBKDF2-HMAC-SHA256.
327    fn hash_password(password: &str) -> (Hash256, [u8; 32]) {
328        use crate::crypto::random::get_random;
329
330        let rng = get_random();
331        let mut salt = [0u8; 32];
332        if let Err(_e) = rng.fill_bytes(&mut salt) {
333            crate::kprintln!(
334                "[AUTH] Warning: RNG fill_bytes failed for password salt, using zeroed salt"
335            );
336        }
337
338        let hash = pbkdf2_hmac_sha256(password.as_bytes(), &salt, Self::PBKDF2_ITERATIONS);
339
340        (hash, salt)
341    }
342
343    /// Hash password with a specific salt (for verification).
344    fn hash_password_with_salt(password: &str, salt: &[u8; 32]) -> Hash256 {
345        pbkdf2_hmac_sha256(password.as_bytes(), salt, Self::PBKDF2_ITERATIONS)
346    }
347
348    /// Verify password
349    pub fn verify_password(&self, password: &str) -> bool {
350        let computed = Self::hash_password_with_salt(password, &self.salt);
351        computed == self.password_hash
352    }
353
354    /// Check if the account has expired.
355    pub fn is_expired(&self) -> bool {
356        if let Some(expires_at) = self.expires_at {
357            let now = crate::arch::timer::get_timestamp_secs();
358            now >= expires_at
359        } else {
360            false
361        }
362    }
363
364    /// Set account expiration time.
365    pub fn set_expiration(&mut self, expires_at: Option<u64>) {
366        self.expires_at = expires_at;
367    }
368
369    /// Change password with history tracking.
370    ///
371    /// Checks that the new password is not the current password and not in
372    /// the password history. The `history_size` parameter controls how many
373    /// old (hash, salt) pairs to retain.
374    pub fn change_password(
375        &mut self,
376        new_password: &str,
377        history_size: usize,
378    ) -> Result<(), KernelError> {
379        // Check if new password matches current password
380        if self.verify_password(new_password) {
381            return Err(KernelError::InvalidArgument {
382                name: "password",
383                value: "must differ from current password",
384            });
385        }
386
387        // Check new password against history using stored (hash, salt) pairs
388        let reused = self.password_history[..self.password_history_len]
389            .iter()
390            .any(|entry| {
391                if let Some((old_hash, old_salt)) = entry {
392                    let candidate = Self::hash_password_with_salt(new_password, old_salt);
393                    crate::crypto::constant_time::ct_eq_bytes(
394                        candidate.as_bytes(),
395                        old_hash.as_bytes(),
396                    ) == 1
397                } else {
398                    false
399                }
400            });
401
402        if reused {
403            return Err(KernelError::InvalidArgument {
404                name: "password",
405                value: "matches a recent password in history",
406            });
407        }
408
409        // Save current (hash, salt) to history (fixed-size ring buffer)
410        let effective_size = history_size.min(MAX_PASSWORD_HISTORY);
411        if effective_size > 0 {
412            if self.password_history_len >= effective_size {
413                // Shift entries left to make room (drop oldest)
414                for i in 0..effective_size - 1 {
415                    self.password_history[i] = self.password_history[i + 1];
416                }
417                self.password_history[effective_size - 1] = Some((self.password_hash, self.salt));
418                self.password_history_len = effective_size;
419            } else {
420                self.password_history[self.password_history_len] =
421                    Some((self.password_hash, self.salt));
422                self.password_history_len += 1;
423            }
424        }
425
426        // Generate new salt and hash
427        let (new_hash, new_salt) = Self::hash_password(new_password);
428        self.password_hash = new_hash;
429        self.salt = new_salt;
430
431        Ok(())
432    }
433
434    /// Enable MFA for this account
435    pub fn enable_mfa(&mut self) -> [u8; 32] {
436        use crate::crypto::random::get_random;
437
438        let rng = get_random();
439        let mut secret = [0u8; 32];
440        if let Err(_e) = rng.fill_bytes(&mut secret) {
441            crate::kprintln!("[AUTH] Warning: RNG fill_bytes failed for MFA secret");
442        }
443
444        self.mfa_secret = Some(secret);
445        self.mfa_enabled = true;
446
447        secret
448    }
449
450    /// Verify MFA token (TOTP-like)
451    pub fn verify_mfa_token(&self, token: u32) -> bool {
452        if !self.mfa_enabled {
453            return true; // MFA not required
454        }
455
456        if let Some(secret) = self.mfa_secret {
457            // TOTP verification using real timestamps
458            let time_step = 30; // 30 second windows
459            let current_time = crate::arch::timer::get_timestamp_secs();
460
461            let time_counter = current_time / time_step;
462
463            // Check current window and one window before/after for clock skew
464            for offset in [0u64, 1u64] {
465                let counter = if offset == 0 {
466                    time_counter
467                } else {
468                    // Check both +1 and -1 windows
469                    if self.check_totp_window(&secret, time_counter.wrapping_add(1), token) {
470                        return true;
471                    }
472                    time_counter.wrapping_sub(1)
473                };
474
475                if self.check_totp_window(&secret, counter, token) {
476                    return true;
477                }
478            }
479
480            false
481        } else {
482            false
483        }
484    }
485
486    /// Check a single TOTP time window.
487    fn check_totp_window(&self, secret: &[u8; 32], time_counter: u64, token: u32) -> bool {
488        // Generate expected token from HMAC(secret, time_counter)
489        let counter_bytes = time_counter.to_be_bytes();
490        let hash = hmac_sha256(secret, &counter_bytes);
491        let expected_token = u32::from_be_bytes([
492            hash.as_bytes()[0],
493            hash.as_bytes()[1],
494            hash.as_bytes()[2],
495            hash.as_bytes()[3],
496        ]) % 1_000_000; // 6-digit token
497
498        token == expected_token
499    }
500}
501
502// ---------------------------------------------------------------------------
503// Fixed-Size Account Database
504// ---------------------------------------------------------------------------
505
506/// A fixed-size account database that avoids heap allocation.
507///
508/// Stores up to [`MAX_ACCOUNTS`] user accounts in a static array.
509/// Lookup is O(n) but n is bounded by MAX_ACCOUNTS (64), which is
510/// acceptable for a kernel authentication module.
511struct AccountDatabase {
512    entries: [Option<UserAccount>; MAX_ACCOUNTS],
513    count: usize,
514}
515
516impl AccountDatabase {
517    /// Create an empty account database.
518    const fn new() -> Self {
519        // const-compatible initialization for array of Option<UserAccount>
520        // We cannot use [None; MAX_ACCOUNTS] because UserAccount is not Copy,
521        // so we build the array manually with a const block.
522        const NONE: Option<UserAccount> = None;
523        Self {
524            entries: [NONE; MAX_ACCOUNTS],
525            count: 0,
526        }
527    }
528
529    /// Look up an account by username (immutable).
530    fn get(&self, username: &str) -> Option<&UserAccount> {
531        self.entries[..self.count]
532            .iter()
533            .flatten()
534            .find(|account| account.username == username)
535    }
536
537    /// Look up an account by username (mutable).
538    fn get_mut(&mut self, username: &str) -> Option<&mut UserAccount> {
539        self.entries[..self.count]
540            .iter_mut()
541            .flatten()
542            .find(|account| account.username == username)
543    }
544
545    /// Check if a username exists.
546    fn contains_key(&self, username: &str) -> bool {
547        self.get(username).is_some()
548    }
549
550    /// Insert a new account. Returns `Err` if the database is full.
551    fn insert(&mut self, account: UserAccount) -> Result<(), KernelError> {
552        if self.count >= MAX_ACCOUNTS {
553            return Err(KernelError::ResourceExhausted {
554                resource: "account_database",
555            });
556        }
557
558        // Find first empty slot (there is guaranteed to be one since count <
559        // MAX_ACCOUNTS)
560        for entry in &mut self.entries {
561            if entry.is_none() {
562                *entry = Some(account);
563                self.count += 1;
564                return Ok(());
565            }
566        }
567
568        // Should not be reached if count is maintained correctly
569        Err(KernelError::ResourceExhausted {
570            resource: "account_database",
571        })
572    }
573
574    /// Remove an account by username. Returns the removed account, or `None`.
575    fn remove(&mut self, username: &str) -> Option<UserAccount> {
576        for entry in &mut self.entries {
577            if let Some(account) = entry {
578                if account.username == username {
579                    let removed = entry.take();
580                    self.count -= 1;
581                    return removed;
582                }
583            }
584        }
585        None
586    }
587
588    /// Iterate over all accounts (immutable).
589    fn iter(&self) -> impl Iterator<Item = &UserAccount> {
590        self.entries.iter().filter_map(|e| e.as_ref())
591    }
592}
593
594// ---------------------------------------------------------------------------
595// Authentication Manager
596// ---------------------------------------------------------------------------
597
598/// Authentication manager
599pub struct AuthManager {
600    accounts: RwLock<AccountDatabase>,
601    next_user_id: RwLock<u32>,
602    max_failed_attempts: u32,
603    password_policy: RwLock<PasswordPolicy>,
604}
605
606impl AuthManager {
607    /// Create new authentication manager
608    pub fn new() -> Self {
609        Self {
610            accounts: RwLock::new(AccountDatabase::new()),
611            next_user_id: RwLock::new(1000), // Start UIDs at 1000
612            max_failed_attempts: 5,
613            password_policy: RwLock::new(PasswordPolicy::relaxed()),
614        }
615    }
616
617    /// Create with a specific password policy.
618    pub fn with_policy(policy: PasswordPolicy) -> Self {
619        Self {
620            accounts: RwLock::new(AccountDatabase::new()),
621            next_user_id: RwLock::new(1000),
622            max_failed_attempts: 5,
623            password_policy: RwLock::new(policy),
624        }
625    }
626
627    /// Set the password policy.
628    pub fn set_password_policy(&self, policy: PasswordPolicy) {
629        *self.password_policy.write() = policy;
630    }
631
632    /// Get the current password policy.
633    pub fn get_password_policy(&self) -> PasswordPolicy {
634        *self.password_policy.read()
635    }
636
637    /// Create new user account.
638    ///
639    /// Validates the password against the active policy before creating
640    /// the account.
641    pub fn create_user(
642        &self,
643        username: &'static str,
644        password: &str,
645    ) -> Result<UserId, KernelError> {
646        // Validate password against policy
647        let policy = *self.password_policy.read();
648        policy.validate_password(password)?;
649
650        let mut accounts = self.accounts.write();
651
652        // Check if username already exists
653        if accounts.contains_key(username) {
654            return Err(KernelError::AlreadyExists {
655                resource: "user",
656                id: 0, // Username lookup, no specific ID
657            });
658        }
659
660        // Allocate new user ID
661        let user_id = {
662            let mut next_id = self.next_user_id.write();
663            let id = *next_id;
664            *next_id += 1;
665            id
666        };
667
668        // Create account
669        let account = UserAccount::new(user_id, username, password);
670
671        accounts.insert(account)?;
672
673        Ok(user_id)
674    }
675
676    /// Authenticate user.
677    ///
678    /// Checks account lock, expiration, password, and MFA status.
679    pub fn authenticate(&self, username: &str, password: &str) -> AuthResult {
680        let mut accounts = self.accounts.write();
681
682        if let Some(account) = accounts.get_mut(username) {
683            // Check if account is locked
684            if account.locked {
685                // Log the failed attempt
686                crate::security::audit::log_auth_attempt(0, account.user_id, username, false);
687                return AuthResult::AccountLocked;
688            }
689
690            // Check if account has expired
691            if account.is_expired() {
692                crate::security::audit::log_auth_attempt(0, account.user_id, username, false);
693                return AuthResult::AccountExpired;
694            }
695
696            // Verify password
697            if account.verify_password(password) {
698                // Reset failed attempts on successful login
699                account.failed_attempts = 0;
700
701                // Check if MFA is required
702                if account.mfa_enabled {
703                    return AuthResult::MfaRequired;
704                }
705
706                // Log successful authentication
707                crate::security::audit::log_auth_attempt(0, account.user_id, username, true);
708
709                return AuthResult::Success;
710            } else {
711                // Increment failed attempts
712                account.failed_attempts += 1;
713
714                // Log failed authentication
715                crate::security::audit::log_auth_attempt(0, account.user_id, username, false);
716
717                // Lock account if max attempts exceeded
718                if account.failed_attempts >= self.max_failed_attempts {
719                    account.locked = true;
720                    return AuthResult::AccountLocked;
721                }
722
723                return AuthResult::InvalidCredentials;
724            }
725        }
726
727        AuthResult::InvalidCredentials
728    }
729
730    /// Authenticate with MFA
731    pub fn authenticate_mfa(&self, username: &str, password: &str, mfa_token: u32) -> AuthResult {
732        // First verify password
733        let result = self.authenticate(username, password);
734
735        if result != AuthResult::MfaRequired {
736            return result;
737        }
738
739        // Verify MFA token
740        let accounts = self.accounts.read();
741        if let Some(account) = accounts.get(username) {
742            if account.verify_mfa_token(mfa_token) {
743                crate::security::audit::log_auth_attempt(0, account.user_id, username, true);
744                return AuthResult::Success;
745            }
746        }
747
748        AuthResult::InvalidCredentials
749    }
750
751    /// Change a user's password.
752    ///
753    /// Validates the new password against the active policy and checks
754    /// password history to prevent reuse.
755    pub fn change_password(
756        &self,
757        username: &str,
758        old_password: &str,
759        new_password: &str,
760    ) -> Result<(), KernelError> {
761        let policy = *self.password_policy.read();
762
763        // Validate new password against policy
764        policy.validate_password(new_password)?;
765
766        let mut accounts = self.accounts.write();
767
768        if let Some(account) = accounts.get_mut(username) {
769            // Verify old password first
770            if !account.verify_password(old_password) {
771                return Err(KernelError::PermissionDenied {
772                    operation: "change_password",
773                });
774            }
775
776            // Change password with history check
777            account.change_password(new_password, policy.history_size)?;
778
779            Ok(())
780        } else {
781            Err(KernelError::NotFound {
782                resource: "user",
783                id: 0,
784            })
785        }
786    }
787
788    /// Set account expiration.
789    pub fn set_account_expiration(
790        &self,
791        username: &str,
792        expires_at: Option<u64>,
793    ) -> Result<(), KernelError> {
794        let mut accounts = self.accounts.write();
795
796        if let Some(account) = accounts.get_mut(username) {
797            account.set_expiration(expires_at);
798            Ok(())
799        } else {
800            Err(KernelError::NotFound {
801                resource: "user",
802                id: 0,
803            })
804        }
805    }
806
807    /// Enable MFA for user
808    pub fn enable_mfa(&self, username: &str) -> Result<[u8; 32], KernelError> {
809        let mut accounts = self.accounts.write();
810
811        if let Some(account) = accounts.get_mut(username) {
812            Ok(account.enable_mfa())
813        } else {
814            Err(KernelError::NotFound {
815                resource: "user",
816                id: 0, // Username lookup, no specific ID
817            })
818        }
819    }
820
821    /// Unlock user account
822    pub fn unlock_account(&self, username: &str) -> Result<(), KernelError> {
823        let mut accounts = self.accounts.write();
824
825        if let Some(account) = accounts.get_mut(username) {
826            account.locked = false;
827            account.failed_attempts = 0;
828            Ok(())
829        } else {
830            Err(KernelError::NotFound {
831                resource: "user",
832                id: 0, // Username lookup, no specific ID
833            })
834        }
835    }
836
837    /// Delete user account
838    pub fn delete_user(&self, username: &str) -> Result<(), KernelError> {
839        let mut accounts = self.accounts.write();
840
841        accounts
842            .remove(username)
843            .map(|_| ())
844            .ok_or(KernelError::NotFound {
845                resource: "user",
846                id: 0, // Username lookup, no specific ID
847            })
848    }
849
850    /// List all usernames. Returns an iterator-friendly fixed-size collection.
851    ///
852    /// Since we cannot return `Vec<String>` without heap allocation, callers
853    /// should use `with_users` or iterate via the returned array.
854    pub fn list_usernames(&self, buf: &mut [Option<&str>]) -> usize {
855        let accounts = self.accounts.read();
856        let mut i = 0;
857        for account in accounts.iter() {
858            if i >= buf.len() {
859                break;
860            }
861            buf[i] = Some(account.username);
862            i += 1;
863        }
864        i
865    }
866
867    /// Get user by ID
868    pub fn get_user_by_id(&self, user_id: UserId) -> Option<&'static str> {
869        let accounts = self.accounts.read();
870
871        for account in accounts.iter() {
872            if account.user_id == user_id {
873                return Some(account.username);
874            }
875        }
876
877        None
878    }
879}
880
881impl Default for AuthManager {
882    fn default() -> Self {
883        Self::new()
884    }
885}
886
887// ---------------------------------------------------------------------------
888// Global State
889// ---------------------------------------------------------------------------
890
891/// Global authentication manager
892static AUTH_MANAGER: OnceLock<AuthManager> = OnceLock::new();
893
894/// Initialize authentication framework
895pub fn init() -> Result<(), KernelError> {
896    AUTH_MANAGER
897        .set(AuthManager::new())
898        .map_err(|_| KernelError::AlreadyExists {
899            resource: "auth_manager",
900            id: 0,
901        })?;
902
903    // Create default root account (uses relaxed policy for initial setup)
904    let auth_manager = get_auth_manager();
905    if let Err(_e) = auth_manager.create_user("root", "veridian") {
906        crate::kprintln!("[AUTH] Warning: Failed to create default root account");
907    }
908
909    crate::println!("[AUTH] Authentication framework initialized");
910    crate::println!("[AUTH] Default root user created");
911    crate::println!(
912        "[AUTH] PBKDF2-HMAC-SHA256 with {} iterations",
913        UserAccount::PBKDF2_ITERATIONS
914    );
915
916    Ok(())
917}
918
919/// Get global authentication manager
920pub fn get_auth_manager() -> &'static AuthManager {
921    AUTH_MANAGER.get().expect("Auth manager not initialized")
922}
923
924/// Validate a password against the default policy (convenience function).
925pub fn validate_password(password: &str) -> Result<(), KernelError> {
926    PasswordPolicy::default_policy().validate_password(password)
927}
928
929// ---------------------------------------------------------------------------
930// Tests
931// ---------------------------------------------------------------------------
932
933#[cfg(test)]
934mod tests {
935    use super::*;
936
937    #[test]
938    fn test_password_hashing() {
939        let account = UserAccount::new(1000, "test", "password123");
940
941        assert!(account.verify_password("password123"));
942        assert!(!account.verify_password("wrongpassword"));
943    }
944
945    #[test]
946    fn test_authentication() {
947        let auth = AuthManager::new();
948
949        let _ = auth.create_user("alice", "secret");
950
951        assert_eq!(auth.authenticate("alice", "secret"), AuthResult::Success);
952        assert_eq!(
953            auth.authenticate("alice", "wrong"),
954            AuthResult::InvalidCredentials
955        );
956        assert_eq!(
957            auth.authenticate("bob", "secret"),
958            AuthResult::InvalidCredentials
959        );
960    }
961
962    #[test]
963    fn test_account_locking() {
964        let auth = AuthManager::new();
965
966        let _ = auth.create_user("bob", "password");
967
968        // Try wrong password multiple times
969        for _ in 0..5 {
970            let _ = auth.authenticate("bob", "wrong");
971        }
972
973        // Account should now be locked
974        assert_eq!(
975            auth.authenticate("bob", "password"),
976            AuthResult::AccountLocked
977        );
978    }
979
980    #[test]
981    fn test_pbkdf2_hmac_sha256() {
982        // Test that PBKDF2 produces consistent output
983        let salt = [0x42u8; 32];
984        let hash1 = pbkdf2_hmac_sha256(b"test_password", &salt, 10);
985        let hash2 = pbkdf2_hmac_sha256(b"test_password", &salt, 10);
986        assert_eq!(hash1, hash2);
987
988        // Different passwords produce different hashes
989        let hash3 = pbkdf2_hmac_sha256(b"different_password", &salt, 10);
990        assert_ne!(hash1, hash3);
991    }
992
993    #[test]
994    fn test_hmac_sha256() {
995        // Basic HMAC test: same key+message = same output
996        let key = b"secret_key";
997        let msg = b"hello world";
998        let h1 = hmac_sha256(key, msg);
999        let h2 = hmac_sha256(key, msg);
1000        assert_eq!(h1, h2);
1001
1002        // Different message = different HMAC
1003        let h3 = hmac_sha256(key, b"different message");
1004        assert_ne!(h1, h3);
1005    }
1006
1007    #[test]
1008    fn test_password_policy_validation() {
1009        let policy = PasswordPolicy::default_policy();
1010
1011        // Too short
1012        assert!(policy.validate_password("Ab1").is_err());
1013
1014        // Missing uppercase
1015        assert!(policy.validate_password("abcdefg1").is_err());
1016
1017        // Missing lowercase
1018        assert!(policy.validate_password("ABCDEFG1").is_err());
1019
1020        // Missing digit
1021        assert!(policy.validate_password("Abcdefgh").is_err());
1022
1023        // Valid password
1024        assert!(policy.validate_password("Abcdefg1").is_ok());
1025    }
1026
1027    #[test]
1028    fn test_account_expiration() {
1029        let mut account = UserAccount::new(1000, "exptest", "password");
1030
1031        // No expiration: not expired
1032        assert!(!account.is_expired());
1033
1034        // Set expiration in the past (0 = already expired since boot time > 0 in tests,
1035        // but on fresh boot timestamp may be 0, so use a small value)
1036        account.set_expiration(Some(0));
1037        // This may or may not be expired depending on boot time;
1038        // just verify the field was set
1039        assert_eq!(account.expires_at, Some(0));
1040    }
1041
1042    #[test]
1043    fn test_password_change_reuse() {
1044        let mut account = UserAccount::new(1000, "chgtest", "original");
1045
1046        // Changing to the same password should fail
1047        let result = account.change_password("original", 5);
1048        assert!(result.is_err());
1049
1050        // Changing to a different password should succeed
1051        let result = account.change_password("newpassword", 5);
1052        assert!(result.is_ok());
1053
1054        // Password history should have one entry
1055        assert_eq!(account.password_history_len, 1);
1056
1057        // Verify new password works
1058        assert!(account.verify_password("newpassword"));
1059        assert!(!account.verify_password("original"));
1060    }
1061}