passwors 0.1.1

Passwors is a simple password handling library that utilises Rust's type system to enfore better password handling. Use it as a basic building block for more complex authentication systems.

use super::PasswordHasher;
use argon2rs::{ Argon2, Variant };
use std::cmp::{ PartialEq, Eq, max };
use std::fmt;
use argon2rs;

/// Convenient re-export of `argon2rs::ParamErr`.
pub use argon2rs::ParamErr as Argon2Err;



/// A serialisable builder for Argon2 password hashers.
///
/// Use this to store and load your hasher's configuration.
#[derive(Serialize, Deserialize, PartialEq, Eq, Copy, Clone, Debug)]
pub struct Argon2Settings {
    /// The number of block matrix iterations to perform.
    pub passes: u32,

    /// The degree of parallelism by which memory is filled
    /// during hash computation.
    pub lanes: u32,

    /// The desired total size of the block matrix, in kibibytes.
    pub kib: u32,
}

impl Argon2Settings {
    /// Creates a new builder with default settings.
    pub fn new() -> Argon2Settings { Argon2Settings {
        passes: max(argon2rs::defaults::PASSES, 12),
        lanes:      argon2rs::defaults::LANES,
        kib:        argon2rs::defaults::KIB,
    }}

    /// Set the number of passes.
    pub fn with_passes(&mut self, passes: u32) -> &mut Argon2Settings {
        self.passes = passes;
        self
    }

    /// Set the number of lanes.
    pub fn with_lanes(&mut self, lanes: u32) -> &mut Argon2Settings {
        self.lanes = lanes;
        self
    }

    /// Set the number of KiB.
    pub fn with_kib(&mut self, kib: u32) -> &mut Argon2Settings {
        self.kib = kib;
        self
    }

    /// Tries to create the hasher based on the
    /// given settings.
    pub fn try_create(self) -> Result<Argon2PasswordHasher, Argon2Err> {
        Argon2PasswordHasher::new(self.passes, self.lanes, self.kib)
    }
}

impl Default for Argon2Settings {
    fn default() -> Self { Argon2Settings::new() }
}



/// A wrapper around `Argon2` from `argon2rs` that
/// ensures that the variant `Argon2i` is chosen.
pub struct Argon2PasswordHasher {
    argon2i: Argon2,
}

impl Argon2PasswordHasher {
    /// Creates a new password hasher with custom time
    /// and memory cost parameters.
    ///
    /// - `passes` is the number of block matrix iterations to perform.
    ///   Increasing this forces hashing to take longer. **Must not be zero**.
    ///   This number should also be quite bigger than 10.
    ///
    ///   > However, Joël Alwen and Jeremiah Blocki improved the attack and
    ///   > showed that in order for the attack to fail, Argon2i 1.3 needs
    ///   > more than 10 passes over memory.
    ///
    ///   [Quoted from Wikipedia *(2016-08-29)*](https://en.wikipedia.org/wiki/Argon2)
    ///
    ///   [Referred paper *(2016-08-29)*](https://eprint.iacr.org/2016/759.pdf)
    /// - `lanes` configures the degree of parallelism by which memory is filled
    ///   during hash computation. Setting this to `N` instructs Argon2 to
    ///   partition the block matrix into `N` lanes, simultaneously filling each
    ///   lane in parallel with `N` threads. **Must not be zero**.
    /// - `kib` is the desired total size of the block matrix, in kibibytes
    ///   *(1 KiB = 1024 bytes)*. Increasing this forces hashing to use more
    ///   memory in order to thwart ASIC-based attacks. **Must be `>= 8 * lanes`**.
    ///
    /// Read [this document](https://tools.ietf.org/id/draft-josefsson-argon2-00.html#rfc.section.4)
    /// if you want to know how to choose good values for those parameters.
    ///
    /// # Errors
    ///
    /// This function will fail if your inputs violate the rules given above.
    /// Additionally, this function will fail unless `passes` is bigger than 10
    /// due to the previously quoted reasons.
    pub fn new(passes: u32, lanes: u32, kib: u32) -> Result<Argon2PasswordHasher, Argon2Err> {
        // Enforce the Wikipedia quote here.
        if passes <= 10 { return Err(Argon2Err::TooFewPasses); }

        Ok(Argon2PasswordHasher {
            argon2i: Argon2::new(passes, lanes, kib, Variant::Argon2i)?
        })
    }

