lich 0.1.0

Minimal password management.
Documentation
use error::Error;
use list::List;
use ring::pbkdf2;
use ring::rand::{SecureRandom, SystemRandom};
use std::collections::HashMap;
use vault::{Vault, NONCE_LEN};

static PRF: &'static pbkdf2::PRF = &pbkdf2::HMAC_SHA256;

const ITERATIONS: u32 = 100000;
const SALT_LEN: usize = 16;
const KEY_LEN: usize = 32;
const DERIVED_KEY_LEN: usize = 32;

#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
pub struct Data {
    // Salt for the password in order to prevent construction of rainbow tables.
    pub salt: [u8; SALT_LEN],
    // Encryption key encrypted with the key derived from the password.
    // Authenticated, so that the password itself can be omitted.
    pub key: Vault,
    // Passwords, keyed by name, encrypted with the key.
    pub entries: HashMap<String, Vault>,
}

pub struct Unlocked<'a> {
    key: [u8; KEY_LEN],
    data: &'a mut Data,
}

impl Data {
    pub fn new(pwd: String) -> Result<Data, Error> {
        let mut salt = [0; SALT_LEN];
        let mut key = vec![0; KEY_LEN];
        randomize(&mut salt)?;
        randomize(&mut key[..])?;

        Ok(Data {
            salt: salt,
            key: Vault::new(
                &derive_key(&pwd, &salt),
                random_nonce()?,
                key
            )?,
            entries: HashMap::new(),
        })
    }

    pub fn change_password(
        &mut self,
        old: &str,
        new: &str
    ) -> Result<(), Error> {
        let mut salt = [0; SALT_LEN];
        let mut nonce = [0; NONCE_LEN];
        randomize(&mut salt)?;
        randomize(&mut nonce)?;

        let old_pbk = derive_key(old, &self.salt);
        let new_pbk = derive_key(new, &salt);

        let mut unlocked = self.key.unlock(&old_pbk)?;
        unlocked.rekey(&new_pbk, nonce);
        self.salt = salt;

        Ok(())
    }

    pub fn unlock<'a>(
        &'a mut self,
        pwd: &str
    ) -> Result<Unlocked<'a>, Error> {        
        let key = {
            let pbk = derive_key(pwd, &self.salt);
            let unlocked = self.key.unlock(&pbk)?;

            if unlocked.len() != KEY_LEN {
                return Err(Error::BadEncryptionKey);
            }

            let mut key = [0; KEY_LEN];
            key.copy_from_slice(&unlocked);
            key
        };

        Ok(Unlocked {
            key: key,
            data: self,
        })
    }

    pub fn list(&self) -> List {
        List(self.entries.keys())
    }

    pub fn delete(&mut self, key: &str) -> bool {
        self.entries.remove(key).is_some()
    }
}

impl<'a> Unlocked<'a> {
    pub fn get(
        &mut self,
        name: &str
    ) -> Result<Option<String>, Error> {
        let bytes = match self.data.entries.get_mut(name) {
            Some(vault) => vault.unlock(&self.key)?.to_vec(),
            None => return Ok(None),
        };

        match String::from_utf8(bytes) {
            Ok(k) => Ok(Some(k)),
            Err(_) => Err(Error::DecodeString),
        }
    }

    pub fn set(
        &mut self,
        name: String,
        pwd: String
    ) -> Result<bool, Error> {
        let vault = Vault::new(
            &self.key,
            random_nonce()?,
            pwd.into_bytes()
        )?;

        Ok(self.data.entries.insert(name, vault).is_some())
    }

    pub fn delete(
        &mut self,
        name: &str
    ) -> bool {
        self.data.entries.remove(name).is_some()
    }
}

fn derive_key(pwd: &str, salt: &[u8]) -> [u8; DERIVED_KEY_LEN] {
    let mut pbk = [0; DERIVED_KEY_LEN];
    pbkdf2::derive(
        PRF,
        ITERATIONS,
        salt,
        pwd.as_bytes(),
        &mut pbk
    );
    pbk
}

fn random_nonce() -> Result<[u8; NONCE_LEN], Error> {
    let mut nonce = [0; NONCE_LEN];
    randomize(&mut nonce)?;
    Ok(nonce)
}

fn randomize(buf: &mut [u8]) -> Result<(), Error> {
    match SystemRandom.fill(buf) {
        Ok(_) => Ok(()),
        Err(_) => Err(Error::Randomize),
    }
}