From 5710c65530acfa52cca07d3cdc72767a89d37507 Mon Sep 17 00:00:00 2001 From: przydatek Date: Wed, 1 Nov 2023 18:06:52 +0100 Subject: [PATCH] Add several utils for handling canister sigs. (#1984) * Add several utils for handling canister sigs. * Update Cargo.lock. * Fix Docker build. * +=clippy * Refactor to remove serial tests. * Address feedback, remove code for verifying canister sigs. * +=clippy * Reuse constant. --- Cargo.lock | 9 ++ src/canister_sig_util/Cargo.toml | 8 +- src/canister_sig_util/src/lib.rs | 141 ++++++++++++++++++++++++ src/internet_identity/src/delegation.rs | 25 +---- 4 files changed, 160 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8441eb86fc..5dc2a803cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,6 +68,12 @@ dependencies = [ "term", ] +[[package]] +name = "assert_matches" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" + [[package]] name = "autocfg" version = "1.1.0" @@ -215,7 +221,10 @@ dependencies = [ name = "canister_sig_util" version = "0.1.0" dependencies = [ + "assert_matches", + "candid", "ic-certified-map", + "lazy_static", "rand", "sha2", ] diff --git a/src/canister_sig_util/Cargo.toml b/src/canister_sig_util/Cargo.toml index 6df521cc09..3255941d0a 100644 --- a/src/canister_sig_util/Cargo.toml +++ b/src/canister_sig_util/Cargo.toml @@ -5,8 +5,14 @@ version = "0.1.0" edition = "2021" [dependencies] +# ic dependencies +candid = "0.9" ic-certified-map = "0.4" +# other dependencies +lazy_static = "1.4" +sha2 = "^0.10" # set bound to match ic-certified-map bound + [dev-dependencies] +assert_matches = "1.5.0" rand = { version ="0.8.5" } -sha2 = "^0.10" # set bound to match ic-certified-map bound diff --git a/src/canister_sig_util/src/lib.rs b/src/canister_sig_util/src/lib.rs index d399a24ab8..94299a704b 100644 --- a/src/canister_sig_util/src/lib.rs +++ b/src/canister_sig_util/src/lib.rs @@ -1 +1,142 @@ +use candid::Principal; +use lazy_static::lazy_static; + pub mod signature_map; + +pub 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"; +pub 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"; +pub const IC_ROOT_PK_LENGTH: usize = 96; + +pub const CANISTER_SIG_PK_DER_PREFIX_LENGTH: usize = 19; +// Canister signatures' public key OID is 1.3.6.1.4.1.56387.1.2, +// cf. https://internetcomputer.org/docs/current/references/ic-interface-spec#canister-signatures +pub const CANISTER_SIG_PK_DER_OID: &[u8; 14] = + b"\x30\x0C\x06\x0A\x2B\x06\x01\x04\x01\x83\xB8\x43\x01\x02"; + +lazy_static! { + /// The IC root public key used when verifying canister signatures. + static ref IC_ROOT_PUBLIC_KEY: Vec = + extract_raw_root_pk_from_der(IC_ROOT_PK_DER).expect("Failed decoding IC root key."); +} + +/// Returns (DER-encoded) public key of the canister signatures for the given canister_id and seed. +/// (cf. https://internetcomputer.org/docs/current/references/ic-interface-spec#canister-signatures)) +pub fn get_canister_sig_pk_der(canister_id: Principal, seed: &[u8]) -> Vec { + let mut bitstring: Vec = vec![]; + bitstring.push(canister_id.as_ref().len() as u8); + bitstring.extend(canister_id.as_ref()); + bitstring.extend(seed); + + let mut der: Vec = vec![]; + // sequence of length 17 + the bit string length + der.push(0x30); + der.push(17 + bitstring.len() as u8); + der.extend(CANISTER_SIG_PK_DER_OID); + // BIT string of given length + der.push(0x03); + der.push(1 + bitstring.len() as u8); + der.push(0x00); + der.extend(bitstring); + der +} + +/// Verifies the structure given public key in DER-format, and returns raw bytes of the key. +fn extract_raw_root_pk_from_der(pk_der: &[u8]) -> Result, String> { + let expected_length = IC_ROOT_PK_DER_PREFIX.len() + IC_ROOT_PK_LENGTH; + if pk_der.len() != expected_length { + return Err(String::from("invalid root pk length")); + } + + let prefix = &pk_der[0..IC_ROOT_PK_DER_PREFIX.len()]; + if prefix[..] != IC_ROOT_PK_DER_PREFIX[..] { + return Err(String::from("invalid OID")); + } + + let key = &pk_der[IC_ROOT_PK_DER_PREFIX.len()..]; + Ok(key.to_vec()) +} + +/// Verifies the structure given public key in DER-format, and returns raw bytes of the key. +pub fn extract_raw_canister_sig_pk_from_der(pk_der: &[u8]) -> Result, String> { + let oid_part = &pk_der[2..(CANISTER_SIG_PK_DER_OID.len() + 2)]; + if oid_part[..] != CANISTER_SIG_PK_DER_OID[..] { + return Err(String::from("invalid OID of canister sig pk")); + } + let bitstring_offset: usize = CANISTER_SIG_PK_DER_PREFIX_LENGTH; + let canister_id_len: usize = if pk_der.len() > bitstring_offset { + usize::from(pk_der[bitstring_offset]) + } else { + return Err(String::from("canister sig pk shorter than DER prefix")); + }; + if pk_der.len() < (bitstring_offset + 1 + canister_id_len) { + return Err(String::from("canister sig pk too short")); + } + Ok(pk_der[(bitstring_offset)..].to_vec()) +} + +#[cfg(test)] +mod tests { + use super::*; + use assert_matches::assert_matches; + + const TEST_SIGNING_CANISTER_ID: &str = "rwlgt-iiaaa-aaaaa-aaaaa-cai"; + const TEST_SEED: [u8; 3] = [42, 72, 44]; + + 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"; + + #[test] + fn should_der_encode_canister_sig_pk() { + let canister_id = Principal::from_text(TEST_SIGNING_CANISTER_ID).expect("wrong principal"); + let cs_pk_der = get_canister_sig_pk_der(canister_id, &TEST_SEED); + assert_eq!(CANISTER_SIG_PK_DER.as_slice(), cs_pk_der.as_slice()); + } + + #[test] + fn should_extract_raw_canister_sig_pk_from_der() { + let raw_pk = extract_raw_canister_sig_pk_from_der(CANISTER_SIG_PK_DER) + .expect("Wrong DER canister sig pk"); + assert_eq!( + raw_pk.as_slice(), + &(*CANISTER_SIG_PK_DER)[CANISTER_SIG_PK_DER_PREFIX_LENGTH..] + ) + } + + #[test] + fn should_fail_extract_raw_canister_sig_pk_from_bad_oid_der() { + let mut bad_oid_der = *CANISTER_SIG_PK_DER; + bad_oid_der[2] += 42; + let result = extract_raw_canister_sig_pk_from_der(&bad_oid_der); + assert_matches!(result, Err(e) if e.contains("invalid OID")); + } + + #[test] + fn should_fail_extract_raw_canister_sig_pk_from_short_der() { + let result = extract_raw_canister_sig_pk_from_der(&CANISTER_SIG_PK_DER[..25]); + assert_matches!(result, Err(e) if e.contains("pk too short")); + } + + #[test] + fn should_extract_raw_root_pk_from_der() { + let raw_pk = + extract_raw_root_pk_from_der(IC_ROOT_PK_DER).expect("Failed decoding IC root key."); + assert_eq!(IC_ROOT_PK_LENGTH, raw_pk.len()); + assert_eq!( + raw_pk.as_slice(), + &(*IC_ROOT_PK_DER)[IC_ROOT_PK_DER_PREFIX.len()..] + ) + } + + #[test] + fn should_fail_extract_raw_root_pk_from_bad_oid_der() { + let mut bad_oid_der = *IC_ROOT_PK_DER; + bad_oid_der[2] += 42; + let result = extract_raw_root_pk_from_der(&bad_oid_der); + assert_matches!(result, Err(e) if e.contains("invalid OID")); + } + + #[test] + fn should_fail_extract_raw_root_pk_from_short_der() { + let result = extract_raw_root_pk_from_der(&IC_ROOT_PK_DER[..42]); + assert_matches!(result, Err(e) if e.contains("invalid root pk length")); + } +} diff --git a/src/internet_identity/src/delegation.rs b/src/internet_identity/src/delegation.rs index 4b7c4bdc70..1057c1f92a 100644 --- a/src/internet_identity/src/delegation.rs +++ b/src/internet_identity/src/delegation.rs @@ -3,6 +3,7 @@ use crate::ii_domain::IIDomain; use crate::state::persistent_state_mut; use crate::{hash, state, update_root_hash, DAY_NS, LABEL_SIG, MINUTE_NS}; use candid::Principal; +use canister_sig_util::get_canister_sig_pk_der; use canister_sig_util::signature_map::SignatureMap; use ic_cdk::api::{data_certificate, time}; use ic_cdk::{id, trap}; @@ -175,28 +176,8 @@ fn calculate_seed(anchor_number: AnchorNumber, frontend: &FrontendHostname) -> H } fn der_encode_canister_sig_key(seed: Vec) -> Vec { - let my_canister_id: Vec = id().as_ref().to_vec(); - - let mut bitstring: Vec = vec![]; - bitstring.push(my_canister_id.len() as u8); - bitstring.extend(my_canister_id); - bitstring.extend(seed); - - let mut der: Vec = vec![]; - // sequence of length 17 + the bit string length - der.push(0x30); - der.push(17 + bitstring.len() as u8); - der.extend(vec![ - // sequence of length 12 for the OID - 0x30, 0x0C, // OID 1.3.6.1.4.1.56387.1.2 - 0x06, 0x0A, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x83, 0xB8, 0x43, 0x01, 0x02, - ]); - // BIT string of given length - der.push(0x03); - der.push(1 + bitstring.len() as u8); - der.push(0x00); - der.extend(bitstring); - der + let my_canister_id = id(); + get_canister_sig_pk_der(my_canister_id, &seed) } fn delegation_signature_msg_hash(d: &Delegation) -> Hash {