Skip to content

Commit e573a9c

Browse files
committed
fix: accept delegation chains issued by a non-mainnet auth provider
A chain from a local Internet Identity carries a canister signature whose certificate only BLS-verifies against that replica's root key. Two things made such an identity unusable: `icp identity principal`, `account-id` and `delegation sign` pass no network root key and have no flag to supply one, so the chain was always checked against mainnet and always failed. `DelegatedIdentity::new` stops at the first link it cannot verify, so the links behind the canister signature went unchecked, at load time and at link time — including in `create_identity`, which documents its session-key check as running before anything is written. Treat a resolved network root key as authoritative and consult nothing else: a chain that fails against it belongs to another network. Where no root key could be resolved, verify each link as far as it can be verified without one. For a canister signature that is everything but the certificate's own BLS signature: that the CBOR decodes, that the signing canister's certified data matches the signature tree, and that the tree carries a signature over exactly this delegation. ic-agent verifies canister signatures only as a whole, so those checks are repeated here rather than skipped with the trust check. Key the identity cache by the root key as well as the selection: the same identity validates differently against different networks, so an entry cached for one must not be handed to a load that resolved another. `canister create` and `canister settings update` now take the caller principal from the agent rather than loading the same identity a second time without a root key. Also check expiry before `icp identity link web` writes a chain, as the import and load paths already do.
1 parent 763a55f commit e573a9c

7 files changed

Lines changed: 826 additions & 67 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ hmac = { version = "0.13", features = ["zeroize"] }
5454
hybrid-array = { version = "0.4.10", features = ["zeroize"] }
5555
httptest = "0.16.3"
5656
ic-agent = { version = "0.49.1" }
57+
ic-certification = { version = "3.2.0" }
5758
ic-ed25519 = "0.6.0"
5859
ic-ledger-types = "0.16.0"
5960
ic-management-canister-types = { version = "0.9.0" }
@@ -95,6 +96,7 @@ send_ctrlc = "0.6"
9596
semver = "1"
9697
serial_test = { version = "3.2.0", features = ["file_locks"] }
9798
serde = { version = "1.0", features = ["derive"] }
99+
serde_bytes = "0.11.19"
98100
serde_cbor = "0.11.2"
99101
serde_json = "1.0"
100102
serde_yaml = "0.9.34"

crates/icp-cli/src/commands/canister/create.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -355,13 +355,12 @@ async fn create_project_canister(ctx: &Context, args: &CreateArgs) -> Result<(),
355355
return Ok(());
356356
}
357357

358-
let identity = ctx.get_identity(&selections.identity, None).await?;
359-
let caller = identity
360-
.sender()
361-
.map_err(|e| anyhow!("failed to get caller principal: {e}"))?;
362358
let agent = ctx
363359
.get_agent_for_env(&selections.identity, &selections.environment)
364360
.await?;
361+
let caller = agent
362+
.get_principal()
363+
.map_err(|e| anyhow!("failed to get caller principal: {e}"))?;
365364
let ids = ctx
366365
.ids_by_environment(&selections.environment)
367366
.await

crates/icp-cli/src/commands/canister/settings/update.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use anyhow::bail;
22
use candid::Nat;
33
use clap::{ArgAction, Args};
44
use dialoguer::Confirm;
5-
use ic_agent::Identity;
65
use ic_agent::export::Principal;
76
use ic_management_canister_types::{
87
CanisterIdRecord, CanisterSettings, CanisterStatusResult, EnvironmentVariable,
@@ -391,18 +390,16 @@ pub(crate) struct UpdateArgs {
391390

392391
pub(crate) async fn exec(ctx: &Context, args: &UpdateArgs) -> Result<(), anyhow::Error> {
393392
let selections = args.cmd_args.selections();
394-
let identity = ctx.get_identity(&selections.identity, None).await?;
395-
let caller_principal = identity
396-
.sender()
397-
.map_err(|e| anyhow::anyhow!("failed to get caller principal: {e}"))?;
398-
399393
let agent = ctx
400394
.get_agent(
401395
&selections.identity,
402396
&selections.network,
403397
&selections.environment,
404398
)
405399
.await?;
400+
let caller_principal = agent
401+
.get_principal()
402+
.map_err(|e| anyhow::anyhow!("failed to get caller principal: {e}"))?;
406403
let cid = ctx
407404
.get_canister_id(
408405
&selections.canister,

crates/icp/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ hex = { workspace = true }
3131
hmac = { workspace = true }
3232
hybrid-array = { workspace = true }
3333
ic-agent = { workspace = true }
34+
ic-certification = { workspace = true }
3435
ic-ed25519 = { workspace = true }
3536
ic-identity-hsm = { workspace = true }
3637
ic-ledger-types = { workspace = true }
@@ -61,6 +62,7 @@ scrypt = { workspace = true }
6162
semver = { workspace = true }
6263
sec1 = { workspace = true }
6364
serde = { workspace = true }
65+
serde_bytes = { workspace = true }
6466
serde_cbor = { workspace = true }
6567
serde_json = { workspace = true }
6668
serde_yaml = { workspace = true }

0 commit comments

Comments
 (0)