Skip to content
Snippets Groups Projects

Draft: Added support for the different SecretFormat within the Vault

Closed Nicolas80 requested to merge Nicolas80/gcli-v2s:vault-support-for-all-secret-format into master
6 unresolved threads
Files
3
+ 121
21
use crate::*;
use age::secrecy::Secret;
use std::io::{Read, Write};
use std::path::PathBuf;
/// define universal dividends subcommands
#[derive(Clone, Default, Debug, clap::Parser)]
@@ -12,8 +13,18 @@ pub enum Subcommand {
Where,
/// Generate a mnemonic
Generate,
/// Import mnemonic with interactive prompt
Import,
/// Import key from (substrate)mnemonic or other format with interactive prompt
Import {
/// Secret key format (substrate, seed, cesium)
#[clap(short = 'S', long, required = false, default_value = SecretFormat::Substrate)]
secret_format: SecretFormat,
},
}
struct VaultItem {
secret_format: SecretFormat,
secret: keys::Secret,
keypair: KeyPair,
}
// encrypt input with passphrase
@@ -62,11 +73,14 @@ pub fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError>
let mnemonic = bip39::Mnemonic::generate(12).unwrap();
println!("{mnemonic}");
}
Subcommand::Import => {
let mnemonic = rpassword::prompt_password("Mnemonic: ")?;
Subcommand::Import {
secret_format,
} => {
let vault_item = prompt_secret_and_compute_vault_item(secret_format)?;
println!("Enter password to protect the key");
let password = rpassword::prompt_password("Password: ")?;
let address = store_mnemonic(&data, &mnemonic, password)?;
let address = store_vault_item(&data, vault_item, password)?;
println!("Stored secret for {address}");
}
};
@@ -74,34 +88,120 @@ pub fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError>
Ok(())
}
/// store mnemonic protected with password
pub fn store_mnemonic(
fn create_vault_item<F, P>(secret_format: SecretFormat, prompt_fn: F) -> Result<VaultItem, GcliError>
where
F: Fn() -> (keys::Secret, P),
P: Into<KeyPair>,
{
let (secret, pair) = prompt_fn();
Ok(VaultItem {
secret_format,
secret,
keypair: pair.into(),
})
}
fn prompt_secret_and_compute_vault_item(secret_format: SecretFormat) -> Result<VaultItem, GcliError> {
match secret_format {
SecretFormat::Substrate =>
create_vault_item(secret_format, prompt_secret_substrate_and_compute_keypair),
SecretFormat::Seed =>
create_vault_item(secret_format, prompt_seed_and_compute_keypair),
SecretFormat::Cesium =>
create_vault_item(secret_format, prompt_secret_cesium_and_compute_keypair),
SecretFormat::Predefined =>
create_vault_item(secret_format, prompt_predefined_and_compute_keypair),
}
}
/// store VaultItem protected with password
fn store_vault_item(
data: &Data,
mnemonic: &str,
vault_item: VaultItem,
password: String,
) -> Result<AccountId, GcliError> {
// check validity by deriving keypair
let keypair = pair_from_str(mnemonic)?;
let address = keypair.public();
// write encrypted mnemonic in file identified by pubkey
let path = data.project_dir.data_dir().join(address.to_string());
let keypair = vault_item.keypair;
let address = keypair.address();
// write encrypted secret in file identified by address pubkey and secret_format
let path = get_vault_key_path(data, vault_item.secret_format, address.to_string());
let mut file = std::fs::File::create(path)?;
file.write_all(&encrypt(mnemonic.as_bytes(), password).map_err(|e| anyhow!(e))?[..])?;
Ok(keypair.public().into())
match vault_item.secret {
keys::Secret::SimpleSecret(secret) => {
file.write_all(&encrypt(secret.as_bytes(), password).map_err(|e| anyhow!(e))?[..])?;
}
keys::Secret::DualSecret(id, pwd) => {
//Making a simple separation of the 2 parts with a newline character
let secret = format!("{id}\n{pwd}");
file.write_all(&encrypt(secret.as_bytes(), password).map_err(|e| anyhow!(e))?[..])?;
}
}
Ok(address)
}
/// try get secret in keystore
pub fn try_fetch_secret(data: &Data, address: AccountId) -> Result<Option<String>, GcliError> {
let path = data.project_dir.data_dir().join(address.to_string());
fn get_vault_key_path(data: &Data, secret_format: SecretFormat, address: String) -> PathBuf {
let format_str: &'static str = From::from(secret_format);
data.project_dir.data_dir().join(format!("{}-{}", address, format_str))
Please register or sign in to reply
}
/// look for different possible paths for vault keys and return both format and path
fn find_vault_key(data: &Data, address: String) -> Result<Option<(SecretFormat, PathBuf)>, GcliError> {
let mut secret_format = SecretFormat::Substrate;
let mut path = get_vault_key_path(data, secret_format, address.to_string());
//Also checking for old file name without secret_format which would be for (default) substrate key
if !path.exists() {
path = data.project_dir.data_dir().join(address.to_string());
}
if !path.exists() {
secret_format = SecretFormat::Seed;
path = get_vault_key_path(data, secret_format, address.to_string());
}
if !path.exists() {
secret_format = SecretFormat::Cesium;
path = get_vault_key_path(data, secret_format, address.to_string());
}
if !path.exists() {
secret_format = SecretFormat::Predefined;
path = get_vault_key_path(data, secret_format, address.to_string());
}
if path.exists() {
Ok(Some((secret_format, path)))
} else {
Ok(None)
}
}
/// try to get secret in keystore, prompt for the password and compute the keypair
pub fn try_fetch_key_pair(data: &Data, address: AccountId) -> Result<Option<KeyPair>, GcliError> {
if let Some((secret_format, path)) = find_vault_key(data, address.to_string())? {
println!("Enter password to unlock account {address}");
let password = rpassword::prompt_password("Password: ")?;
let mut file = std::fs::OpenOptions::new().read(true).open(path)?;
let mut cypher = vec![];
file.read_to_end(&mut cypher)?;
let secret = decrypt(&cypher, password).map_err(|e| GcliError::Input(e.to_string()))?;
let secretstr = String::from_utf8(secret).map_err(|e| anyhow!(e))?;
Ok(Some(secretstr))
let secret_vec = decrypt(&cypher, password).map_err(|e| GcliError::Input(e.to_string()))?;
let secret = String::from_utf8(secret_vec).map_err(|e| anyhow!(e))?;
//Still need to handle different secret formats
match secret_format {
SecretFormat::Substrate => {
Ok(Some(pair_from_str(&secret)?.into()))
}
SecretFormat::Seed => {
Ok(Some(pair_from_seed(&secret)?.into()))
}
SecretFormat::Cesium => {
let mut lines = secret.lines();
//Un-wrapping the 2 secrets from each line
let id = lines.next().unwrap();
let pwd = lines.next().unwrap();
Ok(Some(pair_from_cesium(id.to_string(), pwd.to_string()).into()))
}
SecretFormat::Predefined => {
Ok(Some(pair_from_predefined(&secret)?.into()))
}
}
} else {
Ok(None)
}
Loading