Skip to main content

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}