Skip to main content

dfx_core/identity/
mod.rs

1//! Identity type and module.
2//!
3//! Wallets are a map of network-identity, but don't have their own types or manager
4//! type.
5use crate::config::directories::{get_shared_wallet_config_path, get_user_dfx_config_dir};
6use crate::config::model::network_descriptor::NetworkDescriptor;
7use crate::error::wallet_config::SaveWalletConfigError;
8use crate::error::{
9    identity::{
10        CallSenderFromWalletError,
11        CallSenderFromWalletError::{
12            ParsePrincipalFromIdFailedAndGetWalletCanisterIdFailed,
13            ParsePrincipalFromIdFailedAndNoWallet,
14        },
15        LoadPemIdentityError,
16        LoadPemIdentityError::ReadIdentityFileFailed,
17        MapWalletsToRenamedIdentityError,
18        MapWalletsToRenamedIdentityError::RenameWalletGlobalConfigKeyFailed,
19        NewHardwareIdentityError,
20        NewHardwareIdentityError::InstantiateHardwareIdentityFailed,
21        NewIdentityError, RenameWalletGlobalConfigKeyError,
22        RenameWalletGlobalConfigKeyError::RenameWalletFailed,
23    },
24    wallet_config::{WalletConfigError, WalletConfigError::LoadWalletConfigFailed},
25};
26use crate::fs::composite::ensure_parent_dir_exists;
27use crate::identity::identity_file_locations::IdentityFileLocations;
28use crate::identity::wallet::wallet_canister_id;
29use crate::json::{load_json_file, save_json_file};
30use candid::Principal;
31use ic_agent::Signature;
32use ic_agent::agent::EnvelopeContent;
33use ic_agent::identity::{
34    AnonymousIdentity, BasicIdentity, Delegation, Secp256k1Identity, SignedDelegation,
35};
36use ic_identity_hsm::HardwareIdentity;
37pub use identity_manager::{
38    HardwareIdentityConfiguration, IdentityConfiguration, IdentityCreationParameters,
39    IdentityManager,
40};
41use serde::{Deserialize, Serialize};
42use slog::{Logger, info};
43use std::collections::BTreeMap;
44use std::path::{Path, PathBuf};
45
46mod identity_file_locations;
47pub mod identity_manager;
48pub mod keyring_mock;
49pub mod pem_safekeeping;
50pub mod pem_utils;
51pub mod wallet;
52
53pub const ANONYMOUS_IDENTITY_NAME: &str = "anonymous";
54pub const IDENTITY_JSON: &str = "identity.json";
55pub const TEMP_IDENTITY_PREFIX: &str = if cfg!(all(target_os = "macos", target_arch = "aarch64")) {
56    "___s_temp___"
57} else {
58    "___temp___"
59};
60
61pub const WALLET_CONFIG_FILENAME: &str = "wallets.json";
62const HSM_SLOT_INDEX: usize = 0;
63
64#[derive(Debug, Serialize, Deserialize)]
65pub struct WalletNetworkMap {
66    #[serde(flatten)]
67    pub networks: BTreeMap<String, Principal>,
68}
69
70#[derive(Debug, Serialize, Deserialize)]
71pub struct WalletGlobalConfig {
72    pub identities: BTreeMap<String, WalletNetworkMap>,
73}
74
75pub struct Identity {
76    /// The name of this Identity.
77    name: String,
78
79    /// Whether this identity is stored in unencrypted form.
80    /// False for identities that are not stored at all.
81    pub insecure: bool,
82
83    /// Inner implementation of this identity.
84    inner: Box<dyn ic_agent::Identity + Sync + Send>,
85
86    identity_type: IdentityType,
87}
88
89impl Identity {
90    pub fn anonymous() -> Self {
91        Self {
92            name: ANONYMOUS_IDENTITY_NAME.to_string(),
93            inner: Box::new(AnonymousIdentity {}),
94            insecure: false,
95            identity_type: IdentityType::Anonymous,
96        }
97    }
98
99    fn basic(
100        name: &str,
101        pem_content: &[u8],
102        identity_type: IdentityType,
103    ) -> Result<Self, LoadPemIdentityError> {
104        let inner = Box::new(
105            BasicIdentity::from_pem(pem_content)
106                .map_err(|e| ReadIdentityFileFailed(name.into(), Box::new(e)))?,
107        );
108
109        Ok(Self {
110            name: name.to_string(),
111            inner,
112            insecure: identity_type == IdentityType::Plaintext,
113            identity_type,
114        })
115    }
116
117    fn secp256k1(
118        name: &str,
119        pem_content: &[u8],
120        identity_type: IdentityType,
121    ) -> Result<Self, LoadPemIdentityError> {
122        let inner = Box::new(
123            Secp256k1Identity::from_pem(pem_content)
124                .map_err(|e| ReadIdentityFileFailed(name.into(), Box::new(e)))?,
125        );
126
127        Ok(Self {
128            name: name.to_string(),
129            inner,
130            insecure: identity_type == IdentityType::Plaintext,
131            identity_type,
132        })
133    }
134
135    fn hardware(
136        name: &str,
137        hsm: HardwareIdentityConfiguration,
138    ) -> Result<Self, NewHardwareIdentityError> {
139        let inner = Box::new(
140            HardwareIdentity::new(
141                hsm.pkcs11_lib_path,
142                HSM_SLOT_INDEX,
143                &hsm.key_id,
144                identity_manager::get_dfx_hsm_pin,
145            )
146            .map_err(|e| InstantiateHardwareIdentityFailed(name.into(), Box::new(e)))?,
147        );
148        Ok(Self {
149            name: name.to_string(),
150            inner,
151            insecure: false,
152            identity_type: IdentityType::Hsm,
153        })
154    }
155
156    pub(crate) fn new(
157        name: &str,
158        config: IdentityConfiguration,
159        locations: &IdentityFileLocations,
160        log: &Logger,
161    ) -> Result<Self, NewIdentityError> {
162        if let Some(hsm) = config.hsm {
163            Identity::hardware(name, hsm).map_err(NewIdentityError::NewHardwareIdentityFailed)
164        } else {
165            let (pem_content, identity_type) =
166                pem_safekeeping::load_pem(log, locations, name, &config)
167                    .map_err(NewIdentityError::LoadPemFailed)?;
168            Identity::secp256k1(name, &pem_content, identity_type)
169                .or_else(|e| Identity::basic(name, &pem_content, identity_type).map_err(|_| e))
170                .map_err(NewIdentityError::LoadPemIdentityFailed)
171        }
172    }
173
174    /// Get the name of this identity.
175    #[allow(dead_code)]
176    pub fn name(&self) -> &str {
177        &self.name
178    }
179
180    pub fn identity_type(&self) -> IdentityType {
181        self.identity_type
182    }
183
184    /// Logs all wallets that are configured in a WalletGlobalConfig.
185    pub fn display_linked_wallets(
186        logger: &Logger,
187        wallet_config: &Path,
188    ) -> Result<(), WalletConfigError> {
189        let config = Identity::load_wallet_config(wallet_config)?;
190        info!(
191            logger,
192            "This identity is connected to the following wallets:"
193        );
194        for (identity, map) in config.identities {
195            for (network, wallet) in map.networks {
196                info!(
197                    logger,
198                    "    identity '{}' on network '{}' has wallet {}", identity, network, wallet
199                );
200            }
201        }
202        Ok(())
203    }
204
205    pub fn load_wallet_config(path: &Path) -> Result<WalletGlobalConfig, WalletConfigError> {
206        load_json_file(path).map_err(LoadWalletConfigFailed)
207    }
208
209    pub fn save_wallet_config(
210        path: &Path,
211        config: &WalletGlobalConfig,
212    ) -> Result<(), SaveWalletConfigError> {
213        ensure_parent_dir_exists(path)?;
214
215        save_json_file(path, &config)?;
216        Ok(())
217    }
218
219    fn rename_wallet_global_config_key(
220        original_identity: &str,
221        renamed_identity: &str,
222        wallet_path: PathBuf,
223    ) -> Result<(), RenameWalletGlobalConfigKeyError> {
224        Identity::load_wallet_config(&wallet_path)
225            .and_then(|mut config| {
226                let identities = &mut config.identities;
227                let v = identities
228                    .remove(original_identity)
229                    .unwrap_or(WalletNetworkMap {
230                        networks: BTreeMap::new(),
231                    });
232                identities.insert(renamed_identity.to_string(), v);
233                Identity::save_wallet_config(&wallet_path, &config)
234                    .map_err(WalletConfigError::SaveWalletConfig)
235            })
236            .map_err(|err| {
237                RenameWalletFailed(
238                    Box::new(original_identity.to_string()),
239                    Box::new(renamed_identity.to_string()),
240                    err,
241                )
242            })
243    }
244
245    // used for dfx identity rename foo bar
246    pub fn map_wallets_to_renamed_identity(
247        project_temp_dir: Option<PathBuf>,
248        original_identity: &str,
249        renamed_identity: &str,
250    ) -> Result<(), MapWalletsToRenamedIdentityError> {
251        let persistent_wallet_path = get_user_dfx_config_dir()
252            .map_err(MapWalletsToRenamedIdentityError::GetConfigDirectoryFailed)?
253            .join("identity")
254            .join(original_identity)
255            .join(WALLET_CONFIG_FILENAME);
256        if persistent_wallet_path.exists() {
257            Identity::rename_wallet_global_config_key(
258                original_identity,
259                renamed_identity,
260                persistent_wallet_path,
261            )
262            .map_err(RenameWalletGlobalConfigKeyFailed)?;
263        }
264        if let Some(shared_local_wallet_path) = get_shared_wallet_config_path("local")? {
265            if shared_local_wallet_path.exists() {
266                Identity::rename_wallet_global_config_key(
267                    original_identity,
268                    renamed_identity,
269                    shared_local_wallet_path,
270                )
271                .map_err(RenameWalletGlobalConfigKeyFailed)?;
272            }
273        }
274        if let Some(temp_dir) = project_temp_dir {
275            let local_wallet_path = temp_dir.join("local").join(WALLET_CONFIG_FILENAME);
276            if local_wallet_path.exists() {
277                Identity::rename_wallet_global_config_key(
278                    original_identity,
279                    renamed_identity,
280                    local_wallet_path,
281                )
282                .map_err(RenameWalletGlobalConfigKeyFailed)?;
283            }
284        }
285        Ok(())
286    }
287}
288
289impl ic_agent::Identity for Identity {
290    fn sender(&self) -> Result<Principal, String> {
291        self.inner.sender()
292    }
293
294    fn public_key(&self) -> Option<Vec<u8>> {
295        self.inner.public_key()
296    }
297
298    fn delegation_chain(&self) -> Vec<SignedDelegation> {
299        self.inner.delegation_chain()
300    }
301
302    fn sign(&self, content: &EnvelopeContent) -> Result<Signature, String> {
303        self.inner.sign(content)
304    }
305
306    fn sign_arbitrary(&self, content: &[u8]) -> Result<Signature, String> {
307        self.inner.sign_arbitrary(content)
308    }
309
310    fn sign_delegation(&self, content: &Delegation) -> Result<Signature, String> {
311        self.inner.sign_delegation(content)
312    }
313}
314
315impl AsRef<Identity> for Identity {
316    fn as_ref(&self) -> &Identity {
317        self
318    }
319}
320
321#[derive(Serialize, Copy, Clone, Debug, PartialEq, Eq)]
322#[serde(rename_all = "kebab-case")]
323pub enum IdentityType {
324    Keyring,
325    Plaintext,
326    EncryptedLocal,
327    Hsm,
328    Anonymous,
329}
330
331#[derive(Debug, PartialEq, Eq, Copy, Clone)]
332pub enum CallSender {
333    SelectedId,
334    Impersonate(Principal),
335    Wallet(Principal),
336}
337
338// Determine whether the selected Identity or a wallet should be the sender of the call.
339// If a wallet, the principal can be selected directly, or looked up from an identity name.
340impl CallSender {
341    pub fn from(
342        wallet_principal_or_identity_name: &Option<String>,
343        network: &NetworkDescriptor,
344    ) -> Result<Self, CallSenderFromWalletError> {
345        let sender = if let Some(s) = wallet_principal_or_identity_name {
346            match Principal::from_text(s) {
347                Ok(principal) => CallSender::Wallet(principal),
348                Err(principal_err) => match wallet_canister_id(network, s) {
349                    Ok(Some(principal)) => CallSender::Wallet(principal),
350                    Ok(None) => {
351                        return Err(ParsePrincipalFromIdFailedAndNoWallet(
352                            s.clone(),
353                            principal_err,
354                        ));
355                    }
356                    Err(wallet_err) => {
357                        return Err(ParsePrincipalFromIdFailedAndGetWalletCanisterIdFailed(
358                            s.clone(),
359                            principal_err,
360                            wallet_err,
361                        ));
362                    }
363                },
364            }
365        } else {
366            CallSender::SelectedId
367        };
368        Ok(sender)
369    }
370}