use ic_auth_protocol_types::{IcCanisterSignatureProofV1, Principal};
use ic_canister_sig_creation::CanisterSigPublicKey;
use ic_certification::{Certificate, LookupResult};
use serde::Deserialize;
use sha2::{Digest, Sha256};
use thiserror::Error;
#[derive(Clone, Debug)]
pub struct CanisterSignaturePolicy<'a> {
pub signing_canister: Principal,
pub seed_hash: [u8; 32],
pub ic_root_public_key_raw: &'a [u8],
pub now_ns: u64,
pub max_certificate_age_ns: u64,
pub max_future_skew_ns: u64,
pub max_signature_bytes: usize,
pub max_message_bytes: usize,
}
#[derive(Debug, Error, PartialEq, Eq)]
pub enum CanisterSignatureError {
#[error("signature exceeds the host's byte limit")]
SignatureTooLarge,
#[error("message exceeds the host's byte limit")]
MessageTooLarge,
#[error("invalid or unsupported canister signature public key DER")]
InvalidPublicKey,
#[error("signing canister does not match protected authority")]
CanisterMismatch,
#[error("signature seed does not match protected authority")]
SeedMismatch,
#[error("network root public key must contain 96 raw bytes")]
InvalidRootKeyLength,
#[error("invalid canister signature or certificate: {0}")]
InvalidSignature(String),
#[error("certificate time is absent or not a complete u64 LEB128 value")]
InvalidCertificateTime,
#[error("signing certificate is too old")]
CertificateTooOld,
#[error("signing certificate is too far in the future")]
CertificateInFuture,
#[error("signature domain exceeds the IC's one-byte length prefix")]
DomainTooLong,
}
pub fn domain_separated_message(
domain: &[u8],
payload_hash: [u8; 32],
) -> Result<Vec<u8>, CanisterSignatureError> {
let length = u8::try_from(domain.len()).map_err(|_| CanisterSignatureError::DomainTooLong)?;
let mut message = Vec::with_capacity(1 + domain.len() + payload_hash.len());
message.push(length);
message.extend_from_slice(domain);
message.extend_from_slice(&payload_hash);
Ok(message)
}
pub fn verify_canister_signature(
message: &[u8],
proof: &IcCanisterSignatureProofV1,
policy: &CanisterSignaturePolicy<'_>,
) -> Result<(), CanisterSignatureError> {
if proof.signature_cbor.len() > policy.max_signature_bytes {
return Err(CanisterSignatureError::SignatureTooLarge);
}
if message.len() > policy.max_message_bytes {
return Err(CanisterSignatureError::MessageTooLarge);
}
if !(20..=129).contains(&proof.public_key_der.len()) {
return Err(CanisterSignatureError::InvalidPublicKey);
}
let key = CanisterSigPublicKey::try_from(proof.public_key_der.as_slice())
.map_err(|_| CanisterSignatureError::InvalidPublicKey)?;
if key.to_der() != proof.public_key_der {
return Err(CanisterSignatureError::InvalidPublicKey);
}
if key.canister_id != policy.signing_canister {
return Err(CanisterSignatureError::CanisterMismatch);
}
if <[u8; 32]>::from(Sha256::digest(&key.seed)) != policy.seed_hash {
return Err(CanisterSignatureError::SeedMismatch);
}
if policy.ic_root_public_key_raw.len() != 96 {
return Err(CanisterSignatureError::InvalidRootKeyLength);
}
ic_signature_verification::verify_canister_sig(
message,
&proof.signature_cbor,
&proof.public_key_der,
policy.ic_root_public_key_raw,
)
.map_err(CanisterSignatureError::InvalidSignature)?;
let envelope: SignatureEnvelope = serde_cbor::from_slice(&proof.signature_cbor)
.map_err(|err| CanisterSignatureError::InvalidSignature(err.to_string()))?;
let certificate: Certificate = serde_cbor::from_slice(&envelope.certificate)
.map_err(|err| CanisterSignatureError::InvalidSignature(err.to_string()))?;
let LookupResult::Found(mut time_bytes) = certificate.tree.lookup_path([b"time".as_slice()])
else {
return Err(CanisterSignatureError::InvalidCertificateTime);
};
if time_bytes.len() > 10 {
return Err(CanisterSignatureError::InvalidCertificateTime);
}
let time_ns = leb128::read::unsigned(&mut time_bytes)
.map_err(|_| CanisterSignatureError::InvalidCertificateTime)?;
if !time_bytes.is_empty() {
return Err(CanisterSignatureError::InvalidCertificateTime);
}
if time_ns <= policy.now_ns {
if policy.now_ns - time_ns > policy.max_certificate_age_ns {
return Err(CanisterSignatureError::CertificateTooOld);
}
} else if time_ns - policy.now_ns > policy.max_future_skew_ns {
return Err(CanisterSignatureError::CertificateInFuture);
}
Ok(())
}
#[derive(Deserialize)]
struct SignatureEnvelope {
certificate: serde_bytes::ByteBuf,
}