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.

#[cfg(any(test, feature = "use_serde", feature = "default"))] use serde;

use std::cmp::{ PartialEq, Eq };
use std::fmt;
use std::io;



/// The length of a hashed password in bytes.
///
/// Should be at least 16 for good hashes. Doesn't
/// need to be a huge number.
pub const HASHED_PASSWORD_LEN: usize = 64;



/// A hashed password.
///
/// New password hashes can only be created by using
/// a password hasher and a hash salt on clear text
/// passwords. This is what you store in your databases,
/// compare to other salts, and what not.
///
/// If your hash salt and especially your password hasher
/// are of good quality, an attacker can't easily guess
/// the corresponding clear text password by looking at
/// the password hash and its salt only.
pub struct HashedPassword {
    data: [u8; HASHED_PASSWORD_LEN],
}

impl HashedPassword {
    /// Reads a password hash from a reader.
    ///
    /// Only use this function to load serialised hashes
    /// that have been created using `ClearTextPassword`s.
    ///
    /// # Errors
    ///
    /// This function fails if the reader has not enough
    /// bytes to completely fill the password hash.
    pub fn from_reader<R: io::Read>(r: &mut R) -> io::Result<HashedPassword> {
        let mut hash = HashedPassword { data: [0_u8; HASHED_PASSWORD_LEN] };
        r.read_exact(hash.as_slice_mut())?;
        Ok(hash)
    }

    /// Get a slice of the password hash's bytes.
    pub fn as_slice(&self) -> &[u8] {
        &self.data[..]
    }



    // Only allow `ClearTextPassword` to create new hashes.
    pub(super) fn new() -> HashedPassword {
        HashedPassword { data: [0_u8; HASHED_PASSWORD_LEN] }
    }

    // Only allow `ClearTextPassword` to fill this hash buffer with data.
    pub(super) fn as_slice_mut(&mut self) -> &mut [u8] {
        &mut self.data[..]
    }

    fn copy_from_slice(&mut self, other: &[u8]) {
        self.as_slice_mut().copy_from_slice(other);
    }

    fn fmt_impl(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "HashedPassword[ ")?;
        for x in &self.data[..] { write!(f, "{:02X}", *x)?; }
        write!(f, " ]")
    }
}



// See `HashSalt` for an explanation.
#[cfg(any(test, feature = "use_serde", feature = "default"))]
impl serde::Serialize for HashedPassword {
    fn serialize<S: serde::Serializer>(&self, ser: &mut S) -> Result<(), S::Error> {
        ser.serialize_newtype_struct("HashedPassword", serde::bytes::Bytes::from(self.as_slice()))
    }
}

#[cfg(any(test, feature = "use_serde", feature = "default"))]
impl serde::Deserialize for HashedPassword {
    fn deserialize<D: serde::Deserializer>(de: &mut D) -> Result<Self, D::Error> {
        de.deserialize_newtype_struct("HashedPassword", HashedPasswordSerdeVisitor {})
    }
}

#[cfg(any(test, feature = "use_serde", feature = "default"))]
struct HashedPasswordSerdeVisitor {}

#[cfg(any(test, feature = "use_serde", feature = "default"))]
impl serde::de::Visitor for HashedPasswordSerdeVisitor {
    type Value = HashedPassword;

    fn visit_newtype_struct<D: serde::Deserializer>(&mut self, de: &mut D)
    -> Result<HashedPassword, D::Error> {
        use serde::{ Deserialize, Error };

        let bytes = serde::bytes::ByteBuf::deserialize(de)?;
        if bytes.len() != HASHED_PASSWORD_LEN { return Err(D::Error::invalid_length(bytes.len())); }
        let mut hash = HashedPassword { data: [0_u8; HASHED_PASSWORD_LEN] };
        hash.copy_from_slice(&bytes[..]);

        Ok(hash)
    }

