ic-platform 0.16.0

Controlled Candid and IC runtime dependency boundary
Documentation
use super::*;
use crate::bls::PrivateKey;
use crate::certification::upstream::{
    Certificate, Delegation, HashTree, empty, fork, labeled, leaf, pruned,
};
use crate::principal::Principal;
use crate::signature_upstream::creation::IC_ROOT_PK_DER_PREFIX;
use serde::Serialize;
use serde_bytes::ByteBuf;

fn key(value: u8) -> PrivateKey {
    let mut bytes = [0; 32];
    bytes[31] = value;
    PrivateKey::deserialize(&bytes).unwrap()
}
fn cbor<T: Serialize>(value: &T) -> Vec<u8> {
    let mut bytes = Vec::new();
    let mut serializer = serde_cbor::Serializer::new(&mut bytes);
    serializer.self_describe().unwrap();
    value.serialize(&mut serializer).unwrap();
    bytes
}
fn signer() -> Principal {
    Principal::from_slice(&[1; 10])
}
fn certificate(tree: HashTree, key: &PrivateKey, delegation: Option<Delegation>) -> Certificate {
    let mut message = super::super::IC_STATE_ROOT_DOMAIN_SEPARATOR.to_vec();
    message.extend_from_slice(&tree.digest());
    Certificate {
        tree,
        signature: key.sign(&message).serialize().to_vec(),
        delegation,
    }
}
fn envelope(time_tree: HashTree, delegation: Option<Delegation>) -> (Vec<u8>, Vec<u8>, Vec<u8>) {
    let public_key = CanisterSigPublicKey::new(signer(), b"seed".to_vec());
    let tree = labeled(
        b"sig".to_vec(),
        labeled(
            super::super::hash_sha256(b"seed"),
            labeled(super::super::hash_sha256(b"message"), leaf(Vec::new())),
        ),
    );
    let certified = labeled(
        b"canister".to_vec(),
        labeled(
            signer().as_slice().to_vec(),
            labeled(b"certified_data".to_vec(), leaf(tree.digest())),
        ),
    );
    let signing_key = if delegation.is_some() { key(2) } else { key(1) };
    let cert = certificate(fork(certified, time_tree), &signing_key, delegation);
    (
        cbor(&super::super::CanisterSignature {
            certificate: ByteBuf::from(cbor(&cert)),
            tree,
        }),
        public_key.to_der(),
        key(1).public_key().serialize().to_vec(),
    )
}
fn time(bytes: Vec<u8>) -> HashTree {
    labeled(b"time".to_vec(), leaf(bytes))
}

#[test]
fn certified_ranges_preserve_length_first_order_and_boundary_inclusion() {
    // Length-first Principal order: [255] precedes [0, 0]. Plain byte-slice
    // ordering would reverse these bounds and reject the valid endpoints.
    let bytes = [0x81, 0x82, 0x41, 0xff, 0x42, 0, 0];
    let ranges: Vec<(Principal, Principal)> = serde_cbor::from_slice(&bytes).unwrap();
    for (raw, accepted) in [
        (&[][..], false),
        (&[254][..], false),
        (&[255][..], true),
        (&[0, 0][..], true),
        (&[0, 1][..], false),
    ] {
        assert_eq!(
            super::super::principal_is_within_ranges(&Principal::from_slice(raw), &ranges),
            accepted
        );
    }
    assert_eq!(serde_cbor::to_vec(&ranges).unwrap(), bytes);
}

