ic-auth 0.15.2

Portable authentication, delegation proof verification and local session machinery
Documentation
#![cfg(feature = "certified-proof-verification")]

use ic_auth::{
    canister_signature::domain_separated_message,
    canonical::*,
    delegation::{
        DelegationProofError as Error, DelegationProofVerificationContext, RootKeyPolicy,
        verify_delegation_proof,
    },
    portable_certified::CertifiedProofLimits,
};
use ic_auth_protocol_types::*;
use ic_platform::bls::PrivateKey;
use ic_platform::canister_sig_creation::CanisterSigPublicKey;
use ic_platform::certification::{Certificate, HashTree, fork, labeled, leaf};
use ic_platform::ecdsa::{PrehashSigner, Signature, SigningKey};
use serde::Serialize;
use serde_bytes::ByteBuf;
use sha2::{Digest, Sha256};

const SEED: &[u8] = b"canic-issuer-delegated-token";
const NOW: u64 = 150;

mod delegation;
mod portable_certified;
mod root_bindings;

fn p(id: u8) -> Principal {
    Principal::from_slice(&[id; 10])
}
fn role() -> AuthRole {
    "app_service".parse().unwrap()
}
fn audience() -> AudienceId {
    AudienceId {
        canonical_network_id: CanonicalId::from_bytes([1; 32]),
        fleet_id: CanonicalId::from_bytes([2; 32]),
    }
}
fn grant(scopes: &[&str]) -> DelegatedRoleGrant {
    DelegatedRoleGrant {
        target: role(),
        scopes: scopes.iter().map(|s| s.to_string()).collect(),
    }
}
fn root_signing_key() -> SigningKey {
    SigningKey::from_slice(&[7; 32]).unwrap()
}
fn bls_key(value: u8) -> PrivateKey {
    let mut bytes = [0; 32];
    bytes[31] = value;
    PrivateKey::deserialize(&bytes).unwrap()
}
fn hash(bytes: &[u8]) -> [u8; 32] {
    Sha256::digest(bytes).into()
}
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
}
#[derive(Serialize)]
struct Envelope {
    certificate: ByteBuf,
    tree: HashTree,
}

fn issuer_proof_message(
    issuer: Principal,
    message: &[u8],
    seed: &[u8],
    time: u64,
) -> IcCanisterSignatureProofV1 {
    let tree = labeled(
        b"sig".to_vec(),
        labeled(hash(seed), labeled(hash(message), leaf(vec![]))),
    );
    let mut time_bytes = Vec::new();
    leb128::write::unsigned(&mut time_bytes, time).unwrap();
    let certificate_tree = fork(
        labeled(
            b"canister".to_vec(),
            labeled(
                issuer.as_slice().to_vec(),
                labeled(b"certified_data".to_vec(), leaf(tree.digest())),
            ),
        ),
        labeled(b"time".to_vec(), leaf(time_bytes)),
    );
    let mut message = b"\x0Dic-state-root".to_vec();
    message.extend_from_slice(&certificate_tree.digest());
    let certificate = Certificate {
        tree: certificate_tree,
        signature: bls_key(1).sign(&message).serialize().to_vec(),
        delegation: None,
    };
    IcCanisterSignatureProofV1 {
        signature_cbor: cbor(&Envelope {
            certificate: ByteBuf::from(cbor(&certificate)),
            tree,
        }),
        public_key_der: CanisterSigPublicKey::new(issuer.into(), seed.to_vec()).to_der(),
    }
}

fn merkle_node(left: [u8; 32], right: [u8; 32]) -> [u8; 32] {
    let mut bytes = vec![1];
    bytes.extend_from_slice(&left);
    bytes.extend_from_slice(&right);
    hash(&bytes)
}

fn root_proof(cert: &DelegationCert, policy: &RootKeyPolicy) -> RootProof {
    let leaf = ChainKeyDelegationCertV1 {
        root_canister_id: cert.root_pid,
        issuer_canister_id: cert.issuer_pid,
        proof_epoch: 2,
        registry_epoch: 3,
        registry_hash: [8; 32],
        issuer_proof_algorithm: cert.issuer_proof_alg,
        issuer_proof_binding_hash: cert.issuer_proof_binding_hash,
        issuer_proof_binding: cert.issuer_proof_binding,
        max_token_ttl_ns: cert.max_token_ttl_ns,
        audience: cert.aud.clone(),
        grants: cert.grants.clone(),
        not_before_ns: cert.not_before_ns,
        expires_at_ns: cert.expires_at_ns,
    };
    let leaf_hash = chain_key_delegation_cert_hash(&leaf).unwrap();
    let header = ChainKeyBatchHeaderV1 {
        schema_version: 1,
        root_canister_id: cert.root_pid,
        batch_id: [7; 32],
        proof_epoch: 2,
        registry_epoch: 3,
        registry_hash: [8; 32],
        tree_root: merkle_node(merkle_node([42; 32], leaf_hash), [43; 32]),
        not_before_ns: cert.not_before_ns,
        expires_at_ns: cert.expires_at_ns,
        algorithm: policy.algorithm,
        key_id: policy.key_id.clone(),
        derivation_path_hash: policy.derivation_path_hash,
        key_version: policy.key_version,
    };
    let sig: Signature = root_signing_key()
        .sign_prehash(&chain_key_batch_header_hash(&header).unwrap())
        .unwrap();
    RootProof::IcChainKeyBatchSignatureV1(IcChainKeyBatchSignatureProofV1 {
        header,
        delegation_cert: leaf,
        issuer_witness: ChainKeyBatchWitnessV1 {
            steps: vec![
                ChainKeyBatchWitnessStepV1::LeftSibling([42; 32]),
                ChainKeyBatchWitnessStepV1::RightSibling([43; 32]),
            ],
        },
        signature: ChainKeyRootSignatureV1 {
            algorithm: policy.algorithm,
            key_id: policy.key_id.clone(),
            derivation_path: vec![b"canic".to_vec(), b"delegation".to_vec()],
            public_key: policy.public_key.clone(),
            signature: sig.to_bytes().to_vec(),
        },
    })
}

