Skip to main content

atmos/apps/login/
mod.rs

1//! ログイン画面とパスワード認証。
2//!
3//! 初回起動時のパスワード設定([`LoginState::SetupInput`]/[`LoginState::SetupConfirm`])と、
4//! 通常起動時のロック画面([`LoginState::Locked`])を提供する。パスワードは平文を保持せず
5//! SHA-256 ハッシュ([`sha256`]、FIPS PUB 180-4 準拠のピュア Rust 実装)のみを SylFS に保存する。
6// src/login.rs - 英数字記号対応・SHA-256暗号化パスワードログイン画面と認証
7#![allow(dead_code)]
8#![allow(static_mut_refs)]
9
10extern crate alloc;
11use alloc::string::String;
12use alloc::vec::Vec;
13
14use crate::kernel::draw::{Color, Screen};
15use crate::kernel::fs;
16use crate::kernel::timer;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum LoginState {
20    SetupInput,   // 初回起動:新規パスワード設定入力
21    SetupConfirm, // 初回起動:パスワード確認入力
22    Locked,       // 通常起動:ロック画面(パスワード入力待機)
23    Unlocked,     // 認証成功:ロック解除状態
24}
25
26pub struct LoginManager {
27    state: LoginState,
28    current_input: Vec<u8>,
29    setup_password: Vec<u8>,
30    saved_password_hash: [u8; 32],
31    error_ticks_end: usize,
32    error_msg: &'static str,
33    pub needs_redraw: bool,
34}
35
36/// SHA-256 ハッシュアルゴリズムのピュアRust実装 (FIPS PUB 180-4 規格準拠)
37pub fn sha256(data: &[u8]) -> [u8; 32] {
38    let mut h: [u32; 8] = [
39        0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab,
40        0x5be0cd19,
41    ];
42
43    let k: [u32; 64] = [
44        0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4,
45        0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe,
46        0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f,
47        0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
48        0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc,
49        0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
50        0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116,
51        0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
52        0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7,
53        0xc67178f2,
54    ];
55
56    let len = data.len();
57    let mut padded = alloc::vec![0u8; ((len + 8 + 64) / 64) * 64];
58    padded[..len].copy_from_slice(data);
59    padded[len] = 0x80;
60
61    let bit_len = (len as u64) * 8;
62    let bit_len_bytes = bit_len.to_be_bytes();
63    let pad_len = padded.len();
64    padded[pad_len - 8..].copy_from_slice(&bit_len_bytes);
65
66    for block in padded.chunks_exact(64) {
67        let mut w = [0u32; 64];
68        for i in 0..16 {
69            let mut bytes = [0u8; 4];
70            bytes.copy_from_slice(&block[i * 4..(i + 1) * 4]);
71            w[i] = u32::from_be_bytes(bytes);
72        }
73
74        for i in 16..64 {
75            let s0 = w[i - 15].rotate_right(7) ^ w[i - 15].rotate_right(18) ^ (w[i - 15] >> 3);
76            let s1 = w[i - 2].rotate_right(17) ^ w[i - 2].rotate_right(19) ^ (w[i - 2] >> 10);
77            w[i] = w[i - 16]
78                .wrapping_add(s0)
79                .wrapping_add(w[i - 7])
80                .wrapping_add(s1);
81        }
82
83        let mut a = h[0];
84        let mut b = h[1];
85        let mut c = h[2];
86        let mut d = h[3];
87        let mut e = h[4];
88        let mut f = h[5];
89        let mut g = h[6];
90        let mut h_val = h[7];
91
92        for i in 0..64 {
93            let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
94            let ch = (e & f) ^ ((!e) & g);
95            let temp1 = h_val
96                .wrapping_add(s1)
97                .wrapping_add(ch)
98                .wrapping_add(k[i])
99                .wrapping_add(w[i]);
100            let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
101            let maj = (a & b) ^ (a & c) ^ (b & c);
102            let temp2 = s0.wrapping_add(maj);
103
104            h_val = g;
105            g = f;
106            f = e;
107            e = d.wrapping_add(temp1);
108            d = c;
109            c = b;
110            b = a;
111            a = temp1.wrapping_add(temp2);
112        }
113
114        h[0] = h[0].wrapping_add(a);
115        h[1] = h[1].wrapping_add(b);
116        h[2] = h[2].wrapping_add(c);
117        h[3] = h[3].wrapping_add(d);
118        h[4] = h[4].wrapping_add(e);
119        h[5] = h[5].wrapping_add(f);
120        h[6] = h[6].wrapping_add(g);
121        h[7] = h[7].wrapping_add(h_val);
122    }
123
124    let mut result = [0u8; 32];
125    for i in 0..8 {
126        result[i * 4..(i + 1) * 4].copy_from_slice(&h[i].to_be_bytes());
127    }
128    result
129}
130
131impl Default for LoginManager {
132    fn default() -> Self {
133        Self::new()
134    }
135}
136
137impl LoginManager {
138    pub fn new() -> Self {
139        let mut mgr = Self {
140            state: LoginState::Locked,
141            current_input: Vec::new(),
142            setup_password: Vec::new(),
143            saved_password_hash: [0u8; 32],
144            error_ticks_end: 0,
145            error_msg: "",
146            needs_redraw: true,
147        };
148
149        mgr.check_saved_password();
150        mgr
151    }
152
153    /// SylFS から password.cfg を読み込んで保存されているパスワード情報があるか確認
154    fn check_saved_password(&mut self) {
155        if let Some((_, data)) = fs::get_fs().read_file("password.cfg") {
156            if data.len() == 32 {
157                self.saved_password_hash.copy_from_slice(&data[0..32]);
158                self.state = LoginState::Locked;
159                return;
160            }
161        }
162        // 旧仕様の pin.cfg が残っている場合を考慮し、もし password.cfg がなく pin.cfg があれば
163        // 安全に SetupInput(初回パスワード設定)へ遷移させて新規登録を促す
164        self.state = LoginState::SetupInput;
165    }
166
167    pub fn is_unlocked(&self) -> bool {
168        self.state == LoginState::Unlocked
169    }
170
171    /// キーボード入力イベントの処理
172    pub fn handle_key_input(&mut self, c: char) {
173        let current_ticks = timer::get_ticks();
174        if current_ticks < self.error_ticks_end {
175            self.error_ticks_end = 0;
176            self.current_input.clear();
177            self.needs_redraw = true;
178        }
179
180        // 半角英数字記号 (ASCII 0x20 〜 0x7E)
181        if (' '..='~').contains(&c) {
182            if self.current_input.len() < 32 {
183                self.current_input.push(c as u8);
184                self.needs_redraw = true;
185            }
186        } else if c == '\x08' {
187            // Backspace
188            if !self.current_input.is_empty() {
189                self.current_input.pop();
190                self.needs_redraw = true;
191            }
192        } else if c == '\x1B' {
193            // Escape (入力クリア)
194            if !self.current_input.is_empty() {
195                self.current_input.clear();
196                self.needs_redraw = true;
197            }
198        } else if c == '\n' {
199            // Enter で送信・認証
200            if !self.current_input.is_empty() {
201                self.process_completed_input();
202            }
203        }
204    }
205
206    /// 入力完了(Enter押下時)の処理
207    fn process_completed_input(&mut self) {
208        match self.state {
209            LoginState::Locked => {
210                let input_hash = sha256(&self.current_input);
211                if input_hash == self.saved_password_hash {
212                    crate::warn!("[SYS] === [Login] Success: Locked -> Unlocked ===");
213                    self.state = LoginState::Unlocked;
214                } else {
215                    crate::info!("[SYS] === [Login] Failed: Incorrect password ===");
216                    self.trigger_error("Incorrect password. Please try again.");
217                }
218            }
219            LoginState::SetupInput => {
220                crate::info!("[SYS] === [Login] SetupInput password set. Confirming... ===");
221                self.setup_password = self.current_input.clone();
222                self.current_input.clear();
223                self.state = LoginState::SetupConfirm;
224                self.needs_redraw = true;
225            }
226            LoginState::SetupConfirm => {
227                if self.current_input == self.setup_password {
228                    let hash = sha256(&self.current_input);
229                    crate::info!("[SYS] === [Login] SetupConfirm matched. Saving password... ===");
230                    if fs::get_fs().save_file("password.cfg", &hash, "").is_ok() {
231                        // マイルストーン(CI が expect で待つ) → warn!
232                        crate::warn!("[SYS] === [Login] Password saved. State: Unlocked ===");
233                        self.saved_password_hash = hash;
234                        self.state = LoginState::Unlocked;
235                        // 旧仕様の pin.cfg が存在すれば削除を試みる (互換クリーンアップ)
236                        let _ = fs::get_fs().delete_file("pin.cfg");
237                    } else {
238                        crate::info!("[SYS] === [Login] Password save FAILED ===");
239                        self.trigger_error("Storage Error. Try again.");
240                        self.state = LoginState::SetupInput;
241                    }
242                } else {
243                    crate::info!("[SYS] === [Login] Passwords mismatch ===");
244                    self.trigger_error("Passwords mismatch. Please restart.");
245                    self.state = LoginState::SetupInput;
246                    self.setup_password.clear();
247                }
248            }
249            _ => {}
250        }
251    }
252
253    fn trigger_error(&mut self, msg: &'static str) {
254        self.error_msg = msg;
255        self.error_ticks_end = timer::get_ticks() + 150; // 1.5秒間エラーを表示
256        self.current_input.clear();
257        self.needs_redraw = true;
258    }
259
260    /// マウス入力はパスワードログインでは基本的に不要ですが、画面クリック時に
261    /// エラー表示をリセットする用途として最小限残します
262    pub fn handle_mouse_input(&mut self, _mx: u32, _my: u32, left_btn: bool, _wheel: i32) {
263        if left_btn {
264            let current_ticks = timer::get_ticks();
265            if current_ticks < self.error_ticks_end {
266                self.error_ticks_end = 0;
267                self.current_input.clear();
268                self.needs_redraw = true;
269            }
270        }
271    }
272
273    /// プレミアムなセキュリティダイアログの描画
274    pub fn draw(&mut self, screen: &Screen) {
275        let current_ticks = timer::get_ticks();
276        if current_ticks >= self.error_ticks_end && self.error_ticks_end != 0 {
277            self.error_ticks_end = 0;
278            self.needs_redraw = true;
279        }
280
281        let cx = screen.width / 2;
282        let cy = screen.height / 2;
283        let theme = crate::kernel::config::get_config().theme;
284
285        // 1. 画面全体の背景クリア
286        screen.boxfill(0, 0, screen.width - 1, screen.height - 1, Color(theme.login_screen_bg));
287
288        // 2. 中央のプレミアムダイアログボックス
289        let dw = 500;
290        let dh = 300;
291        let dx0 = cx - dw / 2;
292        let dy0 = cy - dh / 2;
293        let dx1 = cx + dw / 2;
294        let dy1 = cy + dh / 2;
295
296        // ドロップシャドウ風の最背面
297        screen.boxfill(dx0 - 6, dy0 - 6, dx1 + 6, dy1 + 6, Color(theme.login_dialog_shadow));
298        // メインパネル
299        screen.boxfill(dx0, dy0, dx1, dy1, Color(theme.login_dialog_bg));
300
301        // 極細のネオンハイライト境界線 (エラー時は赤、通常時はダイアログ境界線)
302        let border_color = if self.error_ticks_end > 0 {
303            Color(theme.focus_highlight) // エラー時はハイライト色
304        } else {
305            Color(theme.login_dialog_border)
306        };
307        draw_border(screen, dx0, dy0, dx1, dy1, border_color.0);
308
309        // 3. タイトルテキストの描画 (ベクトルフォント)
310        let title = match self.state {
311            LoginState::SetupInput => "Create Your Password",
312            LoginState::SetupConfirm => "Confirm Your Password",
313            LoginState::Locked => "AtmOS Security Shield",
314            _ => "Access Granted",
315        };
316
317        let title_w = crate::kernel::vector_font::get_vector_string_width(title, 24);
318        let title_x = cx - title_w / 2;
319        screen.draw_string_vector(title_x, dy0 + 36, title, Color(theme.login_text_fg), 24);
320
321        // サブタイトルやエラーメッセージの描画
322        if self.error_ticks_end > 0 {
323            let err_w = get_string_width_ja(self.error_msg);
324            let err_x = cx - err_w / 2;
325            screen.draw_string_ja(err_x, dy0 + 72, self.error_msg, Color(theme.focus_highlight));
326        } else {
327            let sub = match self.state {
328                LoginState::SetupInput => "Enter a new secure alphanumeric password",
329                LoginState::SetupConfirm => "Re-enter to verify and commit",
330                LoginState::Locked => "Enter security password to decrypt environment",
331                _ => "",
332            };
333            let sub_w = get_string_width_ja(sub);
334            let sub_x = cx - sub_w / 2;
335            screen.draw_string_ja(sub_x, dy0 + 72, sub, Color(theme.login_dialog_border));
336        }
337
338        // 4. パスワード入力フィールド
339        let field_w = 400;
340        let field_h = 48;
341        let fx0 = cx - field_w / 2;
342        let fy0 = dy0 + 130;
343        let fx1 = cx + field_w / 2;
344        let fy1 = fy0 + field_h;
345
346        // テキストボックスの背景と境界線
347        screen.boxfill(fx0, fy0, fx1, fy1, Color(theme.login_field_bg));
348        let field_border = if self.error_ticks_end > 0 {
349            theme.focus_highlight // エラー時
350        } else {
351            theme.login_field_border
352        };
353        draw_border(screen, fx0, fy0, fx1, fy1, field_border);
354
355        // マスクされたパスワード文字 (`****`) の描画(通常の白文字)
356        let mut mask_str = String::new();
357        for _ in 0..self.current_input.len() {
358            mask_str.push('*');
359        }
360
361        let mask_w = crate::kernel::vector_font::get_vector_string_width(&mask_str, 20);
362        let mask_x = cx - mask_w / 2;
363        screen.draw_string_vector(mask_x, fy0 + 14, &mask_str, Color(theme.login_text_fg), 20);
364
365        // 入力カーソル(縦棒)をマスク末尾に点滅表示(500ms周期)。
366        // 色はターミナルと共通の 0xFF756CE0(赤棒)。
367        let show_caret = (timer::get_ticks() / 25).is_multiple_of(2);
368        if show_caret {
369            let caret_x = mask_x + mask_w;
370            screen.boxfill(caret_x, fy0 + 12, caret_x + 2, fy0 + 36, Color(theme.login_caret));
371        }
372
373        // 5. 操作ヒント表示
374        let hint = "Press [Enter] to submit  /  [Esc] to clear";
375        let hint_w = get_string_width_ja(hint);
376        let hint_x = cx - hint_w / 2;
377        screen.draw_string_ja(hint_x, dy0 + 240, hint, Color(theme.login_hint_fg));
378
379        self.needs_redraw = false;
380    }
381}
382
383// ヘルパー: 矩形領域の境界線を描画
384fn draw_border(screen: &Screen, x0: u32, y0: u32, x1: u32, y1: u32, color_val: u32) {
385    if x0 >= x1 || y0 >= y1 {
386        return;
387    }
388    screen.boxfill(x0, y0, x1, y0 + 1, Color(color_val));
389    screen.boxfill(x0, y1 - 1, x1, y1, Color(color_val));
390    screen.boxfill(x0, y0, x0 + 1, y1, Color(color_val));
391    screen.boxfill(x1 - 1, y0, x1, y1, Color(color_val));
392}
393
394fn get_string_width_ja(s: &str) -> u32 {
395    let mut width = 0;
396    for c in s.chars() {
397        if (c as u32) < 128 {
398            width += 8;
399        } else {
400            width += 16;
401        }
402    }
403    width
404}