tpfs_krypt 7.1.8

An interface for accessing secrets
Documentation
//! The File Key Manager Module which should contain the config and struct for
//! working with a file based key manager.
//!
//! # Examples
//!
//! Creating a KeyManager from config, import a key then sign a message with it.
//!
//!```
//! use secrecy::Secret;
//! use sp_core::{
//!     crypto::{Pair, Ss58Codec, DEV_PHRASE},
//!     sr25519,
//! };
//! use std::fs;
//! use std::path::PathBuf;
//! use tpfs_krypt::{
//!     config::{KeyManagerConfig, KryptConfig},
//!     errors::KeyManagementError,
//!     from_config, KeyIdentifier, FileKeyManagerConfig, KeyManagement, KeyType,
//! };
//!
//! fn create_key_manager() -> Result<Box<dyn KeyManagement>, KeyManagementError> {
//!     let path = PathBuf::from("/tmp/krypt/keypairs/");
//!     if !path.exists() {
//!         fs::create_dir_all(&path).unwrap();
//!     }
//!
//!     let config = KryptConfig {
//!         key_manager_config: KeyManagerConfig::FileKeyManager(FileKeyManagerConfig {
//!             keypair_directory_path: path.into_os_string().into_string().unwrap(),
//!         }),
//!     };
//!
//!     from_config(config)
//! }
//!
//! fn main() -> Result<(), KeyManagementError> {
//!     let secret_phrase = Secret::new(format!("{}//Demo", DEV_PHRASE));
//!     let mut key_manager = create_key_manager()?;
//!     key_manager.import_keypair(secret_phrase, KeyType::SubstrateSr25519);
//!
//!     let pair = sr25519::Pair::from_string("//Demo", None)?;
//!     let address = KeyIdentifier {
//!         value: pair.public().to_ss58check(),
//!         key_type: KeyType::SubstrateSr25519,
//!     };
//!     assert!(key_manager.has_key(&address)?);
//!     let signature = key_manager.sign(&address, b"A Message")?;
//!     println!("Signature as hex: {:x?}", signature.as_ref().as_ref());
//!     Ok(())
//! }
//!```

#[cfg(test)]
mod unit_tests;

use crate::core::KeyStorage;
use crate::{
    config::ConfigValidation,
    core::KeyPair,
    crypto::translator::KeyPairInstance,
    errors::{IoError, KeyManagementError, Result},
    persistence::serialize_keypair,
    persistence::{deserialize_keypair, KEYPAIR_FILE_PREFIX},
    KeyIdentifier, NewKeyId,
};
use secrecy::{ExposeSecret, Secret};
use snafu::ResultExt;
use std::{
    fs,
    path::{Path, PathBuf},
};

/// The configuration used to create a File KeyManager.
///
/// # Examples
///
/// Config File create a file named `krypt.toml` with the following contents.
///
/// ```toml
/// [key_manager_config.FileKeyManager]
/// keypair_directory_path = "/etc/xand-api/keypairs/"
/// ```
///
/// Using Config Structs
///
/// ```
/// use tpfs_krypt::{
///     config::{KeyManagerConfig, KryptConfig},
///     FileKeyManagerConfig
/// };
///
/// let config = KryptConfig {
///     key_manager_config: KeyManagerConfig::FileKeyManager(FileKeyManagerConfig {
///         keypair_directory_path: String::from("/tmp/krypt/keypairs/"),
///     }),
/// };
/// ```
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct FileKeyManagerConfig {
    /// The path to the directory where key pairs will be
    /// read and saved to.
    pub keypair_directory_path: String,
}

impl ConfigValidation for FileKeyManagerConfig {
    fn validate(&self) -> Result<()> {
        let path = PathBuf::from(&self.keypair_directory_path);

        if !path.is_dir() || !path.exists() {
            return Err(KeyManagementError::InvalidDirectoryPath {
                path: self.keypair_directory_path.clone(),
            });
        }

        fs::read_dir(&self.keypair_directory_path).context(IoError {
            message: format!(
                "Could not read directory {:?}",
                &self.keypair_directory_path
            ),
        })?;

        Ok(())
    }
}

