summaryrefslogtreecommitdiff
path: root/src/password.rs
diff options
context:
space:
mode:
authorOwen Jacobson <owen@grimoire.ca>2024-09-28 01:40:22 -0400
committerOwen Jacobson <owen@grimoire.ca>2024-09-28 20:48:40 -0400
commit4d0bb0709b168a24ab6a8dbc86da45d7503596ee (patch)
tree031f2e35f07cef7305809e3a1d310bf304d15460 /src/password.rs
parent72efedf8e96ca6e159ce6146809ee6d3a9e5a0e7 (diff)
Wrap credential and credential-holding types to prevent `Debug` leaks.
The following values are considered confidential, and should never be logged, even by accident: * `Password`, which is a durable bearer token for a specific Login; * `IdentitySecret`, which is an ephemeral but potentially long-lived bearer token for a specific Login; or * `IdentityToken`, which may hold cookies containing an `IdentitySecret`. These values are now wrapped in types whose `Debug` impls output opaque values, so that they can be included in structs that `#[derive(Debug)]` without requiring any additional care. The wrappers also avoid implementing `Display`, to prevent inadvertent `to_string()`s. We don't bother obfuscating `IdentitySecret`s in memory or in the `.hi` database. There's no point: we'd also need to store the information needed to de-obfuscate them, and they can be freely invalidated and replaced by blanking that table and asking everyone to log in again. Passwords _are_ obfuscated for storage, as they're intended to be durable.
Diffstat (limited to 'src/password.rs')
-rw-r--r--src/password.rs47
1 files changed, 37 insertions, 10 deletions
diff --git a/src/password.rs b/src/password.rs
index b14f728..da3930f 100644
--- a/src/password.rs
+++ b/src/password.rs
@@ -1,3 +1,5 @@
+use std::fmt;
+
use argon2::Argon2;
use password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString};
use rand_core::OsRng;
@@ -7,16 +9,7 @@ use rand_core::OsRng;
pub struct StoredHash(String);
impl StoredHash {
- pub fn new(password: &str) -> Result<Self, password_hash::Error> {
- let salt = SaltString::generate(&mut OsRng);
- let argon2 = Argon2::default();
- let hash = argon2
- .hash_password(password.as_bytes(), &salt)?
- .to_string();
- Ok(Self(hash))
- }
-
- pub fn verify(&self, password: &str) -> Result<bool, password_hash::Error> {
+ pub fn verify(&self, password: &Password) -> Result<bool, password_hash::Error> {
let hash = PasswordHash::new(&self.0)?;
match Argon2::default().verify_password(password.as_bytes(), &hash) {
@@ -29,3 +22,37 @@ impl StoredHash {
}
}
}
+
+#[derive(serde::Deserialize)]
+#[serde(transparent)]
+pub struct Password(String);
+
+impl Password {
+ pub fn hash(&self) -> Result<StoredHash, password_hash::Error> {
+ let Self(password) = self;
+ let salt = SaltString::generate(&mut OsRng);
+ let argon2 = Argon2::default();
+ let hash = argon2
+ .hash_password(password.as_bytes(), &salt)?
+ .to_string();
+ Ok(StoredHash(hash))
+ }
+
+ fn as_bytes(&self) -> &[u8] {
+ let Self(value) = self;
+ value.as_bytes()
+ }
+}
+
+impl fmt::Debug for Password {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_tuple("Password").field(&"********").finish()
+ }
+}
+
+#[cfg(test)]
+impl From<String> for Password {
+ fn from(password: String) -> Self {
+ Self(password)
+ }
+}