From 4c743e2cf420097924f40ec58331b23e8aaf8042 Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Mon, 13 Jan 2025 17:04:13 -0500 Subject: [PATCH 01/10] Remove secure store entry when removing identity --- cmd/soroban-cli/src/commands/keys/mod.rs | 2 +- cmd/soroban-cli/src/commands/keys/rm.rs | 6 +++-- cmd/soroban-cli/src/config/locator.rs | 22 ++++++++++++++-- cmd/soroban-cli/src/signer/keyring.rs | 32 ++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 5 deletions(-) diff --git a/cmd/soroban-cli/src/commands/keys/mod.rs b/cmd/soroban-cli/src/commands/keys/mod.rs index 3e36df085..167f54873 100644 --- a/cmd/soroban-cli/src/commands/keys/mod.rs +++ b/cmd/soroban-cli/src/commands/keys/mod.rs @@ -75,7 +75,7 @@ impl Cmd { Cmd::Fund(cmd) => cmd.run(global_args).await?, Cmd::Generate(cmd) => cmd.run(global_args).await?, Cmd::Ls(cmd) => cmd.run()?, - Cmd::Rm(cmd) => cmd.run()?, + Cmd::Rm(cmd) => cmd.run(global_args)?, Cmd::Secret(cmd) => cmd.run()?, Cmd::Default(cmd) => cmd.run(global_args)?, }; diff --git a/cmd/soroban-cli/src/commands/keys/rm.rs b/cmd/soroban-cli/src/commands/keys/rm.rs index df48108d3..c551d05e8 100644 --- a/cmd/soroban-cli/src/commands/keys/rm.rs +++ b/cmd/soroban-cli/src/commands/keys/rm.rs @@ -1,5 +1,7 @@ use clap::command; +use crate::commands::global; + use super::super::config::locator; #[derive(thiserror::Error, Debug)] @@ -19,7 +21,7 @@ pub struct Cmd { } impl Cmd { - pub fn run(&self) -> Result<(), Error> { - Ok(self.config.remove_identity(&self.name)?) + pub fn run(&self, global_args: &global::Args) -> Result<(), Error> { + Ok(self.config.remove_identity(&self.name, global_args)?) } } diff --git a/cmd/soroban-cli/src/config/locator.rs b/cmd/soroban-cli/src/config/locator.rs index 1e26bbf7f..24232482f 100644 --- a/cmd/soroban-cli/src/config/locator.rs +++ b/cmd/soroban-cli/src/config/locator.rs @@ -12,7 +12,7 @@ use std::{ }; use stellar_strkey::{Contract, DecodeError}; -use crate::{commands::HEADING_GLOBAL, utils::find_config_dir, Pwd}; +use crate::{commands::{global, HEADING_GLOBAL}, print::Print, signer::{self, keyring::StellarEntry}, utils::find_config_dir, Pwd}; use super::{ alias, @@ -87,6 +87,8 @@ pub enum Error { ContractAliasCannotOverlapWithKey(String), #[error("Key cannot {0} cannot overlap with contract alias")] KeyCannotOverlapWithContractAlias(String), + #[error(transparent)] + Keyring(#[from] signer::keyring::Error), } #[derive(Debug, clap::Args, Default, Clone)] @@ -260,7 +262,23 @@ impl Args { res } - pub fn remove_identity(&self, name: &str) -> Result<(), Error> { + pub fn remove_identity(&self, name: &str, global_args: &global::Args) -> Result<(), Error> { + let print = Print::new(global_args.quiet); + let identity = self.read_identity(name)?; + if let Secret::SecureStore { entry_name } = identity { + let entry = StellarEntry::new(&entry_name)?; + match entry.delete_seed_phrase() { + Ok(()) => {} + Err(e) => match e { + signer::keyring::Error::Keyring(keyring::Error::NoEntry) => { + print.infoln("This key was already removed from the secure store. Removing the cli config file."); + } + _ => { + return Err(Error::Keyring(e)); + } + }, + } + } KeyType::Identity.remove(name, &self.config_dir()?) } diff --git a/cmd/soroban-cli/src/signer/keyring.rs b/cmd/soroban-cli/src/signer/keyring.rs index 0e6c49137..7dda2e795 100644 --- a/cmd/soroban-cli/src/signer/keyring.rs +++ b/cmd/soroban-cli/src/signer/keyring.rs @@ -32,6 +32,10 @@ impl StellarEntry { Ok(()) } + pub fn delete_seed_phrase(&self) -> Result<(), Error> { + Ok(self.keyring.delete_credential()?) + } + fn get_seed_phrase(&self) -> Result { Ok(self.keyring.get_password()?.parse()?) } @@ -144,4 +148,32 @@ mod test { let sign_tx_env_result = entry.sign_data(tx_xdr.as_bytes(), None); assert!(sign_tx_env_result.is_ok()); } + + #[test] + fn test_delete_seed_phrase() { + set_default_credential_builder(mock::default_credential_builder()); + + //create a seed phrase + let seed_phrase = crate::config::secret::seed_phrase_from_seed(None).unwrap(); + + // create a keyring entry and set the seed_phrase + let entry = StellarEntry::new("test").unwrap(); + entry.set_seed_phrase(seed_phrase).unwrap(); + + // assert it is there + let get_seed_phrase_result = entry.get_seed_phrase(); + assert!(get_seed_phrase_result.is_ok()); + + // delete the password + let delete_seed_phrase_result = entry.delete_seed_phrase(); + assert!(delete_seed_phrase_result.is_ok()); + + // confirm the entry is gone + let get_password_result = entry.get_seed_phrase(); + assert!(get_password_result.is_err()); + assert!(matches!( + get_password_result.unwrap_err(), + Error::Keyring(_) + )); + } } From 0914f06571e5f8a33bc882c2dd54e152fcc5ec1a Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Mon, 13 Jan 2025 17:33:53 -0500 Subject: [PATCH 02/10] Factor out a secure_store mod for code reuse --- cmd/soroban-cli/src/commands/keys/generate.rs | 40 ++---------- cmd/soroban-cli/src/signer.rs | 1 + cmd/soroban-cli/src/signer/secure_store.rs | 64 +++++++++++++++++++ 3 files changed, 69 insertions(+), 36 deletions(-) create mode 100644 cmd/soroban-cli/src/signer/secure_store.rs diff --git a/cmd/soroban-cli/src/commands/keys/generate.rs b/cmd/soroban-cli/src/commands/keys/generate.rs index 8ec0158bb..bd7053f34 100644 --- a/cmd/soroban-cli/src/commands/keys/generate.rs +++ b/cmd/soroban-cli/src/commands/keys/generate.rs @@ -9,7 +9,7 @@ use crate::{ commands::global, config::address::KeyName, print::Print, - signer::keyring::{self, StellarEntry}, + signer::secure_store::{self, SecureStore}, }; #[derive(thiserror::Error, Debug)] @@ -27,7 +27,7 @@ pub enum Error { IdentityAlreadyExists(String), #[error(transparent)] - Keyring(#[from] keyring::Error), + SecureStore(#[from] secure_store::Error) } #[derive(Debug, clap::Parser, Clone)] @@ -124,22 +124,8 @@ impl Cmd { fn secret(&self, print: &Print) -> Result { let seed_phrase = self.seed_phrase()?; if self.secure_store { - // secure_store:org.stellar.cli: - let entry_name_with_prefix = format!( - "{}{}-{}", - keyring::SECURE_STORE_ENTRY_PREFIX, - keyring::SECURE_STORE_ENTRY_SERVICE, - self.name - ); - - //checking that the entry name is valid before writing to the secure store - let secret: Secret = entry_name_with_prefix.parse()?; - - if let Secret::SecureStore { entry_name } = &secret { - Self::write_to_secure_store(entry_name, seed_phrase, print)?; - } - - return Ok(secret); + let secret = SecureStore::secret(print, &self.name, seed_phrase)?; + return Ok(secret) } let secret: Secret = seed_phrase.into(); Ok(if self.as_secret { @@ -156,24 +142,6 @@ impl Cmd { secret::seed_phrase_from_seed(self.seed.as_deref()) }?) } - - fn write_to_secure_store( - entry_name: &String, - seed_phrase: SeedPhrase, - print: &Print, - ) -> Result<(), Error> { - print.infoln(format!("Writing to secure store: {entry_name}")); - let entry = StellarEntry::new(entry_name)?; - if let Ok(key) = entry.get_public_key(None) { - print.warnln(format!("A key for {entry_name} already exists in your operating system's secure store: {key}")); - } else { - print.infoln(format!( - "Saving a new key to your operating system's secure store: {entry_name}" - )); - entry.set_seed_phrase(seed_phrase)?; - } - Ok(()) - } } #[cfg(test)] diff --git a/cmd/soroban-cli/src/signer.rs b/cmd/soroban-cli/src/signer.rs index ebd650d40..332551bbd 100644 --- a/cmd/soroban-cli/src/signer.rs +++ b/cmd/soroban-cli/src/signer.rs @@ -13,6 +13,7 @@ use crate::xdr::{ use crate::{config::network::Network, print::Print, utils::transaction_hash}; pub mod keyring; +pub mod secure_store; #[derive(thiserror::Error, Debug)] pub enum Error { diff --git a/cmd/soroban-cli/src/signer/secure_store.rs b/cmd/soroban-cli/src/signer/secure_store.rs new file mode 100644 index 000000000..1e551b5f7 --- /dev/null +++ b/cmd/soroban-cli/src/signer/secure_store.rs @@ -0,0 +1,64 @@ +use sep5::SeedPhrase; + +use crate::{ + config::{address::KeyName, locator, secret::{self, Secret}}, print::Print, signer::keyring::{self, StellarEntry} +}; + +pub struct SecureStore {} + +#[derive(thiserror::Error, Debug)] +pub enum Error{ + #[error(transparent)] + Config(#[from] locator::Error), + + #[error(transparent)] + Secret(#[from] secret::Error), + + #[error(transparent)] + Keyring(#[from] keyring::Error), + + #[error("Storing an existing private key in Secure Store is not supported")] + DoesNotSupportPrivateKey, + + #[error(transparent)] + SeedPhrase(#[from] sep5::Error) +} + +impl SecureStore { + pub fn secret(print: &Print, entry_name: &KeyName, seed_phrase: SeedPhrase) -> Result { + // secure_store:org.stellar.cli: + let entry_name_with_prefix = format!( + "{}{}-{}", + keyring::SECURE_STORE_ENTRY_PREFIX, + keyring::SECURE_STORE_ENTRY_SERVICE, + entry_name + ); + + //checking that the entry name is valid before writing to the secure store + let secret: Secret = entry_name_with_prefix.parse()?; + + if let Secret::SecureStore { entry_name } = &secret { + Self::write_to_secure_store(entry_name, seed_phrase, print)?; + } + + return Ok(secret); + } + + fn write_to_secure_store( + entry_name: &String, + seed_phrase: SeedPhrase, + print: &Print, + ) -> Result<(), Error> { + print.infoln(format!("Writing to secure store: {entry_name}")); + let entry = StellarEntry::new(entry_name)?; + if let Ok(key) = entry.get_public_key(None) { + print.warnln(format!("A key for {entry_name} already exists in your operating system's secure store: {key}")); + } else { + print.infoln(format!( + "Saving a new key to your operating system's secure store: {entry_name}" + )); + entry.set_seed_phrase(seed_phrase)?; + } + Ok(()) + } +} \ No newline at end of file From b1f7bca3874c74650fce303f362cee22e81eb572 Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Mon, 13 Jan 2025 17:45:20 -0500 Subject: [PATCH 03/10] Allow for adding a seed phrase key and storing in secure store --- cmd/soroban-cli/src/commands/keys/add.rs | 2 +- cmd/soroban-cli/src/config/secret.rs | 23 ++++++++++++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/cmd/soroban-cli/src/commands/keys/add.rs b/cmd/soroban-cli/src/commands/keys/add.rs index 4c5ddbd9b..77029d6ff 100644 --- a/cmd/soroban-cli/src/commands/keys/add.rs +++ b/cmd/soroban-cli/src/commands/keys/add.rs @@ -31,7 +31,7 @@ pub struct Cmd { impl Cmd { pub fn run(&self, global_args: &global::Args) -> Result<(), Error> { let print = Print::new(global_args.quiet); - let secret = self.secrets.read_secret()?; + let secret = self.secrets.read_secret(&self.name)?; let path = self.config_locator.write_identity(&self.name, &secret)?; print.checkln(format!("Key saved with alias {:?} in {path:?}", self.name)); Ok(()) diff --git a/cmd/soroban-cli/src/config/secret.rs b/cmd/soroban-cli/src/config/secret.rs index f32b291e8..b050bf0a3 100644 --- a/cmd/soroban-cli/src/config/secret.rs +++ b/cmd/soroban-cli/src/config/secret.rs @@ -6,9 +6,7 @@ use sep5::SeedPhrase; use stellar_strkey::ed25519::{PrivateKey, PublicKey}; use crate::{ - print::Print, - signer::{self, keyring, LocalKey, SecureStoreEntry, Signer, SignerKind}, - utils, + config::address::KeyName, print::Print, signer::{self, keyring, secure_store::SecureStore, LocalKey, SecureStoreEntry, Signer, SignerKind}, utils }; #[derive(thiserror::Error, Debug)] @@ -31,6 +29,8 @@ pub enum Error { Keyring(#[from] keyring::Error), #[error("Secure Store does not reveal secret key")] SecureStoreDoesNotRevealSecretKey, + #[error("Getting key from secure store failed")] // Todo: update this + SecureStore, } #[derive(Debug, clap::Args, Clone)] @@ -42,15 +42,24 @@ pub struct Args { /// (deprecated) Enter key using 12-24 word seed phrase #[arg(long)] pub seed_phrase: bool, + /// Save the new key in secure store. This only supports seed phrases for now. + #[arg(long)] + pub secure_store: bool, } impl Args { - pub fn read_secret(&self) -> Result { + pub fn read_secret(&self, name: &KeyName) -> Result { if let Ok(secret_key) = std::env::var("SOROBAN_SECRET_KEY") { - Ok(Secret::SecretKey { secret_key }) + return Ok(Secret::SecretKey { secret_key }) + } + + println!("Type a secret key or 12/24 word seed phrase:"); + let secret_key = read_password()?; + if self.secure_store { + let seed_phrase: SeedPhrase = secret_key.parse()?; + let print = &Print::new(false); + SecureStore::secret(print, name, seed_phrase).map_err(|_| Error::SecureStore) } else { - println!("Type a secret key or 12/24 word seed phrase:"); - let secret_key = read_password()?; secret_key .parse() .map_err(|_| Error::InvalidSecretOrSeedPhrase) From 5428c3d3c6b9ff12f1ed8d6dd538a988b16c8c75 Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Mon, 13 Jan 2025 17:50:25 -0500 Subject: [PATCH 04/10] Update doc comments --- cmd/soroban-cli/src/commands/keys/mod.rs | 1 + cmd/soroban-cli/src/config/sign_with.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/soroban-cli/src/commands/keys/mod.rs b/cmd/soroban-cli/src/commands/keys/mod.rs index 167f54873..6d558cf8c 100644 --- a/cmd/soroban-cli/src/commands/keys/mod.rs +++ b/cmd/soroban-cli/src/commands/keys/mod.rs @@ -22,6 +22,7 @@ pub enum Cmd { Fund(fund::Cmd), /// Generate a new identity with a seed phrase, currently 12 words + /// The seed phrase can be stored in a config file (default) or in an OS-specific secure store. Generate(generate::Cmd), /// List identities diff --git a/cmd/soroban-cli/src/config/sign_with.rs b/cmd/soroban-cli/src/config/sign_with.rs index 475013bc8..00222c9cf 100644 --- a/cmd/soroban-cli/src/config/sign_with.rs +++ b/cmd/soroban-cli/src/config/sign_with.rs @@ -34,7 +34,7 @@ pub enum Error { #[derive(Debug, clap::Args, Clone, Default)] #[group(skip)] pub struct Args { - /// Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path. + /// Sign with a local key or key saved in OS secure storage. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path. #[arg(long, env = "STELLAR_SIGN_WITH_KEY")] pub sign_with_key: Option, From 16cfacdab2a6ebb61c823ec3c090360de17e0be6 Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Tue, 14 Jan 2025 12:42:56 -0500 Subject: [PATCH 05/10] Move read_password to add.rs - this is the only place it is being used - also, this help with a cyclic dependency i was noticing when trying to use secure_store in secret and generate --- cmd/soroban-cli/src/commands/keys/add.rs | 39 ++++++++++++++++++++++-- cmd/soroban-cli/src/config/secret.rs | 33 ++------------------ 2 files changed, 38 insertions(+), 34 deletions(-) diff --git a/cmd/soroban-cli/src/commands/keys/add.rs b/cmd/soroban-cli/src/commands/keys/add.rs index 77029d6ff..eb63cfd06 100644 --- a/cmd/soroban-cli/src/commands/keys/add.rs +++ b/cmd/soroban-cli/src/commands/keys/add.rs @@ -1,9 +1,12 @@ +use std::io::Write; + use clap::command; +use sep5::SeedPhrase; use crate::{ commands::global, - config::{address::KeyName, locator, secret}, - print::Print, + config::{address::KeyName, locator, secret::{self, Secret}}, + print::Print, signer::secure_store::{self, SecureStore}, }; #[derive(thiserror::Error, Debug)] @@ -13,6 +16,15 @@ pub enum Error { #[error(transparent)] Config(#[from] locator::Error), + + #[error(transparent)] + SecureStore(#[from] secure_store::Error), + + #[error(transparent)] + SeedPhrase(#[from] sep5::error::Error), + + #[error("secret input error")] + PasswordRead, } #[derive(Debug, clap::Parser, Clone)] @@ -31,9 +43,30 @@ pub struct Cmd { impl Cmd { pub fn run(&self, global_args: &global::Args) -> Result<(), Error> { let print = Print::new(global_args.quiet); - let secret = self.secrets.read_secret(&self.name)?; + let secret = self.read_secret()?; let path = self.config_locator.write_identity(&self.name, &secret)?; print.checkln(format!("Key saved with alias {:?} in {path:?}", self.name)); Ok(()) } + + fn read_secret(&self) -> Result { + if let Ok(secret_key) = std::env::var("SOROBAN_SECRET_KEY") { + return Ok(Secret::SecretKey { secret_key }) + } + + println!("Type a secret key or 12/24 word seed phrase:"); + let secret_key = read_password()?; + if self.secrets.secure_store { + let seed_phrase: SeedPhrase = secret_key.parse()?; + let print = &Print::new(false); + Ok(SecureStore::secret(print, &self.name, seed_phrase)?) + } else { + Ok(secret_key.parse()?) + } + } } + +fn read_password() -> Result { + std::io::stdout().flush().map_err(|_| Error::PasswordRead)?; + rpassword::read_password().map_err(|_| Error::PasswordRead) +} \ No newline at end of file diff --git a/cmd/soroban-cli/src/config/secret.rs b/cmd/soroban-cli/src/config/secret.rs index b050bf0a3..04012ed56 100644 --- a/cmd/soroban-cli/src/config/secret.rs +++ b/cmd/soroban-cli/src/config/secret.rs @@ -1,20 +1,18 @@ use clap::arg; use serde::{Deserialize, Serialize}; -use std::{io::Write, str::FromStr}; +use std::str::FromStr; use sep5::SeedPhrase; use stellar_strkey::ed25519::{PrivateKey, PublicKey}; use crate::{ - config::address::KeyName, print::Print, signer::{self, keyring, secure_store::SecureStore, LocalKey, SecureStoreEntry, Signer, SignerKind}, utils + print::Print, signer::{self, keyring, LocalKey, SecureStoreEntry, Signer, SignerKind}, utils }; #[derive(thiserror::Error, Debug)] pub enum Error { // #[error("seed_phrase must be 12 words long, found {len}")] // InvalidSeedPhrase { len: usize }, - #[error("secret input error")] - PasswordRead, #[error(transparent)] Secret(#[from] stellar_strkey::DecodeError), #[error(transparent)] @@ -29,8 +27,6 @@ pub enum Error { Keyring(#[from] keyring::Error), #[error("Secure Store does not reveal secret key")] SecureStoreDoesNotRevealSecretKey, - #[error("Getting key from secure store failed")] // Todo: update this - SecureStore, } #[derive(Debug, clap::Args, Clone)] @@ -47,26 +43,6 @@ pub struct Args { pub secure_store: bool, } -impl Args { - pub fn read_secret(&self, name: &KeyName) -> Result { - if let Ok(secret_key) = std::env::var("SOROBAN_SECRET_KEY") { - return Ok(Secret::SecretKey { secret_key }) - } - - println!("Type a secret key or 12/24 word seed phrase:"); - let secret_key = read_password()?; - if self.secure_store { - let seed_phrase: SeedPhrase = secret_key.parse()?; - let print = &Print::new(false); - SecureStore::secret(print, name, seed_phrase).map_err(|_| Error::SecureStore) - } else { - secret_key - .parse() - .map_err(|_| Error::InvalidSecretOrSeedPhrase) - } - } -} - #[derive(Debug, Serialize, Deserialize)] #[serde(untagged)] pub enum Secret { @@ -180,11 +156,6 @@ pub fn test_seed_phrase() -> Result { Ok("0000000000000000".parse()?) } -fn read_password() -> Result { - std::io::stdout().flush().map_err(|_| Error::PasswordRead)?; - rpassword::read_password().map_err(|_| Error::PasswordRead) -} - #[cfg(test)] mod tests { use super::*; From 5b763f2760c367441eee8be11e6c19943b9c73c7 Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Tue, 14 Jan 2025 13:02:07 -0500 Subject: [PATCH 06/10] Cleanup & refactor --- cmd/soroban-cli/src/commands/keys/add.rs | 2 +- cmd/soroban-cli/src/commands/keys/generate.rs | 14 ++++++-------- cmd/soroban-cli/src/config/secret.rs | 2 -- cmd/soroban-cli/src/signer/secure_store.rs | 7 +++---- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/cmd/soroban-cli/src/commands/keys/add.rs b/cmd/soroban-cli/src/commands/keys/add.rs index eb63cfd06..56e706df9 100644 --- a/cmd/soroban-cli/src/commands/keys/add.rs +++ b/cmd/soroban-cli/src/commands/keys/add.rs @@ -59,7 +59,7 @@ impl Cmd { if self.secrets.secure_store { let seed_phrase: SeedPhrase = secret_key.parse()?; let print = &Print::new(false); - Ok(SecureStore::secret(print, &self.name, seed_phrase)?) + Ok(SecureStore::save_secret(print, &self.name, seed_phrase)?) } else { Ok(secret_key.parse()?) } diff --git a/cmd/soroban-cli/src/commands/keys/generate.rs b/cmd/soroban-cli/src/commands/keys/generate.rs index bd7053f34..fbad6da16 100644 --- a/cmd/soroban-cli/src/commands/keys/generate.rs +++ b/cmd/soroban-cli/src/commands/keys/generate.rs @@ -124,15 +124,13 @@ impl Cmd { fn secret(&self, print: &Print) -> Result { let seed_phrase = self.seed_phrase()?; if self.secure_store { - let secret = SecureStore::secret(print, &self.name, seed_phrase)?; - return Ok(secret) - } - let secret: Secret = seed_phrase.into(); - Ok(if self.as_secret { - secret.private_key(self.hd_path)?.into() + Ok(SecureStore::save_secret(print, &self.name, seed_phrase)?) + } else if self.as_secret { + let secret: Secret = seed_phrase.into(); + Ok(secret.private_key(self.hd_path)?.into()) } else { - secret - }) + Ok(seed_phrase.into()) + } } fn seed_phrase(&self) -> Result { diff --git a/cmd/soroban-cli/src/config/secret.rs b/cmd/soroban-cli/src/config/secret.rs index 04012ed56..e147ae639 100644 --- a/cmd/soroban-cli/src/config/secret.rs +++ b/cmd/soroban-cli/src/config/secret.rs @@ -11,8 +11,6 @@ use crate::{ #[derive(thiserror::Error, Debug)] pub enum Error { - // #[error("seed_phrase must be 12 words long, found {len}")] - // InvalidSeedPhrase { len: usize }, #[error(transparent)] Secret(#[from] stellar_strkey::DecodeError), #[error(transparent)] diff --git a/cmd/soroban-cli/src/signer/secure_store.rs b/cmd/soroban-cli/src/signer/secure_store.rs index 1e551b5f7..5631e8122 100644 --- a/cmd/soroban-cli/src/signer/secure_store.rs +++ b/cmd/soroban-cli/src/signer/secure_store.rs @@ -25,7 +25,7 @@ pub enum Error{ } impl SecureStore { - pub fn secret(print: &Print, entry_name: &KeyName, seed_phrase: SeedPhrase) -> Result { + pub fn save_secret(print: &Print, entry_name: &KeyName, seed_phrase: SeedPhrase) -> Result { // secure_store:org.stellar.cli: let entry_name_with_prefix = format!( "{}{}-{}", @@ -51,14 +51,13 @@ impl SecureStore { ) -> Result<(), Error> { print.infoln(format!("Writing to secure store: {entry_name}")); let entry = StellarEntry::new(entry_name)?; - if let Ok(key) = entry.get_public_key(None) { + Ok(if let Ok(key) = entry.get_public_key(None) { print.warnln(format!("A key for {entry_name} already exists in your operating system's secure store: {key}")); } else { print.infoln(format!( "Saving a new key to your operating system's secure store: {entry_name}" )); entry.set_seed_phrase(seed_phrase)?; - } - Ok(()) + }) } } \ No newline at end of file From 8e234bc8049ce710c3206d2ebab726a9c678ec3f Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Tue, 14 Jan 2025 13:49:08 -0500 Subject: [PATCH 07/10] Use printer for printing prompt when reading password from cli --- cmd/soroban-cli/src/commands/keys/add.rs | 20 +++++++++++--------- cmd/soroban-cli/src/print.rs | 1 + 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/cmd/soroban-cli/src/commands/keys/add.rs b/cmd/soroban-cli/src/commands/keys/add.rs index 56e706df9..eccb04558 100644 --- a/cmd/soroban-cli/src/commands/keys/add.rs +++ b/cmd/soroban-cli/src/commands/keys/add.rs @@ -6,7 +6,7 @@ use sep5::SeedPhrase; use crate::{ commands::global, config::{address::KeyName, locator, secret::{self, Secret}}, - print::Print, signer::secure_store::{self, SecureStore}, + print::{self, Print}, signer::secure_store::{self, SecureStore}, }; #[derive(thiserror::Error, Debug)] @@ -43,30 +43,32 @@ pub struct Cmd { impl Cmd { pub fn run(&self, global_args: &global::Args) -> Result<(), Error> { let print = Print::new(global_args.quiet); - let secret = self.read_secret()?; + let secret = self.read_secret(&print)?; let path = self.config_locator.write_identity(&self.name, &secret)?; print.checkln(format!("Key saved with alias {:?} in {path:?}", self.name)); Ok(()) } - fn read_secret(&self) -> Result { + fn read_secret(&self, print: &Print) -> Result { if let Ok(secret_key) = std::env::var("SOROBAN_SECRET_KEY") { return Ok(Secret::SecretKey { secret_key }) - } + } else if self.secrets.secure_store { + let prompt = "Type a 12 or 24 word seed phrase:"; + let secret_key = read_password(print, prompt)?; - println!("Type a secret key or 12/24 word seed phrase:"); - let secret_key = read_password()?; - if self.secrets.secure_store { let seed_phrase: SeedPhrase = secret_key.parse()?; - let print = &Print::new(false); + Ok(SecureStore::save_secret(print, &self.name, seed_phrase)?) } else { + let prompt = "Type a secret key or 12/24 word seed phrase:"; + let secret_key = read_password(print, prompt)?; Ok(secret_key.parse()?) } } } -fn read_password() -> Result { +fn read_password(print: &Print, prompt: &str) -> Result { + print.arrowln(prompt); std::io::stdout().flush().map_err(|_| Error::PasswordRead)?; rpassword::read_password().map_err(|_| Error::PasswordRead) } \ No newline at end of file diff --git a/cmd/soroban-cli/src/print.rs b/cmd/soroban-cli/src/print.rs index fb9fb43dd..ac9d7e6be 100644 --- a/cmd/soroban-cli/src/print.rs +++ b/cmd/soroban-cli/src/print.rs @@ -106,3 +106,4 @@ create_print_functions!(save, saveln, "💾"); create_print_functions!(search, searchln, "🔎"); create_print_functions!(warn, warnln, "⚠️"); create_print_functions!(exclaim, exclaimln, "❗️"); +create_print_functions!(arrow, arrowln, "➡️"); From b59fc3da8fab4493e49a4cdf74b881b8bd04fc50 Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Tue, 14 Jan 2025 13:57:08 -0500 Subject: [PATCH 08/10] Simplify secure_store --- cmd/soroban-cli/src/commands/keys/add.rs | 4 +- cmd/soroban-cli/src/commands/keys/generate.rs | 4 +- cmd/soroban-cli/src/signer/secure_store.rs | 64 +++++++++---------- 3 files changed, 34 insertions(+), 38 deletions(-) diff --git a/cmd/soroban-cli/src/commands/keys/add.rs b/cmd/soroban-cli/src/commands/keys/add.rs index eccb04558..271833de3 100644 --- a/cmd/soroban-cli/src/commands/keys/add.rs +++ b/cmd/soroban-cli/src/commands/keys/add.rs @@ -6,7 +6,7 @@ use sep5::SeedPhrase; use crate::{ commands::global, config::{address::KeyName, locator, secret::{self, Secret}}, - print::{self, Print}, signer::secure_store::{self, SecureStore}, + print::Print, signer::secure_store, }; #[derive(thiserror::Error, Debug)] @@ -58,7 +58,7 @@ impl Cmd { let seed_phrase: SeedPhrase = secret_key.parse()?; - Ok(SecureStore::save_secret(print, &self.name, seed_phrase)?) + Ok(secure_store::save_secret(print, &self.name, seed_phrase)?) } else { let prompt = "Type a secret key or 12/24 word seed phrase:"; let secret_key = read_password(print, prompt)?; diff --git a/cmd/soroban-cli/src/commands/keys/generate.rs b/cmd/soroban-cli/src/commands/keys/generate.rs index fbad6da16..8f633fe70 100644 --- a/cmd/soroban-cli/src/commands/keys/generate.rs +++ b/cmd/soroban-cli/src/commands/keys/generate.rs @@ -9,7 +9,7 @@ use crate::{ commands::global, config::address::KeyName, print::Print, - signer::secure_store::{self, SecureStore}, + signer::secure_store, }; #[derive(thiserror::Error, Debug)] @@ -124,7 +124,7 @@ impl Cmd { fn secret(&self, print: &Print) -> Result { let seed_phrase = self.seed_phrase()?; if self.secure_store { - Ok(SecureStore::save_secret(print, &self.name, seed_phrase)?) + Ok(secure_store::save_secret(print, &self.name, seed_phrase)?) } else if self.as_secret { let secret: Secret = seed_phrase.into(); Ok(secret.private_key(self.hd_path)?.into()) diff --git a/cmd/soroban-cli/src/signer/secure_store.rs b/cmd/soroban-cli/src/signer/secure_store.rs index 5631e8122..62113a1fc 100644 --- a/cmd/soroban-cli/src/signer/secure_store.rs +++ b/cmd/soroban-cli/src/signer/secure_store.rs @@ -4,8 +4,6 @@ use crate::{ config::{address::KeyName, locator, secret::{self, Secret}}, print::Print, signer::keyring::{self, StellarEntry} }; -pub struct SecureStore {} - #[derive(thiserror::Error, Debug)] pub enum Error{ #[error(transparent)] @@ -24,40 +22,38 @@ pub enum Error{ SeedPhrase(#[from] sep5::Error) } -impl SecureStore { - pub fn save_secret(print: &Print, entry_name: &KeyName, seed_phrase: SeedPhrase) -> Result { - // secure_store:org.stellar.cli: - let entry_name_with_prefix = format!( - "{}{}-{}", - keyring::SECURE_STORE_ENTRY_PREFIX, - keyring::SECURE_STORE_ENTRY_SERVICE, - entry_name - ); - - //checking that the entry name is valid before writing to the secure store - let secret: Secret = entry_name_with_prefix.parse()?; +pub fn save_secret(print: &Print, entry_name: &KeyName, seed_phrase: SeedPhrase) -> Result { + // secure_store:org.stellar.cli: + let entry_name_with_prefix = format!( + "{}{}-{}", + keyring::SECURE_STORE_ENTRY_PREFIX, + keyring::SECURE_STORE_ENTRY_SERVICE, + entry_name + ); - if let Secret::SecureStore { entry_name } = &secret { - Self::write_to_secure_store(entry_name, seed_phrase, print)?; - } + //checking that the entry name is valid before writing to the secure store + let secret: Secret = entry_name_with_prefix.parse()?; - return Ok(secret); + if let Secret::SecureStore { entry_name } = &secret { + write_to_secure_store(entry_name, seed_phrase, print)?; } - fn write_to_secure_store( - entry_name: &String, - seed_phrase: SeedPhrase, - print: &Print, - ) -> Result<(), Error> { - print.infoln(format!("Writing to secure store: {entry_name}")); - let entry = StellarEntry::new(entry_name)?; - Ok(if let Ok(key) = entry.get_public_key(None) { - print.warnln(format!("A key for {entry_name} already exists in your operating system's secure store: {key}")); - } else { - print.infoln(format!( - "Saving a new key to your operating system's secure store: {entry_name}" - )); - entry.set_seed_phrase(seed_phrase)?; - }) - } + return Ok(secret); +} + +fn write_to_secure_store( + entry_name: &String, + seed_phrase: SeedPhrase, + print: &Print, +) -> Result<(), Error> { + print.infoln(format!("Writing to secure store: {entry_name}")); + let entry = StellarEntry::new(entry_name)?; + Ok(if let Ok(key) = entry.get_public_key(None) { + print.warnln(format!("A key for {entry_name} already exists in your operating system's secure store: {key}")); + } else { + print.infoln(format!( + "Saving a new key to your operating system's secure store: {entry_name}" + )); + entry.set_seed_phrase(seed_phrase)?; + }) } \ No newline at end of file From 9601f22cd49c947c753c1467785549d27ddddb14 Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Tue, 14 Jan 2025 14:01:59 -0500 Subject: [PATCH 09/10] Apply formatting updates --- cmd/soroban-cli/src/commands/keys/add.rs | 13 ++++++---- cmd/soroban-cli/src/commands/keys/generate.rs | 9 ++----- cmd/soroban-cli/src/config/locator.rs | 8 ++++++- cmd/soroban-cli/src/config/secret.rs | 4 +++- cmd/soroban-cli/src/signer/secure_store.rs | 24 ++++++++++++++----- 5 files changed, 39 insertions(+), 19 deletions(-) diff --git a/cmd/soroban-cli/src/commands/keys/add.rs b/cmd/soroban-cli/src/commands/keys/add.rs index 271833de3..c31d850fc 100644 --- a/cmd/soroban-cli/src/commands/keys/add.rs +++ b/cmd/soroban-cli/src/commands/keys/add.rs @@ -5,8 +5,13 @@ use sep5::SeedPhrase; use crate::{ commands::global, - config::{address::KeyName, locator, secret::{self, Secret}}, - print::Print, signer::secure_store, + config::{ + address::KeyName, + locator, + secret::{self, Secret}, + }, + print::Print, + signer::secure_store, }; #[derive(thiserror::Error, Debug)] @@ -51,7 +56,7 @@ impl Cmd { fn read_secret(&self, print: &Print) -> Result { if let Ok(secret_key) = std::env::var("SOROBAN_SECRET_KEY") { - return Ok(Secret::SecretKey { secret_key }) + return Ok(Secret::SecretKey { secret_key }); } else if self.secrets.secure_store { let prompt = "Type a 12 or 24 word seed phrase:"; let secret_key = read_password(print, prompt)?; @@ -71,4 +76,4 @@ fn read_password(print: &Print, prompt: &str) -> Result { print.arrowln(prompt); std::io::stdout().flush().map_err(|_| Error::PasswordRead)?; rpassword::read_password().map_err(|_| Error::PasswordRead) -} \ No newline at end of file +} diff --git a/cmd/soroban-cli/src/commands/keys/generate.rs b/cmd/soroban-cli/src/commands/keys/generate.rs index 8f633fe70..f24d90e10 100644 --- a/cmd/soroban-cli/src/commands/keys/generate.rs +++ b/cmd/soroban-cli/src/commands/keys/generate.rs @@ -5,12 +5,7 @@ use super::super::config::{ locator, network, secret::{self, Secret}, }; -use crate::{ - commands::global, - config::address::KeyName, - print::Print, - signer::secure_store, -}; +use crate::{commands::global, config::address::KeyName, print::Print, signer::secure_store}; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -27,7 +22,7 @@ pub enum Error { IdentityAlreadyExists(String), #[error(transparent)] - SecureStore(#[from] secure_store::Error) + SecureStore(#[from] secure_store::Error), } #[derive(Debug, clap::Parser, Clone)] diff --git a/cmd/soroban-cli/src/config/locator.rs b/cmd/soroban-cli/src/config/locator.rs index 24232482f..0d952b035 100644 --- a/cmd/soroban-cli/src/config/locator.rs +++ b/cmd/soroban-cli/src/config/locator.rs @@ -12,7 +12,13 @@ use std::{ }; use stellar_strkey::{Contract, DecodeError}; -use crate::{commands::{global, HEADING_GLOBAL}, print::Print, signer::{self, keyring::StellarEntry}, utils::find_config_dir, Pwd}; +use crate::{ + commands::{global, HEADING_GLOBAL}, + print::Print, + signer::{self, keyring::StellarEntry}, + utils::find_config_dir, + Pwd, +}; use super::{ alias, diff --git a/cmd/soroban-cli/src/config/secret.rs b/cmd/soroban-cli/src/config/secret.rs index e147ae639..e49cb2011 100644 --- a/cmd/soroban-cli/src/config/secret.rs +++ b/cmd/soroban-cli/src/config/secret.rs @@ -6,7 +6,9 @@ use sep5::SeedPhrase; use stellar_strkey::ed25519::{PrivateKey, PublicKey}; use crate::{ - print::Print, signer::{self, keyring, LocalKey, SecureStoreEntry, Signer, SignerKind}, utils + print::Print, + signer::{self, keyring, LocalKey, SecureStoreEntry, Signer, SignerKind}, + utils, }; #[derive(thiserror::Error, Debug)] diff --git a/cmd/soroban-cli/src/signer/secure_store.rs b/cmd/soroban-cli/src/signer/secure_store.rs index 62113a1fc..12c7d41fc 100644 --- a/cmd/soroban-cli/src/signer/secure_store.rs +++ b/cmd/soroban-cli/src/signer/secure_store.rs @@ -1,11 +1,17 @@ use sep5::SeedPhrase; use crate::{ - config::{address::KeyName, locator, secret::{self, Secret}}, print::Print, signer::keyring::{self, StellarEntry} + config::{ + address::KeyName, + locator, + secret::{self, Secret}, + }, + print::Print, + signer::keyring::{self, StellarEntry}, }; #[derive(thiserror::Error, Debug)] -pub enum Error{ +pub enum Error { #[error(transparent)] Config(#[from] locator::Error), @@ -19,10 +25,14 @@ pub enum Error{ DoesNotSupportPrivateKey, #[error(transparent)] - SeedPhrase(#[from] sep5::Error) + SeedPhrase(#[from] sep5::Error), } -pub fn save_secret(print: &Print, entry_name: &KeyName, seed_phrase: SeedPhrase) -> Result { +pub fn save_secret( + print: &Print, + entry_name: &KeyName, + seed_phrase: SeedPhrase, +) -> Result { // secure_store:org.stellar.cli: let entry_name_with_prefix = format!( "{}{}-{}", @@ -49,11 +59,13 @@ fn write_to_secure_store( print.infoln(format!("Writing to secure store: {entry_name}")); let entry = StellarEntry::new(entry_name)?; Ok(if let Ok(key) = entry.get_public_key(None) { - print.warnln(format!("A key for {entry_name} already exists in your operating system's secure store: {key}")); + print.warnln(format!( + "A key for {entry_name} already exists in your operating system's secure store: {key}" + )); } else { print.infoln(format!( "Saving a new key to your operating system's secure store: {entry_name}" )); entry.set_seed_phrase(seed_phrase)?; }) -} \ No newline at end of file +} From c1423c3768483a49bafd27106c387b596ea0576d Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Tue, 14 Jan 2025 14:04:49 -0500 Subject: [PATCH 10/10] Clippy --- cmd/soroban-cli/src/commands/keys/add.rs | 2 +- cmd/soroban-cli/src/signer/secure_store.rs | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cmd/soroban-cli/src/commands/keys/add.rs b/cmd/soroban-cli/src/commands/keys/add.rs index c31d850fc..409409fa1 100644 --- a/cmd/soroban-cli/src/commands/keys/add.rs +++ b/cmd/soroban-cli/src/commands/keys/add.rs @@ -56,7 +56,7 @@ impl Cmd { fn read_secret(&self, print: &Print) -> Result { if let Ok(secret_key) = std::env::var("SOROBAN_SECRET_KEY") { - return Ok(Secret::SecretKey { secret_key }); + Ok(Secret::SecretKey { secret_key }) } else if self.secrets.secure_store { let prompt = "Type a 12 or 24 word seed phrase:"; let secret_key = read_password(print, prompt)?; diff --git a/cmd/soroban-cli/src/signer/secure_store.rs b/cmd/soroban-cli/src/signer/secure_store.rs index 12c7d41fc..e659e7f63 100644 --- a/cmd/soroban-cli/src/signer/secure_store.rs +++ b/cmd/soroban-cli/src/signer/secure_store.rs @@ -48,7 +48,7 @@ pub fn save_secret( write_to_secure_store(entry_name, seed_phrase, print)?; } - return Ok(secret); + Ok(secret) } fn write_to_secure_store( @@ -58,7 +58,7 @@ fn write_to_secure_store( ) -> Result<(), Error> { print.infoln(format!("Writing to secure store: {entry_name}")); let entry = StellarEntry::new(entry_name)?; - Ok(if let Ok(key) = entry.get_public_key(None) { + if let Ok(key) = entry.get_public_key(None) { print.warnln(format!( "A key for {entry_name} already exists in your operating system's secure store: {key}" )); @@ -67,5 +67,6 @@ fn write_to_secure_store( "Saving a new key to your operating system's secure store: {entry_name}" )); entry.set_seed_phrase(seed_phrase)?; - }) + }; + Ok(()) }