/// The File Key Manager stuct which can be used to point to a specific
/// path on the file system to manage keys from.
///
pub(crate) struct FileKeyManager {
    pub keypair_directory_path: PathBuf,
}

impl FileKeyManager {
    pub(crate) fn new(config: FileKeyManagerConfig) -> Self {
        FileKeyManager {
            keypair_directory_path: PathBuf::from(&config.keypair_directory_path),
        }
    }
}

/// Given a path this will retrieve just the file name portion
/// of the path.
fn starts_with_keypair_prefix(path: &Path) -> Option<bool> {
    let filename = path.file_name()?.to_str()?;
    Some(filename.starts_with(KEYPAIR_FILE_PREFIX))
}

impl KeyStorage for FileKeyManager {
    fn store_has_key(&self, id: &KeyIdentifier) -> Result<bool> {
        let filepath = self
            .keypair_directory_path
            .join(format!("{}{}", KEYPAIR_FILE_PREFIX, id.value));

        Ok(filepath.exists())
    }

    fn get_key(&self, id: &KeyIdentifier) -> Result<KeyPairInstance> {
        let filepath = self
            .keypair_directory_path
            .join(format!("{}{}", KEYPAIR_FILE_PREFIX, id.value));

        if !filepath.exists() {
            return Err(KeyManagementError::AddressNotFound {
                address: id.value.clone(),
            });
        }

        let serialized = Secret::new(fs::read_to_string(&filepath).context(IoError {
            message: format!("Error reading from filepath {:?}", &filepath),
        })?);

        deserialize_keypair(serialized)
    }

    /// For reading through the filesystem to retrieve addresses. get_addresses will do the
    /// following to be fairly fault tolerant while exposing certain errors sooner:
    /// * Error out if the directory is inaccessible
    /// * Ignore any files that it can't read or access the filenames for within the directory
    /// * Ignore any files that don't match the KEYPAIR_FILE_PREFIX
    /// * Error out if it can't access or deserialize the files that match the KEYPAIR_FILE_PREFIX
    fn get_keys(&self) -> Result<Vec<KeyIdentifier>> {
        let entries = fs::read_dir(&self.keypair_directory_path).context(IoError {
            message: format!(
                "Could not read directory {:?}",
                &self.keypair_directory_path
            ),
        })?;

        // Loops through just the first level directory entries
        // Filters to files that start with designated prefix.
        let filepaths = entries
            .flat_map(|res| res.map(|e| e.path()))
            .filter(|path| !path.is_dir())
            .filter(|path| starts_with_keypair_prefix(path).unwrap_or(false));

        // Reads contents of the keypair files then deserializes to just
        // retrieve the address from the file.
        // This wil convert Iter<Result<Address>> to Result<Vec<Address>>.
        filepaths
            .map(|path| {
                let data = fs::read_to_string(&path).context(IoError {
                    message: format!("Error reading from filepath {:?}", path),
                })?;

                let keypair = deserialize_keypair(Secret::new(data))?;
                Ok(keypair.identifier())
            })
            .collect()
    }

    fn save_key(&mut self, key_pair: &KeyPairInstance) -> Result<NewKeyId> {
        let serialized = serialize_keypair(key_pair)?;
        let id = key_pair.identifier();

        let filepath = &self
            .keypair_directory_path
            .join(format!("{}{}", KEYPAIR_FILE_PREFIX, id.value));
        fs::write(filepath, serialized.expose_secret()).context(IoError {
            message: format!("Error writing to filepath {:?}", &filepath),
        })?;

        Ok(NewKeyId {
            id,
            pubkey: key_pair.public(),
        })
    }
}