1use super::pem_utils::validate_pem_file;
2use super::{WALLET_CONFIG_FILENAME, keyring_mock};
3use crate::config::directories::get_user_dfx_config_dir;
4use crate::error::encryption::EncryptionError;
5use crate::error::encryption::EncryptionError::{NonceGenerationFailed, SaltGenerationFailed};
6use crate::error::fs::ReadDirError;
7use crate::error::identity::{
8 ConvertMnemonicToKeyError,
9 ConvertMnemonicToKeyError::DeriveExtendedKeyFromPathFailed,
10 CreateIdentityConfigError,
11 CreateIdentityConfigError::{
12 GenerateFreshEncryptionConfigurationFailed, KeyringAvailabilityCheckFailed,
13 },
14 CreateNewIdentityError,
15 CreateNewIdentityError::{
16 ConvertSecretKeyToSec1PemFailed, CreateMnemonicFromPhraseFailed,
17 CreateTemporaryIdentityDirectoryFailed, SwitchBackToIdentityFailed,
18 SwitchToAnonymousIdentityFailed,
19 },
20 ExportIdentityError,
21 ExportIdentityError::TranslatePemContentToTextFailed,
22 GenerateKeyError,
23 GenerateKeyError::GenerateFreshSecp256k1KeyFailed,
24 GetIdentityConfigOrDefaultError,
25 GetIdentityConfigOrDefaultError::LoadIdentityConfigurationFailed,
26 GetLegacyCredentialsPemPathError,
27 GetLegacyCredentialsPemPathError::GetLegacyPemPathFailed,
28 InitializeIdentityManagerError,
29 InitializeIdentityManagerError::{
30 CreateIdentityDirectoryFailed, GenerateKeyFailed, MigrateLegacyIdentityFailed,
31 WritePemToFileFailed,
32 },
33 InstantiateIdentityFromNameError,
34 InstantiateIdentityFromNameError::{GetIdentityPrincipalFailed, LoadIdentityFailed},
35 LoadIdentityError, NewIdentityManagerError,
36 NewIdentityManagerError::LoadIdentityManagerConfigurationFailed,
37 RemoveIdentityError,
38 RemoveIdentityError::{
39 DisplayLinkedWalletsFailed, DropWalletsFlagRequiredToRemoveIdentityWithWallets,
40 RemoveIdentityDirectoryFailed,
41 },
42 RenameIdentityError,
43 RenameIdentityError::{
44 GetIdentityConfigFailed, LoadPemFailed, MapWalletsToRenamedIdentityFailed, SavePemFailed,
45 SwitchDefaultIdentitySettingsFailed,
46 },
47 RequireIdentityExistsError, SaveIdentityConfigurationError,
48 SaveIdentityConfigurationError::EnsureIdentityConfigurationDirExistsFailed,
49 UseIdentityByNameError,
50 UseIdentityByNameError::WriteDefaultIdentityFailed,
51 WriteDefaultIdentityError,
52 WriteDefaultIdentityError::SaveIdentityManagerConfigurationFailed,
53};
54use crate::error::structured_file::StructuredFileError;
55use crate::foundation::get_user_home;
56use crate::fs::composite::ensure_parent_dir_exists;
57use crate::identity::identity_file_locations::{IDENTITY_PEM, IdentityFileLocations};
58use crate::identity::identity_manager::IdentityStorageModeError::UnknownStorageMode;
59use crate::identity::{
60 ANONYMOUS_IDENTITY_NAME, IDENTITY_JSON, Identity as DfxIdentity, TEMP_IDENTITY_PREFIX,
61 pem_safekeeping, pem_utils,
62};
63use crate::json::{load_json_file, save_json_file};
64use bip32::XPrv;
65use bip39::{Language, Mnemonic, MnemonicType, Seed};
66use candid::Principal;
67use k256::SecretKey;
68use k256::pkcs8::LineEnding;
69use ring::{rand, rand::SecureRandom};
70use sec1::EncodeEcPrivateKey;
71use serde::{Deserialize, Serialize};
72use slog::{Logger, debug, trace};
73use std::boxed::Box;
74use std::collections::BTreeMap;
75use std::path::{Path, PathBuf};
76use std::str::FromStr;
77use thiserror::Error;
78
79const DEFAULT_IDENTITY_NAME: &str = "default";
80
81#[derive(Clone, Debug, Default, Serialize, Deserialize)]
82struct Configuration {
83 #[serde(default = "default_identity")]
84 pub default: String,
85}
86
87fn default_identity() -> String {
88 String::from(DEFAULT_IDENTITY_NAME)
89}
90
91#[derive(Clone, Debug, Default, Serialize, Deserialize)]
92pub struct IdentityConfiguration {
93 pub hsm: Option<HardwareIdentityConfiguration>,
94
95 pub encryption: Option<EncryptionConfiguration>,
97
98 pub keyring_identity_suffix: Option<String>,
100}
101
102#[derive(Clone, Debug, Serialize, Deserialize)]
104pub struct EncryptionConfiguration {
105 pub pw_salt: String,
107
108 pub file_nonce: Vec<u8>,
110}
111
112impl EncryptionConfiguration {
113 pub fn new() -> Result<Self, EncryptionError> {
115 let mut nonce: [u8; 12] = [0; 12];
116 let mut salt: [u8; 32] = [0; 32];
117 let sr = rand::SystemRandom::new();
118 sr.fill(&mut nonce).map_err(NonceGenerationFailed)?;
119 sr.fill(&mut salt).map_err(SaltGenerationFailed)?;
120
121 let pw_salt = hex::encode(salt);
122 let file_nonce = nonce.into();
123
124 Ok(Self {
125 pw_salt,
126 file_nonce,
127 })
128 }
129}
130
131#[derive(Clone, Debug, Default, Serialize, Deserialize)]
132pub struct HardwareIdentityConfiguration {
133 #[cfg_attr(
134 not(windows),
135 doc = r#"The file path to the opensc-pkcs11 library e.g. "/usr/local/lib/opensc-pkcs11.so""#
136 )]
137 #[cfg_attr(
138 windows,
139 doc = r#"The file path to the opensc-pkcs11 library e.g. "C:\Program Files (x86)\OpenSC Project\OpenSC\pkcs11\opensc-pkcs11.dll"#
140 )]
141 pub pkcs11_lib_path: String,
142
143 pub key_id: String,
145}
146
147#[derive(Clone, Debug, Serialize, Deserialize, Copy, PartialEq, Eq)]
148pub enum IdentityStorageMode {
149 Keyring,
150 PasswordProtected,
151 Plaintext,
152}
153
154#[derive(Error, Debug)]
155pub enum IdentityStorageModeError {
156 #[error("Unknown storage mode: {0}")]
157 UnknownStorageMode(String),
158}
159
160impl FromStr for IdentityStorageMode {
161 type Err = IdentityStorageModeError;
162
163 fn from_str(input: &str) -> Result<Self, Self::Err> {
164 match input {
165 "keyring" => Ok(IdentityStorageMode::Keyring),
166 "password-protected" => Ok(IdentityStorageMode::PasswordProtected),
167 "plaintext" => Ok(IdentityStorageMode::Plaintext),
168 other => Err(UnknownStorageMode(other.to_string())),
169 }
170 }
171}
172
173impl Default for IdentityStorageMode {
174 fn default() -> Self {
175 Self::Keyring
176 }
177}
178
179pub enum IdentityCreationParameters {
180 Pem {
181 mode: IdentityStorageMode,
182 },
183 PemFile {
184 src_pem_file: PathBuf,
185 mode: IdentityStorageMode,
186 },
187 SeedPhrase {
188 mnemonic: String,
189 mode: IdentityStorageMode,
190 },
191 Hardware {
192 hsm: HardwareIdentityConfiguration,
193 },
194}
195
196#[derive(Clone, Debug)]
197pub struct IdentityManager {
198 identity_json_path: PathBuf,
199 file_locations: IdentityFileLocations,
200 configuration: Configuration,
201 selected_identity: String,
202 selected_identity_principal: Option<Principal>,
203}
204
205#[derive(PartialEq)]
206pub enum InitializeIdentity {
207 Allow,
208 Disallow,
209}
210
211impl IdentityManager {
212 pub fn new(
213 logger: &Logger,
214 identity_override: Option<&str>,
215 initialize_identity: InitializeIdentity,
216 ) -> Result<Self, NewIdentityManagerError> {
217 let config_dfx_dir_path =
218 get_user_dfx_config_dir().map_err(NewIdentityManagerError::GetConfigDirectoryFailed)?;
219 let identity_root_path = config_dfx_dir_path.join("identity");
220 let identity_json_path = config_dfx_dir_path.join("identity.json");
221
222 let configuration = if identity_json_path.exists() {
223 load_configuration(&identity_json_path)
224 .map_err(LoadIdentityManagerConfigurationFailed)?
225 } else if initialize_identity == InitializeIdentity::Disallow {
226 return Err(NewIdentityManagerError::NoIdentityConfigurationFound);
227 } else {
228 initialize(logger, &identity_json_path, &identity_root_path)
229 .map_err(NewIdentityManagerError::InitializeFailed)?
230 };
231
232 let selected_identity = identity_override
233 .unwrap_or(&configuration.default)
234 .to_string();
235
236 let file_locations = IdentityFileLocations::new(identity_root_path);
237
238 let mgr = IdentityManager {
239 identity_json_path,
240 file_locations,
241 configuration,
242 selected_identity,
243 selected_identity_principal: None,
244 };
245
246 if let Some(identity) = identity_override {
247 mgr.require_identity_exists(logger, identity)
248 .map_err(NewIdentityManagerError::OverrideIdentityMustExist)?;
249 }
250
251 Ok(mgr)
252 }
253
254 pub fn get_selected_identity_principal(&self) -> Option<Principal> {
255 self.selected_identity_principal
256 }
257
258 pub fn instantiate_selected_identity(
260 &mut self,
261 log: &Logger,
262 ) -> Result<Box<DfxIdentity>, InstantiateIdentityFromNameError> {
263 let name = self.selected_identity.clone();
264 self.instantiate_identity_from_name(name.as_str(), log)
265 }
266
267 pub fn instantiate_identity_from_name(
269 &mut self,
270 identity_name: &str,
271 log: &Logger,
272 ) -> Result<Box<DfxIdentity>, InstantiateIdentityFromNameError> {
273 let identity = match identity_name {
274 ANONYMOUS_IDENTITY_NAME => Box::new(DfxIdentity::anonymous()),
275 identity_name => {
276 self.require_identity_exists(log, identity_name)
277 .map_err(InstantiateIdentityFromNameError::RequireIdentityExistsFailed)?;
278 Box::new(
279 self.load_identity(identity_name, log)
280 .map_err(LoadIdentityFailed)?,
281 )
282 }
283 };
284 use ic_agent::identity::Identity;
285 self.selected_identity_principal =
286 Some(identity.sender().map_err(GetIdentityPrincipalFailed)?);
287 Ok(identity)
288 }
289
290 fn load_identity(&self, name: &str, log: &Logger) -> Result<DfxIdentity, LoadIdentityError> {
291 let config = self
292 .get_identity_config_or_default(name)
293 .map_err(LoadIdentityError::GetIdentityConfigOrDefaultFailed)?;
294 DfxIdentity::new(name, config, self.file_locations(), log)
295 .map_err(LoadIdentityError::NewIdentityFailed)
296 }
297
298 pub fn create_new_identity(
302 &mut self,
303 log: &Logger,
304 name: &str,
305 parameters: IdentityCreationParameters,
306 force: bool,
307 ) -> Result<(), CreateNewIdentityError> {
308 if name == ANONYMOUS_IDENTITY_NAME {
309 return Err(CreateNewIdentityError::CannotCreateAnonymousIdentity());
310 }
311
312 trace!(log, "Creating identity '{name}'.");
313 let identity_in_use = self.get_selected_identity_name().clone();
314 let temporarily_use_anonymous_identity = identity_in_use == name && force;
316
317 if self.require_identity_exists(log, name).is_ok() {
318 trace!(log, "Identity already exists.");
319 if force {
320 if temporarily_use_anonymous_identity {
321 self.use_identity_named(log, ANONYMOUS_IDENTITY_NAME)
322 .map_err(SwitchToAnonymousIdentityFailed)?;
323 }
324 self.remove(log, name, true, None)
325 .map_err(CreateNewIdentityError::RemoveIdentityFailed)?;
326 } else {
327 return Err(CreateNewIdentityError::IdentityAlreadyExists());
328 }
329 }
330
331 fn create_identity_config(
332 log: &Logger,
333 mode: IdentityStorageMode,
334 name: &str,
335 hardware_config: Option<HardwareIdentityConfiguration>,
336 ) -> Result<IdentityConfiguration, CreateIdentityConfigError> {
337 if let Some(hsm) = hardware_config {
338 Ok(IdentityConfiguration {
339 hsm: Some(hsm),
340 ..Default::default()
341 })
342 } else {
343 match mode {
344 IdentityStorageMode::Keyring => {
345 if keyring_mock::keyring_available(log)
346 .map_err(KeyringAvailabilityCheckFailed)?
347 {
348 Ok(IdentityConfiguration {
349 keyring_identity_suffix: Some(String::from(name)),
350 ..Default::default()
351 })
352 } else {
353 Ok(IdentityConfiguration {
354 encryption: Some(
355 EncryptionConfiguration::new()
356 .map_err(GenerateFreshEncryptionConfigurationFailed)?,
357 ),
358 ..Default::default()
359 })
360 }
361 }
362 IdentityStorageMode::PasswordProtected => Ok(IdentityConfiguration {
363 encryption: Some(
364 EncryptionConfiguration::new()
365 .map_err(GenerateFreshEncryptionConfigurationFailed)?,
366 ),
367 ..Default::default()
368 }),
369 IdentityStorageMode::Plaintext => Ok(IdentityConfiguration::default()),
370 }
371 }
372 }
373
374 let temp_identity_name = format!("{TEMP_IDENTITY_PREFIX}{name}");
377 let temp_identity_dir = self.get_identity_dir_path(&temp_identity_name);
378 if temp_identity_dir.exists() {
379 crate::fs::remove_dir_all(&temp_identity_dir)?;
381 }
382
383 let identity_config;
384 match parameters {
385 IdentityCreationParameters::Pem { mode } => {
386 let (pem_content, mnemonic) =
387 generate_key().map_err(CreateNewIdentityError::GenerateKeyFailed)?;
388 identity_config = create_identity_config(log, mode, name, None)
389 .map_err(CreateNewIdentityError::CreateIdentityConfigFailed)?;
390 pem_safekeeping::save_pem(
391 log,
392 self.file_locations(),
393 &temp_identity_name,
394 &identity_config,
395 pem_content.as_slice(),
396 )
397 .map_err(CreateNewIdentityError::SavePemFailed)?;
398 eprintln!(
399 "Your seed phrase for identity '{name}': {}\nThis can be used to reconstruct your key in case of emergency, so write it down in a safe place.",
400 mnemonic.phrase()
401 );
402 }
403 IdentityCreationParameters::PemFile { src_pem_file, mode } => {
404 identity_config = create_identity_config(log, mode, name, None)
405 .map_err(CreateNewIdentityError::CreateIdentityConfigFailed)?;
406 let (src_pem_content, _) = pem_safekeeping::load_pem_from_file(&src_pem_file, None)
407 .map_err(CreateNewIdentityError::LoadPemFromFileFailed)?;
408 pem_utils::validate_pem_file(&src_pem_content)
409 .map_err(CreateNewIdentityError::ValidatePemFileFailed)?;
410 pem_safekeeping::save_pem(
411 log,
412 self.file_locations(),
413 &temp_identity_name,
414 &identity_config,
415 src_pem_content.as_slice(),
416 )
417 .map_err(CreateNewIdentityError::SavePemFailed)?;
418 }
419 IdentityCreationParameters::Hardware { hsm } => {
420 identity_config =
421 create_identity_config(log, IdentityStorageMode::default(), name, Some(hsm))
422 .map_err(CreateNewIdentityError::CreateIdentityConfigFailed)?;
423 crate::fs::create_dir_all(&temp_identity_dir)
424 .map_err(CreateTemporaryIdentityDirectoryFailed)?;
425 }
426 IdentityCreationParameters::SeedPhrase { mnemonic, mode } => {
427 identity_config = create_identity_config(log, mode, name, None)
428 .map_err(CreateNewIdentityError::CreateIdentityConfigFailed)?;
429 let mnemonic = Mnemonic::from_phrase(&mnemonic, Language::English)
430 .map_err(|e| CreateMnemonicFromPhraseFailed(format!("{e}")))?;
431 let key = mnemonic_to_key(&mnemonic)
432 .map_err(CreateNewIdentityError::ConvertMnemonicToKeyFailed)?;
433 let pem = key
434 .to_sec1_pem(k256::pkcs8::LineEnding::CRLF)
435 .map_err(|e| ConvertSecretKeyToSec1PemFailed(Box::new(e)))?;
436 let pem_content = pem.as_bytes();
437 pem_safekeeping::save_pem(
438 log,
439 self.file_locations(),
440 &temp_identity_name,
441 &identity_config,
442 pem_content,
443 )
444 .map_err(CreateNewIdentityError::SavePemFailed)?;
445 }
446 }
447 let identity_config_location = self.get_identity_json_path(&temp_identity_name);
448 save_identity_configuration(log, &identity_config_location, &identity_config)
449 .map_err(CreateNewIdentityError::SaveIdentityConfigurationFailed)?;
450
451 let identity_dir = self.get_identity_dir_path(name);
453 crate::fs::rename(&temp_identity_dir, &identity_dir)?;
454
455 if temporarily_use_anonymous_identity {
456 self.use_identity_named(log, &identity_in_use)
457 .map_err(SwitchBackToIdentityFailed)?;
458 }
459 Ok(())
460 }
461
462 pub fn get_identity_names(&self, log: &Logger) -> Result<Vec<String>, ReadDirError> {
464 let mut names = crate::fs::read_dir(self.file_locations.root())?
465 .filter_map(|entry_result| match entry_result {
466 Ok(dir_entry) => match dir_entry.file_type() {
467 Ok(file_type) if file_type.is_dir() => Some(dir_entry),
468 _ => None,
469 },
470 _ => None,
471 })
472 .map(|entry| entry.file_name().to_string_lossy().to_string())
473 .filter(|identity_name| self.require_identity_exists(log, identity_name).is_ok())
474 .collect::<Vec<_>>();
475 names.push(ANONYMOUS_IDENTITY_NAME.to_string());
476
477 names.sort();
478
479 Ok(names)
480 }
481
482 pub fn get_selected_identity_name(&self) -> &String {
484 &self.selected_identity
485 }
486
487 pub(crate) fn file_locations(&self) -> &IdentityFileLocations {
488 &self.file_locations
489 }
490
491 pub fn export(&self, log: &Logger, name: &str) -> Result<String, ExportIdentityError> {
493 self.require_identity_exists(log, name)
494 .map_err(ExportIdentityError::IdentityDoesNotExist)?;
495 let config = self
496 .get_identity_config_or_default(name)
497 .map_err(ExportIdentityError::GetIdentityConfigFailed)?;
498 let (pem_content, _) = pem_safekeeping::load_pem(log, &self.file_locations, name, &config)
499 .map_err(ExportIdentityError::LoadPemFailed)?;
500
501 validate_pem_file(&pem_content).map_err(ExportIdentityError::ValidatePemFileFailed)?;
502 String::from_utf8(pem_content).map_err(TranslatePemContentToTextFailed)
503 }
504
505 pub fn remove(
510 &self,
511 log: &Logger,
512 name: &str,
513 drop_wallets: bool,
514 display_linked_wallets_to: Option<&Logger>,
515 ) -> Result<(), RemoveIdentityError> {
516 self.require_identity_exists(log, name)
517 .map_err(RemoveIdentityError::RequireIdentityExistsFailed)?;
518
519 if name == ANONYMOUS_IDENTITY_NAME {
520 return Err(RemoveIdentityError::CannotDeleteAnonymousIdentity());
521 }
522
523 if self.configuration.default == name {
524 return Err(RemoveIdentityError::CannotDeleteDefaultIdentity());
525 }
526
527 let wallet_config_file = self.get_persistent_wallet_config_file(name);
528 if wallet_config_file.exists() {
529 if let Some(logger) = display_linked_wallets_to {
530 DfxIdentity::display_linked_wallets(logger, &wallet_config_file)
531 .map_err(DisplayLinkedWalletsFailed)?;
532 }
533 if drop_wallets {
534 remove_identity_file(&wallet_config_file)?;
535 } else {
536 return Err(DropWalletsFlagRequiredToRemoveIdentityWithWallets());
537 }
538 }
539
540 if let Ok(config) = self.get_identity_config_or_default(name) {
541 if let Some(suffix) = config.keyring_identity_suffix {
542 keyring_mock::delete_pem_from_keyring(&suffix)
543 .map_err(RemoveIdentityError::RemoveIdentityFromKeyringFailed)?;
544 }
545 }
546 remove_identity_file(&self.get_identity_json_path(name))?;
547 remove_identity_file(&self.file_locations.get_plaintext_identity_pem_path(name))?;
548 remove_identity_file(&self.file_locations.get_encrypted_identity_pem_path(name))?;
549
550 let dir = self.get_identity_dir_path(name);
551 if dir.exists() {
552 crate::fs::remove_dir(&dir).map_err(RemoveIdentityDirectoryFailed)?;
553 }
554
555 Ok(())
556 }
557
558 pub fn rename(
562 &mut self,
563 log: &Logger,
564 project_temp_dir: Option<PathBuf>,
565 from: &str,
566 to: &str,
567 ) -> Result<bool, RenameIdentityError> {
568 if to == ANONYMOUS_IDENTITY_NAME {
569 return Err(RenameIdentityError::CannotCreateAnonymousIdentity());
570 }
571 self.require_identity_exists(log, from)
572 .map_err(RenameIdentityError::IdentityDoesNotExist)?;
573
574 let identity_config = self
575 .get_identity_config_or_default(from)
576 .map_err(GetIdentityConfigFailed)?;
577 let from_dir = self.get_identity_dir_path(from);
578 let to_dir = self.get_identity_dir_path(to);
579
580 if to_dir.exists() {
581 return Err(RenameIdentityError::IdentityAlreadyExists());
582 }
583
584 DfxIdentity::map_wallets_to_renamed_identity(project_temp_dir, from, to)
585 .map_err(MapWalletsToRenamedIdentityFailed)?;
586 crate::fs::rename(&from_dir, &to_dir)?;
587 if let Some(keyring_identity_suffix) = &identity_config.keyring_identity_suffix {
588 debug!(log, "Migrating keyring content.");
589 let (pem, _) =
590 pem_safekeeping::load_pem(log, &self.file_locations, from, &identity_config)
591 .map_err(LoadPemFailed)?;
592 let new_config = IdentityConfiguration {
593 keyring_identity_suffix: Some(to.to_string()),
594 ..identity_config
595 };
596 pem_safekeeping::save_pem(log, &self.file_locations, to, &new_config, pem.as_ref())
597 .map_err(SavePemFailed)?;
598 let config_path = self.get_identity_json_path(to);
599 save_identity_configuration(log, &config_path, &new_config)
600 .map_err(RenameIdentityError::SaveIdentityConfigurationFailed)?;
601 keyring_mock::delete_pem_from_keyring(keyring_identity_suffix)
602 .map_err(RenameIdentityError::RemoveIdentityFromKeyringFailed)?;
603 }
604
605 if from == self.configuration.default {
606 self.write_default_identity(to)
607 .map_err(SwitchDefaultIdentitySettingsFailed)?;
608 Ok(true)
609 } else {
610 Ok(false)
611 }
612 }
613
614 pub fn use_identity_named(
616 &mut self,
617 log: &Logger,
618 name: &str,
619 ) -> Result<(), UseIdentityByNameError> {
620 self.require_identity_exists(log, name)
621 .map_err(UseIdentityByNameError::RequireIdentityExistsFailed)?;
622 self.write_default_identity(name)
623 .map_err(WriteDefaultIdentityFailed)?;
624 self.configuration.default = name.to_string();
625 Ok(())
626 }
627
628 fn write_default_identity(&self, name: &str) -> Result<(), WriteDefaultIdentityError> {
629 let config = Configuration {
630 default: String::from(name),
631 };
632 save_configuration(&self.identity_json_path, &config)
633 .map_err(SaveIdentityManagerConfigurationFailed)?;
634 Ok(())
635 }
636
637 pub fn require_identity_exists(
640 &self,
641 log: &Logger,
642 name: &str,
643 ) -> Result<(), RequireIdentityExistsError> {
644 trace!(log, "Checking if identity '{name}' exists.");
645 if name == ANONYMOUS_IDENTITY_NAME {
646 return Ok(());
647 }
648
649 if name.starts_with(TEMP_IDENTITY_PREFIX) {
650 return Err(RequireIdentityExistsError::ReservedIdentityName(
651 String::from(name),
652 ));
653 }
654
655 let json_path = self.get_identity_json_path(name);
656 let plaintext_pem_path = self.file_locations.get_plaintext_identity_pem_path(name);
657 let encrypted_pem_path = self.file_locations.get_encrypted_identity_pem_path(name);
658
659 if !plaintext_pem_path.exists() && !encrypted_pem_path.exists() && !json_path.exists() {
660 Err(RequireIdentityExistsError::IdentityDoesNotExist(
661 String::from(name),
662 json_path,
663 ))
664 } else {
665 Ok(())
666 }
667 }
668
669 pub fn get_identity_dir_path(&self, identity: &str) -> PathBuf {
670 self.file_locations.get_identity_dir_path(identity)
671 }
672
673 fn get_persistent_wallet_config_file(&self, identity: &str) -> PathBuf {
675 self.get_identity_dir_path(identity)
676 .join(WALLET_CONFIG_FILENAME)
677 }
678
679 pub fn get_identity_json_path(&self, identity: &str) -> PathBuf {
681 self.get_identity_dir_path(identity).join(IDENTITY_JSON)
682 }
683
684 pub fn get_unencrypted_principal_map(&self, log: &Logger) -> BTreeMap<String, String> {
687 use ic_agent::Identity;
688 let mut res = if let Ok(names) = self.get_identity_names(log) {
689 names
690 .iter()
691 .filter_map(|name| {
692 let config = self.get_identity_config_or_default(name).ok()?;
693 if let IdentityConfiguration {
694 encryption: None,
695 keyring_identity_suffix: None,
696 hsm: None,
697 } = config
698 {
699 let sender = self.load_identity(name, log).ok()?.sender().ok()?;
700 Some((name.clone(), sender.to_text()))
701 } else {
702 None
703 }
704 })
705 .collect()
706 } else {
707 BTreeMap::new()
708 };
709 res.insert(
710 ANONYMOUS_IDENTITY_NAME.to_string(),
711 Principal::anonymous().to_string(),
712 );
713 res
714 }
715
716 pub fn get_identity_config_or_default(
717 &self,
718 identity: &str,
719 ) -> Result<IdentityConfiguration, GetIdentityConfigOrDefaultError> {
720 let json_path = self.get_identity_json_path(identity);
721 if json_path.exists() {
722 load_json_file(&json_path)
723 .map_err(|err| LoadIdentityConfigurationFailed(identity.to_string(), err))
724 } else {
725 Ok(IdentityConfiguration::default())
726 }
727 }
728}
729
730pub(super) fn get_dfx_hsm_pin() -> Result<String, String> {
731 std::env::var("DFX_HSM_PIN")
732 .map_err(|_| "There is no DFX_HSM_PIN environment variable.".to_string())
733}
734
735fn initialize(
736 logger: &Logger,
737 identity_json_path: &Path,
738 identity_root_path: &Path,
739) -> Result<Configuration, InitializeIdentityManagerError> {
740 slog::info!(
741 logger,
742 r#"Creating the "default" identity.
743WARNING: The "default" identity is not stored securely. Do not use it to control a lot of cycles/ICP.
744To create a more secure identity, create and use an identity that is protected by a password using the following commands:
745 dfx identity new <my-secure-identity-name> # creates a password protected identity
746 dfx identity use <my-secure-identity-name> # uses this identity by default
747"#
748 );
749
750 let identity_dir = identity_root_path.join(DEFAULT_IDENTITY_NAME);
751 let identity_pem_path = identity_dir.join(IDENTITY_PEM);
752 if !identity_pem_path.exists() {
753 if !identity_dir.exists() {
754 crate::fs::create_dir_all(&identity_dir).map_err(CreateIdentityDirectoryFailed)?;
755 }
756
757 let maybe_creds_pem_path = get_legacy_creds_pem_path()?;
758 if maybe_creds_pem_path
759 .as_ref()
760 .map(|p| p.exists())
761 .unwrap_or_default()
762 {
763 let creds_pem_path =
764 maybe_creds_pem_path.expect("Unreachable - Just checked for existence.");
765 slog::info!(
766 logger,
767 " - migrating key from {} to {}",
768 creds_pem_path.display(),
769 identity_pem_path.display()
770 );
771 crate::fs::copy(&creds_pem_path, &identity_pem_path)
772 .map_err(MigrateLegacyIdentityFailed)?;
773 } else {
774 slog::info!(
775 logger,
776 " - generating new key at {}",
777 identity_pem_path.display()
778 );
779 let (key, mnemonic) = generate_key().map_err(GenerateKeyFailed)?;
780 pem_safekeeping::write_pem_to_file(&identity_pem_path, None, key.as_slice())
781 .map_err(WritePemToFileFailed)?;
782 eprintln!(
783 "Your seed phrase: {}\nThis can be used to reconstruct your key in case of emergency, so write it down in a safe place.",
784 mnemonic.phrase()
785 );
786 }
787 } else {
788 slog::info!(
789 logger,
790 " - using key already in place at {}",
791 identity_pem_path.display()
792 );
793 }
794
795 let config = Configuration {
796 default: String::from(DEFAULT_IDENTITY_NAME),
797 };
798 save_configuration(identity_json_path, &config)
799 .map_err(InitializeIdentityManagerError::SaveConfigurationFailed)?;
800 slog::info!(logger, r#"Created the "default" identity."#);
801
802 Ok(config)
803}
804
805fn get_legacy_creds_pem_path() -> Result<Option<PathBuf>, GetLegacyCredentialsPemPathError> {
806 if cfg!(windows) {
807 Ok(None)
809 } else {
810 let config_root = crate::config::directories::DFX_CONFIG_ROOT
811 .lock()
812 .unwrap()
813 .clone();
814 let home = get_user_home().map_err(GetLegacyPemPathFailed)?;
815 let root = config_root.unwrap_or(home);
816
817 Ok(Some(
818 PathBuf::from(root)
819 .join(".dfinity")
820 .join("identity")
821 .join("creds.pem"),
822 ))
823 }
824}
825
826fn load_configuration(path: &Path) -> Result<Configuration, StructuredFileError> {
827 load_json_file(path)
828}
829
830fn save_configuration(path: &Path, config: &Configuration) -> Result<(), StructuredFileError> {
831 save_json_file(path, config)
832}
833
834pub(super) fn save_identity_configuration(
835 log: &Logger,
836 path: &Path,
837 config: &IdentityConfiguration,
838) -> Result<(), SaveIdentityConfigurationError> {
839 trace!(log, "Writing identity configuration to {}", path.display());
840 ensure_parent_dir_exists(path).map_err(EnsureIdentityConfigurationDirExistsFailed)?;
841
842 save_json_file(path, &config)
843 .map_err(SaveIdentityConfigurationError::SaveIdentityConfigurationFailed)
844}
845
846fn remove_identity_file(file: &Path) -> Result<(), RemoveIdentityError> {
848 if file.exists() {
849 crate::fs::remove_file(file)?;
850 }
851 Ok(())
852}
853
854pub(super) fn generate_key() -> Result<(Vec<u8>, Mnemonic), GenerateKeyError> {
856 let mnemonic = Mnemonic::new(MnemonicType::for_key_size(256).unwrap(), Language::English);
857 let secret =
858 mnemonic_to_key(&mnemonic).map_err(GenerateKeyError::ConvertMnemonicToKeyFailed)?;
859 let pem = secret
860 .to_sec1_pem(LineEnding::CRLF)
861 .map_err(|e| GenerateFreshSecp256k1KeyFailed(Box::new(e)))?;
862 Ok((pem.as_bytes().to_vec(), mnemonic))
863}
864
865pub fn mnemonic_to_key(mnemonic: &Mnemonic) -> Result<SecretKey, ConvertMnemonicToKeyError> {
866 const DEFAULT_DERIVATION_PATH: &str = "m/44'/223'/0'/0/0";
867 let path = DEFAULT_DERIVATION_PATH.parse().unwrap();
868 let seed = Seed::new(mnemonic, "");
869 let pk =
870 XPrv::derive_from_path(seed.as_bytes(), &path).map_err(DeriveExtendedKeyFromPathFailed)?;
871 Ok(SecretKey::from(pk.private_key()))
872}