diff options
| author | Owen Jacobson <owen@grimoire.ca> | 2024-09-28 01:40:22 -0400 |
|---|---|---|
| committer | Owen Jacobson <owen@grimoire.ca> | 2024-09-28 20:48:40 -0400 |
| commit | 4d0bb0709b168a24ab6a8dbc86da45d7503596ee (patch) | |
| tree | 031f2e35f07cef7305809e3a1d310bf304d15460 /src/password.rs | |
| parent | 72efedf8e96ca6e159ce6146809ee6d3a9e5a0e7 (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.rs | 47 |
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) + } +} |
