use super::*;
use crate::mutation::MutationAuthorizationContext;
use bacnet_transport::sc_tls::{ScPeerCertificate, ScSubjectAltName};
type Seen = Arc<StdMutex<Vec<(Vec<String>, Vec<String>, bool, String)>>>;
fn dns_names(certificate: Option<&ScPeerCertificate>) -> Vec<String> {
certificate
.and_then(ScPeerCertificate::metadata)
.map(|metadata| {
metadata
.subject_alt_names()
.iter()
.filter_map(|name| match name {
ScSubjectAltName::Dns(dns) => Some(dns.clone()),
_ => None,
})
.collect()
})
.unwrap_or_default()
}
fn san_policy(seen: Seen) -> ServerConfig {
ServerConfig {
policy: Some(Arc::new(crate::FnPolicy::new().mutation(
move |context: &MutationAuthorizationContext| {
let names = dns_names(context.certificate());
let same = match (context.certificate(), context.forwarding_peer_certificate()) {
(Some(a), Some(b)) => a.der() == b.der(),
_ => false,
};
seen.lock().unwrap().push((
names.clone(),
dns_names(context.forwarding_peer_certificate()),
same,
format!("{context:?}"),
));
names.iter().any(|name| name == "operator")
},
))),
..Default::default()
}
}
fn write(text: &str, invoke: u8) -> Apdu {
confirmed(
ConfirmedServiceChoice::WRITE_PROPERTY,
invoke,
write_payload(text),
)
}
#[tokio::test]
async fn a_policy_allows_by_subject_alt_name_across_renewal() {
let ca = TestCa::new();
let seen = Seen::default();
let mut f = Fixture::new(
&ca,
san_policy(seen.clone()),
database(Arc::new(AtomicUsize::new(0))),
)
.await;
let mut operator = f.peer(ca.tls("operator")).await;
let first = f.capture_from(&mut operator, &write("one", 1), None).await;
let first_principal = first.provenance.principal().unwrap();
f.feed(first).await;
assert!(matches!(f.response().await, Apdu::SimpleAck(_)));
let mut renewed = f.peer(ca.tls("operator")).await;
let second = f.capture_from(&mut renewed, &write("two", 2), None).await;
assert_ne!(second.provenance.principal(), Some(first_principal));
f.feed(second).await;
assert!(matches!(f.response().await, Apdu::SimpleAck(_)));
let mut intruder = f.peer(ca.tls("intruder")).await;
let third = f
.capture_from(&mut intruder, &write("three", 3), None)
.await;
f.feed(third).await;
assert!(denied(&f.response().await));
let routed = f.capture(&mut intruder, &write("four", 4)).await;
f.feed(routed).await;
assert!(denied(&f.response().await));
let seen = seen.lock().unwrap().clone();
let decisions: Vec<_> = seen
.iter()
.map(|(names, forwarder, same, _)| (names.clone(), forwarder.clone(), *same))
.collect();
let names = |name: &str| vec![name.to_owned()];
assert_eq!(
decisions,
[
(names("operator"), names("operator"), true),
(names("operator"), names("operator"), true),
(names("intruder"), names("intruder"), true),
(Vec::new(), names("intruder"), false),
]
);
for (_, _, _, debug) in &seen {
assert!(
!debug.contains("operator") && !debug.contains("intruder"),
"{debug}"
);
assert!(!debug.contains("rcgen"), "{debug}");
}
f.stop().await;
}