diff --git a/src/commands/vault.rs b/src/commands/vault.rs index b21731d21c2e870173164fe52507cb40a0e70ad3..b6e09b74b15a3aaff00c38751633c6233e3ba11d 100644 --- a/src/commands/vault.rs +++ b/src/commands/vault.rs @@ -1,7 +1,6 @@ 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)] @@ -13,18 +12,8 @@ pub enum Subcommand { Where, /// Generate a mnemonic Generate, - /// 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, + /// Import mnemonic with interactive prompt + Import, } // encrypt input with passphrase @@ -73,14 +62,11 @@ pub fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> let mnemonic = bip39::Mnemonic::generate(12).unwrap(); println!("{mnemonic}"); } - Subcommand::Import { - secret_format, - } => { - let vault_item = prompt_secret_and_compute_vault_item(secret_format)?; - + Subcommand::Import => { + let mnemonic = rpassword::prompt_password("Mnemonic: ")?; println!("Enter password to protect the key"); let password = rpassword::prompt_password("Password: ")?; - let address = store_vault_item(&data, vault_item, password)?; + let address = store_mnemonic(&data, &mnemonic, password)?; println!("Stored secret for {address}"); } }; @@ -88,120 +74,34 @@ pub fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> Ok(()) } -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( +/// store mnemonic protected with password +pub fn store_mnemonic( data: &Data, - vault_item: VaultItem, + mnemonic: &str, password: String, ) -> Result<AccountId, GcliError> { - 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()); + // 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 mut file = std::fs::File::create(path)?; - - 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) + file.write_all(&encrypt(mnemonic.as_bytes(), password).map_err(|e| anyhow!(e))?[..])?; + Ok(keypair.public().into()) } -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)) -} - -/// 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()); - } +/// 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()); 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_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())) - } - } + 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)) } else { Ok(None) } diff --git a/src/keys.rs b/src/keys.rs index 570900fa47d770c9ca16ed339957f3ec98c6dda8..ae695c901cd7a205ab7be45c6fa4e22846eb3f4e 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -1,4 +1,3 @@ -use sp_core::sr25519::Pair; use crate::*; use sr25519::Pair as Sr25519Pair; @@ -18,12 +17,6 @@ pub enum SecretFormat { /// Cesium (scrypt + nacl) Cesium, } - -pub enum Secret { - SimpleSecret(String), - DualSecret(String, String), -} - impl FromStr for SecretFormat { type Err = std::io::Error; @@ -159,15 +152,10 @@ pub fn pair_from_cesium(id: String, pwd: String) -> nacl::sign::Keypair { /// ask user to input a secret pub fn prompt_secret_substrate() -> Sr25519Pair { - // Only interested in the keypair which is the second element of the tuple - prompt_secret_substrate_and_compute_keypair().1 -} - -pub fn prompt_secret_substrate_and_compute_keypair() -> (Secret, Sr25519Pair) { loop { - let mnemonic = rpassword::prompt_password("Mnemonic: ").unwrap(); - match pair_from_str(&mnemonic) { - Ok(pair) => return (Secret::SimpleSecret(mnemonic), pair), + let mnemonic = &rpassword::prompt_password("Mnemonic: ").unwrap(); + match pair_from_str(mnemonic) { + Ok(pair) => return pair, Err(_) => println!("Invalid secret"), } } @@ -175,27 +163,17 @@ pub fn prompt_secret_substrate_and_compute_keypair() -> (Secret, Sr25519Pair) { /// ask user pass (Cesium format) pub fn prompt_secret_cesium() -> nacl::sign::Keypair { - // Only interested in the keypair which is the second element of the tuple - prompt_secret_cesium_and_compute_keypair().1 -} - -pub fn prompt_secret_cesium_and_compute_keypair() -> (Secret, nacl::sign::Keypair) { let id = rpassword::prompt_password("Cesium id: ").unwrap(); let pwd = rpassword::prompt_password("Cesium password: ").unwrap(); - (Secret::DualSecret(id.clone(),pwd.clone()), pair_from_cesium(id, pwd)) + pair_from_cesium(id, pwd) } /// ask user to input a seed pub fn prompt_seed() -> Sr25519Pair { - // Only interested in the keypair which is the second element of the tuple - prompt_seed_and_compute_keypair().1 -} - -pub fn prompt_seed_and_compute_keypair() -> (Secret,Pair) { loop { - let seed = rpassword::prompt_password("Seed: ").unwrap(); - match pair_from_seed(&seed) { - Ok(pair) => return (Secret::SimpleSecret(seed), pair), + let seed = &rpassword::prompt_password("Seed: ").unwrap(); + match pair_from_seed(seed) { + Ok(pair) => return pair, Err(_) => println!("Invalid seed"), } } @@ -203,13 +181,8 @@ pub fn prompt_seed_and_compute_keypair() -> (Secret,Pair) { /// ask user pass (Cesium format) pub fn prompt_predefined() -> Sr25519Pair { - // Only interested in the keypair which is the second element of the tuple - prompt_predefined_and_compute_keypair().1 -} - -pub fn prompt_predefined_and_compute_keypair() -> (Secret, Sr25519Pair) { let deriv = rpassword::prompt_password("Enter derivation path: ").unwrap(); - (Secret::SimpleSecret(deriv.clone()), pair_from_predefined(&deriv).expect("invalid secret")) + pair_from_predefined(&deriv).expect("invalid secret") } /// ask user secret in relevant format @@ -231,9 +204,9 @@ pub fn fetch_or_get_keypair(data: &Data, address: Option<AccountId>) -> Result<K return Ok(pair_from_predefined(d).unwrap().into()); }; - // look for corresponding KeyPair in keystore - if let Some(key_pair) = commands::vault::try_fetch_key_pair(data, address)? { - return Ok(key_pair); + // look for corresponding secret in keystore + if let Some(secret) = commands::vault::try_fetch_secret(data, address)? { + return get_keypair(SecretFormat::Substrate, Some(&secret)); }; } // at the moment, there is no way to confg gcli to use an other kind of secret