Skip to content

Commit c055eac

Browse files
feat: add support for OS specific keychains (#1703)
* feat: initial work into system keychain * chore: clean up * Add KeyName struct in address * Add Secret::Keychain * keys generate: allow for generating keys that are stored in keychain * keys generate: Namespace keychain entry to identity name * keys generate: don't allow 'keychain:' as a key name * keys address: use keychain entry in secret to get the pub key * tx sign: allow a keychain identity sign a tx * Cleanup * Use keyring mock for generate tests * Refactor keyring: add keyring entry as StellarEntry field - previously we were creating a new keyring entry for each interaction with the keyring - this change will allow us use a mock keyring entry for testing * Add tests for keyring * Update config/secret tests * Cleanup * Rename keychain arg to secure_store in generate * Rename Secret::Keychain to Secret::SecureStore * Rename SignerKind::Keychain to SignerKind::SecureStore * Use print for new fns in generate * Return error when trying to get Secure Store secret * Cleanup tests * Install libdbus for rpc-tests and bindings-ts workflows required for keyring crate * Update generated docs * Install libdbus for binaries workflow when target aarch64-unknown-linux-gnu * Clippy * Install libdbus for rust workflow * Install libdbus-1-dev in binaries workflow for build step * Impl Display for KeyName this change was made so that we can concat the KeyName with secure story prefix and service * Use resolve_muxed_account in resolve_secret * Use resolve_muxed_account to get public key * Clippy * fix: Sign tx hash instead of tx env with keychain * Remove unused bin/secret * Fix after merging with main * Apply suggestion from code review Co-authored-by: Willem Wyndham <willem@ahalabs.dev> * Limit key name length * Update public_key to work with secure storage keys * fix(address): remove private key function & use unresolved Address This simplifies the lookup of the address. * feat: store seedphrase instead of private key This will allow for exporting the phrase later * fix: clean up --------- Co-authored-by: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com>
1 parent c928006 commit c055eac

File tree

16 files changed

+615
-75
lines changed

16 files changed

+615
-75
lines changed

.github/workflows/binaries.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ jobs:
4646
- run: rustup target add ${{ matrix.sys.target }}
4747

4848
- if: matrix.sys.target == 'aarch64-unknown-linux-gnu'
49-
run: sudo apt-get update && sudo apt-get -y install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu libudev-dev
49+
run: sudo apt-get update && sudo apt-get -y install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu libudev-dev libdbus-1-dev
5050

5151
- name: Setup vars
5252
run: |
@@ -69,6 +69,7 @@ jobs:
6969
env:
7070
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
7171
working-directory: ${{ env.BUILD_WORKING_DIR }}
72+
run: sudo apt-get update && sudo apt-get -y install libdbus-1-dev
7273
run: cargo build --target-dir="$GITHUB_WORKSPACE/target" --package ${{ matrix.crate.name }} --features opt --release --target ${{ matrix.sys.target }}
7374

7475
- name: Build provenance for attestation (release only)

.github/workflows/bindings-ts.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ jobs:
3838
target/
3939
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
4040
- run: rustup update
41+
- run: sudo apt install -y libdbus-1-dev
4142
- run: cargo build
4243
- run: rustup target add wasm32-unknown-unknown
4344
- run: make build-test-wasms

.github/workflows/rpc-tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ jobs:
3939
target/
4040
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
4141
- run: rustup update
42+
- run: sudo apt install -y libdbus-1-dev
4243
- run: cargo build
4344
- run: rustup target add wasm32-unknown-unknown
4445
- run: make build-test-wasms

.github/workflows/rust.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ jobs:
4949
- uses: actions/checkout@v4
5050
- uses: stellar/actions/rust-cache@main
5151
- run: rustup update
52+
- run: sudo apt install -y libdbus-1-dev
5253
- run: make generate-full-help-doc
5354
- run: git add -N . && git diff HEAD --exit-code
5455

@@ -90,7 +91,7 @@ jobs:
9091
- run: rustup target add ${{ matrix.sys.target }}
9192
- run: rustup target add wasm32-unknown-unknown
9293
- if: runner.os == 'Linux'
93-
run: sudo apt-get update && sudo apt-get -y install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu libudev-dev
94+
run: sudo apt-get update && sudo apt-get -y install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu libudev-dev libdbus-1-dev
9495
- run: cargo clippy --all-targets --target ${{ matrix.sys.target }}
9596
- run: make test
9697
env:

Cargo.lock

Lines changed: 111 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

FULL_HELP_DOCS.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -938,7 +938,7 @@ Create and manage identities including keys and addresses
938938

939939
###### **Subcommands:**
940940

941-
* `add` — Add a new identity (keypair, ledger, macOS keychain)
941+
* `add` — Add a new identity (keypair, ledger, OS specific secure store)
942942
* `address` — Given an identity return its address (public key)
943943
* `fund` — Fund an identity on a test network
944944
* `generate` — Generate a new identity with a seed phrase, currently 12 words
@@ -951,7 +951,7 @@ Create and manage identities including keys and addresses
951951

952952
## `stellar keys add`
953953

954-
Add a new identity (keypair, ledger, macOS keychain)
954+
Add a new identity (keypair, ledger, OS specific secure store)
955955

956956
**Usage:** `stellar keys add [OPTIONS] <NAME>`
957957

@@ -1023,6 +1023,7 @@ Generate a new identity with a seed phrase, currently 12 words
10231023
* `--no-fund` — Do not fund address
10241024
* `--seed <SEED>` — Optional seed to use when generating seed phrase. Random otherwise
10251025
* `-s`, `--as-secret` — Output the generated identity as a secret key
1026+
* `--secure-store` — Save in OS-specific secure store
10261027
* `--global` — Use global config
10271028
* `--config-dir <CONFIG_DIR>` — Location of config directory, default is "."
10281029
* `--hd-path <HD_PATH>` — When generating a secret key, which `hd_path` should be used from the original `seed_phrase`

cmd/soroban-cli/Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ rand = "0.8.5"
7272
wasmparser = { workspace = true }
7373
sha2 = { workspace = true }
7474
csv = "1.1.6"
75-
ed25519-dalek = { workspace = true }
75+
# zeroize feature ensures that all sensitive data is zeroed out when dropped
76+
ed25519-dalek = { workspace = true, features = ["zeroize"] }
7677
reqwest = { version = "0.12.7", default-features = false, features = [
7778
"rustls-tls",
7879
"http2",
@@ -124,6 +125,10 @@ fqdn = "0.3.12"
124125
open = "5.3.0"
125126
url = "2.5.2"
126127
wasm-gen = "0.1.4"
128+
zeroize = "1.8.1"
129+
keyring = { version = "3", features = ["apple-native", "windows-native", "sync-secret-service"] }
130+
whoami = "1.5.2"
131+
127132

128133
[build-dependencies]
129134
crate-git-revision = "0.0.6"

cmd/soroban-cli/src/commands/contract/arg_parsing.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -269,10 +269,5 @@ fn resolve_address(addr_or_alias: &str, config: &config::Args) -> Result<String,
269269
}
270270

271271
fn resolve_signer(addr_or_alias: &str, config: &config::Args) -> Option<SigningKey> {
272-
let cmd = crate::commands::keys::address::Cmd {
273-
name: addr_or_alias.to_string(),
274-
hd_path: Some(0),
275-
locator: config.locator.clone(),
276-
};
277-
cmd.private_key().ok()
272+
config.locator.key(addr_or_alias).ok()?.key_pair(None).ok()
278273
}

cmd/soroban-cli/src/commands/keys/add.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use clap::command;
22

33
use crate::{
44
commands::global,
5-
config::{locator, secret},
5+
config::{address::KeyName, locator, secret},
66
print::Print,
77
};
88

@@ -19,7 +19,7 @@ pub enum Error {
1919
#[group(skip)]
2020
pub struct Cmd {
2121
/// Name of identity
22-
pub name: String,
22+
pub name: KeyName,
2323

2424
#[command(flatten)]
2525
pub secrets: secret::Args,
Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,21 @@
1-
use crate::commands::config::secret;
2-
3-
use super::super::config::locator;
41
use clap::arg;
52

3+
use crate::{
4+
commands::config::{address, locator},
5+
config::UnresolvedMuxedAccount,
6+
};
7+
68
#[derive(thiserror::Error, Debug)]
79
pub enum Error {
810
#[error(transparent)]
9-
Config(#[from] locator::Error),
10-
11-
#[error(transparent)]
12-
Secret(#[from] secret::Error),
13-
14-
#[error(transparent)]
15-
StrKey(#[from] stellar_strkey::DecodeError),
11+
Address(#[from] address::Error),
1612
}
1713

1814
#[derive(Debug, clap::Parser, Clone)]
1915
#[group(skip)]
2016
pub struct Cmd {
2117
/// Name of identity to lookup, default test identity used if not provided
22-
pub name: String,
18+
pub name: UnresolvedMuxedAccount,
2319

2420
/// If identity is a seed phrase use this hd path, default is 0
2521
#[arg(long)]
@@ -35,20 +31,14 @@ impl Cmd {
3531
Ok(())
3632
}
3733

38-
pub fn private_key(&self) -> Result<ed25519_dalek::SigningKey, Error> {
39-
Ok(self
40-
.locator
41-
.read_identity(&self.name)?
42-
.key_pair(self.hd_path)?)
43-
}
44-
4534
pub fn public_key(&self) -> Result<stellar_strkey::ed25519::PublicKey, Error> {
46-
if let Ok(key) = stellar_strkey::ed25519::PublicKey::from_string(&self.name) {
47-
Ok(key)
48-
} else {
49-
Ok(stellar_strkey::ed25519::PublicKey::from_payload(
50-
self.private_key()?.verifying_key().as_bytes(),
51-
)?)
52-
}
35+
let muxed = self
36+
.name
37+
.resolve_muxed_account(&self.locator, self.hd_path)?;
38+
let bytes = match muxed {
39+
soroban_sdk::xdr::MuxedAccount::Ed25519(uint256) => uint256.0,
40+
soroban_sdk::xdr::MuxedAccount::MuxedEd25519(muxed_account) => muxed_account.ed25519.0,
41+
};
42+
Ok(stellar_strkey::ed25519::PublicKey(bytes))
5343
}
5444
}

0 commit comments

Comments
 (0)