diff --git a/CHANGELOG.md b/CHANGELOG.md index 16ac230b..aae9b255 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Function `Agent::fetch_api_boundary_nodes()` is split into two functions: `fetch_api_boundary_nodes_by_canister_id()` and `fetch_api_boundary_nodes_by_subnet_id()`. * `ReqwestTransport` and `HyperTransport` structures storing the trait object `route_provider: Box` have been modified to allow for shared ownership via `Arc`. * Added `wasm_memory_limit` to canister creation and canister setting update options. +* Agent::fetch_root_key() now returns an error, and sets its root key to an empty vector, if got the same root key as the mainnet. This reduce the potentiality of MITM attack. ## [0.34.0] - 2024-03-18 diff --git a/ic-agent/src/agent/agent_error.rs b/ic-agent/src/agent/agent_error.rs index 08cf77a1..abb42202 100644 --- a/ic-agent/src/agent/agent_error.rs +++ b/ic-agent/src/agent/agent_error.rs @@ -205,6 +205,11 @@ pub enum AgentError { /// Route provider failed to generate a url for some reason. #[error("Route provider failed to generate url: {0}")] RouteProviderError(String), + + /// It's an error to fetch the root key on the mainnet. + /// Doing so would enable a man-in-the-middle attack. + #[error("Never fetch the root key on the mainnet")] + NeverFetchRootKeyOnMainNet(), } impl PartialEq for AgentError { diff --git a/ic-agent/src/agent/agent_test.rs b/ic-agent/src/agent/agent_test.rs index c43f6a1f..f3efcd48 100644 --- a/ic-agent/src/agent/agent_test.rs +++ b/ic-agent/src/agent/agent_test.rs @@ -45,6 +45,20 @@ fn make_certifying_agent(url: &str) -> Agent { .unwrap() } +#[cfg_attr(not(target_family = "wasm"), tokio::test)] +async fn refuse_to_install_mainnet_root_key() -> Result<(), AgentError> { + let url = "https://icp0.io"; + + let agent = make_agent(url); + let result = agent.fetch_root_key().await; + assert!(matches!( + result, + Err(AgentError::NeverFetchRootKeyOnMainNet()) + )); + assert_eq!(agent.read_root_key(), Vec::::new()); + Ok(()) +} + #[cfg_attr(not(target_family = "wasm"), tokio::test)] #[cfg_attr(target_family = "wasm", wasm_bindgen_test)] async fn query() -> Result<(), AgentError> { diff --git a/ic-agent/src/agent/mod.rs b/ic-agent/src/agent/mod.rs index 89e28435..fbbe6d8b 100644 --- a/ic-agent/src/agent/mod.rs +++ b/ic-agent/src/agent/mod.rs @@ -333,6 +333,11 @@ impl Agent { } let status = self.status().await?; let root_key = match status.root_key { + Some(key) if key[..] == IC_ROOT_KEY[..] => { + // even if the caller ignores this error, we're done here. + self.set_root_key(vec![]); + return Err(AgentError::NeverFetchRootKeyOnMainNet()); + } Some(key) => key, None => return Err(AgentError::NoRootKeyInStatus(status)), }; @@ -343,7 +348,7 @@ impl Agent { /// By default, the agent is configured to talk to the main Internet Computer, and verifies /// responses using a hard-coded public key. /// - /// Using this function you can set the root key to a known one if you know if beforehand. + /// Using this function you can set the root key to a known one if you know it beforehand. pub fn set_root_key(&self, root_key: Vec) { *self.root_key.write().unwrap() = root_key; }