1use super::authorization::Role;
58use super::device_id::DeviceId;
59use super::error::SecurityError;
60use argon2::{
61 password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
62 Argon2,
63};
64use hmac::{Hmac, Mac};
65use rand_core::RngCore;
66use serde::{Deserialize, Serialize};
67use sha2::Sha256;
68use std::collections::{HashMap, HashSet};
69use std::sync::{Arc, RwLock};
70use std::time::{Duration, SystemTime, UNIX_EPOCH};
71use uuid::Uuid;
72
73pub const DEFAULT_SESSION_EXPIRY_HOURS: u64 = 8;
75
76pub const TOTP_TIME_STEP_SECS: u64 = 30;
78
79pub const TOTP_CODE_LENGTH: usize = 6;
81
82pub const TOTP_CLOCK_DRIFT_STEPS: i64 = 1;
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
87pub enum MilitaryRank {
88 Private,
90 Specialist,
91 Corporal,
92 Sergeant,
93 StaffSergeant,
94 SergeantFirstClass,
95 MasterSergeant,
96 FirstSergeant,
97 SergeantMajor,
98
99 WarrantOfficer1,
101 ChiefWarrantOfficer2,
102 ChiefWarrantOfficer3,
103 ChiefWarrantOfficer4,
104 ChiefWarrantOfficer5,
105
106 SecondLieutenant,
108 FirstLieutenant,
109 Captain,
110 Major,
111 LieutenantColonel,
112 Colonel,
113 BrigadierGeneral,
114 MajorGeneral,
115 LieutenantGeneral,
116 General,
117
118 Civilian,
120}
121
122impl std::fmt::Display for MilitaryRank {
123 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124 match self {
125 MilitaryRank::Private => write!(f, "PVT"),
126 MilitaryRank::Specialist => write!(f, "SPC"),
127 MilitaryRank::Corporal => write!(f, "CPL"),
128 MilitaryRank::Sergeant => write!(f, "SGT"),
129 MilitaryRank::StaffSergeant => write!(f, "SSG"),
130 MilitaryRank::SergeantFirstClass => write!(f, "SFC"),
131 MilitaryRank::MasterSergeant => write!(f, "MSG"),
132 MilitaryRank::FirstSergeant => write!(f, "1SG"),
133 MilitaryRank::SergeantMajor => write!(f, "SGM"),
134 MilitaryRank::WarrantOfficer1 => write!(f, "WO1"),
135 MilitaryRank::ChiefWarrantOfficer2 => write!(f, "CW2"),
136 MilitaryRank::ChiefWarrantOfficer3 => write!(f, "CW3"),
137 MilitaryRank::ChiefWarrantOfficer4 => write!(f, "CW4"),
138 MilitaryRank::ChiefWarrantOfficer5 => write!(f, "CW5"),
139 MilitaryRank::SecondLieutenant => write!(f, "2LT"),
140 MilitaryRank::FirstLieutenant => write!(f, "1LT"),
141 MilitaryRank::Captain => write!(f, "CPT"),
142 MilitaryRank::Major => write!(f, "MAJ"),
143 MilitaryRank::LieutenantColonel => write!(f, "LTC"),
144 MilitaryRank::Colonel => write!(f, "COL"),
145 MilitaryRank::BrigadierGeneral => write!(f, "BG"),
146 MilitaryRank::MajorGeneral => write!(f, "MG"),
147 MilitaryRank::LieutenantGeneral => write!(f, "LTG"),
148 MilitaryRank::General => write!(f, "GEN"),
149 MilitaryRank::Civilian => write!(f, "CIV"),
150 }
151 }
152}
153
154#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
156pub enum SecurityClearance {
157 Unclassified,
159 Cui,
161 Confidential,
163 Secret,
165 TopSecret,
167 TopSecretSci,
169}
170
171impl std::fmt::Display for SecurityClearance {
172 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173 match self {
174 SecurityClearance::Unclassified => write!(f, "UNCLASSIFIED"),
175 SecurityClearance::Cui => write!(f, "CUI"),
176 SecurityClearance::Confidential => write!(f, "CONFIDENTIAL"),
177 SecurityClearance::Secret => write!(f, "SECRET"),
178 SecurityClearance::TopSecret => write!(f, "TOP SECRET"),
179 SecurityClearance::TopSecretSci => write!(f, "TS/SCI"),
180 }
181 }
182}
183
184#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
186pub struct OrganizationUnit {
187 pub name: String,
189
190 pub parent: Option<String>,
192
193 pub uic: Option<String>,
195}
196
197impl OrganizationUnit {
198 pub fn new(name: impl Into<String>, parent: impl Into<String>) -> Self {
200 Self {
201 name: name.into(),
202 parent: Some(parent.into()),
203 uic: None,
204 }
205 }
206
207 pub fn top_level(name: impl Into<String>) -> Self {
209 Self {
210 name: name.into(),
211 parent: None,
212 uic: None,
213 }
214 }
215
216 pub fn with_uic(mut self, uic: impl Into<String>) -> Self {
218 self.uic = Some(uic.into());
219 self
220 }
221}
222
223impl std::fmt::Display for OrganizationUnit {
224 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
225 if let Some(parent) = &self.parent {
226 write!(f, "{}, {}", self.name, parent)
227 } else {
228 write!(f, "{}", self.name)
229 }
230 }
231}
232
233#[derive(Debug, Clone, Serialize, Deserialize)]
235pub struct UserIdentity {
236 pub username: String,
238
239 pub display_name: String,
241
242 pub rank: MilitaryRank,
244
245 pub clearance: SecurityClearance,
247
248 pub unit: OrganizationUnit,
250
251 pub roles: HashSet<Role>,
253}
254
255impl UserIdentity {
256 pub fn builder(username: impl Into<String>) -> UserIdentityBuilder {
258 UserIdentityBuilder::new(username)
259 }
260}
261
262pub struct UserIdentityBuilder {
264 username: String,
265 display_name: Option<String>,
266 rank: MilitaryRank,
267 clearance: SecurityClearance,
268 unit: Option<OrganizationUnit>,
269 roles: HashSet<Role>,
270}
271
272impl UserIdentityBuilder {
273 pub fn new(username: impl Into<String>) -> Self {
275 Self {
276 username: username.into(),
277 display_name: None,
278 rank: MilitaryRank::Civilian,
279 clearance: SecurityClearance::Unclassified,
280 unit: None,
281 roles: HashSet::new(),
282 }
283 }
284
285 pub fn display_name(mut self, name: impl Into<String>) -> Self {
287 self.display_name = Some(name.into());
288 self
289 }
290
291 pub fn rank(mut self, rank: MilitaryRank) -> Self {
293 self.rank = rank;
294 self
295 }
296
297 pub fn clearance(mut self, clearance: SecurityClearance) -> Self {
299 self.clearance = clearance;
300 self
301 }
302
303 pub fn unit(mut self, unit: OrganizationUnit) -> Self {
305 self.unit = Some(unit);
306 self
307 }
308
309 pub fn role(mut self, role: Role) -> Self {
311 self.roles.insert(role);
312 self
313 }
314
315 pub fn roles(mut self, roles: impl IntoIterator<Item = Role>) -> Self {
317 self.roles.extend(roles);
318 self
319 }
320
321 pub fn build(self) -> UserIdentity {
323 UserIdentity {
324 username: self.username.clone(),
325 display_name: self.display_name.unwrap_or_else(|| self.username.clone()),
326 rank: self.rank,
327 clearance: self.clearance,
328 unit: self
329 .unit
330 .unwrap_or_else(|| OrganizationUnit::top_level("Unknown")),
331 roles: self.roles,
332 }
333 }
334}
335
336#[derive(Debug, Clone)]
338pub struct UserRecord {
339 pub identity: UserIdentity,
341
342 pub auth_method: AuthMethod,
344
345 pub status: AccountStatus,
347
348 pub created_at: SystemTime,
350
351 pub last_login: Option<SystemTime>,
353
354 pub failed_attempts: u32,
356}
357
358#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
360pub enum AccountStatus {
361 Active,
363 Locked,
365 Disabled,
367 Pending,
369}
370
371#[derive(Debug, Clone)]
373pub enum AuthMethod {
374 PasswordMfa {
376 password_hash: String,
378 totp_secret: Vec<u8>,
380 },
381
382 SmartCard {
384 card_id: String,
386 pin_hash: String,
388 },
389
390 Certificate {
392 certificate_fingerprint: String,
394 },
395}
396
397#[derive(Debug, Clone)]
399pub enum Credential {
400 PasswordMfa {
402 password: String,
404 totp_code: String,
406 },
407
408 SmartCard {
410 card_id: String,
412 pin: String,
414 },
415
416 Certificate {
418 fingerprint: String,
420 },
421}
422
423#[derive(Debug, Clone, Serialize, Deserialize)]
425pub struct UserSession {
426 pub session_id: SessionId,
428
429 pub identity: UserIdentity,
431
432 pub device_id: Option<DeviceId>,
434
435 pub created_at: SystemTime,
437
438 pub expires_at: SystemTime,
440}
441
442impl UserSession {
443 pub fn is_expired(&self) -> bool {
445 SystemTime::now() > self.expires_at
446 }
447
448 pub fn remaining_time(&self) -> Option<Duration> {
450 self.expires_at.duration_since(SystemTime::now()).ok()
451 }
452}
453
454#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
456pub struct SessionId(Uuid);
457
458impl SessionId {
459 pub fn new() -> Self {
461 Self(Uuid::new_v4())
462 }
463}
464
465impl Default for SessionId {
466 fn default() -> Self {
467 Self::new()
468 }
469}
470
471impl std::fmt::Display for SessionId {
472 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
473 write!(f, "{}", self.0)
474 }
475}
476
477pub trait UserStore: Send + Sync {
479 fn get_user(&self, username: &str) -> Option<UserRecord>;
481
482 fn store_user(&self, record: UserRecord) -> Result<(), SecurityError>;
484
485 fn update_user(&self, record: UserRecord) -> Result<(), SecurityError>;
487
488 fn delete_user(&self, username: &str) -> Result<(), SecurityError>;
490
491 fn list_users(&self) -> Vec<String>;
493}
494
495#[derive(Debug, Default)]
497pub struct LocalUserStore {
498 users: RwLock<HashMap<String, UserRecord>>,
499}
500
501impl LocalUserStore {
502 pub fn new() -> Self {
504 Self {
505 users: RwLock::new(HashMap::new()),
506 }
507 }
508
509 pub fn with_users(users: Vec<UserRecord>) -> Self {
511 let store = Self::new();
512 {
513 let mut map = store.users.write().expect("users lock poisoned");
514 for user in users {
515 map.insert(user.identity.username.clone(), user);
516 }
517 }
518 store
519 }
520}
521
522impl UserStore for LocalUserStore {
523 fn get_user(&self, username: &str) -> Option<UserRecord> {
524 self.users.read().ok()?.get(username).cloned()
525 }
526
527 fn store_user(&self, record: UserRecord) -> Result<(), SecurityError> {
528 let mut users = self
529 .users
530 .write()
531 .map_err(|e| SecurityError::Internal(format!("users lock poisoned: {e}")))?;
532 if users.contains_key(&record.identity.username) {
533 return Err(SecurityError::UserAlreadyExists {
534 username: record.identity.username,
535 });
536 }
537 users.insert(record.identity.username.clone(), record);
538 Ok(())
539 }
540
541 fn update_user(&self, record: UserRecord) -> Result<(), SecurityError> {
542 let mut users = self
543 .users
544 .write()
545 .map_err(|e| SecurityError::Internal(format!("users lock poisoned: {e}")))?;
546 if !users.contains_key(&record.identity.username) {
547 return Err(SecurityError::UserNotFound {
548 username: record.identity.username,
549 });
550 }
551 users.insert(record.identity.username.clone(), record);
552 Ok(())
553 }
554
555 fn delete_user(&self, username: &str) -> Result<(), SecurityError> {
556 let mut users = self
557 .users
558 .write()
559 .map_err(|e| SecurityError::Internal(format!("users lock poisoned: {e}")))?;
560 if users.remove(username).is_none() {
561 return Err(SecurityError::UserNotFound {
562 username: username.to_string(),
563 });
564 }
565 Ok(())
566 }
567
568 fn list_users(&self) -> Vec<String> {
569 self.users
570 .read()
571 .map(|u| u.keys().cloned().collect())
572 .unwrap_or_default()
573 }
574}
575
576pub struct UserAuthenticator {
578 user_store: Box<dyn UserStore>,
580
581 sessions: Arc<RwLock<HashMap<SessionId, UserSession>>>,
583
584 session_expiry: Duration,
586
587 max_failed_attempts: u32,
589}
590
591impl UserAuthenticator {
592 pub fn new(user_store: Box<dyn UserStore>) -> Self {
594 Self {
595 user_store,
596 sessions: Arc::new(RwLock::new(HashMap::new())),
597 session_expiry: Duration::from_secs(DEFAULT_SESSION_EXPIRY_HOURS * 3600),
598 max_failed_attempts: 5,
599 }
600 }
601
602 pub fn with_session_expiry(mut self, expiry: Duration) -> Self {
604 self.session_expiry = expiry;
605 self
606 }
607
608 pub fn with_max_failed_attempts(mut self, max: u32) -> Self {
610 self.max_failed_attempts = max;
611 self
612 }
613
614 pub fn register_user(
619 &self,
620 _username: &str,
621 password: &str,
622 identity: UserIdentity,
623 ) -> Result<String, SecurityError> {
624 let salt = SaltString::generate(&mut OsRng);
626 let argon2 = Argon2::default();
627 let password_hash = argon2
628 .hash_password(password.as_bytes(), &salt)
629 .map_err(|e| SecurityError::PasswordHashError {
630 message: e.to_string(),
631 })?
632 .to_string();
633
634 let totp_secret: Vec<u8> = (0..20).map(|_| rand_core::OsRng.next_u64() as u8).collect();
636
637 let totp_secret_b32 = base32_encode(&totp_secret);
639
640 let record = UserRecord {
641 identity,
642 auth_method: AuthMethod::PasswordMfa {
643 password_hash,
644 totp_secret,
645 },
646 status: AccountStatus::Active,
647 created_at: SystemTime::now(),
648 last_login: None,
649 failed_attempts: 0,
650 };
651
652 self.user_store.store_user(record)?;
653
654 Ok(totp_secret_b32)
655 }
656
657 pub fn authenticate(
659 &self,
660 username: &str,
661 credential: &Credential,
662 ) -> Result<UserSession, SecurityError> {
663 let mut user =
665 self.user_store
666 .get_user(username)
667 .ok_or_else(|| SecurityError::UserNotFound {
668 username: username.to_string(),
669 })?;
670
671 match user.status {
673 AccountStatus::Locked => {
674 return Err(SecurityError::AccountLocked {
675 username: username.to_string(),
676 })
677 }
678 AccountStatus::Disabled => {
679 return Err(SecurityError::AccountDisabled {
680 username: username.to_string(),
681 })
682 }
683 AccountStatus::Pending => {
684 return Err(SecurityError::AccountPending {
685 username: username.to_string(),
686 })
687 }
688 AccountStatus::Active => {}
689 }
690
691 let verified = self.verify_credentials(&user.auth_method, credential)?;
693
694 if !verified {
695 user.failed_attempts += 1;
697 if user.failed_attempts >= self.max_failed_attempts {
698 user.status = AccountStatus::Locked;
699 }
700 let _ = self.user_store.update_user(user);
701
702 return Err(SecurityError::InvalidCredential {
703 username: username.to_string(),
704 });
705 }
706
707 user.failed_attempts = 0;
709 user.last_login = Some(SystemTime::now());
710 let _ = self.user_store.update_user(user.clone());
711
712 let now = SystemTime::now();
714 let session = UserSession {
715 session_id: SessionId::new(),
716 identity: user.identity,
717 device_id: None,
718 created_at: now,
719 expires_at: now + self.session_expiry,
720 };
721
722 self.sessions
724 .write()
725 .map_err(|e| SecurityError::Internal(format!("sessions lock poisoned: {e}")))?
726 .insert(session.session_id, session.clone());
727
728 Ok(session)
729 }
730
731 pub fn authenticate_password_only(
736 &self,
737 username: &str,
738 password: &str,
739 ) -> Result<UserSession, SecurityError> {
740 let mut user =
742 self.user_store
743 .get_user(username)
744 .ok_or_else(|| SecurityError::UserNotFound {
745 username: username.to_string(),
746 })?;
747
748 match user.status {
750 AccountStatus::Locked => {
751 return Err(SecurityError::AccountLocked {
752 username: username.to_string(),
753 })
754 }
755 AccountStatus::Disabled => {
756 return Err(SecurityError::AccountDisabled {
757 username: username.to_string(),
758 })
759 }
760 AccountStatus::Pending => {
761 return Err(SecurityError::AccountPending {
762 username: username.to_string(),
763 })
764 }
765 AccountStatus::Active => {}
766 }
767
768 match &user.auth_method {
770 AuthMethod::PasswordMfa { password_hash, .. } => {
771 let parsed_hash = PasswordHash::new(password_hash).map_err(|e| {
772 SecurityError::PasswordHashError {
773 message: e.to_string(),
774 }
775 })?;
776
777 if Argon2::default()
778 .verify_password(password.as_bytes(), &parsed_hash)
779 .is_err()
780 {
781 user.failed_attempts += 1;
782 if user.failed_attempts >= self.max_failed_attempts {
783 user.status = AccountStatus::Locked;
784 }
785 let _ = self.user_store.update_user(user);
786
787 return Err(SecurityError::InvalidCredential {
788 username: username.to_string(),
789 });
790 }
791 }
792 _ => {
793 return Err(SecurityError::UnsupportedAuthMethod {
794 method: "non-password".to_string(),
795 })
796 }
797 }
798
799 user.failed_attempts = 0;
801 user.last_login = Some(SystemTime::now());
802 let _ = self.user_store.update_user(user.clone());
803
804 let now = SystemTime::now();
806 let session = UserSession {
807 session_id: SessionId::new(),
808 identity: user.identity,
809 device_id: None,
810 created_at: now,
811 expires_at: now + self.session_expiry,
812 };
813
814 self.sessions
816 .write()
817 .map_err(|e| SecurityError::Internal(format!("sessions lock poisoned: {e}")))?
818 .insert(session.session_id, session.clone());
819
820 Ok(session)
821 }
822
823 pub fn validate_session(&self, session_id: &SessionId) -> Result<UserIdentity, SecurityError> {
825 let sessions = self
826 .sessions
827 .read()
828 .map_err(|e| SecurityError::Internal(format!("sessions lock poisoned: {e}")))?;
829 let session = sessions
830 .get(session_id)
831 .ok_or(SecurityError::SessionNotFound)?;
832
833 if session.is_expired() {
834 drop(sessions);
835 self.invalidate_session(session_id);
836 return Err(SecurityError::SessionExpired);
837 }
838
839 Ok(session.identity.clone())
840 }
841
842 pub fn get_session(&self, session_id: &SessionId) -> Option<UserSession> {
844 self.sessions.read().ok()?.get(session_id).cloned()
845 }
846
847 pub fn invalidate_session(&self, session_id: &SessionId) {
849 if let Ok(mut sessions) = self.sessions.write() {
850 sessions.remove(session_id);
851 }
852 }
853
854 pub fn invalidate_user_sessions(&self, username: &str) {
856 if let Ok(mut sessions) = self.sessions.write() {
857 sessions.retain(|_, session| session.identity.username != username);
858 }
859 }
860
861 pub fn cleanup_expired_sessions(&self) {
863 let now = SystemTime::now();
864 if let Ok(mut sessions) = self.sessions.write() {
865 sessions.retain(|_, session| session.expires_at > now);
866 }
867 }
868
869 pub fn active_session_count(&self) -> usize {
871 self.sessions.read().map(|s| s.len()).unwrap_or(0)
872 }
873
874 pub fn bind_session_to_device(
876 &self,
877 session_id: &SessionId,
878 device_id: DeviceId,
879 ) -> Result<(), SecurityError> {
880 let mut sessions = self
881 .sessions
882 .write()
883 .map_err(|e| SecurityError::Internal(format!("sessions lock poisoned: {e}")))?;
884 let session = sessions
885 .get_mut(session_id)
886 .ok_or(SecurityError::SessionNotFound)?;
887 session.device_id = Some(device_id);
888 Ok(())
889 }
890
891 fn verify_credentials(
893 &self,
894 auth_method: &AuthMethod,
895 credential: &Credential,
896 ) -> Result<bool, SecurityError> {
897 match (auth_method, credential) {
898 (
899 AuthMethod::PasswordMfa {
900 password_hash,
901 totp_secret,
902 },
903 Credential::PasswordMfa {
904 password,
905 totp_code,
906 },
907 ) => {
908 let parsed_hash = PasswordHash::new(password_hash).map_err(|e| {
910 SecurityError::PasswordHashError {
911 message: e.to_string(),
912 }
913 })?;
914
915 if Argon2::default()
916 .verify_password(password.as_bytes(), &parsed_hash)
917 .is_err()
918 {
919 return Ok(false);
920 }
921
922 if !verify_totp(totp_secret, totp_code)? {
924 return Err(SecurityError::InvalidMfaCode);
925 }
926
927 Ok(true)
928 }
929
930 (
931 AuthMethod::SmartCard {
932 card_id: stored_id,
933 pin_hash,
934 },
935 Credential::SmartCard { card_id, pin },
936 ) => {
937 if stored_id != card_id {
939 return Ok(false);
940 }
941
942 let parsed_hash =
944 PasswordHash::new(pin_hash).map_err(|e| SecurityError::PasswordHashError {
945 message: e.to_string(),
946 })?;
947
948 Ok(Argon2::default()
949 .verify_password(pin.as_bytes(), &parsed_hash)
950 .is_ok())
951 }
952
953 (
954 AuthMethod::Certificate {
955 certificate_fingerprint: stored_fp,
956 },
957 Credential::Certificate { fingerprint },
958 ) => {
959 Ok(stored_fp == fingerprint)
961 }
962
963 _ => Err(SecurityError::UnsupportedAuthMethod {
964 method: "mismatched auth method and credential".to_string(),
965 }),
966 }
967 }
968
969 pub fn unlock_account(&self, username: &str) -> Result<(), SecurityError> {
971 let mut user =
972 self.user_store
973 .get_user(username)
974 .ok_or_else(|| SecurityError::UserNotFound {
975 username: username.to_string(),
976 })?;
977
978 user.status = AccountStatus::Active;
979 user.failed_attempts = 0;
980 self.user_store.update_user(user)
981 }
982
983 pub fn disable_account(&self, username: &str) -> Result<(), SecurityError> {
985 let mut user =
986 self.user_store
987 .get_user(username)
988 .ok_or_else(|| SecurityError::UserNotFound {
989 username: username.to_string(),
990 })?;
991
992 user.status = AccountStatus::Disabled;
993 self.user_store.update_user(user)?;
994
995 self.invalidate_user_sessions(username);
997 Ok(())
998 }
999
1000 pub fn change_password(
1002 &self,
1003 user_name: &str,
1004 old_password: &str,
1005 new_password: &str,
1006 ) -> Result<(), SecurityError> {
1007 let mut user =
1008 self.user_store
1009 .get_user(user_name)
1010 .ok_or_else(|| SecurityError::UserNotFound {
1011 username: user_name.to_string(),
1012 })?;
1013
1014 match &user.auth_method {
1016 AuthMethod::PasswordMfa {
1017 password_hash,
1018 totp_secret,
1019 } => {
1020 let parsed_hash = PasswordHash::new(password_hash).map_err(|e| {
1021 SecurityError::PasswordHashError {
1022 message: e.to_string(),
1023 }
1024 })?;
1025
1026 if Argon2::default()
1027 .verify_password(old_password.as_bytes(), &parsed_hash)
1028 .is_err()
1029 {
1030 return Err(SecurityError::InvalidCredential {
1031 username: user_name.to_string(),
1032 });
1033 }
1034
1035 let salt = SaltString::generate(&mut OsRng);
1037 let argon2 = Argon2::default();
1038 let new_hash = argon2
1039 .hash_password(new_password.as_bytes(), &salt)
1040 .map_err(|e| SecurityError::PasswordHashError {
1041 message: e.to_string(),
1042 })?
1043 .to_string();
1044
1045 user.auth_method = AuthMethod::PasswordMfa {
1046 password_hash: new_hash,
1047 totp_secret: totp_secret.clone(),
1048 };
1049 }
1050 _ => {
1051 return Err(SecurityError::UnsupportedAuthMethod {
1052 method: "non-password".to_string(),
1053 })
1054 }
1055 }
1056
1057 self.user_store.update_user(user)?;
1058
1059 self.invalidate_user_sessions(user_name);
1061 Ok(())
1062 }
1063}
1064
1065#[allow(dead_code)]
1067pub fn generate_totp(secret: &[u8]) -> Result<String, SecurityError> {
1068 let time = SystemTime::now()
1069 .duration_since(UNIX_EPOCH)
1070 .map_err(|_| SecurityError::TotpError {
1071 message: "System time before UNIX epoch".to_string(),
1072 })?
1073 .as_secs();
1074
1075 let counter = time / TOTP_TIME_STEP_SECS;
1076 generate_hotp(secret, counter)
1077}
1078
1079fn generate_hotp(secret: &[u8], counter: u64) -> Result<String, SecurityError> {
1081 let mut mac = Hmac::<Sha256>::new_from_slice(secret).map_err(|e| SecurityError::TotpError {
1082 message: format!("HMAC error: {}", e),
1083 })?;
1084
1085 mac.update(&counter.to_be_bytes());
1086 let result = mac.finalize().into_bytes();
1087
1088 let offset = (result[result.len() - 1] & 0x0f) as usize;
1090 let binary = ((result[offset] & 0x7f) as u32) << 24
1091 | (result[offset + 1] as u32) << 16
1092 | (result[offset + 2] as u32) << 8
1093 | (result[offset + 3] as u32);
1094
1095 let otp = binary % 10u32.pow(TOTP_CODE_LENGTH as u32);
1096 Ok(format!("{:0width$}", otp, width = TOTP_CODE_LENGTH))
1097}
1098
1099pub fn verify_totp(secret: &[u8], code: &str) -> Result<bool, SecurityError> {
1101 let time = SystemTime::now()
1102 .duration_since(UNIX_EPOCH)
1103 .map_err(|_| SecurityError::TotpError {
1104 message: "System time before UNIX epoch".to_string(),
1105 })?
1106 .as_secs();
1107
1108 let counter = (time / TOTP_TIME_STEP_SECS) as i64;
1109
1110 for offset in -TOTP_CLOCK_DRIFT_STEPS..=TOTP_CLOCK_DRIFT_STEPS {
1112 let check_counter = (counter + offset) as u64;
1113 let expected = generate_hotp(secret, check_counter)?;
1114 if constant_time_compare(code.as_bytes(), expected.as_bytes()) {
1115 return Ok(true);
1116 }
1117 }
1118
1119 Ok(false)
1120}
1121
1122fn constant_time_compare(a: &[u8], b: &[u8]) -> bool {
1124 if a.len() != b.len() {
1125 return false;
1126 }
1127
1128 let mut result = 0u8;
1129 for (x, y) in a.iter().zip(b.iter()) {
1130 result |= x ^ y;
1131 }
1132 result == 0
1133}
1134
1135fn base32_encode(data: &[u8]) -> String {
1137 const ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
1138
1139 let mut result = String::new();
1140 let mut buffer: u64 = 0;
1141 let mut bits_left = 0;
1142
1143 for &byte in data {
1144 buffer = (buffer << 8) | (byte as u64);
1145 bits_left += 8;
1146
1147 while bits_left >= 5 {
1148 bits_left -= 5;
1149 let index = ((buffer >> bits_left) & 0x1f) as usize;
1150 result.push(ALPHABET[index] as char);
1151 }
1152 }
1153
1154 if bits_left > 0 {
1155 let index = ((buffer << (5 - bits_left)) & 0x1f) as usize;
1156 result.push(ALPHABET[index] as char);
1157 }
1158
1159 result
1160}
1161
1162#[cfg(test)]
1163mod tests {
1164 use super::*;
1165
1166 #[test]
1167 fn test_military_rank_display() {
1168 assert_eq!(MilitaryRank::Captain.to_string(), "CPT");
1169 assert_eq!(MilitaryRank::Sergeant.to_string(), "SGT");
1170 assert_eq!(MilitaryRank::Colonel.to_string(), "COL");
1171 }
1172
1173 #[test]
1174 fn test_security_clearance_ordering() {
1175 assert!(SecurityClearance::Secret > SecurityClearance::Confidential);
1176 assert!(SecurityClearance::TopSecret > SecurityClearance::Secret);
1177 assert!(SecurityClearance::TopSecretSci > SecurityClearance::TopSecret);
1178 }
1179
1180 #[test]
1181 fn test_organization_unit() {
1182 let unit = OrganizationUnit::new("1st Platoon", "Alpha Company");
1183 assert_eq!(unit.to_string(), "1st Platoon, Alpha Company");
1184
1185 let top = OrganizationUnit::top_level("Battalion HQ");
1186 assert_eq!(top.to_string(), "Battalion HQ");
1187 }
1188
1189 #[test]
1190 fn test_user_identity_builder() {
1191 let identity = UserIdentity::builder("alpha_6")
1192 .display_name("CPT John Smith")
1193 .rank(MilitaryRank::Captain)
1194 .clearance(SecurityClearance::Secret)
1195 .unit(OrganizationUnit::new("1st Plt", "A Co"))
1196 .role(Role::Commander)
1197 .build();
1198
1199 assert_eq!(identity.username, "alpha_6");
1200 assert_eq!(identity.rank, MilitaryRank::Captain);
1201 assert!(identity.roles.contains(&Role::Commander));
1202 }
1203
1204 #[test]
1205 fn test_session_id_generation() {
1206 let id1 = SessionId::new();
1207 let id2 = SessionId::new();
1208 assert_ne!(id1, id2);
1209 }
1210
1211 #[test]
1212 fn test_local_user_store() {
1213 let store = LocalUserStore::new();
1214
1215 let identity = UserIdentity::builder("test_user")
1216 .rank(MilitaryRank::Sergeant)
1217 .build();
1218
1219 let record = UserRecord {
1220 identity,
1221 auth_method: AuthMethod::PasswordMfa {
1222 password_hash: "hash".to_string(),
1223 totp_secret: vec![1, 2, 3],
1224 },
1225 status: AccountStatus::Active,
1226 created_at: SystemTime::now(),
1227 last_login: None,
1228 failed_attempts: 0,
1229 };
1230
1231 store.store_user(record.clone()).unwrap();
1233
1234 let retrieved = store.get_user("test_user").unwrap();
1236 assert_eq!(retrieved.identity.username, "test_user");
1237
1238 let users = store.list_users();
1240 assert_eq!(users.len(), 1);
1241 assert!(users.contains(&"test_user".to_string()));
1242
1243 store.delete_user("test_user").unwrap();
1245 assert!(store.get_user("test_user").is_none());
1246 }
1247
1248 #[test]
1249 fn test_user_store_duplicate_prevention() {
1250 let store = LocalUserStore::new();
1251
1252 let identity = UserIdentity::builder("test_user").build();
1253 let record = UserRecord {
1254 identity: identity.clone(),
1255 auth_method: AuthMethod::PasswordMfa {
1256 password_hash: "hash".to_string(),
1257 totp_secret: vec![1, 2, 3],
1258 },
1259 status: AccountStatus::Active,
1260 created_at: SystemTime::now(),
1261 last_login: None,
1262 failed_attempts: 0,
1263 };
1264
1265 store.store_user(record.clone()).unwrap();
1266
1267 let result = store.store_user(record);
1269 assert!(matches!(
1270 result,
1271 Err(SecurityError::UserAlreadyExists { .. })
1272 ));
1273 }
1274
1275 #[test]
1276 fn test_hotp_generation() {
1277 let secret = b"12345678901234567890";
1279 let code = generate_hotp(secret, 0).unwrap();
1280 assert_eq!(code.len(), 6);
1281 }
1282
1283 #[test]
1284 fn test_totp_generation_and_verification() {
1285 let secret = b"test_secret_key_1234";
1286
1287 let code = generate_totp(secret).unwrap();
1289 assert_eq!(code.len(), 6);
1290
1291 assert!(verify_totp(secret, &code).unwrap());
1293
1294 assert!(!verify_totp(secret, "000000").unwrap());
1296 }
1297
1298 #[test]
1299 fn test_base32_encode() {
1300 assert_eq!(base32_encode(b""), "");
1302 assert_eq!(base32_encode(b"f"), "MY");
1303 assert_eq!(base32_encode(b"fo"), "MZXQ");
1304 assert_eq!(base32_encode(b"foo"), "MZXW6");
1305 assert_eq!(base32_encode(b"foob"), "MZXW6YQ");
1306 assert_eq!(base32_encode(b"fooba"), "MZXW6YTB");
1307 assert_eq!(base32_encode(b"foobar"), "MZXW6YTBOI");
1308 }
1309
1310 #[test]
1311 fn test_constant_time_compare() {
1312 assert!(constant_time_compare(b"abc", b"abc"));
1313 assert!(!constant_time_compare(b"abc", b"abd"));
1314 assert!(!constant_time_compare(b"abc", b"ab"));
1315 }
1316
1317 #[test]
1318 fn test_user_registration_and_password_auth() {
1319 let store = LocalUserStore::new();
1320 let authenticator = UserAuthenticator::new(Box::new(store));
1321
1322 let identity = UserIdentity::builder("test_commander")
1323 .display_name("MAJ Test")
1324 .rank(MilitaryRank::Major)
1325 .clearance(SecurityClearance::Secret)
1326 .role(Role::Commander)
1327 .build();
1328
1329 let totp_secret = authenticator
1331 .register_user("test_commander", "secure_password_123", identity)
1332 .unwrap();
1333
1334 assert!(!totp_secret.is_empty());
1335
1336 let session = authenticator
1338 .authenticate_password_only("test_commander", "secure_password_123")
1339 .unwrap();
1340
1341 assert_eq!(session.identity.username, "test_commander");
1342 assert!(!session.is_expired());
1343 }
1344
1345 #[test]
1346 fn test_session_validation() {
1347 let store = LocalUserStore::new();
1348 let authenticator = UserAuthenticator::new(Box::new(store));
1349
1350 let identity = UserIdentity::builder("session_test")
1351 .role(Role::Observer)
1352 .build();
1353
1354 authenticator
1355 .register_user("session_test", "password123", identity)
1356 .unwrap();
1357
1358 let session = authenticator
1359 .authenticate_password_only("session_test", "password123")
1360 .unwrap();
1361
1362 let validated = authenticator.validate_session(&session.session_id).unwrap();
1364 assert_eq!(validated.username, "session_test");
1365
1366 authenticator.invalidate_session(&session.session_id);
1368
1369 assert!(matches!(
1371 authenticator.validate_session(&session.session_id),
1372 Err(SecurityError::SessionNotFound)
1373 ));
1374 }
1375
1376 #[test]
1377 fn test_account_lockout() {
1378 let store = LocalUserStore::new();
1379 let authenticator = UserAuthenticator::new(Box::new(store)).with_max_failed_attempts(3);
1380
1381 let identity = UserIdentity::builder("lockout_test").build();
1382
1383 authenticator
1384 .register_user("lockout_test", "correct_password", identity)
1385 .unwrap();
1386
1387 for _ in 0..3 {
1389 let _ = authenticator.authenticate_password_only("lockout_test", "wrong_password");
1390 }
1391
1392 let result = authenticator.authenticate_password_only("lockout_test", "correct_password");
1394 assert!(matches!(result, Err(SecurityError::AccountLocked { .. })));
1395
1396 authenticator.unlock_account("lockout_test").unwrap();
1398
1399 let session = authenticator
1401 .authenticate_password_only("lockout_test", "correct_password")
1402 .unwrap();
1403 assert_eq!(session.identity.username, "lockout_test");
1404 }
1405
1406 #[test]
1407 fn test_password_change() {
1408 let store = LocalUserStore::new();
1409 let authenticator = UserAuthenticator::new(Box::new(store));
1410
1411 let identity = UserIdentity::builder("pwd_change_test").build();
1412
1413 authenticator
1414 .register_user("pwd_change_test", "old_password", identity)
1415 .unwrap();
1416
1417 authenticator
1419 .change_password("pwd_change_test", "old_password", "new_password")
1420 .unwrap();
1421
1422 let result = authenticator.authenticate_password_only("pwd_change_test", "old_password");
1424 assert!(result.is_err());
1425
1426 let session = authenticator
1428 .authenticate_password_only("pwd_change_test", "new_password")
1429 .unwrap();
1430 assert_eq!(session.identity.username, "pwd_change_test");
1431 }
1432
1433 #[test]
1434 fn test_session_count_and_cleanup() {
1435 let store = LocalUserStore::new();
1436 let authenticator =
1437 UserAuthenticator::new(Box::new(store)).with_session_expiry(Duration::from_millis(10)); let identity = UserIdentity::builder("cleanup_test").build();
1440
1441 authenticator
1442 .register_user("cleanup_test", "password", identity)
1443 .unwrap();
1444
1445 let _session = authenticator
1447 .authenticate_password_only("cleanup_test", "password")
1448 .unwrap();
1449
1450 assert_eq!(authenticator.active_session_count(), 1);
1451
1452 std::thread::sleep(Duration::from_millis(20));
1454
1455 authenticator.cleanup_expired_sessions();
1457 assert_eq!(authenticator.active_session_count(), 0);
1458 }
1459
1460 #[test]
1461 fn test_disable_account() {
1462 let store = LocalUserStore::new();
1463 let authenticator = UserAuthenticator::new(Box::new(store));
1464
1465 let identity = UserIdentity::builder("disable_test").build();
1466
1467 authenticator
1468 .register_user("disable_test", "password", identity)
1469 .unwrap();
1470
1471 let session = authenticator
1473 .authenticate_password_only("disable_test", "password")
1474 .unwrap();
1475
1476 authenticator.disable_account("disable_test").unwrap();
1478
1479 assert!(authenticator.validate_session(&session.session_id).is_err());
1481
1482 let result = authenticator.authenticate_password_only("disable_test", "password");
1484 assert!(matches!(result, Err(SecurityError::AccountDisabled { .. })));
1485 }
1486
1487 #[test]
1488 fn test_bind_session_to_device() {
1489 let store = LocalUserStore::new();
1490 let authenticator = UserAuthenticator::new(Box::new(store));
1491
1492 let identity = UserIdentity::builder("device_bind_test").build();
1493
1494 authenticator
1495 .register_user("device_bind_test", "password", identity)
1496 .unwrap();
1497
1498 let session = authenticator
1499 .authenticate_password_only("device_bind_test", "password")
1500 .unwrap();
1501
1502 assert!(session.device_id.is_none());
1503
1504 let keypair = crate::security::DeviceKeypair::generate();
1506 let device_id = keypair.device_id();
1507
1508 authenticator
1509 .bind_session_to_device(&session.session_id, device_id)
1510 .unwrap();
1511
1512 let updated_session = authenticator.get_session(&session.session_id).unwrap();
1514 assert!(updated_session.device_id.is_some());
1515 }
1516}