liminal-server 0.25.4

Standalone server for the liminal messaging bus
Documentation
//! The link handshake: a Noise `KK` exchange whose static keys are the two
//! machines' Lys identities, through lys-core's X25519 derivation and never a
//! second one (ACCESS-002 R1).
//!
//! `KK` means each side knows the other's static key before the first byte:
//! the importer's and exporter's Lys-recorded Ed25519 keys, mapped to their
//! Montgomery form. A third machine, whose static key is not the one the peer
//! expected, fails at the first message; so does a key derived any other way
//! from the same identity, because it is simply a different key.

use ed25519_dalek::VerifyingKey;
use lys_core::keys::Ed25519Identity;

/// The Noise pattern and primitives every link uses; both ends must agree.
pub const LINK_NOISE_PATTERN: &str = "Noise_KK_25519_ChaChaPoly_BLAKE2s";
/// Bytes a handshake message or transport frame may need above its payload.
pub const NOISE_OVERHEAD: usize = 96;
/// The largest frame Noise carries.
pub const NOISE_MAX_MESSAGE: usize = 65_535;

/// A machine as Lys records it: its Ed25519 public key.
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct MachineIdentity {
    ed25519_public: [u8; 32],
}

impl std::fmt::Debug for MachineIdentity {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        formatter
            .debug_struct("MachineIdentity")
            .field("ed25519_public", &"32 bytes")
            .finish()
    }
}

impl MachineIdentity {
    /// A machine from the Ed25519 public key Lys recorded for it.
    #[must_use]
    pub const fn new(ed25519_public: [u8; 32]) -> Self {
        Self { ed25519_public }
    }

    /// The recorded Ed25519 public key.
    #[must_use]
    pub const fn ed25519_public(&self) -> &[u8; 32] {
        &self.ed25519_public
    }

    /// The machine's X25519 static public key: the Montgomery form of its
    /// Ed25519 point, the same map lys-core applies to its own identity.
    ///
    /// # Errors
    /// The recorded bytes are not a valid Ed25519 public key.
    pub fn x25519_public(&self) -> Result<[u8; 32], LinkHandshakeError> {
        VerifyingKey::from_bytes(&self.ed25519_public)
            .map(|key| key.to_montgomery().to_bytes())
            .map_err(|_| LinkHandshakeError::InvalidIdentity)
    }
}

/// This machine, holding the Lys identity whose derivation keys the link.
pub struct LocalMachine {
    identity: Ed25519Identity,
}

impl std::fmt::Debug for LocalMachine {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        formatter
            .debug_struct("LocalMachine")
            .field("identity", &self.machine())
            .finish()
    }
}

impl LocalMachine {
    /// Wraps the Lys identity this machine signs with.
    #[must_use]
    pub const fn new(identity: Ed25519Identity) -> Self {
        Self { identity }
    }

    /// This machine as Lys records it.
    #[must_use]
    pub fn machine(&self) -> MachineIdentity {
        MachineIdentity::new(self.identity.public_key_bytes())
    }

    fn x25519_secret(&self) -> [u8; 32] {
        self.identity.x25519_static_secret().to_bytes()
    }

    /// Signs the issuer's exact pass request as current certificate-standing proof.
    /// The nonce is fresh for this open and the issuer rejects its reuse.
    ///
    /// # Errors
    /// Randomness or the wall clock is unavailable.
    pub(crate) fn certificate_proof(
        &self,
        agent: &str,
        path: &str,
        body: &[u8],
    ) -> Result<String, crate::link::certificate::CertificateRefusal> {
        use crate::link::certificate::CertificateRefusal;
        use std::io::Read;
        let mut nonce = [0; 16];
        std::fs::File::open("/dev/urandom")
            .and_then(|mut file| file.read_exact(&mut nonce))
            .map_err(|_| CertificateRefusal::StatusUnavailable)?;
        let at = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map_err(|_| CertificateRefusal::StatusUnavailable)?;
        let at =
            u64::try_from(at.as_millis()).map_err(|_| CertificateRefusal::StatusUnavailable)?;
        Ok(crate::link_pass::cose::header(
            &self.identity,
            agent,
            path,
            body,
            at,
            &hex::encode(nonce),
        ))
    }
}

/// Why a handshake did not complete.
#[derive(Debug, PartialEq, Eq)]
pub enum LinkHandshakeError {
    /// A recorded public key that is not an Ed25519 point.
    InvalidIdentity,
    /// The peer's message did not authenticate against the expected static
    /// key: a third machine, or a key derived some other way.
    PeerNotTheExpectedMachine,
    /// A message or buffer outside Noise's bounds.
    Framing(String),
    /// The handshake pattern or keys could not be set up.
    Setup(String),
    /// Transport was requested before the handshake finished.
    NotFinished,
}

impl std::fmt::Display for LinkHandshakeError {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::InvalidIdentity => formatter.write_str("machine identity is not an Ed25519 key"),
            Self::PeerNotTheExpectedMachine => {
                formatter.write_str("peer is not the machine this link was opened with")
            }
            Self::Framing(detail) => write!(formatter, "noise framing refused: {detail}"),
            Self::Setup(detail) => write!(formatter, "noise setup refused: {detail}"),
            Self::NotFinished => formatter.write_str("handshake has not finished"),
        }
    }
}

impl std::error::Error for LinkHandshakeError {}

