dfinity / dfinity/agent-rs

InvalidCanisterSignature conflates an untrusted root key with a malformed signature

Open
#742 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
144
Forks
84
Avg merge
20h 4m
Merged PRs (30d)
1

Description

DelegationError::InvalidCanisterSignature reports 16 distinct conditions, only one of which is
the certificate's BLS trust check. That makes it impossible for a caller to tell "I do not have the
root key this signature is anchored to" from "this signature is malformed", and the two need
opposite handling.

Where this bites

A delegation chain issued by a non-mainnet Internet Identity (a local replica, a testnet) starts
with a canister signature whose certificate only BLS-verifies against that network's root key. A
client that resolves no network — deriving a principal, signing a delegation offline — has no root
key to check it against, and the provider's root key is not derivable from the chain.

Such a client wants to accept the chain while still rejecting a corrupt one. Today it cannot:

match DelegatedIdentity::new(from_key, session, chain) {
    Err(DelegationError::InvalidCanisterSignature(_)) => {
        // Untrusted root key, or malformed CBOR, or a certified-data mismatch,
        // or a canister outside the delegated ranges, or a missing sig leaf.
        // No way to tell, short of matching on the message string.
    }
    ...
}

In 0.49.1, verify_cert_bls is the only trust-root site; the other 15 are in extract_bls_key,
resolve_cert_key and verify_canister_sig and all indicate corruption.

Why a client cannot work around it cleanly

Of the verification procedure in the interface spec, only step 3 needs a root key. Steps 1, 2, 4
and 5 — the signature CBOR decodes, the certificate decodes, certified_data matches
reconstruct(sig.tree), and the tree carries a leaf for this payload — do not. But
verify_canister_sig is pub(crate) and takes the root key, so there is no way to run the
root-key-independent part alone. The only options are matching on the error string, or
reimplementing those steps against ic-certification and serde_cbor.

Possible shapes

  1. A separate variant for the trust decision, e.g. DelegationError::UntrustedRootKey, leaving
    InvalidCanisterSignature for structural failures.
  2. A constructor taking Option<&[u8]> for the root key, verifying everything and skipping only
    BLS when it is None.
  3. Making the root-key-independent verification public in some form.

Any of the three would remove the need for a client to duplicate certificate handling.

Context

Worked around in dfinity/icp-cli#767 by reimplementing steps 1, 2, 4 and 5 and classifying links by
signing-key type (SPKI OID 1.3.6.1.4.1.56387.1.2) rather than by error variant. Happy to send a PR
here for whichever shape you prefer.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Read the canister-signature verification path, especially verify_cert_bls, extract_bls_key, resolve_cert_key, and verify_canister_sig; the issue identifies these as the trust-root and structural-failure sites. Compare the three proposed API shapes and the interface-spec verification steps, then determine how to preserve rejection of malformed certificates while distinguishing an unavailable or untrusted root key. Done means callers can distinguish the trust decision from structural corruption without duplicating certificate handling.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
backend-api-design
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.