tpfs_krypt 7.1.8

An interface for accessing secrets
Documentation
//! The Hashicorp Vault Key Manager Module which should contain the config and struct for
//! working with a hashicorp vault key manager.
//!
//! 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, HashicorpVaultKeyManagerConfig, KeyManagement, KeyType,
// };
// use url::Url;
//
// fn create_key_manager() -> Result<Box<dyn KeyManagement>, KeyManagementError> {
//     let addr = std::env::var("VAULT_ADDR").unwrap_or("http://127.0.0.1:8200".into());
//     let config = KryptConfig {
//         key_manager_config: KeyManagerConfig::HashicorpVaultKeyManager(HashicorpVaultKeyManagerConfig {
//             vault_path: format!("rust_doc/examples/keypairs/"),
//             vault_token: std::env::var("VAULT_TOKEN").unwrap_or("testtoken".into()),
//             vault_addr: Url::parse(&addr)?,
//         }),
//     };
//
//     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::{
    config::ConfigValidation,
    core::{KeyPair, KeyStorage},
    crypto::translator::KeyPairInstance,
    errors::Result,
    persistence::{deserialize_keypair, serialize_keypair},
    KeyIdentifier, NewKeyId,
};
use base64::{decode, encode};
use hashicorp_vault::client::{TokenData, VaultClient};
use secrecy::{ExposeSecret, Secret};
use url::Url;

const KEYPAIR_PREFIX: &str = "kp-";

/// The configuration used to create a Hashicorp Vault KeyManager.
///
/// # Examples
///
/// Config File create a file named `krypt.toml` with the following contents.
///
/// ```toml
/// [key_manager_config.HashicorpVaultKeyManager]
/// vault_path = "rust_doc/examples/keypairs/"
/// vault_token = "testtoken"
/// vault_addr = "http://127.0.0.1:8200"
/// ```
///
/// Using Config Structs
///
/// ```
/// # use tpfs_krypt::errors::KeyManagementError;
/// use tpfs_krypt::{
///     config::{KeyManagerConfig, KryptConfig},
///     HashicorpVaultKeyManagerConfig
/// };
/// use url::Url;
///
/// let addr = std::env::var("VAULT_ADDR").unwrap_or("http://127.0.0.1:8200".into());
/// let config = KryptConfig {
///     key_manager_config: KeyManagerConfig::HashicorpVaultKeyManager(HashicorpVaultKeyManagerConfig {
///         vault_path: format!("rust_doc/examples/keypairs/"),
///         vault_token: std::env::var("VAULT_TOKEN").unwrap_or("testtoken".into()),
///         vault_addr: Url::parse(&addr)?,
///     }),
/// };
/// # Ok::<(), KeyManagementError>(())
/// ```
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct HashicorpVaultKeyManagerConfig {
    /// The path within vault secrets that keymanager will use to
    /// save keys.
    pub vault_path: String,
    /// The access token to use to manage keys in vault.
    pub vault_token: String,
    /// The address of the vault instance to access vault with.
    pub vault_addr: Url,
}

impl ConfigValidation for HashicorpVaultKeyManagerConfig {
    fn validate(&self) -> Result<()> {
        VaultClient::new(self.vault_addr.as_str(), &self.vault_token)?;

        Ok(())
    }
}

pub(crate) struct HashicorpVaultKeyManager {
    pub(crate) client: VaultClient<TokenData>,
    pub(crate) vault_path: String,
}

impl HashicorpVaultKeyManager {
    pub(crate) fn new(config: HashicorpVaultKeyManagerConfig) -> Self {
        let mut path = config.vault_path;

        if path.starts_with('/') {
            path.remove(0);
        }

        if !path.ends_with('/') {
            path.push('/');
        }

        HashicorpVaultKeyManager {
            client: VaultClient::new(config.vault_addr.as_str(), &config.vault_token).unwrap(),
            vault_path: path,
        }
    }

    fn get_key_by_string(&self, key: &str) -> Result<KeyPairInstance> {
        let encoded = Secret::new(self.client.get_secret(self.vault_path.clone() + key)?);
        let serialized: String = String::from_utf8(decode(encoded.expose_secret())?)?;

        deserialize_keypair(Secret::new(serialized))
    }

    fn get_keys_at_vault_path(&self) -> Result<Vec<String>> {
        let entries = self.client.list_secrets(&self.vault_path);

        Ok(
            if let Err(hashicorp_vault::Error::VaultResponse(err, response)) = entries {
                if response.status() == reqwest::StatusCode::NOT_FOUND {
                    Ok(vec![])
                } else {
                    Err(hashicorp_vault::Error::VaultResponse(err, response))
                }
            } else {
                entries
            }?,
        )
    }
}

impl KeyStorage for HashicorpVaultKeyManager {
    fn store_has_key(&self, id: &KeyIdentifier) -> Result<bool> {
        let entries = &self.get_keys_at_vault_path()?;

        Ok(entries.contains(&format!("{}{}", KEYPAIR_PREFIX, id.value)))
    }

    fn get_key(&self, id: &KeyIdentifier) -> Result<KeyPairInstance> {
        let key = format!("{}{}", KEYPAIR_PREFIX, id.value);
        self.get_key_by_string(&key)
    }

    fn get_keys(&self) -> Result<Vec<KeyIdentifier>> {
        let entries = &self.get_keys_at_vault_path()?;
        let keypair_keys = entries.iter().filter(|key| key.starts_with(KEYPAIR_PREFIX));

        keypair_keys
            .map(|key| Ok(self.get_key_by_string(key)?.identifier()))
            .collect()
    }

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

        let path = format!("{}{}{}", &self.vault_path, KEYPAIR_PREFIX, id.value);
        self.client.set_secret(path, encoded.expose_secret())?;

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