liminal-server 0.20.0

Standalone server for the liminal messaging bus
Documentation
//! The link route on one connection (ACCESS-002 R1).
//!
//! A machine admitted on a Lys pass offers to open one end of a link on an
//! application stream (`LinkOpen`). The server proves the offered machine
//! key is the one Lys recorded for the connection's verified holder, asks
//! Lys live for the offered end's grant on the link (never the pass alone),
//! and answers the Noise `KK` handshake keyed from the two machines' Lys
//! identities; the stream is then a keyed link. A message crossing it
//! (`LinkMessage`) is opened by the link's transport and decided from both
//! machines' passes, `link_export` on the exporting machine's and
//! `link_import` on this machine's, before it is published here; a refusal
//! names its end on the link's stream.

use liminal::protocol::{Frame, LinkOffer, LinkOpenOutcome, MessageEnvelope};

use super::apply::{publish_response, unix_seconds_now};
use super::state::{ConnectionProcessState, FrameAction};
use super::supervisor::ConnectionRuntime;
use crate::auth_lys::session::LysSession;
use crate::auth_lys::{AccessDecisionError, DeliberateDecision, GrantReceipt, SubjectBinding};
use crate::link::{Link, LinkAction, LinkRefusal, LinkRights};
use crate::link_handshake::{LinkHandshake, LinkTransport, NOISE_OVERHEAD};

/// The reason code a refused crossing answers with, on the link's stream.
pub(super) const LINK_CROSSING_REFUSED_CODE: u16 = 0x0203;

/// A link open on this connection: its description, the end the remote
/// machine took, and the transport its handshake keyed.
#[derive(Debug)]
pub(super) struct OpenLink {
    link: Link,
    remote_end: LinkAction,
    transport: LinkTransport,
}

impl OpenLink {
    /// The end the remote machine took.
    pub(super) const fn remote_end(&self) -> LinkAction {
        self.remote_end
    }
}

/// The connection's admitted pass as the remote machine's link rights.
#[derive(Debug)]
struct SessionLinkRights<'a>(&'a dyn LysSession);

impl LinkRights for SessionLinkRights<'_> {
    fn holds(
        &self,
        action: LinkAction,
        subject: &SubjectBinding,
        now: u64,
    ) -> Result<GrantReceipt, AccessDecisionError> {
        self.0.decide_link(subject, action, now)
    }
}

/// Why an open did not key a link.
enum OpenRefusal {
    Held { grant: String, draft: String },
    Refused(String),
}

/// The four-field refusal as the wire carries it, or the cause's words.
fn refusal_text(error: &AccessDecisionError) -> String {
    serde_json::to_string(&error.refusal).unwrap_or_else(|_| error.cause.to_string())
}

/// A `LinkOpen` frame: the ack carries the handshake answer, the draft Lys
/// holds the open under, or the refusal by name.
pub(super) fn link_open_frame_response(
    runtime: &ConnectionRuntime,
    state: &mut ConnectionProcessState,
    stream_id: u32,
    offer: &LinkOffer,
) -> FrameAction {
    let outcome = match open_link(runtime, state, stream_id, offer) {
        Ok((open, handshake)) => {
            state.links.insert(stream_id, open);
            LinkOpenOutcome::Opened { handshake }
        }
        Err(OpenRefusal::Held { grant, draft }) => LinkOpenOutcome::Held { grant, draft },
        Err(OpenRefusal::Refused(reason)) => LinkOpenOutcome::Refused { reason },
    };
    FrameAction::Respond(Frame::LinkOpenAck {
        flags: 0,
        stream_id,
        outcome,
    })
}

