use crate::{decode_canonical, integration::*};
use candid::Principal;
use dmsg_types::{integration::*, *};
use ic_certification::{HashTree, LookupResult};
fn timestamp(bytes: &[u8]) -> Result<u64> {
let mut n = 0u64;
for (i, byte) in bytes.iter().copied().enumerate() {
ensure(i < 10 && (i != 9 || byte <= 1), Error::IntegrityFailed)?;
n |= u64::from(byte & 127) << (7 * i);
if byte & 128 == 0 {
ensure(
i + 1 == bytes.len() && (i == 0 || byte != 0),
Error::IntegrityFailed,
)?;
return Ok(n);
}
}
Err(Error::IntegrityFailed)
}
pub fn verify_authentication(
batch: &CertifiedBatch,
expected: &AuthenticationRequest,
trusted_home: Principal,
trusted_ic_root_der: &[u8],
now_ms: u64,
) -> Result<AuthenticationResult> {
let entry = batch.entries.first().ok_or(Error::IntegrityFailed)?;
let result: AuthenticationResult =
decode_canonical(entry.value.as_ref().ok_or(Error::IntegrityFailed)?)?;
let key = authentication_key(&result.account_id, &expected.operation_id);
let (_, at_ms) = verify_certified_leaf(batch, &key, trusted_home, trusted_ic_root_der, now_ms)?;
match_authentication_result(&result, expected, trusted_home, at_ms, now_ms)?;
Ok(result)
}
pub fn verify_certified_leaf(
batch: &CertifiedBatch,
key: &[u8],
trusted_home: Principal,
trusted_ic_root_der: &[u8],
now_ms: u64,
) -> Result<(Vec<u8>, u64)> {
verify_leaf(
batch,
key,
trusted_home,
trusted_ic_root_der,
u128::from(now_ms) * 1_000_000,
60_000_000_000,
)
}
pub fn verify_recorded_leaf(
batch: &CertifiedBatch,
key: &[u8],
trusted_home: Principal,
trusted_ic_root_der: &[u8],
) -> Result<(Vec<u8>, u64)> {
verify_leaf(batch, key, trusted_home, trusted_ic_root_der, 0, u128::MAX)
}
fn verify_leaf(
batch: &CertifiedBatch,
key: &[u8],
trusted_home: Principal,
trusted_ic_root_der: &[u8],
now_ns: u128,
max_offset_ns: u128,
) -> Result<(Vec<u8>, u64)> {
ensure(
batch.schema == 1
&& batch.canister == trusted_home
&& batch.entries.len() == 1
&& batch.certificate.len() <= MAX_PAYLOAD
&& trusted_ic_root_der.len() <= 256,
Error::IntegrityFailed,
)?;
let entry = &batch.entries[0];
ensure(entry.witness.len() <= MAX_PAYLOAD, Error::QuotaExceeded)?;
let value = entry.value.as_ref().ok_or(Error::IntegrityFailed)?;
ensure(entry.key.as_ref() == key, Error::IntegrityFailed)?;
let certificate = ic_auth_verifier::parse_certificate_cbor(&batch.certificate)
.map_err(|_| Error::IntegrityFailed)?;
ic_auth_verifier::verify_certificate(
&certificate,
trusted_home.as_slice(),
trusted_ic_root_der,
now_ns,
max_offset_ns,
)
.map_err(|_| Error::IntegrityFailed)?;
let LookupResult::Found(time) = certificate.tree.lookup_path([b"time".as_slice()]) else {
return Err(Error::IntegrityFailed);
};
let at_ms = timestamp(time)? / 1_000_000;
let witness: HashTree =
cbor2::from_slice(&entry.witness).map_err(|_| Error::IntegrityFailed)?;
let LookupResult::Found(root) = certificate.tree.lookup_path([
b"canister".as_slice(),
trusted_home.as_slice(),
b"certified_data".as_slice(),
]) else {
return Err(Error::IntegrityFailed);
};
ensure(root == witness.digest(), Error::IntegrityFailed)?;
ensure(
matches!(witness.lookup_path([key]), LookupResult::Found(bytes) if bytes == value.as_ref()),
Error::IntegrityFailed,
)?;
Ok((value.to_vec(), at_ms))
}
#[cfg(test)]
#[path = "authentication_tests.rs"]
mod tests;