keybase-keystore 0.2.0

Keybase keystore implementation.
Documentation
use crate::error::Error;
use crate::file::{AuthSecretFile, NoiseFile, SecretFile};
use crate::types::{
    DeviceKey, EncryptedDeviceKey, EncryptedRandomKey, Mask, Password, PublicDeviceKey, RandomKey,
};
use async_std::path::{Path, PathBuf};
use fail::fail_point;

pub struct Generation {
    gen: u16,
    path: PathBuf,
    edk: AuthSecretFile,
    erk: SecretFile,
    noise: NoiseFile,
    pdk: SecretFile,
}

impl Generation {
    /// Creates a generation.
    pub fn new(path: &Path, gen: u16) -> Self {
        let path = path.join(gen.to_string());
        Self {
            gen,
            edk: AuthSecretFile::new(path.join("encrypted_device_key")),
            erk: SecretFile::new(path.join("encrypted_random_key")),
            noise: NoiseFile::new(path.join("noise")),
            pdk: SecretFile::new(path.join("public_device_key")),
            path,
        }
    }

    /// Returns the generation number.
    pub fn gen(&self) -> u16 {
        self.gen
    }

    /// Checks if the keystore is initialized.
    pub async fn is_initialized(&self) -> bool {
        self.edk.exists().await
    }

    /// Initializes the keystore.
    pub async fn initialize(
        &self,
        dk: &DeviceKey,
        pass: &Password,
        force: bool,
    ) -> Result<(), Error> {
        if !force && self.is_initialized().await {
            return Err(Error::Initialized);
        }

        let path = self.edk.parent().expect("joined a file name on init; qed");
        async_std::fs::create_dir_all(path).await?;

        let rk = RandomKey::generate().await;

        let edk = dk.encrypt(&rk).await;

        let pdk = rk.public(&pass);
        self.pdk.write(&pdk.0).await?;

        // Unlock
        // So we can delay writing the private key we unlock manually
        self.noise.generate().await?;
        let nk = self.noise.read_secret().await?;

        let erk = rk.encrypt(&nk);
        self.erk.write(&erk.0).await?;
        // End unlock

        // Write private key at the end.
        fail_point!("edk-write-fail", |_| Err(Error::Corrupted));
        self.edk.write(&edk.0).await?;

        // Make sure keystore is in a valid state.
        self.device_key().await?;

        Ok(())
    }

    /// Unlocking the keystore makes the random key decryptable.
    pub async fn unlock(&self, pass: &Password) -> Result<DeviceKey, Error> {
        let pdk = self.public().await?;
        let rk = pdk.private(pass);

        self.noise.generate().await?;
        let nk = self.noise.read_secret().await?;

        let erk = rk.encrypt(&nk);
        self.erk.write(&erk.0).await?;

        self.device_key().await
    }

    /// Locks the keystore by zeroizing the noise file. This makes the encrypted
    /// random key undecryptable without a password.
    pub async fn lock(&self) -> Result<(), Error> {
        self.noise.zeroize().await?;
        Ok(())
    }

    async fn random_key(&self) -> Result<RandomKey, Error> {
        let nk = self.noise.read_secret().await?;
        let erk = EncryptedRandomKey(self.erk.read().await?);
        Ok(erk.decrypt(&nk))
    }

    /// The random key is used to decrypt the device key.
    ///
    /// NOTE: Only works if the keystore was unlocked.
    pub async fn device_key(&self) -> Result<DeviceKey, Error> {
        let rk = self.random_key().await?;
        let edk = EncryptedDeviceKey(self.edk.read().await?);
        let dk = edk.decrypt(&rk)?;
        Ok(dk)
    }

    /// The random key is used to recover the password.
    ///
    /// NOTE: Only works if the keystore was unlocked.
    pub async fn password(&self) -> Result<Password, Error> {
        let rk = self.random_key().await?;
        let pdk = self.public().await?;
        Ok(rk.password(&pdk))
    }

    /// Returns the public device key.
    pub async fn public(&self) -> Result<PublicDeviceKey, Error> {
        Ok(PublicDeviceKey(self.pdk.read().await?))
    }

    /// Change password.
    pub async fn change_password_mask(&self, password: &Password) -> Result<Mask, Error> {
        let old_password = self.password().await?;
        let mask = old_password.mask(password);
        Ok(mask)
    }

    /// Removes a generation.
    pub async fn remove(self) -> Result<(), Error> {
        fail_point!("gen-rm-fail", |_| Ok(()));
        Ok(async_std::fs::remove_dir_all(&self.path).await?)
    }
}