user.rs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. use crate::{schema, UIDCError};
  2. use microrm::prelude::*;
  3. use microrm::schema::Stored;
  4. #[derive(Debug)]
  5. pub enum UserError {
  6. NoSuchUser,
  7. NoSuchChallenge,
  8. ChallengeInvalid,
  9. InvalidInput,
  10. }
  11. static PBKDF2_ROUNDS: std::num::NonZeroU32 = unsafe { std::num::NonZeroU32::new_unchecked(20000) };
  12. fn generate_totp_digits(secret: &[u8], time_offset: isize) -> Result<u32, UIDCError> {
  13. use hmac::Mac;
  14. let mut mac = hmac::Hmac::<sha1::Sha1>::new_from_slice(secret)
  15. .map_err(|_| UserError::ChallengeInvalid)?;
  16. let timestamp = std::time::SystemTime::now()
  17. .duration_since(std::time::SystemTime::UNIX_EPOCH)
  18. .unwrap()
  19. .as_secs();
  20. let interval = 30;
  21. let time_index = (timestamp / interval) as isize + time_offset;
  22. mac.update(u64::to_be_bytes(time_index as u64).as_slice());
  23. let hmac = mac.finalize().into_bytes();
  24. // this is from RFC4226
  25. let offset = (hmac[19] & 0xf) as usize;
  26. let truncation = u32::from_be_bytes(hmac[offset..offset + 4].try_into().unwrap()) & 0x7fff_ffff;
  27. Ok(truncation % 1_000_000)
  28. }
  29. pub trait UserExt {
  30. fn stored_user(&self) -> &Stored<schema::User>;
  31. /*fn change_username(&mut self, new_name: &String) -> Result<(), UIDCError> {
  32. let realm = self.stored_user().realm;
  33. // check to ensure the new username isn't already in use
  34. if self.realm.users.with(schema::User::Username, new_name).first().get()?.is_some() {
  35. Err(UIDCError::Abort("username already in use"))
  36. } else {
  37. self.user.username = new_name.clone();
  38. self.user.sync()?;
  39. Ok(())
  40. }
  41. }*/
  42. /// returns Ok(true) if challenge passed, Ok(false) if challenge failed, and
  43. /// UserError::NoSuchChallenge if challenge not found
  44. fn verify_challenge_by_type(
  45. &self,
  46. challenge_type: schema::AuthChallengeType,
  47. response: &[u8],
  48. ) -> Result<bool, UIDCError> {
  49. let ct = challenge_type.into();
  50. let challenge = self
  51. .stored_user()
  52. .auth
  53. .with(schema::AuthChallenge::ChallengeType, &ct)
  54. .first()
  55. .get()?
  56. .ok_or(UserError::NoSuchChallenge)?;
  57. match challenge_type {
  58. schema::AuthChallengeType::Password => challenge.verify_password_challenge(response),
  59. schema::AuthChallengeType::TOTP => challenge.verify_totp_challenge(response),
  60. _ => todo!(),
  61. }
  62. }
  63. fn set_new_password(&self, password: &[u8]) -> Result<(), UIDCError> {
  64. self.stored_user()
  65. .auth
  66. .with(
  67. schema::AuthChallenge::ChallengeType,
  68. &schema::AuthChallengeType::Password.into(),
  69. )
  70. .delete()?;
  71. let rng = ring::rand::SystemRandom::new();
  72. let salt: [u8; 16] = ring::rand::generate(&rng)
  73. .expect("Couldn't generate random salt?")
  74. .expose();
  75. let mut generated = [0u8; ring::digest::SHA256_OUTPUT_LEN];
  76. ring::pbkdf2::derive(
  77. ring::pbkdf2::PBKDF2_HMAC_SHA256,
  78. PBKDF2_ROUNDS,
  79. &salt,
  80. password,
  81. &mut generated,
  82. );
  83. self.stored_user().auth.insert(schema::AuthChallenge {
  84. user_id: self.stored_user().id(),
  85. challenge_type: schema::AuthChallengeType::Password.into(),
  86. public: salt.into(),
  87. secret: generated.into(),
  88. enabled: true,
  89. })?;
  90. Ok(())
  91. }
  92. fn generate_totp_with_uri(&self) -> Result<(Vec<u8>, String), UIDCError> {
  93. let rng = ring::rand::SystemRandom::new();
  94. let secret: [u8; 16] = ring::rand::generate(&rng)
  95. .expect("Couldn't generate random secret?")
  96. .expose();
  97. let uri_secret = base32::encode(
  98. base32::Alphabet::RFC4648 { padding: false },
  99. secret.as_slice(),
  100. );
  101. let uri = format!(
  102. "otpauth://totp/uidc:{username}@uidc?secret={uri_secret}&issuer=uidc",
  103. username = self.stored_user().username
  104. );
  105. Ok((secret.into(), uri))
  106. }
  107. fn set_new_totp(&self, secret: &[u8]) -> Result<(), UIDCError> {
  108. self.clear_totp()?;
  109. self.stored_user().auth.insert(schema::AuthChallenge {
  110. user_id: self.stored_user().id(),
  111. challenge_type: schema::AuthChallengeType::TOTP.into(),
  112. public: vec![],
  113. secret: secret.into(),
  114. enabled: true,
  115. })?;
  116. Ok(())
  117. }
  118. fn clear_totp(&self) -> Result<(), UIDCError> {
  119. self.stored_user()
  120. .auth
  121. .with(
  122. schema::AuthChallenge::ChallengeType,
  123. &schema::AuthChallengeType::TOTP.into(),
  124. )
  125. .delete()?;
  126. Ok(())
  127. }
  128. }
  129. impl UserExt for Stored<schema::User> {
  130. fn stored_user(&self) -> &Stored<schema::User> {
  131. self
  132. }
  133. }
  134. impl schema::AuthChallenge {
  135. pub fn verify_password_challenge(&self, response: &[u8]) -> Result<bool, UIDCError> {
  136. use ring::pbkdf2;
  137. if *self.challenge_type.as_ref() != schema::AuthChallengeType::Password {
  138. return Err(UIDCError::Abort(
  139. "verifying password challenge on non-password challenge",
  140. ));
  141. }
  142. Ok(pbkdf2::verify(
  143. pbkdf2::PBKDF2_HMAC_SHA256,
  144. PBKDF2_ROUNDS,
  145. self.public.as_slice(),
  146. response,
  147. self.secret.as_slice(),
  148. )
  149. .is_ok())
  150. }
  151. pub fn verify_totp_challenge(&self, response: &[u8]) -> Result<bool, UIDCError> {
  152. let response_digits = std::str::from_utf8(response)
  153. .map_err(|_| UserError::InvalidInput)?
  154. .parse::<u32>()
  155. .map_err(|_| UserError::InvalidInput)?;
  156. if *self.challenge_type.as_ref() != schema::AuthChallengeType::TOTP {
  157. return Err(UIDCError::Abort(
  158. "verifying TOTP challenge on non-TOTP challenge",
  159. ));
  160. }
  161. // allow for some clock skew
  162. for time_offset in -2..3 {
  163. if generate_totp_digits(self.secret.as_slice(), time_offset)? == response_digits {
  164. return Ok(true);
  165. }
  166. }
  167. Ok(false)
  168. }
  169. }