bacnet_transport/principal.rs
1//! The certificate principal of a verified direct BACnet/SC peer (#524, #518).
2
3use std::fmt;
4
5/// Who sent a request, as far as this node can prove it: the leaf
6/// certificate of a verified direct BACnet/SC connection.
7///
8/// It is the SHA-256 of the exact leaf certificate DER the TLS handshake
9/// verified, the same digest the hub's certificate bindings pin
10/// (`ScHubCertificateBinding`, with the `sc-tls` feature).
11/// Unlike [`DirectScIdentity`](crate::port::DirectScIdentity), it carries no
12/// connection incarnation, so it stays the same when that certificate
13/// reconnects. A renewed certificate is a new principal; list both while it
14/// rolls out.
15///
16/// Only direct SC ingress has one. Requests over B/IP, B/IPv6, MS/TP and
17/// Ethernet have none, and neither do requests a hub relays: the hub
18/// authenticated its own TLS client, and the origin's certificate never
19/// reaches this node. The VMAC, UUID and routed SNET/SADR a peer sends are
20/// claims, never principals. A direct peer that forwards a routed request is
21/// only the router: the server's policy contexts report its principal as the
22/// forwarding peer's and give the routed request none.
23///
24/// A principal identifies; it doesn't authorize. Compare it against the
25/// fingerprints an installation provisions. Building one with
26/// [`from_leaf_sha256`](Self::from_leaf_sha256) makes such a pin; it can't
27/// make a request carry it.
28///
29/// `Debug` shows only the first eight hex digits; `Display` writes all 64
30/// in lowercase, the form to copy into configuration. Neither
31/// shows any other certificate content.
32#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
33pub struct ScPrincipal {
34 leaf_sha256: [u8; 32],
35}
36
37impl ScPrincipal {
38 /// The principal whose leaf certificate DER has SHA-256 `leaf_sha256`,
39 /// for comparing against the principal a request carries.
40 pub const fn from_leaf_sha256(leaf_sha256: [u8; 32]) -> Self {
41 Self { leaf_sha256 }
42 }
43
44 /// SHA-256 of the leaf certificate DER.
45 pub const fn leaf_sha256(&self) -> [u8; 32] {
46 self.leaf_sha256
47 }
48}
49
50impl fmt::Display for ScPrincipal {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 self.leaf_sha256
53 .iter()
54 .try_for_each(|octet| write!(f, "{octet:02x}"))
55 }
56}
57
58impl fmt::Debug for ScPrincipal {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 write!(f, "ScPrincipal(sha256:")?;
61 self.leaf_sha256[..4]
62 .iter()
63 .try_for_each(|octet| write!(f, "{octet:02x}"))?;
64 write!(f, "..)")
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71
72 #[test]
73 fn display_is_full_hex_and_debug_is_a_prefix() {
74 let mut digest = [0u8; 32];
75 digest[0] = 0xab;
76 digest[31] = 0x01;
77 let principal = ScPrincipal::from_leaf_sha256(digest);
78 let hex = principal.to_string();
79 assert_eq!(hex.len(), 64);
80 assert!(hex.starts_with("ab000000") && hex.ends_with("01"));
81 assert_eq!(format!("{principal:?}"), "ScPrincipal(sha256:ab000000..)");
82 assert_eq!(principal.leaf_sha256(), digest);
83 }
84}