    /// Provides read-only access to `passes`, `lanes`, `kib`
    /// in that order.
    pub fn params(&self) -> (u32, u32, u32) {
        let (_, kib, passes, lanes) = self.argon2i.params();
        (passes, lanes, kib)
    }

    /// Extracts the hasher's settings.
    pub fn settings(&self) -> Argon2Settings {
        let (_, kib, passes, lanes) = self.argon2i.params();
        Argon2Settings { passes: passes, lanes: lanes, kib: kib }
    }
}

impl Default for Argon2PasswordHasher {
    /// Creates a new password hasher with default time
    /// and memory cost parameters.
    ///
    /// While this might be convenient for testing
    /// purposes, using `new` instead is highly recommended.
    /// In addition to using `new` being more scalable in
    /// the future, you don't know how good the chosen
    /// default parameters really are.
    ///
    /// **In fact, as of `argon2rs-0.2.5`, the default value
    /// for `passes` is 3.**
    fn default() -> Self {
        Argon2PasswordHasher { argon2i: Argon2::default(Variant::Argon2i) }
    }
}

impl PasswordHasher for Argon2PasswordHasher {
    unsafe fn hash(&self, hashed_password: &mut [u8], clear_text_password: &[u8], hash_salt: &[u8]) {
        self.argon2i.hash(hashed_password, clear_text_password, hash_salt, &[], &[]);
    }
}

impl PartialEq for Argon2PasswordHasher {
    /// Checks whether both hasher's configurations are equal.
    fn eq(&self, other: &Argon2PasswordHasher) -> bool {
        self.params() == other.params()
    }
}

impl Eq for Argon2PasswordHasher {}

impl fmt::Debug for Argon2PasswordHasher {
    /// Displays the hasher's configuration.
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let (passes, lanes, kib) = self.params();
        write!(f,
            "Argon2PasswordHasher {{ passes: {}, lanes: {}, kib: {} }}",
            passes, lanes, kib
        )
    }
}



#[cfg(test)]
mod test {
    use super::{ Argon2PasswordHasher, Argon2Err, Argon2Settings };
    use serde_test::{ Token, assert_tokens };

    #[test]
    fn require_many_passes() {
        assert_eq!(Err(Argon2Err::TooFewPasses), Argon2PasswordHasher::new(5, 8, 8*8));
    }

    #[test]
    fn debug_does_its_thing() {
        assert_eq!(
            "Argon2PasswordHasher { passes: 13, lanes: 8, kib: 64 }",
            format!("{:?}", Argon2PasswordHasher::new(13, 8, 8*8).unwrap())
        );
    }

    #[test]
    fn eq_does_its_thing() {
        assert!(Argon2PasswordHasher::default()    == Argon2PasswordHasher::default());
        assert!(Argon2PasswordHasher::new(13,8,64) != Argon2PasswordHasher::new(14,8,64));
    }

    #[test]
    fn params_does_its_thing() {
        assert_eq!(
            (13_u32, 8_u32, 64_u32),
            Argon2PasswordHasher::new(13, 8, 64).unwrap().params()
        );
    }

    #[test]
    fn serde_works() {
        let builder = Argon2Settings { passes: 3, lanes: 4, kib: 7 };

        let mut tokens: Vec<Token> = Vec::with_capacity(11);
        tokens.push(Token::StructStart("Argon2Settings", 3));
        tokens.push(Token::StructSep);
        tokens.push(Token::Str("passes"));
        tokens.push(Token::U32(3));
        tokens.push(Token::StructSep);
        tokens.push(Token::Str("lanes"));
        tokens.push(Token::U32(4));
        tokens.push(Token::StructSep);
        tokens.push(Token::Str("kib"));
        tokens.push(Token::U32(7));
        tokens.push(Token::StructEnd);

        assert_tokens(&builder, &tokens[..]);
    }
}