Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/edda

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)
    }
}