#[cfg(all(feature = "agent", not(target_family = "wasm")))]
#[test]
fn certified_range_decoding_and_order_match_the_registry_reference() {
    use crate::principal::ExternalPrincipal;
    type OwnedRanges = Vec<(Principal, Principal)>;
    type ExternalRanges = Vec<(ExternalPrincipal, ExternalPrincipal)>;

    let principals: Vec<_> = (0..=29)
        .flat_map(|length| {
            [0, 2, 255].map(move |value| Principal::from_slice(&vec![value; length]))
        })
        .collect();
    for left in &principals {
        for right in &principals {
            let external_left = ExternalPrincipal::from(*left);
            let external_right = ExternalPrincipal::from(*right);
            assert_eq!(left.cmp(right), external_left.cmp(&external_right));
            let owned = vec![(*left, *right)];
            let external = vec![(external_left, external_right)];
            let bytes = serde_cbor::to_vec(&external).unwrap();
            assert_eq!(serde_cbor::to_vec(&owned).unwrap(), bytes);
            assert_eq!(
                serde_cbor::from_slice::<OwnedRanges>(&bytes).unwrap(),
                owned
            );
        }
    }

    // Definite/indefinite bytes and arrays, the self-describing tag, empty and
    // marker-looking principal bytes, and malformed/overlong range components.
    let cases = [
        vec![0x81, 0x82, 0x41, 0xff, 0x42, 0, 0],
        vec![
            0x81, 0x82, 0x5f, 0x41, 0xff, 0xff, 0x5f, 0x41, 0, 0x41, 0, 0xff,
        ],
        vec![0x9f, 0x9f, 0x40, 0x41, 2, 0xff, 0xff],
        vec![0xd9, 0xd9, 0xf7, 0x81, 0x82, 0x40, 0x41, 2],
        vec![0x81, 0x81, 0x40],
        vec![0x81, 0x82, 0, 0x40],
        vec![0x81, 0x82, 0x41],
        [vec![0x81, 0x82, 0x58, 30], vec![0; 30], vec![0x40]].concat(),
    ];
    for bytes in cases {
        let owned = serde_cbor::from_slice::<OwnedRanges>(&bytes);
        let external = serde_cbor::from_slice::<ExternalRanges>(&bytes);
        assert_eq!(owned.is_ok(), external.is_ok(), "{bytes:x?}");
        if let (Ok(owned), Ok(external)) = (owned, external) {
            let converted: OwnedRanges = external
                .into_iter()
                .map(|(left, right)| (left.into(), right.into()))
                .collect();
            assert_eq!(owned, converted);
            assert_eq!(
                serde_cbor::from_reader::<OwnedRanges, _>(&bytes[..]).unwrap(),
                owned
            );
            let external: ExternalRanges = serde_cbor::from_reader(&bytes[..]).unwrap();
            assert_eq!(
                serde_cbor::to_vec(&external).unwrap(),
                serde_cbor::to_vec(&owned).unwrap()
            );
        }
    }
}

#[test]
fn authenticated_times_cover_u64_and_existing_nonminimal_encodings() {
    for (bytes, expected) in [
        (vec![0], 0),
        (vec![0x80, 1], 128),
        (vec![0x80, 0], 0),
        (
            {
                let mut b = vec![255; 9];
                b.push(1);
                b
            },
            u64::MAX,
        ),
    ] {
        let (signature, public, root) = envelope(time(bytes), None);
        assert_eq!(
            verify_canister_sig_with_time(b"message", &signature, &public, &root),
            Ok(expected)
        );
        assert_eq!(
            super::super::verify_canister_sig(b"message", &signature, &public, &root),
            Ok(())
        );
        if expected == 128
            && let Some(path) = std::env::var_os("PLATFORM_SIGNATURE_TIME_FIXTURE")
        {
            let text: String = signature.iter().map(|byte| format!("{byte:02x}")).collect();
            std::fs::write(path, text + "\n").unwrap();
        }
    }
}

#[test]
fn malformed_time_is_separate_from_signature_verification() {
    for bytes in [
        vec![],
        vec![0x80],
        vec![255; 10],
        vec![0x80; 11],
        vec![0, 0],
        {
            let mut b = vec![0x80; 9];
            b.push(2);
            b
        },
    ] {
        let (signature, public, root) = envelope(time(bytes), None);
        assert_eq!(
            super::super::verify_canister_sig(b"message", &signature, &public, &root),
            Ok(())
        );
        assert_eq!(
            verify_canister_sig_with_time(b"message", &signature, &public, &root),
            Err(CanisterSigTimeError::InvalidCertificateTime)
        );
        assert!(matches!(
            verify_canister_sig_with_time(b"changed", &signature, &public, &root),
            Err(CanisterSigTimeError::Verification(_))
        ));
    }
    for tree in [
        empty(),
        pruned([0; 32]),
        labeled(b"time".to_vec(), fork(leaf(vec![1]), leaf(vec![2]))),
        fork(time(vec![1]), time(vec![2])),
    ] {
        let (signature, public, root) = envelope(tree, None);
        assert_eq!(
            verify_canister_sig_with_time(b"message", &signature, &public, &root),
            Err(CanisterSigTimeError::InvalidCertificateTime)
        );
    }
}

#[test]
fn original_failures_are_preserved_and_never_return_time() {
    let (signature, public, root) = envelope(time(vec![42]), None);
    let mut bad_key = public.clone();
    bad_key[2] ^= 1;
    let mut bad_signature = signature.clone();
    let last = bad_signature.len() - 1;
    bad_signature[last] ^= 1;
    for (message, signature, public, root) in [
        (
            b"changed".as_slice(),
            signature.as_slice(),
            public.as_slice(),
            root.as_slice(),
        ),
        (b"message", &signature, &bad_key, &root),
        (b"message", &signature, &public, &[0; 96]),
        (b"message", &bad_signature, &public, &root),
        (
            b"message",
            &signature[..signature.len() - 1],
            &public,
            &root,
        ),
    ] {
        let error =
            super::super::verify_canister_sig(message, signature, public, root).unwrap_err();
        assert_eq!(
            verify_canister_sig_with_time(message, signature, public, root),
            Err(CanisterSigTimeError::Verification(error))
        );
    }
}

