crates/nornir-auth-webauthn/src/client_key.rs
//! **Who a caller is, as a key** (feature `client-key`, no dependencies; RA5-34: the ONE copy
//! of the partition rule, which the ceremony store, the SCIM bearer's flood guard and the
//! estate front's per-caller buckets each used to spell out).
use std::net::IpAddr;
/// **The client a ceremony is held for**: the partition key of the store, and
/// read for nothing else (a `finish` from another address is an ordinary phone
/// moving from wifi to mobile data, not an attack).
///
/// IPv4 is keyed by its address; IPv6 by its /64, because one host is handed a
/// whole /64 by any provider and could otherwise open a fresh partition per
/// address (A34-8). An IPv4-mapped IPv6 address counts as the IPv4 address.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ClientKey(String);
/// The longest named client kept, so a hint cannot grow the store's keys.
pub const MAX_CLIENT_NAME: usize = 128;
impl ClientKey {
/// The partition of an IP address.
pub fn of_ip(ip: IpAddr) -> Self {
match ip {
IpAddr::V4(v4) => ClientKey(v4.to_string()),
IpAddr::V6(v6) => match v6.to_ipv4_mapped() {
Some(v4) => ClientKey(v4.to_string()),
None => {
let s = v6.segments();
ClientKey(format!("{:x}:{:x}:{:x}:{:x}::/64", s[0], s[1], s[2], s[3]))
}
},
}
}
/// The partition of a peer as a server prints it: `1.2.3.4:5678`,
/// `[2001:db8::1]:443`, or a bare address. Anything else is kept as a
/// [`ClientKey::named`] client.
pub fn of_peer(peer: &str) -> Self {
let peer = peer.trim();
if let Ok(addr) = peer.parse::<std::net::SocketAddr>() {
return Self::of_ip(addr.ip());
}
if let Ok(ip) = peer
.trim_start_matches('[')
.trim_end_matches(']')
.parse::<IpAddr>()
{
return Self::of_ip(ip);
}
Self::named(peer)
}
/// A client a trusted relay names in its own words (`via admin: 10.0.0.7`),
/// kept as text, bounded to [`MAX_CLIENT_NAME`] bytes.
pub fn named(text: &str) -> Self {
let text = text.trim();
let mut end = text.len().min(MAX_CLIENT_NAME);
while !text.is_char_boundary(end) {
end -= 1;
}
ClientKey(text[..end].to_owned())
}
/// The key as text.
pub fn as_str(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for ClientKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}