ic_canister_sig_creation/
lib.rs

1use candid::Principal;
2use ic_certification::{Hash, HashTree};
3use ic_representation_independent_hash::{representation_independent_hash, Value};
4use lazy_static::lazy_static;
5use serde::{Deserialize, Serialize};
6use serde_bytes::ByteBuf;
7use sha2::{Digest, Sha256};
8
9pub mod signature_map;
10
11pub const IC_ROOT_PK_DER_PREFIX: &[u8; 37] = b"\x30\x81\x82\x30\x1d\x06\x0d\x2b\x06\x01\x04\x01\x82\xdc\x7c\x05\x03\x01\x02\x01\x06\x0c\x2b\x06\x01\x04\x01\x82\xdc\x7c\x05\x03\x02\x01\x03\x61\x00";
12pub const IC_ROOT_PK_DER: &[u8; 133] = b"\x30\x81\x82\x30\x1d\x06\x0d\x2b\x06\x01\x04\x01\x82\xdc\x7c\x05\x03\x01\x02\x01\x06\x0c\x2b\x06\x01\x04\x01\x82\xdc\x7c\x05\x03\x02\x01\x03\x61\x00\x81\x4c\x0e\x6e\xc7\x1f\xab\x58\x3b\x08\xbd\x81\x37\x3c\x25\x5c\x3c\x37\x1b\x2e\x84\x86\x3c\x98\xa4\xf1\xe0\x8b\x74\x23\x5d\x14\xfb\x5d\x9c\x0c\xd5\x46\xd9\x68\x5f\x91\x3a\x0c\x0b\x2c\xc5\x34\x15\x83\xbf\x4b\x43\x92\xe4\x67\xdb\x96\xd6\x5b\x9b\xb4\xcb\x71\x71\x12\xf8\x47\x2e\x0d\x5a\x4d\x14\x50\x5f\xfd\x74\x84\xb0\x12\x91\x09\x1c\x5f\x87\xb9\x88\x83\x46\x3f\x98\x09\x1a\x0b\xaa\xae";
13pub const IC_ROOT_PK_LENGTH: usize = 96;
14
15pub const CANISTER_SIG_PK_DER_PREFIX_LENGTH: usize = 19;
16// Canister signatures' public key OID is 1.3.6.1.4.1.56387.1.2,
17// cf. https://internetcomputer.org/docs/current/references/ic-interface-spec#canister-signatures
18pub const CANISTER_SIG_PK_DER_OID: &[u8; 14] =
19    b"\x30\x0C\x06\x0A\x2B\x06\x01\x04\x01\x83\xB8\x43\x01\x02";
20
21/// Signature domain for IC request auth delegations as specified in the IC interface specification:
22/// https://internetcomputer.org/docs/current/references/ic-interface-spec/#authentication
23pub const DELEGATION_SIG_DOMAIN: &[u8] = b"ic-request-auth-delegation";
24
25lazy_static! {
26    /// The IC root public key used when verifying canister signatures.
27    pub static ref IC_ROOT_PUBLIC_KEY: Vec<u8> =
28        extract_raw_root_pk_from_der(IC_ROOT_PK_DER).expect("Failed decoding IC root key.");
29}
30
31/// A public key of canister signatures,
32/// see https://internetcomputer.org/docs/current/references/ic-interface-spec#canister-signatures
33#[derive(Clone, Eq, PartialEq, Debug)]
34pub struct CanisterSigPublicKey {
35    pub canister_id: Principal,
36    pub seed: Vec<u8>,
37}
38
39impl TryFrom<&[u8]> for CanisterSigPublicKey {
40    type Error = String;
41
42    fn try_from(pk_der: &[u8]) -> Result<Self, Self::Error> {
43        let pk_raw = extract_raw_canister_sig_pk_from_der(pk_der)?;
44        Self::try_from_raw(pk_raw.as_slice())
45    }
46}
47
48impl CanisterSigPublicKey {
49    /// Constructs a new canister signatures public key.
50    pub fn new(canister_id: Principal, seed: Vec<u8>) -> Self {
51        CanisterSigPublicKey { canister_id, seed }
52    }
53
54    pub fn try_from_raw(pk_raw: &[u8]) -> Result<Self, String> {
55        let canister_id_len: usize = if !pk_raw.is_empty() {
56            usize::from(pk_raw[0])
57        } else {
58            return Err("empty raw canister sig pk".to_string());
59        };
60        if pk_raw.len() < (1 + canister_id_len) {
61            return Err("canister sig pk too short".to_string());
62        }
63        let canister_id_raw = &pk_raw[1..(1 + canister_id_len)];
64        let seed = &pk_raw[canister_id_len + 1..];
65        let canister_id = Principal::try_from_slice(canister_id_raw)
66            .map_err(|e| format!("invalid canister id in canister sig pk: {}", e))?;
67        Ok(CanisterSigPublicKey {
68            canister_id,
69            seed: seed.to_vec(),
70        })
71    }
72
73    /// Returns a byte vector with DER-encoding of this key, see
74    /// https://internetcomputer.org/docs/current/references/ic-interface-spec#canister-signatures
75    pub fn to_der(&self) -> Vec<u8> {
76        let raw_pk = self.to_raw();
77
78        let mut der_pk: Vec<u8> = vec![];
79        // sequence of length 17 + the bit string length
80        der_pk.push(0x30);
81        der_pk.push(17 + raw_pk.len() as u8);
82        der_pk.extend(CANISTER_SIG_PK_DER_OID);
83        // BIT string of given length
84        der_pk.push(0x03);
85        der_pk.push(1 + raw_pk.len() as u8);
86        der_pk.push(0x00);
87        der_pk.extend(raw_pk);
88        der_pk
89    }
90
91    /// Returns a byte vector with raw encoding of this key (i.e. a bit string with
92    /// canister id length, canister id, and seed, without the DER-envelope)
93    /// https://internetcomputer.org/docs/current/references/ic-interface-spec#canister-signatures
94    pub fn to_raw(&self) -> Vec<u8> {
95        let mut raw_pk: Vec<u8> = vec![];
96        raw_pk.push(self.canister_id.as_ref().len() as u8);
97        raw_pk.extend(self.canister_id.as_ref());
98        raw_pk.extend(self.seed.as_slice());
99        raw_pk
100    }
101}
102
103/// Verifies the structure given public key in DER-format, and returns raw bytes of the key.
104pub fn extract_raw_root_pk_from_der(pk_der: &[u8]) -> Result<Vec<u8>, String> {
105    let expected_length = IC_ROOT_PK_DER_PREFIX.len() + IC_ROOT_PK_LENGTH;
106    if pk_der.len() != expected_length {
107        return Err(String::from("invalid root pk length"));
108    }
109
110    let prefix = &pk_der[0..IC_ROOT_PK_DER_PREFIX.len()];
111    if prefix[..] != IC_ROOT_PK_DER_PREFIX[..] {
112        return Err(String::from("invalid OID"));
113    }
114
115    let key = &pk_der[IC_ROOT_PK_DER_PREFIX.len()..];
116    Ok(key.to_vec())
117}
118
119/// Verifies the structure given public key in DER-format, and returns raw bytes of the key.
120pub fn extract_raw_canister_sig_pk_from_der(pk_der: &[u8]) -> Result<Vec<u8>, String> {
121    let oid_part = &pk_der[2..(CANISTER_SIG_PK_DER_OID.len() + 2)];
122    if oid_part[..] != CANISTER_SIG_PK_DER_OID[..] {
123        return Err(String::from("invalid OID of canister sig pk"));
124    }
125    let bitstring_offset: usize = CANISTER_SIG_PK_DER_PREFIX_LENGTH;
126    let canister_id_len: usize = if pk_der.len() > bitstring_offset {
127        usize::from(pk_der[bitstring_offset])
128    } else {
129        return Err(String::from("canister sig pk shorter than DER prefix"));
130    };
131    if pk_der.len() < (bitstring_offset + 1 + canister_id_len) {
132        return Err(String::from("canister sig pk too short"));
133    }
134    Ok(pk_der[(bitstring_offset)..].to_vec())
135}
136
137pub fn hash_bytes(value: impl AsRef<[u8]>) -> Hash {
138    let mut hasher = Sha256::new();
139    hasher.update(value.as_ref());
140    hasher.finalize().into()
141}
142
143pub fn hash_with_domain(sep: &[u8], bytes: &[u8]) -> Hash {
144    let mut hasher = Sha256::new();
145    let buf = [sep.len() as u8];
146    hasher.update(buf);
147    hasher.update(sep);
148    hasher.update(bytes);
149    hasher.finalize().into()
150}
151
152/// Computes the signing input for a signature on an IC request authentication delegation.
153/// It can be used in conjunction with the [DELEGATION_SIG_DOMAIN] to create
154/// a signed `sender_delegation`.
155/// Relevant part of the IC interface specification:
156/// https://internetcomputer.org/docs/current/references/ic-interface-spec#authentication
157pub fn delegation_signature_msg(
158    pubkey: &[u8],
159    expiration: u64,
160    targets: Option<&Vec<Vec<u8>>>,
161) -> Vec<u8> {
162    let mut m: Vec<(String, Value)> = vec![];
163    m.push(("pubkey".into(), Value::Bytes(pubkey.to_vec())));
164    m.push(("expiration".into(), Value::Number(expiration)));
165    if let Some(targets) = targets.as_ref() {
166        let mut arr = Vec::with_capacity(targets.len());
167        for t in targets.iter() {
168            arr.push(Value::Bytes(t.to_vec()));
169        }
170        m.push(("targets".into(), Value::Array(arr)));
171    }
172    representation_independent_hash(m.as_slice()).to_vec()
173}
174
175/// A canister signature,
176/// see https://internetcomputer.org/docs/current/references/ic-interface-spec#canister-signatures
177#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
178pub struct CanisterSig {
179    certificate: ByteBuf,
180    tree: HashTree,
181}
182
183/// Parses the given bytes as a CBOR-encoded `CanisterSig`-struct.
184pub fn parse_canister_sig_cbor(signature_cbor: &[u8]) -> Result<CanisterSig, String> {
185    // 0xd9d9f7 (cf. https://tools.ietf.org/html/rfc7049#section-2.4.5) is the
186    // self-describing CBOR tag required to be present by the interface spec.
187    if signature_cbor.len() < 3 || signature_cbor[0..3] != [0xd9, 0xd9, 0xf7] {
188        return Err("signature CBOR doesn't have a self-describing tag".to_string());
189    }
190    serde_cbor::from_slice::<CanisterSig>(signature_cbor)
191        .map_err(|e| format!("failed to parse canister signature CBOR: {}", e))
192}
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197    use assert_matches::assert_matches;
198
199    const TEST_SIGNING_CANISTER_ID: &str = "rwlgt-iiaaa-aaaaa-aaaaa-cai";
200    const TEST_SEED: [u8; 3] = [42, 72, 44];
201
202    const CANISTER_SIG_PK_DER: &[u8; 33] = b"\x30\x1f\x30\x0c\x06\x0a\x2b\x06\x01\x04\x01\x83\xb8\x43\x01\x02\x03\x0f\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x2a\x48\x2c";
203    const CANISTER_SIG_CBOR: &[u8; 265] = b"\xd9\xd9\xf7\xa2\x6b\x63\x65\x72\x74\x69\x66\x69\x63\x61\x74\x65\x58\xa1\xd9\xd9\xf7\xa2\x64\x74\x72\x65\x65\x83\x01\x83\x02\x48\x63\x61\x6e\x69\x73\x74\x65\x72\x83\x02\x4a\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x83\x02\x4e\x63\x65\x72\x74\x69\x66\x69\x65\x64\x5f\x64\x61\x74\x61\x82\x03\x58\x20\xa9\xea\x05\x9d\xf2\x7a\x09\x7e\xc4\x38\xdb\x35\x62\xb9\x55\xc3\xd3\xfa\x08\xeb\x17\xc1\x3c\xda\x63\x90\x42\xfa\xe0\xcf\x60\x36\x83\x02\x44\x74\x69\x6d\x65\x82\x03\x43\x87\xad\x4b\x69\x73\x69\x67\x6e\x61\x74\x75\x72\x65\x58\x30\xa4\xd5\xfd\x47\xa0\x88\x13\x5b\xed\x52\x22\x0c\xca\xa4\x76\xfb\x6c\x88\x95\xdd\xa3\x1e\x2a\x86\xa7\xa2\x97\xdc\x7a\x30\x81\x27\x1e\xf1\x1a\xee\xb5\xd2\xbb\x25\x83\x0d\xcb\xdd\x82\xad\x7a\x52\x64\x74\x72\x65\x65\x83\x02\x43\x73\x69\x67\x83\x02\x58\x20\x00\x42\xcd\x04\x7a\xad\x32\x06\x37\xce\xae\xe2\x1d\x48\x9e\xf4\xe5\x14\xce\x20\x1f\x19\x60\x68\x30\xa2\xaf\x7b\x7d\x9c\x86\x7d\x83\x02\x58\x20\x14\x9b\x80\x95\x11\x98\x27\xcf\xea\x0a\xa6\x6e\x7b\x7f\x80\xe9\x13\xca\xef\xa3\x1a\x60\x6d\xe4\x02\x69\xc3\xd8\x6c\xfe\xa5\x8d\x82\x03\x40";
204
205    #[test]
206    fn should_der_encode_canister_sig_pk() {
207        let canister_id = Principal::from_text(TEST_SIGNING_CANISTER_ID).expect("wrong principal");
208        let cs_pk = CanisterSigPublicKey::new(canister_id, TEST_SEED.to_vec());
209        let cs_pk_der = cs_pk.to_der();
210        assert_eq!(CANISTER_SIG_PK_DER.as_slice(), cs_pk_der.as_slice());
211    }
212
213    #[test]
214    fn should_raw_encode_canister_sig_pk() {
215        let canister_id = Principal::from_text(TEST_SIGNING_CANISTER_ID).expect("wrong principal");
216        let cs_pk = CanisterSigPublicKey::new(canister_id, TEST_SEED.to_vec());
217        let cs_pk_raw = cs_pk.to_raw();
218        assert_eq!(
219            &CANISTER_SIG_PK_DER.as_slice()[CANISTER_SIG_PK_DER_PREFIX_LENGTH..],
220            cs_pk_raw.as_slice()
221        );
222    }
223
224    #[test]
225    fn should_parse_canister_sig_pk_from_der() {
226        let cs_pk = CanisterSigPublicKey::try_from(CANISTER_SIG_PK_DER.as_slice())
227            .expect("Failed parsing canister sig pk DER");
228        let canister_id = Principal::from_text(TEST_SIGNING_CANISTER_ID).expect("wrong principal");
229
230        assert_eq!(cs_pk.canister_id, canister_id);
231        assert_eq!(cs_pk.seed.as_slice(), TEST_SEED.as_slice());
232        assert_eq!(cs_pk.to_der().as_slice(), CANISTER_SIG_PK_DER.as_slice());
233    }
234
235    #[test]
236    fn should_fail_parsing_canister_sig_pk_from_bad_oid_der() {
237        let mut bad_oid_der = *CANISTER_SIG_PK_DER;
238        bad_oid_der[2] += 42;
239        let result = CanisterSigPublicKey::try_from(bad_oid_der.as_slice());
240        assert_matches!(result, Err(e) if e.contains("invalid OID"));
241    }
242
243    #[test]
244    fn should_fail_parsing_canister_sig_pk_from_short_der() {
245        let result = CanisterSigPublicKey::try_from(CANISTER_SIG_PK_DER[..25].to_vec().as_slice());
246        assert_matches!(result, Err(e) if e.contains("pk too short"));
247    }
248
249    #[test]
250    fn should_parse_canister_sig_pk_from_raw() {
251        let cs_pk = CanisterSigPublicKey::try_from_raw(
252            &CANISTER_SIG_PK_DER.as_slice()[CANISTER_SIG_PK_DER_PREFIX_LENGTH..],
253        )
254        .expect("Failed parsing canister sig pk DER");
255        let canister_id = Principal::from_text(TEST_SIGNING_CANISTER_ID).expect("wrong principal");
256
257        assert_eq!(cs_pk.canister_id, canister_id);
258        assert_eq!(cs_pk.seed.as_slice(), TEST_SEED.as_slice());
259        assert_eq!(cs_pk.to_der().as_slice(), CANISTER_SIG_PK_DER.as_slice());
260    }
261
262    #[test]
263    fn should_fail_parsing_canister_sig_pk_from_short_raw() {
264        let result = CanisterSigPublicKey::try_from_raw(
265            &CANISTER_SIG_PK_DER.as_slice()
266                [CANISTER_SIG_PK_DER_PREFIX_LENGTH..(CANISTER_SIG_PK_DER_PREFIX_LENGTH + 10)],
267        );
268        assert_matches!(result, Err(e) if e.contains("pk too short"));
269    }
270
271    #[test]
272    fn should_extract_raw_canister_sig_pk_from_der() {
273        let raw_pk = extract_raw_canister_sig_pk_from_der(CANISTER_SIG_PK_DER)
274            .expect("Wrong DER canister sig pk");
275        assert_eq!(
276            raw_pk.as_slice(),
277            &(*CANISTER_SIG_PK_DER)[CANISTER_SIG_PK_DER_PREFIX_LENGTH..]
278        )
279    }
280
281    #[test]
282    fn should_fail_extract_raw_canister_sig_pk_from_bad_oid_der() {
283        let mut bad_oid_der = *CANISTER_SIG_PK_DER;
284        bad_oid_der[2] += 42;
285        let result = extract_raw_canister_sig_pk_from_der(&bad_oid_der);
286        assert_matches!(result, Err(e) if e.contains("invalid OID"));
287    }
288
289    #[test]
290    fn should_fail_extract_raw_canister_sig_pk_from_short_der() {
291        let result = extract_raw_canister_sig_pk_from_der(&CANISTER_SIG_PK_DER[..25]);
292        assert_matches!(result, Err(e) if e.contains("pk too short"));
293    }
294
295    #[test]
296    fn should_extract_raw_root_pk_from_der() {
297        let raw_pk =
298            extract_raw_root_pk_from_der(IC_ROOT_PK_DER).expect("Failed decoding IC root key.");
299        assert_eq!(IC_ROOT_PK_LENGTH, raw_pk.len());
300        assert_eq!(
301            raw_pk.as_slice(),
302            &(*IC_ROOT_PK_DER)[IC_ROOT_PK_DER_PREFIX.len()..]
303        )
304    }
305
306    #[test]
307    fn should_fail_extract_raw_root_pk_from_bad_oid_der() {
308        let mut bad_oid_der = *IC_ROOT_PK_DER;
309        bad_oid_der[2] += 42;
310        let result = extract_raw_root_pk_from_der(&bad_oid_der);
311        assert_matches!(result, Err(e) if e.contains("invalid OID"));
312    }
313
314    #[test]
315    fn should_fail_extract_raw_root_pk_from_short_der() {
316        let result = extract_raw_root_pk_from_der(&IC_ROOT_PK_DER[..42]);
317        assert_matches!(result, Err(e) if e.contains("invalid root pk length"));
318    }
319
320    #[test]
321    fn should_parse_canister_sig_cbor() {
322        let result = parse_canister_sig_cbor(CANISTER_SIG_CBOR);
323        assert_matches!(result, Ok(_));
324    }
325
326    #[test]
327    fn should_fail_parse_canister_sig_cbor_if_bad_prefix() {
328        let mut bad_prefix_cbor = *CANISTER_SIG_CBOR;
329        bad_prefix_cbor[0] = 42;
330        let result = parse_canister_sig_cbor(&bad_prefix_cbor);
331        assert_matches!(result, Err(e) if e.contains("doesn't have a self-describing tag"));
332    }
333
334    #[test]
335    fn should_fail_parse_canister_sig_cbor_if_incomplete_cbor() {
336        let result = parse_canister_sig_cbor(&CANISTER_SIG_CBOR[..100]);
337        assert_matches!(result, Err(e) if e.contains("failed to parse canister signature"));
338    }
339
340    #[test]
341    fn should_fail_parse_canister_sig_cbor_if_corrupted_cbor() {
342        let mut corrupted_cbor = *CANISTER_SIG_CBOR;
343        // `HashTree` starts around this byte.
344        corrupted_cbor[180] = 42;
345        let result = parse_canister_sig_cbor(&corrupted_cbor);
346        assert_matches!(result, Err(e) if e.contains("failed to parse canister signature"));
347    }
348}