#[test]
fn identity_root_cannot_authenticate_a_certificate() {
    let (signature, public, root) = envelope(time(vec![42]), None);
    assert_eq!(
        verify_canister_sig_with_time(b"message", &signature, &public, &root),
        Ok(42)
    );
    let mut envelope: super::super::CanisterSignature = serde_cbor::from_slice(&signature).unwrap();
    let cert: Certificate = serde_cbor::from_slice(&envelope.certificate).unwrap();
    let identity = key(0);
    let cert = certificate(cert.tree, &identity, None);
    envelope.certificate = ByteBuf::from(cbor(&cert));
    let forged = cbor(&envelope);
    let root = identity.public_key().serialize();
    let error = super::super::verify_canister_sig(b"message", &forged, &public, &root).unwrap_err();
    assert_eq!(
        verify_canister_sig_with_time(b"message", &forged, &public, &root),
        Err(CanisterSigTimeError::Verification(error))
    );
    if let Some(path) = std::env::var_os("PLATFORM_SIGNATURE_IDENTITY_FIXTURE") {
        std::fs::write(path, hex::encode(&forged) + "\n").unwrap();
    }
}

#[test]
fn delegation_time_is_not_the_signing_time() {
    let subnet = vec![3; 10];
    let mut subnet_key = IC_ROOT_PK_DER_PREFIX.to_vec();
    subnet_key.extend_from_slice(&key(2).public_key().serialize());
    let delegation_tree = fork(
        labeled(
            b"subnet".to_vec(),
            labeled(
                subnet.clone(),
                fork(
                    labeled(
                        b"canister_ranges".to_vec(),
                        leaf(cbor(&vec![(signer(), signer())])),
                    ),
                    labeled(b"public_key".to_vec(), leaf(subnet_key)),
                ),
            ),
        ),
        time(vec![1]),
    );
    let delegation = Delegation {
        subnet_id: subnet,
        certificate: cbor(&certificate(delegation_tree, &key(1), None)),
    };
    let (signature, public, root) = envelope(time(vec![42]), Some(delegation));
    assert_eq!(
        verify_canister_sig_with_time(b"message", &signature, &public, &root),
        Ok(42)
    );
}

#[test]
fn delegated_range_formats_preserve_owned_principal_admission() {
    let subnet = vec![3; 10];
    let mut subnet_key = IC_ROOT_PK_DER_PREFIX.to_vec();
    subnet_key.extend_from_slice(&key(2).public_key().serialize());
    for sharded in [false, true] {
        for allowed in [true, false] {
            // Literal protocol bytes: one range with equal 10-byte endpoints.
            // Avoid using the owned serializer to generate its own acceptance oracle.
            let endpoint = if allowed { 1 } else { 2 };
            let ranges = [
                vec![0x81, 0x82, 0x4a],
                vec![endpoint; 10],
                vec![0x4a],
                vec![endpoint; 10],
            ]
            .concat();
            let public_key_tree = labeled(b"public_key".to_vec(), leaf(subnet_key.clone()));
            let delegation_tree = if sharded {
                fork(
                    labeled(
                        b"canister_ranges".to_vec(),
                        labeled(subnet.clone(), labeled(vec![0; 10], leaf(ranges))),
                    ),
                    labeled(b"subnet".to_vec(), labeled(subnet.clone(), public_key_tree)),
                )
            } else {
                labeled(
                    b"subnet".to_vec(),
                    labeled(
                        subnet.clone(),
                        fork(
                            labeled(b"canister_ranges".to_vec(), leaf(ranges)),
                            public_key_tree,
                        ),
                    ),
                )
            };
            let delegation = Delegation {
                subnet_id: subnet.clone(),
                certificate: cbor(&certificate(delegation_tree, &key(1), None)),
            };
            let (signature, public, root) = envelope(time(vec![42]), Some(delegation));
            let result = verify_canister_sig_with_time(b"message", &signature, &public, &root);
            if allowed {
                assert_eq!(result, Ok(42));
            } else {
                assert_eq!(
                    result,
                    Err(CanisterSigTimeError::Verification(
                        "signing canister id not in canister_ranges".into()
                    ))
                );
            }
            if let Some(directory) = std::env::var_os("PLATFORM_SIGNATURE_RANGE_FIXTURES") {
                let name = format!(
                    "signature-range-{}-{}.hex",
                    if sharded { "sharded" } else { "legacy" },
                    if allowed { "valid" } else { "outside" }
                );
                let text: String = signature.iter().map(|byte| format!("{byte:02x}")).collect();
                std::fs::write(std::path::Path::new(&directory).join(name), text + "\n").unwrap();
            }
        }
    }
}