Skip to content

Commit

Permalink
Add several utils for handling canister sigs. (#1984)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
przydatek committed Nov 1, 2023
1 parent deb8057 commit 5710c65
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 23 deletions.
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion src/canister_sig_util/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
141 changes: 141 additions & 0 deletions src/canister_sig_util/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<u8> =
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<u8> {
let mut bitstring: Vec<u8> = vec![];
bitstring.push(canister_id.as_ref().len() as u8);
bitstring.extend(canister_id.as_ref());
bitstring.extend(seed);

let mut der: Vec<u8> = 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<Vec<u8>, 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<Vec<u8>, 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"));
}
}
25 changes: 3 additions & 22 deletions src/internet_identity/src/delegation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -175,28 +176,8 @@ fn calculate_seed(anchor_number: AnchorNumber, frontend: &FrontendHostname) -> H
}

fn der_encode_canister_sig_key(seed: Vec<u8>) -> Vec<u8> {
let my_canister_id: Vec<u8> = id().as_ref().to_vec();

let mut bitstring: Vec<u8> = vec![];
bitstring.push(my_canister_id.len() as u8);
bitstring.extend(my_canister_id);
bitstring.extend(seed);

let mut der: Vec<u8> = 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 {
Expand Down

0 comments on commit 5710c65

Please sign in to comment.