#![cfg(feature = "net")]
use net::adapter::net::identity::EntityKeypair;
use net::adapter::net::subnet::{
auth::verify_credential_set, SubnetAuthError, SubnetAuthorityConfig, SubnetCredentialSet,
SubnetFloorRegistry, SubnetGrant, SubnetRef, SubnetRevocationFloor, SubnetRights,
TopologySubnetId,
};
const NOW: u64 = 1_800_000_000;
const DAY: u64 = 24 * 60 * 60;
fn kp(seed: u8) -> EntityKeypair {
EntityKeypair::from_bytes([seed; 32])
}
fn config(root: &EntityKeypair) -> SubnetAuthorityConfig {
SubnetAuthorityConfig {
authority: root.entity_id().clone(),
roots: vec![root.entity_id().clone()],
maximum_grant_lifetime_secs: 7 * DAY,
}
}
fn floor(
root: &EntityKeypair,
levels: &[u8],
minimum_generation: u32,
revision: u64,
) -> SubnetRevocationFloor {
SubnetRevocationFloor::try_issue(
root,
SubnetRef {
authority: root.entity_id().clone(),
path: TopologySubnetId::new(levels),
},
0,
minimum_generation,
revision,
NOW,
)
.expect("issue floor")
}
fn grant(
root: &EntityKeypair,
subject: &EntityKeypair,
levels: &[u8],
generation: u32,
) -> SubnetCredentialSet {
SubnetCredentialSet::Direct(
SubnetGrant::try_issue(
root,
root.entity_id().clone(),
TopologySubnetId::new(levels),
0,
subject.entity_id().clone(),
SubnetRights::ATTACH,
generation,
NOW - 60,
DAY,
)
.expect("issue grant"),
)
}
#[test]
fn floors_apply_monotonically_and_survive_replay_reorder() {
let root = kp(1);
let cfg = config(&root);
let reg = SubnetFloorRegistry::new();
let scope = TopologySubnetId::new(&[3, 7]);
assert!(reg
.apply(&floor(&root, &[3, 7], 5, 1), &cfg)
.expect("apply"));
assert_eq!(reg.max_floor(root.entity_id(), 0, scope), 5);
assert_eq!(reg.auth_epoch(root.entity_id()), 1);
assert!(!reg
.apply(&floor(&root, &[3, 7], 5, 1), &cfg)
.expect("apply"));
assert_eq!(reg.auth_epoch(root.entity_id()), 1);
assert!(reg
.apply(&floor(&root, &[3, 7], 9, 3), &cfg)
.expect("apply"));
assert_eq!(reg.max_floor(root.entity_id(), 0, scope), 9);
assert_eq!(reg.auth_epoch(root.entity_id()), 2);
assert!(!reg
.apply(&floor(&root, &[3, 7], 7, 2), &cfg)
.expect("apply"));
assert_eq!(reg.max_floor(root.entity_id(), 0, scope), 9);
assert_eq!(reg.auth_epoch(root.entity_id()), 2);
}
#[test]
fn child_floor_revokes_child_scope_but_not_parent_grant() {
let root = kp(1);
let subject = kp(2);
let cfg = config(&root);
let reg = SubnetFloorRegistry::new();
reg.apply(&floor(&root, &[3, 7], 5, 1), &cfg)
.expect("apply");
assert_eq!(
verify_credential_set(
&grant(&root, &subject, &[3, 7], 1),
subject.entity_id(),
&cfg,
0,
®,
NOW,
60
)
.unwrap_err(),
SubnetAuthError::Revoked
);
assert_eq!(
verify_credential_set(
&grant(&root, &subject, &[3, 7, 2], 1),
subject.entity_id(),
&cfg,
0,
®,
NOW,
60
)
.unwrap_err(),
SubnetAuthError::Revoked
);
verify_credential_set(
&grant(&root, &subject, &[3, 7], 5),
subject.entity_id(),
&cfg,
0,
®,
NOW,
60,
)
.expect("generation at the floor verifies");
verify_credential_set(
&grant(&root, &subject, &[3], 1),
subject.entity_id(),
&cfg,
0,
®,
NOW,
60,
)
.expect("parent-scoped grant survives a child floor");
verify_credential_set(
&grant(&root, &subject, &[3, 8], 1),
subject.entity_id(),
&cfg,
0,
®,
NOW,
60,
)
.expect("sibling-scoped grant survives");
}
#[test]
fn parent_floor_covers_the_subtree() {
let root = kp(1);
let subject = kp(2);
let cfg = config(&root);
let reg = SubnetFloorRegistry::new();
reg.apply(&floor(&root, &[3], 4, 1), &cfg).expect("apply");
for levels in [&[3u8][..], &[3, 7], &[3, 7, 2], &[3, 8]] {
assert_eq!(
verify_credential_set(
&grant(&root, &subject, levels, 3),
subject.entity_id(),
&cfg,
0,
®,
NOW,
60
)
.unwrap_err(),
SubnetAuthError::Revoked,
"scope {levels:?} must be covered by the [3] floor",
);
}
verify_credential_set(
&grant(&root, &subject, &[], 3),
subject.entity_id(),
&cfg,
0,
®,
NOW,
60,
)
.expect("authority-root grant dominates a [3] floor");
reg.apply(&floor(&root, &[], 4, 1), &cfg).expect("apply");
assert_eq!(
verify_credential_set(
&grant(&root, &subject, &[], 3),
subject.entity_id(),
&cfg,
0,
®,
NOW,
60
)
.unwrap_err(),
SubnetAuthError::Revoked
);
}
#[test]
fn floor_epochs_are_independent() {
let root = kp(1);
let cfg = config(&root);
let reg = SubnetFloorRegistry::new();
reg.apply(&floor(&root, &[3], 5, 1), &cfg).expect("apply");
assert_eq!(
reg.max_floor(root.entity_id(), 0, TopologySubnetId::new(&[3, 7])),
5
);
assert_eq!(
reg.max_floor(root.entity_id(), 1, TopologySubnetId::new(&[3, 7])),
0
);
}
#[test]
fn unauthorized_or_forged_floors_change_no_state() {
let root = kp(1);
let rogue = kp(3);
let cfg = config(&root);
let reg = SubnetFloorRegistry::new();
let rogue_floor = SubnetRevocationFloor::try_issue(
&rogue,
SubnetRef {
authority: root.entity_id().clone(),
path: TopologySubnetId::new(&[3]),
},
0,
99,
1,
NOW,
)
.expect("issue");
assert_eq!(
reg.apply(&rogue_floor, &cfg).unwrap_err(),
SubnetAuthError::IssuerNotAuthorized
);
let other = kp(4);
let wrong_authority = SubnetRevocationFloor::try_issue(
&root,
SubnetRef {
authority: other.entity_id().clone(),
path: TopologySubnetId::new(&[3]),
},
0,
99,
1,
NOW,
)
.expect("issue");
assert_eq!(
reg.apply(&wrong_authority, &cfg).unwrap_err(),
SubnetAuthError::WrongAuthority
);
let mut tampered = floor(&root, &[3], 5, 1);
tampered.minimum_generation = 99;
assert_eq!(
reg.apply(&tampered, &cfg).unwrap_err(),
SubnetAuthError::InvalidSignature
);
let mut no_roots = config(&root);
no_roots.roots.clear();
assert_eq!(
reg.apply(&floor(&root, &[3], 5, 1), &no_roots).unwrap_err(),
SubnetAuthError::UnknownAuthority
);
assert_eq!(
reg.max_floor(root.entity_id(), 0, TopologySubnetId::new(&[3])),
0
);
assert_eq!(reg.auth_epoch(root.entity_id()), 0);
}
#[test]
fn a_floor_that_revokes_nothing_is_stored_without_costing_the_epoch() {
let root = kp(1);
let cfg = config(&root);
let reg = SubnetFloorRegistry::new();
let scope = TopologySubnetId::new(&[3, 7]);
assert!(!reg
.apply(&floor(&root, &[3, 7], 0, 1), &cfg)
.expect("a zero floor is accepted"));
assert_eq!(reg.auth_epoch(root.entity_id()), 0);
assert_eq!(reg.max_floor(root.entity_id(), 0, scope), 0);
assert!(!reg
.apply(&floor(&root, &[3, 7], 0, 1), &cfg)
.expect("replay"));
for levels in [&[3u8][..], &[3, 8], &[3, 7, 2]] {
assert!(!reg.apply(&floor(&root, levels, 0, 1), &cfg).expect("apply"));
}
assert_eq!(reg.auth_epoch(root.entity_id()), 0);
assert!(!reg
.apply(&floor(&root, &[3, 7], 5, 1), &cfg)
.expect("stale revision refused"));
assert_eq!(reg.auth_epoch(root.entity_id()), 0);
assert!(reg
.apply(&floor(&root, &[3, 7], 5, 2), &cfg)
.expect("apply"));
assert_eq!(reg.max_floor(root.entity_id(), 0, scope), 5);
assert_eq!(reg.auth_epoch(root.entity_id()), 1);
}
#[test]
fn floor_wire_round_trips() {
let root = kp(1);
let f = floor(&root, &[3, 7], 5, 42);
let decoded = SubnetRevocationFloor::from_bytes(&f.to_bytes()).expect("round trip");
assert_eq!(decoded, f);
decoded.verify().expect("signature verifies");
}