    fn visit_bytes<E: serde::Error>(&mut self, v: &[u8]) -> Result<HashedPassword, E> {
        if v.len() == HASHED_PASSWORD_LEN {
            let mut hash = HashedPassword { data: [0_u8; HASHED_PASSWORD_LEN] };
            hash.copy_from_slice(v);
            Ok(hash)
        }
        else { Err(E::invalid_length(v.len())) }
    }

    fn visit_seq<V: serde::de::SeqVisitor>(&mut self, mut visitor: V)
    -> Result<HashedPassword, V::Error> {
        use serde::Error;

        let bytes = match visitor.visit::<serde::bytes::ByteBuf>()? {
            Some(bytes) => { visitor.end()?; bytes },
            None        => { visitor.end()?; return Err(V::Error::invalid_length(0)); },
        };
        if bytes.len() != HASHED_PASSWORD_LEN { return Err(V::Error::invalid_length(bytes.len())); }
        let mut hash = HashedPassword { data: [0_u8; HASHED_PASSWORD_LEN] };
        hash.copy_from_slice(&bytes[..]);

        Ok(hash)
    }
}



// See `HashSalt` for an explanation.
impl PartialEq for HashedPassword {
    fn eq(&self, other: &Self) -> bool { self.as_slice() == other.as_slice() }
}

impl Eq for HashedPassword {}



impl fmt::Debug for HashedPassword {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt_impl(f) }
}

impl fmt::Display for HashedPassword {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt_impl(f) }
}



#[cfg(test)]
mod test {
    use super::{ HashedPassword, HASHED_PASSWORD_LEN };
    use serde_test::{ Token, assert_tokens };
    use std::io;

    #[test]
    fn max_len_is_reasonably_large() {
        assert!(HASHED_PASSWORD_LEN >= 16, "Are you sure you want less than 128 bits?");
    }

    #[test]
    fn eq_does_its_thing() {
        assert!(
            HashedPassword { data: [42_u8; HASHED_PASSWORD_LEN] }
         == HashedPassword { data: [42_u8; HASHED_PASSWORD_LEN] }
        );

        assert!(
            HashedPassword { data: [3_u8; HASHED_PASSWORD_LEN] }
         != HashedPassword { data: [4_u8; HASHED_PASSWORD_LEN] }
        );
    }

    #[test]
    fn as_slice_does_its_thing() {
        assert_eq!(
            HashedPassword { data: [7_u8; HASHED_PASSWORD_LEN] }.as_slice(),
            &[7_u8; HASHED_PASSWORD_LEN][..]
        );
    }

    #[test]
    fn serde_works() {
        let hash = HashedPassword { data: [42_u8; HASHED_PASSWORD_LEN] };

        // `serde_test` is very stupid, as in it doesn't
        // know about bytes. Just handle them like ordinary
        // integer arrays, 'cause who cares.
        let mut tokens: Vec<Token> = Vec::with_capacity(3 + (2 * hash.as_slice().len()));
        tokens.push(Token::StructNewType("HashedPassword"));
        tokens.push(Token::SeqStart(Some(HASHED_PASSWORD_LEN)));
        for x in hash.as_slice() {
            tokens.push(Token::SeqSep);
            tokens.push(Token::U8(*x));
        }
        tokens.push(Token::SeqEnd);

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

    #[test]
    fn from_reader_works() {
        const ARR1: &'static [u8] = &[42_u8; HASHED_PASSWORD_LEN];
        const ARR2: &'static [u8] = &[ 1_u8; 1];

        let mut data1 = io::Cursor::new(ARR1);
        let mut data2 = io::Cursor::new(ARR2); // Way too short.

        assert_eq!(
            HashedPassword::from_reader(&mut data1).unwrap(),
            HashedPassword { data: [42_u8; HASHED_PASSWORD_LEN] }
        );

        assert_eq!(
            HashedPassword::from_reader(&mut data2).unwrap_err().kind(),
            io::ErrorKind::UnexpectedEof
        );
    }
}