use serde::de::Visitor; use serde::{Deserialize, Serialize}; use sodiumoxide::crypto::pwhash::argon2id13::{self, HashedPassword}; use sodiumoxide::utils::memzero; #[derive(Serialize, PartialEq, Eq)] pub struct Password(String); impl Password { pub fn hashed(&self) -> Result { argon2id13::pwhash( self.0.as_bytes(), argon2id13::OPSLIMIT_INTERACTIVE, argon2id13::MEMLIMIT_INTERACTIVE, ) } pub fn verify(&self, password: &HashedPassword) -> bool { argon2id13::pwhash_verify(password, self.0.as_bytes()) } } /// A custom drop implementation is necessary for this type as we need to ensure /// that the password is not stored in memory for an extended period of time. impl Drop for Password { fn drop(&mut self) { // SAFETY: self.0 is never accessed after calling drop. memzero(unsafe { self.0.as_bytes_mut() }) } } impl<'de> Deserialize<'de> for Password { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { use serde::de::{Error, Unexpected}; struct SecretDeserializer; impl<'de> Visitor<'de> for SecretDeserializer { type Value = Password; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("a password between 8 and 64 bytes") } fn visit_str(self, v: &str) -> Result { if v.len() < 8 || v.len() > 64 { println!("password failed"); return Err(Error::invalid_value( Unexpected::Str("password with invalid size"), &"a password between 8 and 64 bytes", )); } Ok(Password(v.to_owned())) } } deserializer.deserialize_string(SecretDeserializer) } }