Skip to content

Commit 543a252

Browse files
Add storage mode parameter
1 parent 615130d commit 543a252

File tree

6 files changed

+342
-92
lines changed

6 files changed

+342
-92
lines changed

crates/icp-cli/src/commands/identity/link/ii.rs

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,51 @@
11
use clap::Args;
2+
use dialoguer::Password;
3+
use elliptic_curve::zeroize::Zeroizing;
24
use ic_agent::{Identity as _, export::Principal, identity::BasicIdentity};
3-
use icp::{context::Context, identity::key};
5+
use icp::{context::Context, fs::read_to_string, identity::key, prelude::*};
46
use snafu::{ResultExt, Snafu};
5-
use tracing::info;
7+
use tracing::{info, warn};
68

7-
use crate::operations::ii_poll;
9+
use crate::{commands::identity::StorageMode, operations::ii_poll};
810

911
/// Link an Internet Identity to a new identity
1012
#[derive(Debug, Args)]
1113
pub(crate) struct IiArgs {
1214
/// Name for the linked identity
1315
name: String,
16+
17+
/// Where to store the session private key
18+
#[arg(long, value_enum, default_value_t)]
19+
storage: StorageMode,
20+
21+
/// Read the storage password from a file instead of prompting (for --storage password)
22+
#[arg(long, value_name = "FILE")]
23+
storage_password_file: Option<PathBuf>,
1424
}
1525

1626
pub(crate) async fn exec(ctx: &Context, args: &IiArgs) -> Result<(), IiError> {
27+
let create_format = match args.storage {
28+
StorageMode::Plaintext => key::CreateFormat::Plaintext,
29+
StorageMode::Keyring => key::CreateFormat::Keyring,
30+
StorageMode::Password => {
31+
let password = if let Some(path) = &args.storage_password_file {
32+
read_to_string(path)
33+
.context(ReadStoragePasswordFileSnafu)?
34+
.trim()
35+
.to_string()
36+
} else {
37+
Password::new()
38+
.with_prompt("Enter password to encrypt identity")
39+
.with_confirmation("Confirm password", "Passwords do not match")
40+
.interact()
41+
.context(StoragePasswordTermReadSnafu)?
42+
};
43+
key::CreateFormat::Pbes2 {
44+
password: Zeroizing::new(password),
45+
}
46+
}
47+
};
48+
1749
let secret_key = ic_ed25519::PrivateKey::generate();
1850
let identity_key = key::IdentityKey::Ed25519(secret_key.clone());
1951
let basic = BasicIdentity::from_raw_key(&secret_key.serialize_raw());
@@ -29,18 +61,37 @@ pub(crate) async fn exec(ctx: &Context, args: &IiArgs) -> Result<(), IiError> {
2961
ctx.dirs
3062
.identity()?
3163
.with_write(async |dirs| {
32-
key::link_ii_identity(dirs, &args.name, identity_key, &chain, ii_principal)
64+
key::link_ii_identity(
65+
dirs,
66+
&args.name,
67+
identity_key,
68+
&chain,
69+
ii_principal,
70+
create_format,
71+
)
3372
})
3473
.await?
3574
.context(LinkSnafu)?;
3675

3776
info!("Identity `{}` linked to Internet Identity", args.name);
3877

78+
if matches!(args.storage, StorageMode::Plaintext) {
79+
warn!(
80+
"This identity is stored in plaintext and is not secure. Do not use it for anything of significant value."
81+
);
82+
}
83+
3984
Ok(())
4085
}
4186

4287
#[derive(Debug, Snafu)]
4388
pub(crate) enum IiError {
89+
#[snafu(display("failed to read storage password file"))]
90+
ReadStoragePasswordFile { source: icp::fs::IoError },
91+
92+
#[snafu(display("failed to read storage password from terminal"))]
93+
StoragePasswordTermRead { source: dialoguer::Error },
94+
4495
#[snafu(display("failed during II authentication"))]
4596
Poll { source: ii_poll::IiPollError },
4697

crates/icp-cli/src/commands/identity/login.rs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use clap::Args;
2+
use dialoguer::Password;
23
use icp::{
34
context::Context,
45
identity::{
@@ -19,7 +20,7 @@ pub(crate) struct LoginArgs {
1920
}
2021

2122
pub(crate) async fn exec(ctx: &Context, args: &LoginArgs) -> Result<(), LoginError> {
22-
let algorithm = ctx
23+
let (algorithm, storage) = ctx
2324
.dirs
2425
.identity()?
2526
.with_read(async |dirs| {
@@ -29,14 +30,27 @@ pub(crate) async fn exec(ctx: &Context, args: &LoginArgs) -> Result<(), LoginErr
2930
.get(&args.name)
3031
.context(IdentityNotFoundSnafu { name: &args.name })?;
3132
match spec {
32-
IdentitySpec::InternetIdentity { algorithm, .. } => Ok(algorithm.clone()),
33+
IdentitySpec::InternetIdentity {
34+
algorithm, storage, ..
35+
} => Ok((algorithm.clone(), storage.clone())),
3336
_ => NotIiSnafu { name: &args.name }.fail(),
3437
}
3538
})
3639
.await??;
3740

38-
let der_public_key =
39-
key::load_ii_session_public_key(&args.name, &algorithm).context(LoadSessionKeySnafu)?;
41+
let der_public_key = ctx
42+
.dirs
43+
.identity()?
44+
.with_read(async |dirs| {
45+
key::load_ii_session_public_key(dirs, &args.name, &algorithm, &storage, || {
46+
Password::new()
47+
.with_prompt("Enter identity password")
48+
.interact()
49+
.map_err(|e| e.to_string())
50+
})
51+
})
52+
.await?
53+
.context(LoadSessionKeySnafu)?;
4054

4155
let chain = ii_poll::poll_for_delegation(&der_public_key)
4256
.await
@@ -71,7 +85,7 @@ pub(crate) enum LoginError {
7185
))]
7286
NotIi { name: String },
7387

74-
#[snafu(display("failed to load II session key from keyring"))]
88+
#[snafu(display("failed to load II session key"))]
7589
LoadSessionKey { source: key::LoadIdentityError },
7690

7791
#[snafu(display("failed during II authentication"))]

crates/icp-cli/src/operations/ii_poll.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ use tokio::{net::TcpListener, sync::oneshot};
1515
use url::Url;
1616

1717
/// The hosted II login frontend for the dev account canister.
18-
/// `ut7yr-7iaaa-aaaag-ak7ca-cai` (text) = canister on the IC mainnet.
1918
const CLI_LOGIN_BASE: &str = "https://ut7yr-7iaaa-aaaag-ak7ca-cai.icp0.io/cli-login";
2019

2120
#[derive(Debug, Snafu)]

0 commit comments

Comments
 (0)