struct Fixture {
    proof: DelegationProof,
    policy: RootKeyPolicy,
    ic_root: [u8; 96],
}
impl Fixture {
    fn new() -> Self {
        let policy = RootKeyPolicy {
            root_canister_id: p(1),
            algorithm: ChainKeyAlgorithm::EcdsaSecp256k1,
            key_id: ChainKeyKeyId {
                name: "test_key_1".into(),
            },
            derivation_path_hash: chain_key_derivation_path_hash(&[
                b"canic".to_vec(),
                b"delegation".to_vec(),
            ])
            .unwrap(),
            public_key: root_signing_key()
                .verifying_key()
                .to_encoded_point(true)
                .as_bytes()
                .to_vec(),
            key_version: 4,
            min_accepted_key_version: 4,
            min_accepted_proof_epoch: 2,
            min_accepted_registry_epoch: 3,
            valid_from_ns: 1,
            accept_until_ns: 1000,
            max_revocation_latency_ns: 500,
        };
        let binding = IssuerProofBinding::IcCanisterSignatureV1 {
            seed_hash: hash(SEED),
        };
        let cert = DelegationCert {
            root_pid: p(1),
            issuer_pid: p(2),
            issuer_proof_alg: IssuerProofAlgorithm::IcCanisterSignatureV1,
            issuer_proof_binding_hash: issuer_proof_binding_hash(
                p(2),
                IssuerProofAlgorithm::IcCanisterSignatureV1,
                binding,
            )
            .unwrap(),
            issuer_proof_binding: binding,
            issued_at_ns: 100,
            not_before_ns: 100,
            expires_at_ns: 500,
            max_token_ttl_ns: 100,
            aud: DelegationAudience::Fleet(audience()),
            grants: vec![grant(&["read", "write"])],
        };
        Self {
            proof: DelegationProof {
                root_proof: root_proof(&cert, &policy),
                cert,
            },
            policy,
            ic_root: bls_key(1).public_key().serialize(),
        }
    }
    fn context(&self) -> DelegationProofVerificationContext<'_> {
        DelegationProofVerificationContext {
            expected_issuer: p(2),
            root_key: &self.policy,
            now_ns: NOW,
            limits: limits().into(),
        }
    }
    fn assert_error(&self, expected: Error) {
        assert_eq!(
            verify_delegation_proof(&self.proof, &self.context()).unwrap_err(),
            expected
        );
    }
}
fn limits() -> CertifiedProofLimits {
    CertifiedProofLimits {
        max_cert_ttl_ns: 600,
        max_token_ttl_ns: 120,
        max_future_skew_ns: 10,
        max_certificate_age_ns: 100,
        max_variable_bytes: 64 * 1024,
        max_issuer_signature_bytes: 16 * 1024,
        max_witness_steps: 32,
    }
}

#[test]
fn constructed_batch_witness_authenticates_existing_signed_bytes() {
    let mut f = Fixture::new();
    let before = proof_hash(&f.proof).unwrap();
    let RootProof::IcChainKeyBatchSignatureV1(root) = &mut f.proof.root_proof;
    let leaves = [
        [42; 32],
        chain_key_delegation_cert_hash(&root.delegation_cert).unwrap(),
        [43; 32],
    ];
    let (tree_root, witnesses) =
        ic_auth::chain_key_batch::merkle_root_and_witnesses(&leaves, 3).unwrap();
    assert_eq!(tree_root, root.header.tree_root);
    root.issuer_witness = witnesses[1].clone();
    assert_eq!(proof_hash(&f.proof).unwrap(), before);
    assert!(verify_delegation_proof(&f.proof, &f.context()).is_ok());
    let RootProof::IcChainKeyBatchSignatureV1(root) = &mut f.proof.root_proof;
    root.issuer_witness = witnesses[0].clone();
    f.assert_error(Error::InvalidMerkleWitness);
}