Skip to content

Commit 67d9d5d

Browse files
modularize
1 parent 1526e19 commit 67d9d5d

File tree

8 files changed

+347
-320
lines changed

8 files changed

+347
-320
lines changed

bin/icp-cli/src/commands/identity/default.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ pub struct DefaultCmd {
1010
}
1111

1212
pub fn exec(env: &Env, cmd: DefaultCmd) -> Result<DefaultIdentityMessage, DefaultIdentityError> {
13-
let list = icp_identity::load_identity_list(env.dirs())?;
13+
let list = icp_identity::manifest::load_identity_list(env.dirs())?;
1414
ensure!(
1515
list.identities.contains_key(&cmd.name),
1616
NoSuchIdentitySnafu { name: &cmd.name }
1717
);
18-
let mut defaults = icp_identity::load_identity_defaults(env.dirs())?;
18+
let mut defaults = icp_identity::manifest::load_identity_defaults(env.dirs())?;
1919
defaults.default = cmd.name.clone();
20-
icp_identity::write_identity_defaults(env.dirs(), &defaults)?;
20+
icp_identity::manifest::write_identity_defaults(env.dirs(), &defaults)?;
2121
Ok(DefaultIdentityMessage {
2222
default: cmd.name.clone(),
2323
})

bin/icp-cli/src/commands/identity/import.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ use camino::{Utf8Path, Utf8PathBuf};
66
use clap::{ArgGroup, Parser};
77
use dialoguer::Password;
88
use icp_fs::fs;
9-
use icp_identity::{CreateFormat, CreateIdentityError, IdentityKey};
9+
use icp_identity::{
10+
CreateIdentityError,
11+
key::{CreateFormat, IdentityKey},
12+
};
1013
use itertools::Itertools;
1114
use k256::{Secp256k1, SecretKey};
1215
use parse_display::Display;
@@ -122,7 +125,7 @@ fn import_from_pem(
122125
)?,
123126
_ => unreachable!(),
124127
};
125-
icp_identity::create_identity(
128+
icp_identity::key::create_identity(
126129
env.dirs(),
127130
name,
128131
IdentityKey::Secp256k1(key),
@@ -229,7 +232,7 @@ fn import_from_seed_phrase(env: &Env, name: &str, phrase: &str) -> Result<(), De
229232
let seed = Seed::new(&mnemonic, "");
230233
let pk = XPrv::derive_from_path(seed.as_bytes(), &path).context(DerivationSnafu)?;
231234
let key = SecretKey::from(pk.private_key());
232-
icp_identity::create_identity(
235+
icp_identity::key::create_identity(
233236
env.dirs(),
234237
name,
235238
IdentityKey::Secp256k1(key),

bin/icp-cli/src/commands/identity/list.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ use std::fmt::{self, Display, Formatter};
99
pub struct ListCmd;
1010

1111
pub fn exec(env: &Env, _cmd: ListCmd) -> Result<ListKeysMessage, ListKeysError> {
12-
let list = icp_identity::load_identity_list(env.dirs())?;
13-
let defaults = icp_identity::load_identity_defaults(env.dirs())?;
12+
let list = icp_identity::manifest::load_identity_list(env.dirs())?;
13+
let defaults = icp_identity::manifest::load_identity_defaults(env.dirs())?;
1414
Ok(ListKeysMessage {
1515
identities: list.identities.into_keys().collect_vec(),
1616
default: defaults.default,

bin/icp-cli/src/commands/identity/new.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ use bip39::{Language, Mnemonic, MnemonicType, Seed};
55
use camino::Utf8PathBuf;
66
use clap::Parser;
77
use icp_fs::fs;
8-
use icp_identity::{CreateFormat, CreateIdentityError, IdentityKey};
8+
use icp_identity::{
9+
CreateIdentityError,
10+
key::{CreateFormat, IdentityKey},
11+
};
912
use k256::SecretKey;
1013
use parse_display::Display;
1114
use serde::Serialize;
@@ -24,7 +27,7 @@ pub fn exec(env: &Env, cmd: NewCmd) -> Result<NewIdentityMessage, NewIdentityErr
2427
let seed = Seed::new(&mnemonic, "");
2528
let pk = XPrv::derive_from_path(seed.as_bytes(), &path).context(DerivationSnafu)?;
2629
let key = SecretKey::from(pk.private_key());
27-
icp_identity::create_identity(
30+
icp_identity::key::create_identity(
2831
env.dirs(),
2932
&cmd.name,
3033
IdentityKey::Secp256k1(key),

bin/icp-cli/src/env.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,14 @@ impl Env {
3939
}
4040
pub fn load_identity(&self) -> Result<Arc<dyn Identity>, LoadIdentityError> {
4141
if let Some(identity) = &self.identity {
42-
icp_identity::load_identity(
42+
icp_identity::key::load_identity(
4343
&self.dirs,
44-
&icp_identity::load_identity_list(&self.dirs)?,
44+
&icp_identity::manifest::load_identity_list(&self.dirs)?,
4545
identity,
4646
|| todo!(),
4747
)
4848
} else {
49-
icp_identity::load_identity_in_context(&self.dirs, || todo!())
49+
icp_identity::key::load_identity_in_context(&self.dirs, || todo!())
5050
}
5151
}
5252
pub fn dirs(&self) -> &IcpCliDirs {

lib/icp-identity/src/key.rs

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
use crate::{
2+
CreateIdentityError, LoadIdentityError, WriteIdentityError,
3+
manifest::{
4+
IdentityKeyAlgorithm, IdentityList, IdentitySpec, PemFormat, load_identity_defaults,
5+
load_identity_list, write_identity_list,
6+
},
7+
s_create::*,
8+
s_load::*,
9+
};
10+
use ic_agent::{
11+
Identity,
12+
identity::{AnonymousIdentity, Secp256k1Identity},
13+
};
14+
use icp_dirs::IcpCliDirs;
15+
use icp_fs::fs;
16+
use pem::Pem;
17+
use pkcs8::{
18+
DecodePrivateKey, EncodePrivateKey, EncryptedPrivateKeyInfo, PrivateKeyInfo, SecretDocument,
19+
pkcs5::pbes2::Parameters,
20+
};
21+
use rand::RngCore;
22+
use scrypt::Params;
23+
use sec1::{der::Decode, pem::PemLabel};
24+
use snafu::{OptionExt, ResultExt, ensure};
25+
use std::{
26+
path::{Path, PathBuf},
27+
sync::Arc,
28+
};
29+
use zeroize::Zeroizing;
30+
31+
pub fn load_identity(
32+
dirs: &IcpCliDirs,
33+
list: &IdentityList,
34+
name: &str,
35+
password_func: impl FnOnce() -> Result<String, String>,
36+
) -> Result<Arc<dyn Identity>, LoadIdentityError> {
37+
// todo support p256, ed25519
38+
let identity = list
39+
.identities
40+
.get(name)
41+
.context(NoSuchIdentitySnafu { name })?;
42+
match identity {
43+
IdentitySpec::Pem { format, algorithm } => {
44+
load_pem_identity(dirs, name, format, algorithm, password_func)
45+
}
46+
IdentitySpec::Anonymous => Ok(Arc::new(AnonymousIdentity)),
47+
}
48+
}
49+
50+
fn load_pem_identity(
51+
dirs: &IcpCliDirs,
52+
name: &str,
53+
format: &PemFormat,
54+
algorithm: &IdentityKeyAlgorithm,
55+
password_func: impl FnOnce() -> Result<String, String>,
56+
) -> Result<Arc<dyn Identity>, LoadIdentityError> {
57+
let pem_path = key_pem_path(dirs, name);
58+
let pem = fs::read_to_string(&pem_path)?;
59+
let doc = pem
60+
.parse::<Pem>()
61+
.context(ParsePemSnafu { path: &pem_path })?;
62+
match format {
63+
PemFormat::Pbes2 => load_pbes2_identity(&doc, algorithm, password_func, &pem_path),
64+
PemFormat::Plaintext => load_plaintext_identity(&doc, algorithm, &pem_path),
65+
}
66+
}
67+
68+
fn load_pbes2_identity(
69+
doc: &Pem,
70+
algorithm: &IdentityKeyAlgorithm,
71+
password_func: impl FnOnce() -> Result<String, String>,
72+
path: &Path,
73+
) -> Result<Arc<dyn Identity>, LoadIdentityError> {
74+
assert!(
75+
doc.tag() == pkcs8::EncryptedPrivateKeyInfo::PEM_LABEL,
76+
"internal error: wrong identity format found"
77+
);
78+
let password =
79+
password_func().map_err(|message| LoadIdentityError::GetPasswordError { message })?;
80+
match algorithm {
81+
IdentityKeyAlgorithm::Secp256k1 => {
82+
let key = k256::SecretKey::from_pkcs8_encrypted_der(doc.contents(), &password)
83+
.context(ParseKeySnafu { path })?;
84+
Ok(Arc::new(Secp256k1Identity::from_private_key(key)))
85+
}
86+
}
87+
}
88+
89+
fn load_plaintext_identity(
90+
doc: &Pem,
91+
algorithm: &IdentityKeyAlgorithm,
92+
path: &Path,
93+
) -> Result<Arc<dyn Identity>, LoadIdentityError> {
94+
assert!(
95+
doc.tag() == PrivateKeyInfo::PEM_LABEL,
96+
"internal error: wrong identity format found"
97+
);
98+
match algorithm {
99+
IdentityKeyAlgorithm::Secp256k1 => {
100+
let key =
101+
k256::SecretKey::from_pkcs8_der(doc.contents()).context(ParseKeySnafu { path })?;
102+
Ok(Arc::new(Secp256k1Identity::from_private_key(key)))
103+
}
104+
}
105+
}
106+
107+
pub fn key_pem_path(dirs: &IcpCliDirs, name: &str) -> PathBuf {
108+
dirs.identity_dir().join(format!("keys/{name}.pem"))
109+
}
110+
111+
pub fn load_identity_in_context(
112+
dirs: &IcpCliDirs,
113+
password_func: impl FnOnce() -> Result<String, String>,
114+
) -> Result<Arc<dyn Identity>, LoadIdentityError> {
115+
let defaults = load_identity_defaults(dirs)?;
116+
let list = load_identity_list(dirs)?;
117+
load_identity(dirs, &list, &defaults.default, password_func)
118+
}
119+
120+
pub fn create_identity(
121+
dirs: &IcpCliDirs,
122+
name: &str,
123+
key: IdentityKey,
124+
format: CreateFormat,
125+
) -> Result<(), CreateIdentityError> {
126+
let algorithm = match key {
127+
IdentityKey::Secp256k1(_) => IdentityKeyAlgorithm::Secp256k1,
128+
};
129+
let pem_format = match format {
130+
CreateFormat::Plaintext => PemFormat::Plaintext,
131+
CreateFormat::Pbes2 { .. } => PemFormat::Pbes2,
132+
};
133+
let spec = IdentitySpec::Pem {
134+
format: pem_format,
135+
algorithm,
136+
};
137+
let mut identity_list = load_identity_list(dirs)?;
138+
ensure!(
139+
!identity_list.identities.contains_key(name),
140+
IdentityAlreadyExistsSnafu { name }
141+
);
142+
let doc = match key {
143+
IdentityKey::Secp256k1(key) => key.to_pkcs8_der().expect("infallible PKI encoding"),
144+
};
145+
let pem = match format {
146+
CreateFormat::Plaintext => doc
147+
.to_pem(PrivateKeyInfo::PEM_LABEL, Default::default())
148+
.expect("infallible PKI encoding"),
149+
CreateFormat::Pbes2 { password } => make_pkcs5_encrypted_pem(&doc, &password),
150+
};
151+
let pem_path = key_pem_path(dirs, name);
152+
let parent = pem_path.parent().unwrap();
153+
fs::create_dir_all(parent).map_err(WriteIdentityError::from)?;
154+
fs::write(&pem_path, pem.as_bytes()).map_err(WriteIdentityError::from)?;
155+
identity_list.identities.insert(name.to_string(), spec);
156+
write_identity_list(dirs, &identity_list)?;
157+
Ok(())
158+
}
159+
160+
fn make_pkcs5_encrypted_pem(doc: &SecretDocument, password: &str) -> Zeroizing<String> {
161+
let pki = PrivateKeyInfo::from_der(doc.as_bytes()).expect("infallible PKI roundtrip");
162+
let mut salt = [0; 16];
163+
let mut iv = [0; 16];
164+
let mut rng = rand::rng();
165+
rng.fill_bytes(&mut salt);
166+
rng.fill_bytes(&mut iv);
167+
let encrypted_doc = pki
168+
.encrypt_with_params(
169+
Parameters::scrypt_aes256cbc(
170+
Params::new(17, 8, 1, 32).expect("valid scrypt params"),
171+
&salt,
172+
&iv,
173+
)
174+
.expect("valid pbes2 params"),
175+
password,
176+
)
177+
.expect("infallible PKI encryption");
178+
encrypted_doc
179+
.to_pem(EncryptedPrivateKeyInfo::PEM_LABEL, Default::default())
180+
.expect("infallible EPKI encoding")
181+
}
182+
183+
#[derive(Debug, Clone)]
184+
pub enum IdentityKey {
185+
Secp256k1(k256::SecretKey),
186+
}
187+
188+
#[derive(Debug, Clone)]
189+
pub enum CreateFormat {
190+
Plaintext,
191+
Pbes2 { password: Zeroizing<String> },
192+
// Keyring,
193+
}

0 commit comments

Comments
 (0)