Skip to main content

ic_agent/identity/
delegated.rs

1use candid::Principal;
2use ecdsa::signature::Verifier;
3use ic_certification::{Certificate, LookupResult};
4use k256::Secp256k1;
5use p256::NistP256;
6use pkcs8::der::{Decode, SliceReader};
7use pkcs8::{spki::SubjectPublicKeyInfoRef, AssociatedOid, ObjectIdentifier};
8use sec1::{EcParameters, EncodedPoint};
9use sha2::{Digest, Sha256};
10
11use crate::{
12    agent::{
13        response_authentication::{lookup_canister_ranges, DER_PREFIX, KEY_LENGTH},
14        EnvelopeContent, IC_ROOT_KEY, IC_STATE_ROOT_DOMAIN_SEPARATOR,
15    },
16    Signature,
17};
18
19use super::{error::DelegationError, Delegation, Identity, SignedDelegation};
20
21// OID for canister signatures per IC interface spec §Canister signatures: 1.3.6.1.4.1.56387.1.2
22pub(crate) const CANISTER_SIG_OID: ObjectIdentifier =
23    ObjectIdentifier::new_unwrap("1.3.6.1.4.1.56387.1.2");
24
25/// CBOR body of a canister signature per the IC interface spec.
26///
27/// The wire encoding is `tag(55799, {"certificate": bytes, "tree": hash-tree})`.
28/// `serde_cbor` transparently strips CBOR tags, so no special handling is needed.
29#[derive(serde::Deserialize)]
30struct CanisterSig {
31    #[serde(with = "serde_bytes")]
32    certificate: Vec<u8>,
33    tree: ic_certification::HashTree,
34}
35
36/// An identity that has been delegated the authority to authenticate as a different principal.
37pub struct DelegatedIdentity {
38    to: Box<dyn Identity>,
39    chain: Vec<SignedDelegation>,
40    from_key: Vec<u8>,
41}
42
43impl DelegatedIdentity {
44    /// Creates a delegated identity that signs using `to`, for the principal corresponding to the public key `from_key`.
45    ///
46    /// `chain` must be a list of delegations connecting `from_key` to `to.public_key()`, and in that order;
47    /// otherwise, this function will return an error.
48    ///
49    /// Canister signature keys in the chain are verified against the IC mainnet root key.
50    /// Use [`new_with_root_key`](Self::new_with_root_key) to verify against a different root key
51    /// (e.g. for a local replica or testnet).
52    pub fn new(
53        from_key: Vec<u8>,
54        to: Box<dyn Identity>,
55        chain: Vec<SignedDelegation>,
56    ) -> Result<Self, DelegationError> {
57        Self::new_impl(from_key, to, chain, IC_ROOT_KEY)
58    }
59
60    /// Creates a delegated identity that signs using `to`, for the principal corresponding to the public key `from_key`.
61    ///
62    /// `chain` must be a list of delegations connecting `from_key` to `to.public_key()`, and in that order;
63    /// otherwise, this function will return an error.
64    ///
65    /// `root_key` is the DER-encoded BLS public key of the IC root (or whichever trust anchor is
66    /// appropriate for the network), required to verify canister signature keys in the chain.
67    pub fn new_with_root_key(
68        from_key: Vec<u8>,
69        to: Box<dyn Identity>,
70        chain: Vec<SignedDelegation>,
71        root_key: &[u8],
72    ) -> Result<Self, DelegationError> {
73        Self::new_impl(from_key, to, chain, root_key)
74    }
75
76    /// Creates a delegated identity that signs using `to`, for the principal corresponding to the public key `from_key`.
77    ///
78    /// `chain` must be a list of delegations connecting `from_key` to `to.public_key()`, and in that order;
79    /// otherwise, the replica will reject this delegation when used as an identity.
80    pub fn new_unchecked(
81        from_key: Vec<u8>,
82        to: Box<dyn Identity>,
83        chain: Vec<SignedDelegation>,
84    ) -> Self {
85        Self {
86            to,
87            chain,
88            from_key,
89        }
90    }
91
92    fn new_impl(
93        from_key: Vec<u8>,
94        to: Box<dyn Identity>,
95        chain: Vec<SignedDelegation>,
96        root_key: &[u8],
97    ) -> Result<Self, DelegationError> {
98        let mut last_verified = &from_key;
99        for delegation in &chain {
100            verify_delegation_link(
101                last_verified,
102                &delegation.delegation,
103                &delegation.signature,
104                root_key,
105            )?;
106            last_verified = &delegation.delegation.pubkey;
107        }
108        let delegated_principal = Principal::self_authenticating(last_verified);
109        if delegated_principal != to.sender().map_err(DelegationError::IdentityError)? {
110            return Err(DelegationError::BrokenChain {
111                from: last_verified.clone(),
112                to: None,
113            });
114        }
115
116        Ok(Self::new_unchecked(from_key, to, chain))
117    }
118
119    fn chain_signature(&self, mut sig: Signature) -> Signature {
120        sig.public_key = self.public_key();
121        sig.delegations
122            .get_or_insert(vec![])
123            .extend(self.chain.iter().cloned());
124        sig
125    }
126}
127
128impl Identity for DelegatedIdentity {
129    fn sender(&self) -> Result<Principal, String> {
130        Ok(Principal::self_authenticating(&self.from_key))
131    }
132    fn public_key(&self) -> Option<Vec<u8>> {
133        Some(self.from_key.clone())
134    }
135    fn sign(&self, content: &EnvelopeContent) -> Result<Signature, String> {
136        self.to.sign(content).map(|sig| self.chain_signature(sig))
137    }
138    fn sign_delegation(&self, content: &Delegation) -> Result<Signature, String> {
139        self.to
140            .sign_delegation(content)
141            .map(|sig| self.chain_signature(sig))
142    }
143    fn sign_arbitrary(&self, content: &[u8]) -> Result<Signature, String> {
144        self.to
145            .sign_arbitrary(content)
146            .map(|sig| self.chain_signature(sig))
147    }
148    fn delegation_chain(&self) -> Vec<SignedDelegation> {
149        let mut chain = self.to.delegation_chain();
150        chain.extend(self.chain.iter().cloned());
151        chain
152    }
153    fn sender_info(&self) -> Option<ic_transport_types::SenderInfo> {
154        self.to.sender_info()
155    }
156}
157
158/// Verify one link in the delegation chain: that `delegation` was signed by the key `from_key`.
159fn verify_delegation_link(
160    from_key: &[u8],
161    delegation: &ic_transport_types::Delegation,
162    signature: &[u8],
163    root_key: &[u8],
164) -> Result<(), DelegationError> {
165    let spki = SubjectPublicKeyInfoRef::decode(
166        &mut SliceReader::new(from_key).map_err(|_| DelegationError::Parse)?,
167    )
168    .map_err(|_| DelegationError::Parse)?;
169
170    let payload = delegation.signable();
171
172    if spki.algorithm.oid == elliptic_curve::ALGORITHM_OID {
173        let Some(params) = spki.algorithm.parameters else {
174            return Err(DelegationError::UnknownAlgorithm);
175        };
176        let params = params
177            .decode_as::<EcParameters>()
178            .map_err(|_| DelegationError::Parse)?;
179        let curve = params
180            .named_curve()
181            .ok_or(DelegationError::UnknownAlgorithm)?;
182        if curve == Secp256k1::OID {
183            let pt = EncodedPoint::from_bytes(spki.subject_public_key.raw_bytes())
184                .map_err(|_| DelegationError::Parse)?;
185            let vk = k256::ecdsa::VerifyingKey::from_encoded_point(&pt)
186                .map_err(|_| DelegationError::Parse)?;
187            let sig =
188                k256::ecdsa::Signature::try_from(signature).map_err(|_| DelegationError::Parse)?;
189            vk.verify(&payload, &sig)
190                .map_err(|_| DelegationError::BrokenChain {
191                    from: from_key.to_vec(),
192                    to: Some(delegation.clone()),
193                })?;
194        } else if curve == NistP256::OID {
195            let pt = EncodedPoint::from_bytes(spki.subject_public_key.raw_bytes())
196                .map_err(|_| DelegationError::Parse)?;
197            let vk = p256::ecdsa::VerifyingKey::from_encoded_point(&pt)
198                .map_err(|_| DelegationError::Parse)?;
199            let sig =
200                p256::ecdsa::Signature::try_from(signature).map_err(|_| DelegationError::Parse)?;
201            vk.verify(&payload, &sig)
202                .map_err(|_| DelegationError::BrokenChain {
203                    from: from_key.to_vec(),
204                    to: Some(delegation.clone()),
205                })?;
206        } else {
207            return Err(DelegationError::UnknownAlgorithm);
208        }
209    } else if spki.algorithm.oid == ObjectIdentifier::new_unwrap("1.3.101.112") {
210        let vk = ic_ed25519::PublicKey::deserialize_raw(spki.subject_public_key.raw_bytes())
211            .map_err(|_| DelegationError::Parse)?;
212        vk.verify_signature(&payload, signature)
213            .map_err(|_| DelegationError::BrokenChain {
214                from: from_key.to_vec(),
215                to: Some(delegation.clone()),
216            })?;
217    } else if spki.algorithm.oid == CANISTER_SIG_OID {
218        let (signing_canister_id, seed) =
219            parse_canister_sig_pubkey(spki.subject_public_key.raw_bytes())?;
220        verify_canister_sig(&payload, signature, signing_canister_id, &seed, root_key)?;
221    } else {
222        return Err(DelegationError::UnknownAlgorithm);
223    }
224
225    Ok(())
226}
227
228/// Parse the raw BIT STRING bytes of a canister signature public key into (canister_id, seed).
229///
230/// Format per the IC interface spec:
231///   `| canister_id_length (1 byte) | canister_id_bytes | seed_bytes |`
232pub(crate) fn parse_canister_sig_pubkey(
233    raw: &[u8],
234) -> Result<(Principal, Vec<u8>), DelegationError> {
235    if raw.is_empty() {
236        return Err(DelegationError::Parse);
237    }
238    let id_len = raw[0] as usize;
239    if raw.len() < 1 + id_len {
240        return Err(DelegationError::Parse);
241    }
242    let canister_id =
243        Principal::try_from_slice(&raw[1..1 + id_len]).map_err(|_| DelegationError::Parse)?;
244    let seed = raw[1 + id_len..].to_vec();
245    Ok((canister_id, seed))
246}
247
248/// Strip the DER prefix from a DER-encoded BLS12-381 public key, validating both length and prefix.
249fn extract_bls_key(der: &[u8]) -> Result<&[u8], DelegationError> {
250    if der.len() != DER_PREFIX.len() + KEY_LENGTH {
251        return Err(DelegationError::InvalidCanisterSignature(
252            "invalid BLS public key DER encoding: wrong length".into(),
253        ));
254    }
255    if &der[..DER_PREFIX.len()] != DER_PREFIX.as_ref() {
256        return Err(DelegationError::InvalidCanisterSignature(
257            "invalid BLS public key DER encoding: wrong prefix".into(),
258        ));
259    }
260    Ok(&der[DER_PREFIX.len()..])
261}
262
263/// BLS-verify an IC certificate's signature.
264fn verify_cert_bls(cert: &Certificate, raw_bls_key: &[u8]) -> Result<(), DelegationError> {
265    let root_hash = cert.tree.digest();
266    let mut msg = Vec::with_capacity(IC_STATE_ROOT_DOMAIN_SEPARATOR.len() + 32);
267    msg.extend_from_slice(IC_STATE_ROOT_DOMAIN_SEPARATOR);
268    msg.extend_from_slice(&root_hash);
269    ic_verify_bls_signature::verify_bls_signature(&cert.signature, &msg, raw_bls_key)
270        .map_err(|_| DelegationError::InvalidCanisterSignature("BLS verification failed".into()))
271}
272
273/// Determine the DER-encoded BLS key to use when verifying `cert`, following at most one level of
274/// subnet delegation and checking that `signing_canister_id` is within the delegated ranges.
275fn resolve_cert_key(
276    cert: &Certificate,
277    signing_canister_id: Principal,
278    root_key_der: &[u8],
279) -> Result<Vec<u8>, DelegationError> {
280    let delegation = match &cert.delegation {
281        None => return Ok(root_key_der.to_vec()),
282        Some(d) => d,
283    };
284
285    // Parse the inner delegation certificate (nesting not allowed).
286    let delegation_cert: Certificate =
287        serde_cbor::from_slice(&delegation.certificate).map_err(|e| {
288            DelegationError::InvalidCanisterSignature(format!(
289                "invalid delegation certificate CBOR: {e}"
290            ))
291        })?;
292    if delegation_cert.delegation.is_some() {
293        return Err(DelegationError::InvalidCanisterSignature(
294            "nested delegations in certificate are not allowed".into(),
295        ));
296    }
297
298    // BLS-verify the inner cert against the root key.
299    let raw_root_key = extract_bls_key(root_key_der)?;
300    verify_cert_bls(&delegation_cert, raw_root_key)?;
301
302    // Verify the signing canister is within the subnet's authorised ranges.
303    let subnet_id = Principal::try_from_slice(&delegation.subnet_id).map_err(|_| {
304        DelegationError::InvalidCanisterSignature("invalid subnet_id in delegation".into())
305    })?;
306    let ranges = lookup_canister_ranges(&subnet_id, &delegation_cert).map_err(|e| {
307        DelegationError::InvalidCanisterSignature(format!("canister range lookup failed: {e}"))
308    })?;
309    if !ranges.contains(&signing_canister_id) {
310        return Err(DelegationError::InvalidCanisterSignature(
311            "signing canister is not within the delegation's authorised canister ranges".into(),
312        ));
313    }
314
315    // Return the subnet's public key (DER-encoded) from the inner cert.
316    let pk_path: [&[u8]; 3] = [b"subnet", &delegation.subnet_id, b"public_key"];
317    match delegation_cert.tree.lookup_path(pk_path) {
318        LookupResult::Found(pk) => Ok(pk.to_vec()),
319        _ => Err(DelegationError::InvalidCanisterSignature(
320            "subnet public key not found in delegation certificate".into(),
321        )),
322    }
323}
324
325/// Verify a canister signature over `payload`.
326///
327/// Implements the verification procedure from the IC interface spec §Canister signatures:
328/// 1. The certificate must be valid.
329/// 2. `lookup_path(["canister", canister_id, "certified_data"], cert.tree) == reconstruct(sig.tree)`.
330/// 3. `lookup_path(["sig", sha256(seed), sha256(payload)], sig.tree) == Found("")`.
331pub(crate) fn verify_canister_sig(
332    payload: &[u8],
333    sig_bytes: &[u8],
334    signing_canister_id: Principal,
335    seed: &[u8],
336    root_key_der: &[u8],
337) -> Result<(), DelegationError> {
338    // 1. Decode the CBOR signature envelope.
339    //    CBOR tag 55799 (Self-Described CBOR) is transparently stripped by serde_cbor.
340    let canister_sig: CanisterSig = serde_cbor::from_slice(sig_bytes).map_err(|e| {
341        DelegationError::InvalidCanisterSignature(format!("invalid canister signature CBOR: {e}"))
342    })?;
343
344    // 2. Parse the IC certificate.
345    let cert: Certificate = serde_cbor::from_slice(&canister_sig.certificate).map_err(|e| {
346        DelegationError::InvalidCanisterSignature(format!("invalid certificate CBOR: {e}"))
347    })?;
348
349    // 3. Verify the certificate's BLS signature (following delegation if present).
350    let verification_key_der = resolve_cert_key(&cert, signing_canister_id, root_key_der)?;
351    let raw_bls_key = extract_bls_key(&verification_key_der)?;
352    verify_cert_bls(&cert, raw_bls_key)?;
353
354    // 4. Verify certified_data == reconstruct(sig.tree).
355    //    reconstruct(tree) is the tree's root digest per the IC spec.
356    let tree_root_hash = canister_sig.tree.digest();
357    let certified_data_path: [&[u8]; 3] = [
358        b"canister",
359        signing_canister_id.as_slice(),
360        b"certified_data",
361    ];
362    let certified_data = match cert.tree.lookup_path(certified_data_path) {
363        LookupResult::Found(v) => v,
364        LookupResult::Absent => {
365            return Err(DelegationError::InvalidCanisterSignature(
366                "certified_data is absent from the certificate tree".into(),
367            ))
368        }
369        _ => {
370            return Err(DelegationError::InvalidCanisterSignature(
371                "certified_data lookup returned Unknown or Error".into(),
372            ))
373        }
374    };
375    if certified_data != tree_root_hash.as_ref() {
376        return Err(DelegationError::InvalidCanisterSignature(
377            "certified_data does not match the signature tree root hash".into(),
378        ));
379    }
380
381    // 5. Verify lookup_path(["sig", sha256(seed), sha256(payload)], sig.tree) == Found("").
382    let seed_hash: [u8; 32] = Sha256::digest(seed).into();
383    let payload_hash: [u8; 32] = Sha256::digest(payload).into();
384    let sig_path: [&[u8]; 3] = [b"sig", &seed_hash, &payload_hash];
385    match canister_sig.tree.lookup_path(sig_path) {
386        LookupResult::Found([]) => Ok(()),
387        LookupResult::Found(_) => Err(DelegationError::InvalidCanisterSignature(
388            "sig leaf in the signature tree is not empty".into(),
389        )),
390        _ => Err(DelegationError::InvalidCanisterSignature(
391            "sig not found in the signature tree".into(),
392        )),
393    }
394}