Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/os keychain followup #1770

Open
wants to merge 8 commits into
base: feat/os_keychain
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions FULL_HELP_DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -959,6 +959,8 @@ Add a new identity (keypair, ledger, OS specific secure store)

* `--secret-key` — Add using `secret_key` Can provide with `SOROBAN_SECRET_KEY`
* `--seed-phrase` — Add using 12 word seed phrase to generate `secret_key`
* `--secure-store` — Add using a key saved in a secure store entry. Requires the entry name to be provided with `--entry_name`
* `--entry-name <ENTRY_NAME>` — Name of the secure store entry, to be used with `--secure_store`
* `--global` — Use global config
* `--config-dir <CONFIG_DIR>` — Location of config directory, default is "."

Expand Down Expand Up @@ -1006,7 +1008,9 @@ Fund an identity on a test network

## `stellar keys generate`

Generate a new identity with a seed phrase, currently 12 words
Generate a new identity with a seed phrase, currently 12 words.

The identity's secret can be stored in a config file (default), in an OS-specific secure store, or be printed out to the console.

**Usage:** `stellar keys generate [OPTIONS] <NAME>`

Expand Down Expand Up @@ -2014,7 +2018,7 @@ Sign a transaction envelope appending the signature to the envelope

###### **Options:**

* `--sign-with-key <SIGN_WITH_KEY>` — 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-key <SIGN_WITH_KEY>` — Sign with a local key or a key saved in OS's 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
* `--hd-path <HD_PATH>` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0`
* `--sign-with-lab` — Sign with https://lab.stellar.org
* `--rpc-url <RPC_URL>` — RPC server endpoint
Expand Down
6 changes: 4 additions & 2 deletions cmd/soroban-cli/src/commands/keys/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ pub enum Cmd {
/// Fund an identity on a test network
Fund(fund::Cmd),

/// Generate a new identity with a seed phrase, currently 12 words
/// Generate a new identity with a seed phrase, currently 12 words.
///
/// The identity's secret can be stored in a config file (default), in an OS-specific secure store, or be printed out to the console.
Generate(generate::Cmd),

/// List identities
Expand Down Expand Up @@ -75,7 +77,7 @@ impl Cmd {
Cmd::Fund(cmd) => cmd.run().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::Show(cmd) => cmd.run()?,
Cmd::Default(cmd) => cmd.run(global_args)?,
};
Expand Down
6 changes: 4 additions & 2 deletions cmd/soroban-cli/src/commands/keys/rm.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use clap::command;

use crate::commands::global;

use super::super::config::locator;

#[derive(thiserror::Error, Debug)]
Expand All @@ -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)?)
}
}
29 changes: 27 additions & 2 deletions cmd/soroban-cli/src/config/locator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@ 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,
Expand Down Expand Up @@ -83,6 +89,8 @@ pub enum Error {
UpgradeCheckReadFailed { path: PathBuf, error: io::Error },
#[error("Failed to write upgrade check file: {path}: {error}")]
UpgradeCheckWriteFailed { path: PathBuf, error: io::Error },
#[error(transparent)]
Keyring(#[from] signer::keyring::Error),
}

#[derive(Debug, clap::Args, Default, Clone)]
Expand Down Expand Up @@ -253,7 +261,24 @@ 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_password() {
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()?)
}

Expand Down
29 changes: 27 additions & 2 deletions cmd/soroban-cli/src/config/secret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,25 @@ pub enum Error {
pub struct Args {
/// Add using `secret_key`
/// Can provide with `SOROBAN_SECRET_KEY`
#[arg(long, conflicts_with = "seed_phrase")]
#[arg(long, conflicts_with = "seed_phrase", conflicts_with = "secure_store")]
pub secret_key: bool,

/// Add using 12 word seed phrase to generate `secret_key`
#[arg(long, conflicts_with = "secret_key")]
#[arg(long, conflicts_with = "secret_key", conflicts_with = "secure_store")]
pub seed_phrase: bool,

/// Add using a key saved in a secure store entry. Requires the entry name to be provided with `--entry_name`
#[arg(
long,
requires = "entry_name",
conflicts_with = "seed_phrase",
conflicts_with = "secret_key"
)]
pub secure_store: bool,

/// Name of the secure store entry, to be used with `--secure_store`
#[arg(long, requires = "secure_store")]
pub entry_name: Option<String>,
}

impl Args {
Expand Down Expand Up @@ -71,6 +85,17 @@ impl Args {
.collect::<Vec<_>>()
.join(" "),
})
} else if self.secure_store {
let entry_name_with_prefix = format!(
"{}{}-{}",
keyring::SECURE_STORE_ENTRY_PREFIX,
keyring::SECURE_STORE_ENTRY_SERVICE,
self.entry_name.as_ref().unwrap()
);

Ok(Secret::SecureStore {
entry_name: entry_name_with_prefix,
})
} else {
Err(Error::PasswordRead {})
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/soroban-cli/src/config/sign_with.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 a key saved in OS's 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<String>,

Expand Down
31 changes: 30 additions & 1 deletion cmd/soroban-cli/src/signer/keyring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ impl StellarEntry {
Ok(base64.decode(self.keyring.get_password()?)?)
}

pub fn delete_password(&self) -> Result<(), Error> {
Ok(self.keyring.delete_credential()?)
}

fn use_key<T>(
&self,
f: impl FnOnce(ed25519_dalek::SigningKey) -> Result<T, Error>,
Expand Down Expand Up @@ -116,7 +120,7 @@ mod test {
fn test_sign_data() {
set_default_credential_builder(mock::default_credential_builder());

//create a secret
// create a secret
let secret = crate::config::secret::Secret::from_seed(None).unwrap();
let key_pair = secret.key_pair(None).unwrap();

Expand All @@ -129,4 +133,29 @@ mod test {
let sign_tx_env_result = entry.sign_data(tx_xdr.as_bytes());
assert!(sign_tx_env_result.is_ok());
}

#[test]
fn test_delete_password() {
set_default_credential_builder(mock::default_credential_builder());

// add a keyring entry
let entry = StellarEntry::new("test").unwrap();
entry.set_password("test password".as_bytes()).unwrap();

// assert it is there
let get_password_result = entry.get_password();
assert!(get_password_result.is_ok());

// delete the password
let delete_password_result = entry.delete_password();
assert!(delete_password_result.is_ok());

// confirm the entry is gone
let get_password_result = entry.get_password();
assert!(get_password_result.is_err());
assert!(matches!(
get_password_result.unwrap_err(),
Error::Keyring(_)
));
}
}
Loading