diff --git a/Cargo.lock b/Cargo.lock index 0533ffc394..78c9c1938c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -861,6 +861,7 @@ dependencies = [ "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "hotwatch 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "ic-http-agent 0.1.0", + "ic-identity-manager 0.1.0", "indicatif 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy-init 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/src/dfx/Cargo.toml b/src/dfx/Cargo.toml index 4819ac768f..58c65b14ef 100644 --- a/src/dfx/Cargo.toml +++ b/src/dfx/Cargo.toml @@ -29,6 +29,7 @@ flate2 = "1.0.11" futures = "0.1.28" hex = "0.3.2" ic-http-agent = { path = "../ic_http_agent" } +ic-identity-manager = { path = "../ic_identity_manager" } indicatif = "0.13.0" lazy-init = "0.3.0" lazy_static = "1.4.0" diff --git a/src/dfx/src/config/cache.rs b/src/dfx/src/config/cache.rs index 8146cede10..9fc4cd01fd 100644 --- a/src/dfx/src/config/cache.rs +++ b/src/dfx/src/config/cache.rs @@ -67,6 +67,29 @@ impl Cache for DiskBasedCache { } } +/// Provides a profile for the user. +pub fn get_profile_path() -> DfxResult { + let home = std::env::var("HOME") + .map_err(|_| CacheError(CacheErrorKind::CannotFindUserHomeDirectory()))?; + + let p = PathBuf::from(home) + .join(".cache") + .join("dfinity") + .join("profile"); + + if !p.exists() { + if let Err(e) = std::fs::create_dir_all(&p) { + return Err(CacheError(CacheErrorKind::CannotCreateCacheDirectory(p, e))); + } + } else if !p.is_dir() { + return Err(CacheError(CacheErrorKind::CacheShouldBeADirectory(p))); + } + + Ok(p) +} + +/// Return the binary cache root. It constructs it if not present +/// already. pub fn get_bin_cache_root() -> DfxResult { let home = std::env::var("HOME") .map_err(|_| CacheError(CacheErrorKind::CannotFindUserHomeDirectory()))?; diff --git a/src/dfx/src/lib/environment.rs b/src/dfx/src/lib/environment.rs index 1da4343fca..c3284154ad 100644 --- a/src/dfx/src/lib/environment.rs +++ b/src/dfx/src/lib/environment.rs @@ -1,13 +1,15 @@ -use crate::config::cache::{Cache, DiskBasedCache}; +use crate::config::cache::{get_profile_path, Cache, DiskBasedCache}; use crate::config::dfinity::Config; use crate::config::dfx_version; use crate::lib::error::DfxResult; +use crate::lib::identity_interface::Identity; use crate::lib::progress_bar::ProgressBar; use ic_http_agent::{Agent, AgentConfig}; use lazy_init::Lazy; use semver::Version; use slog::Record; +use std::fs::create_dir_all; use std::path::{Path, PathBuf}; use std::rc::Rc; @@ -75,7 +77,7 @@ impl EnvironmentImpl { .into_path(), Some(c) => c.get_path().parent().unwrap().join(".dfx"), }; - std::fs::create_dir_all(&temp_dir)?; + create_dir_all(&temp_dir)?; // Figure out which version of DFX we should be running. This will use the following // fallback sequence: @@ -156,9 +158,14 @@ impl Environment for EnvironmentImpl { let start = config.get_config().get_defaults().get_start(); let address = start.get_address("localhost"); let port = start.get_port(8000); - + // This is the default to keep it simple. + let local_project_identity = match get_profile_path() { + Ok(p) => p, + Err(_) => return None, + }; Agent::new(AgentConfig { url: format!("http://{}:{}", address, port).as_str(), + signer: Box::new(Identity::new(local_project_identity)), ..AgentConfig::default() }) .ok() @@ -195,13 +202,15 @@ pub struct AgentEnvironment<'a> { impl<'a> AgentEnvironment<'a> { pub fn new(backend: &'a dyn Environment, agent_url: &str) -> Self { + let local_project_identity = get_profile_path().expect("Failed to access profile"); AgentEnvironment { backend, agent: Agent::new(AgentConfig { url: agent_url, + signer: Box::new(Identity::new(local_project_identity)), ..AgentConfig::default() }) - .unwrap(), + .expect("Failed to construct agent"), } } } diff --git a/src/dfx/src/lib/identity_interface.rs b/src/dfx/src/lib/identity_interface.rs new file mode 100644 index 0000000000..4b8ea364d3 --- /dev/null +++ b/src/dfx/src/lib/identity_interface.rs @@ -0,0 +1,75 @@ +use ic_http_agent::to_request_id; +use ic_http_agent::AgentError; +use ic_http_agent::Blob; +use ic_http_agent::RequestId; +use ic_http_agent::SignedMessage; +use ic_http_agent::Signer; +use std::path::PathBuf; + +pub struct Identity(ic_identity_manager::Identity); + +impl Identity { + /// Construct a new identity handling object, providing given + /// configuration. + pub fn new(identity_config_path: PathBuf) -> Self { + Self( + // We panic as discussed, as that should not be the + // case. I personally prefer this to be an error. + ic_identity_manager::Identity::new(identity_config_path) + .expect("Expected a valid identity configuration"), + ) + } +} + +impl Signer for Identity { + fn sign<'a>( + &self, + request: Box<(dyn erased_serde::Serialize + Send + Sync + 'a)>, + ) -> Result< + ( + RequestId, + Box, + ), + AgentError, + > { + let request_id = to_request_id(&request).map_err(AgentError::from)?; + let signature_tuple = self + .0 + .sign(Blob::from(request_id).as_slice()) + .map_err(|e| AgentError::SigningError(e.to_string()))?; + let signature = Blob::from(signature_tuple.signature.clone()); + let sender_pubkey = Blob::from(signature_tuple.public_key); + let signed_request = SignedMessage { + request_with_sender: request, + signature, + sender_pubkey, + }; + Ok((request_id, Box::new(signed_request))) + } +} + +#[cfg(test)] +mod test { + use super::*; + use proptest::prelude::*; + use serde::Serialize; + use tempfile::tempdir; + + // Dummy proptest checking request id is correct for now. + proptest! { + #[test] + fn request_id_identity(request: String) { + let dir = tempdir().unwrap(); + + #[derive(Clone,Serialize)] + struct TestAPI { inner : String} + let request = TestAPI { inner: request}; + + let request_with_sender = request.clone(); + let actual_request_id = to_request_id(&request_with_sender).expect("Failed to produce request id"); + + let signer = Identity::new(dir.into_path()); + let request_id = signer.sign(Box::new(request)).expect("Failed to sign").0; + assert_eq!(request_id, actual_request_id) + }} +} diff --git a/src/dfx/src/lib/mod.rs b/src/dfx/src/lib/mod.rs index 79a5299b7b..3968e75559 100644 --- a/src/dfx/src/lib/mod.rs +++ b/src/dfx/src/lib/mod.rs @@ -1,6 +1,7 @@ pub mod canister_info; pub mod environment; pub mod error; +pub mod identity_interface; pub mod logger; pub mod message; pub mod progress_bar; diff --git a/src/ic_http_agent/src/agent/agent_error.rs b/src/ic_http_agent/src/agent/agent_error.rs index a88fda44ed..ef3a0e2ac8 100644 --- a/src/ic_http_agent/src/agent/agent_error.rs +++ b/src/ic_http_agent/src/agent/agent_error.rs @@ -10,6 +10,8 @@ pub enum AgentError { ClientError(u16, String), TimeoutWaitingForResponse, + SigningError(String), + InvalidCborData(serde_cbor::Error), ReqwestError(reqwest::Error), SerdeError(SerdeError), diff --git a/src/ic_identity_manager/src/lib.rs b/src/ic_identity_manager/src/lib.rs index 0ca7df5fb3..b5aec0286f 100644 --- a/src/ic_identity_manager/src/lib.rs +++ b/src/ic_identity_manager/src/lib.rs @@ -78,3 +78,5 @@ pub mod identity; pub mod principal; mod provider; mod types; + +pub use identity::Identity; diff --git a/src/ic_identity_manager/src/provider.rs b/src/ic_identity_manager/src/provider.rs index 4dd0e82170..7be48c3793 100644 --- a/src/ic_identity_manager/src/provider.rs +++ b/src/ic_identity_manager/src/provider.rs @@ -18,7 +18,7 @@ use crate::types::Signature; /// constraint, or open connections to all possible means of /// authentication, that are provided in the identity profile we /// loaded. -pub trait Provider { +pub trait Provider: Sync { /// Setup the corresponding principal and return a value /// that can provide signing. fn provide(&self) -> Result>;