fn open_link(
    runtime: &ConnectionRuntime,
    state: &ConnectionProcessState,
    stream_id: u32,
    offer: &LinkOffer,
) -> Result<(OpenLink, Vec<u8>), OpenRefusal> {
    let Some(session) = state.lys_session.as_ref() else {
        return Err(OpenRefusal::Refused(
            "a link is opened on a connection admitted on a Lys pass".to_owned(),
        ));
    };
    let Some((_, network_id)) = runtime.lys_admitter() else {
        return Err(OpenRefusal::Refused(
            "no lys admitter is installed".to_owned(),
        ));
    };
    let Some(host) = runtime.link_host() else {
        return Err(OpenRefusal::Refused(
            "this liminal holds no machine identity; [auth.lys.machine] is not configured"
                .to_owned(),
        ));
    };
    if state.links.contains_key(&stream_id) {
        return Err(OpenRefusal::Refused(format!(
            "a link is already open on stream {stream_id}"
        )));
    }
    let Some(recorded) = host.recorded_machine(session.holder()) else {
        return Err(OpenRefusal::Refused(
            "no machine key is recorded for this holder".to_owned(),
        ));
    };
    if recorded.ed25519_public() != &offer.machine {
        return Err(OpenRefusal::Refused(
            "the offered machine key is not the holder's recorded key".to_owned(),
        ));
    }
    let remote_end = LinkAction::from(offer.end);
    let local = host.local().machine();
    let (exporter, importer) = match remote_end {
        LinkAction::Export => (recorded, local),
        LinkAction::Import => (local, recorded),
    };
    let link = Link::new(
        network_id.to_owned(),
        offer.link_id.clone(),
        exporter,
        importer,
    )
    .map_err(|error| OpenRefusal::Refused(error.to_string()))?;
    match session.open_link(&link.resource(), remote_end, unix_seconds_now()) {
        Ok(DeliberateDecision::Outright(_)) => {}
        Ok(
            DeliberateDecision::ByDraft { grant, draft }
            | DeliberateDecision::ByTwo { grant, draft },
        ) => return Err(OpenRefusal::Held { grant, draft }),
        Err(error) => return Err(OpenRefusal::Refused(refusal_text(&error))),
    }
    let handshake_refused = |error: crate::link_handshake::LinkHandshakeError| {
        OpenRefusal::Refused(format!("handshake refused: {error}"))
    };
    let mut handshake =
        LinkHandshake::responder(host.local(), recorded).map_err(handshake_refused)?;
    handshake
        .read(&offer.handshake)
        .map_err(handshake_refused)?;
    let mut answer = vec![0_u8; NOISE_OVERHEAD];
    let written = handshake.write(&mut answer).map_err(handshake_refused)?;
    answer.truncate(written);
    let transport = handshake.finish().map_err(handshake_refused)?;
    Ok((
        OpenLink {
            link,
            remote_end,
            transport,
        },
        answer,
    ))
}

/// A `LinkMessage` frame: the crossing opened, decided on both ends and
/// published here, answered as a publish on the link's stream; or refused by
/// name under [`LINK_CROSSING_REFUSED_CODE`].
pub(super) fn link_message_frame_response(
    pid: u64,
    runtime: &ConnectionRuntime,
    state: &mut ConnectionProcessState,
    stream_id: u32,
    subject: &str,
    sealed: &[u8],
) -> FrameAction {
    match cross(runtime, state, stream_id, subject, sealed) {
        Ok(envelope) => publish_response(
            pid,
            runtime.services(),
            state.negotiated_version,
            stream_id,
            subject,
            &envelope,
            None,
        ),
        Err(reason) => FrameAction::Respond(Frame::PublishError {
            flags: 0,
            stream_id,
            reason_code: LINK_CROSSING_REFUSED_CODE,
            message: Some(reason),
        }),
    }
}

fn cross(
    runtime: &ConnectionRuntime,
    state: &mut ConnectionProcessState,
    stream_id: u32,
    subject: &str,
    sealed: &[u8],
) -> Result<MessageEnvelope, String> {
    let ConnectionProcessState {
        lys_session, links, ..
    } = state;
    let Some(session) = lys_session.as_ref() else {
        return Err("a crossing is decided on a connection admitted on a Lys pass".to_owned());
    };
    let Some(host) = runtime.link_host() else {
        return Err("this liminal holds no machine identity".to_owned());
    };
    let Some(open) = links.get_mut(&stream_id) else {
        return Err(format!("no link is open on stream {stream_id}"));
    };
    if open.remote_end() != LinkAction::Export {
        return Err(
            "messages cross from the exporting end; the remote machine imports on this link"
                .to_owned(),
        );
    }
    let mut plain = vec![0_u8; sealed.len()];
    let written = open
        .transport
        .open(sealed, &mut plain)
        .map_err(|error| format!("link frame refused: {error}"))?;
    plain.truncate(written);
    let remote = SessionLinkRights(session.as_ref());
    open.link
        .crossing(&remote, host.rights(), subject, unix_seconds_now())
        .map_err(|refusal| match &refusal {
            LinkRefusal::Export(error) => {
                format!("crossing refused at export: {}", refusal_text(error))
            }
            LinkRefusal::Import(error) => {
                format!("crossing refused at import: {}", refusal_text(error))
            }
            LinkRefusal::Binding(_) => refusal.to_string(),
        })?;
    MessageEnvelope::from_wire_bytes(&plain)
        .map_err(|error| format!("the crossed message is not an envelope: {error}"))
}