Skip to content

Commit

Permalink
fix: disallow nested delegations when verifying certificates
Browse files Browse the repository at this point in the history
  • Loading branch information
nathanosdev committed Dec 19, 2023
1 parent c25ea12 commit b122bcc
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ impl VerifyCertificate<Vec<u8>> for Delegation {
root_public_key: &[u8],
) -> CertificateVerificationResult<Vec<u8>> {
let cert: Certificate = Certificate::from_cbor(&self.certificate)?;
if cert.delegation.is_some() {
return Err(CertificateVerificationError::CertificateHasTooManyDelegations);
}
cert.verify(canister_id, root_public_key)?;

let canister_range_path = [
Expand Down Expand Up @@ -158,18 +161,17 @@ pub fn validate_certificate_time(

#[cfg(test)]
mod tests {
use std::{
ops::{Add, Sub},
time::{Duration, SystemTime},
};

use super::*;
use ic_cbor::CertificateToCbor;
use ic_certification::Certificate;
use ic_certification_testing::{CertificateBuilder, CertificateData};
use ic_response_verification_test_utils::{
create_canister_id, get_current_timestamp, get_timestamp, AssetTree,
};
use std::{
ops::{Add, Sub},
time::{Duration, SystemTime},
};

static CANISTER_ID: &str = "r7inp-6aaaa-aaaaa-aaabq-cai";
const MAX_CERT_TIME_OFFSET_NS: u128 = 300_000_000_000; // 5 min
Expand All @@ -194,6 +196,33 @@ mod tests {
certificate.verify(canister_id.as_ref(), &root_key).unwrap();
}

#[test]
fn verify_certificate_with_nested_delegation_should_fail() {
let canister_id = create_canister_id(CANISTER_ID);
let CertificateData {
cbor_encoded_certificate,
certificate: _,
root_key,
} = CertificateBuilder::new(
&canister_id.to_string(),
&AssetTree::new().get_certified_data(),
)
.unwrap()
.with_delegation(123, vec![(0, 9)])
.with_nested_delegation(456, vec![(20, 19)])
.build()
.unwrap();

let certificate = Certificate::from_cbor(&cbor_encoded_certificate).unwrap();

let result = certificate.verify(canister_id.as_ref(), &root_key);

assert!(matches!(
result.err(),
Some(CertificateVerificationError::CertificateHasTooManyDelegations),
))
}

#[test]
fn verify_certificate_should_fail() {
let canister_id = create_canister_id(CANISTER_ID);
Expand Down
4 changes: 4 additions & 0 deletions packages/ic-certificate-verification/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,8 @@ pub enum CertificateVerificationError {
/// Failed to decode CBOR
#[error("CBOR decoding failed")]
CborDecodingFailed(#[from] CborError),

/// The certificate contained more than one delegation.
#[error("The certificate contained more than one delegation")]
CertificateHasTooManyDelegations,
}
135 changes: 103 additions & 32 deletions packages/ic-certification-testing/src/certificate_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ pub struct CertificateBuilder {
time: Option<u128>,
canister: Option<CanisterData>,
subnet: Option<SubnetData>,
nested_subnet: Option<SubnetData>,
signature: Option<Blob>,
custom_tree: Option<LabeledTree<Vec<u8>>>,
}
Expand All @@ -67,6 +68,7 @@ impl CertificateBuilder {
certified_data: certified_data.to_vec(),
}),
subnet: None,
nested_subnet: None,
signature: None,
custom_tree: None,
})
Expand All @@ -77,6 +79,7 @@ impl CertificateBuilder {
time: None,
canister: None,
subnet: None,
nested_subnet: None,
signature: None,
custom_tree: Some(custom_tree),
}
Expand All @@ -87,17 +90,17 @@ impl CertificateBuilder {
subnet_id: u64,
canister_id_ranges: Vec<(u64, u64)>,
) -> &mut Self {
let canister_id_ranges = canister_id_ranges
.into_iter()
.map(|(low, high)| (CanisterId::from_u64(low), CanisterId::from_u64(high)))
.collect();
self.subnet = Some(create_subnet_data(subnet_id, canister_id_ranges));

let subnet_id = SubnetId::from(PrincipalId::new_subnet_test_id(subnet_id));
self
}

self.subnet = Some(SubnetData {
subnet_id,
canister_id_ranges,
});
pub fn with_nested_delegation(
&mut self,
subnet_id: u64,
canister_id_ranges: Vec<(u64, u64)>,
) -> &mut Self {
self.nested_subnet = Some(create_subnet_data(subnet_id, canister_id_ranges));

self
}
Expand Down Expand Up @@ -134,10 +137,25 @@ impl CertificateBuilder {
})?;

let (keypair, tree, signature) = build_certificate(&tree)?;
let delegation = None;
let delegation_data = self.build_delegation(&keypair, &encoded_time)?;
let signature = self.signature.as_ref().unwrap_or(&signature);

let nested_delegation_data = self.build_nested_delegation(&keypair, &encoded_time)?;
if let Some((delegation, keypair)) = nested_delegation_data {
let certificate = Certificate {
tree,
signature: signature.clone(),
delegation: Some(delegation),
};
let certificate_cbor = serialize_to_cbor(&certificate);

return Ok(CertificateData {
certificate,
root_key: keypair.public_key,
cbor_encoded_certificate: certificate_cbor,
});
}

let delegation_data = self.build_delegation(&keypair, &encoded_time)?;
if let Some((delegation, keypair)) = delegation_data {
let certificate = Certificate {
tree,
Expand All @@ -156,7 +174,7 @@ impl CertificateBuilder {
let certificate = Certificate {
tree,
signature: signature.clone(),
delegation,
delegation: None,
};

let certificate_cbor = serialize_to_cbor(&certificate);
Expand All @@ -173,31 +191,84 @@ impl CertificateBuilder {
delegatee_keypair: &KeyPair,
encoded_time: &[u8],
) -> CertificationTestResult<Option<(CertificateDelegation, KeyPair)>> {
if let Some(subnet) = &self.subnet {
let tree = create_delegation_tree(
&delegatee_keypair.public_key,
encoded_time,
&subnet.subnet_id,
&subnet.canister_id_ranges,
)?;
let (keypair, tree, signature) = build_certificate(&tree)?;
let certificate = Certificate {
tree,
signature,
delegation: None,
};
if let Some(subnet_data) = &self.subnet {
let delegation_data =
create_delegation_data(delegatee_keypair, encoded_time, subnet_data, None)?;

return Ok(Some((
CertificateDelegation {
certificate: Blob(serialize_to_cbor(&certificate)),
subnet_id: Blob(subnet.subnet_id.get().to_vec()),
},
keypair,
)));
return Ok(Some(delegation_data));
}

Ok(None)
}

fn build_nested_delegation(
&self,
delegatee_keypair: &KeyPair,
encoded_time: &[u8],
) -> CertificationTestResult<Option<(CertificateDelegation, KeyPair)>> {
match (&self.subnet, &self.nested_subnet) {
(Some(subnet_data), Some(nested_subnet_data)) => {
let (nested_delegation, nested_keypair) = create_delegation_data(
delegatee_keypair,
encoded_time,
nested_subnet_data,
None,
)?;

let (delegation, _keypair) = create_delegation_data(
&nested_keypair,
encoded_time,
subnet_data,
Some(nested_delegation),
)?;

return Ok(Some((delegation, nested_keypair)));
}
(_, _) => Ok(None),
}
}
}

fn create_delegation_data(
delegatee_keypair: &KeyPair,
encoded_time: &[u8],
subnet_data: &SubnetData,
nested_delegation: Option<CertificateDelegation>,
) -> CertificationTestResult<(CertificateDelegation, KeyPair)> {
let tree = create_delegation_tree(
&delegatee_keypair.public_key,
encoded_time,
&subnet_data.subnet_id,
&subnet_data.canister_id_ranges,
)?;
let (keypair, tree, signature) = build_certificate(&tree)?;
let certificate = Certificate {
tree,
signature,
delegation: nested_delegation,
};

Ok((
CertificateDelegation {
certificate: Blob(serialize_to_cbor(&certificate)),
subnet_id: Blob(subnet_data.subnet_id.get().to_vec()),
},
keypair,
))
}

fn create_subnet_data(subnet_id: u64, canister_id_ranges: Vec<(u64, u64)>) -> SubnetData {
let canister_id_ranges = canister_id_ranges
.into_iter()
.map(|(low, high)| (CanisterId::from_u64(low), CanisterId::from_u64(high)))
.collect();

let subnet_id = SubnetId::from(PrincipalId::new_subnet_test_id(subnet_id));

SubnetData {
subnet_id,
canister_id_ranges,
}
}

fn build_certificate(
Expand Down

0 comments on commit b122bcc

Please sign in to comment.