fn classify(error: &snow::Error) -> LinkHandshakeError {
    match error {
        snow::Error::Decrypt => LinkHandshakeError::PeerNotTheExpectedMachine,
        snow::Error::Input => LinkHandshakeError::Framing("input outside bounds".to_owned()),
        other => LinkHandshakeError::Framing(other.to_string()),
    }
}

/// One end of a link handshake in progress.
pub struct LinkHandshake {
    state: snow::HandshakeState,
    peer: MachineIdentity,
}

impl std::fmt::Debug for LinkHandshake {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        formatter
            .debug_struct("LinkHandshake")
            .field("peer", &self.peer)
            .field("finished", &self.state.is_handshake_finished())
            .finish()
    }
}

impl LinkHandshake {
    fn build(
        local: &LocalMachine,
        peer: MachineIdentity,
        initiator: bool,
    ) -> Result<Self, LinkHandshakeError> {
        let params: snow::params::NoiseParams = LINK_NOISE_PATTERN
            .parse()
            .map_err(|error: snow::Error| LinkHandshakeError::Setup(error.to_string()))?;
        let secret = local.x25519_secret();
        let remote = peer.x25519_public()?;
        let builder = snow::Builder::new(params)
            .local_private_key(&secret)
            .map_err(|error| LinkHandshakeError::Setup(error.to_string()))?
            .remote_public_key(&remote)
            .map_err(|error| LinkHandshakeError::Setup(error.to_string()))?;
        let state = if initiator {
            builder.build_initiator()
        } else {
            builder.build_responder()
        }
        .map_err(|error| LinkHandshakeError::Setup(error.to_string()))?;
        Ok(Self { state, peer })
    }

    /// The end that sends the first message, expecting `peer` to answer.
    ///
    /// # Errors
    /// A peer key that is not an Ed25519 point, or a Noise setup refusal.
    pub fn initiator(
        local: &LocalMachine,
        peer: MachineIdentity,
    ) -> Result<Self, LinkHandshakeError> {
        Self::build(local, peer, true)
    }

    /// The end that waits for the first message, expecting it from `peer`.
    ///
    /// # Errors
    /// A peer key that is not an Ed25519 point, or a Noise setup refusal.
    pub fn responder(
        local: &LocalMachine,
        peer: MachineIdentity,
    ) -> Result<Self, LinkHandshakeError> {
        Self::build(local, peer, false)
    }

    /// Writes the next handshake message into `out`, returning its length.
    ///
    /// # Errors
    /// A buffer too small, or a message out of turn.
    pub fn write(&mut self, out: &mut [u8]) -> Result<usize, LinkHandshakeError> {
        self.state
            .write_message(&[], out)
            .map_err(|error| classify(&error))
    }

    /// Reads the peer's handshake message.
    ///
    /// # Errors
    /// `PeerNotTheExpectedMachine` when the message does not authenticate
    /// against the static key this end expected; framing otherwise.
    pub fn read(&mut self, message: &[u8]) -> Result<(), LinkHandshakeError> {
        let mut payload = [0_u8; NOISE_OVERHEAD];
        self.state
            .read_message(message, &mut payload)
            .map(|_| ())
            .map_err(|error| classify(&error))
    }

    /// Whether both messages of `KK` have been exchanged.
    #[must_use]
    pub fn is_finished(&self) -> bool {
        self.state.is_handshake_finished()
    }

    /// The transport the finished handshake keyed, bound to the proved peer.
    ///
    /// # Errors
    /// `NotFinished` before both messages were exchanged.
    pub fn finish(self) -> Result<LinkTransport, LinkHandshakeError> {
        if !self.state.is_handshake_finished() {
            return Err(LinkHandshakeError::NotFinished);
        }
        let transport = self
            .state
            .into_transport_mode()
            .map_err(|error| LinkHandshakeError::Setup(error.to_string()))?;
        Ok(LinkTransport {
            transport,
            peer: self.peer,
        })
    }
}

/// A keyed link between two proved machines.
pub struct LinkTransport {
    transport: snow::TransportState,
    peer: MachineIdentity,
}

impl std::fmt::Debug for LinkTransport {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        formatter
            .debug_struct("LinkTransport")
            .field("peer", &self.peer)
            .finish_non_exhaustive()
    }
}

impl LinkTransport {
    /// The nonce Noise will use to seal the next transport message.
    #[must_use]
    pub fn sending_nonce(&self) -> u64 {
        self.transport.sending_nonce()
    }

    /// Selects the explicit nonce of the next received transport message.
    pub fn set_receiving_nonce(&mut self, nonce: u64) {
        self.transport.set_receiving_nonce(nonce);
    }

    /// The machine the handshake proved on the other end.
    #[must_use]
    pub const fn peer(&self) -> &MachineIdentity {
        &self.peer
    }

    /// Seals one frame for the peer, returning the bytes written to `out`.
    ///
    /// # Errors
    /// A payload or buffer outside Noise's bounds.
    pub fn seal(&mut self, payload: &[u8], out: &mut [u8]) -> Result<usize, LinkHandshakeError> {
        self.transport
            .write_message(payload, out)
            .map_err(|error| classify(&error))
    }

    /// Opens one frame from the peer, returning the bytes written to `out`.
    ///
    /// # Errors
    /// `PeerNotTheExpectedMachine` when the frame was not sealed by the keyed
    /// peer; framing otherwise.
    pub fn open(&mut self, frame: &[u8], out: &mut [u8]) -> Result<usize, LinkHandshakeError> {
        self.transport
            .read_message(frame, out)
            .map_err(|error| classify(&error))
    }
}