use super::{
ConnectionProcessState, ConnectionRuntime, DeliberateDecision, Link, LinkAction, LinkHandshake,
LinkLife, LinkOffer, NOISE_OVERHEAD, OpenLink, OpenRefusal, unix_seconds_now,
};
pub(super) 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)
.map_err(OpenRefusal::Refused)?
{
return Err(OpenRefusal::Refused(format!(
"a link is already open on stream {stream_id}"
)));
}
let mut peer = offered_peer(host, session.holder(), offer)?;
let recorded = peer.machine;
let first = peer.first;
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()))?;
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)?;
if peer.certificate_required {
handshake.read(first).map_err(handshake_refused)?;
prove_standing(
runtime,
host,
network_id,
session.holder(),
&mut peer,
&link,
remote_end,
)?;
}
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::Pass(error)),
}
if !peer.certificate_required {
handshake.read(first).map_err(handshake_refused)?;
}
if peer.until.is_some_and(|until| unix_seconds_now() >= until) {
return Err(OpenRefusal::Refused("certificate_pass_expired".into()));
}
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)?;
let life = LinkLife::new(*transport.peer());
Ok((
OpenLink {
link,
remote_end,
transport,
life,
calls: super::calls::IncomingCalls::new(
host.peers()
.map_or(0, |peers| peers.max_pending_calls.get()),
),
certificate_until: peer.until,
},
answer,
))
}
struct PeerOffer<'a> {
machine: crate::link_handshake::MachineIdentity,
first: &'a [u8],
certificate: Option<crate::link::certificate::offer::OfferedCertificate<'a>>,
certificate_required: bool,
until: Option<u64>,
}
fn offered_peer<'a>(
host: &crate::link::LinkHost,
holder: &str,
offer: &'a LinkOffer,
) -> Result<PeerOffer<'a>, OpenRefusal> {
let bound = host
.peers()
.map_or(NOISE_OVERHEAD, |peers| peers.max_certificate_bytes.get());
let proof_bound = host.peers().map_or(0, |peers| peers.max_proof_bytes.get());
let (certificate, first) =
crate::link::certificate::offer::unpack(&offer.handshake, bound, proof_bound)
.map_err(|error| OpenRefusal::Refused(error.to_string()))?;
let recorded = host.recorded_machine(holder);
let mut until = None;
let machine = if let Some(recorded) = recorded {
if recorded.ed25519_public() != &offer.machine {
return Err(OpenRefusal::Refused(
"the offered machine key is not the holder's recorded key".into(),
));
}
recorded
} else {
let offered = certificate.as_ref().ok_or_else(|| {
OpenRefusal::Refused("no machine key is recorded for this holder".into())
})?;
let key = host.service_key().ok_or_else(|| {
OpenRefusal::Refused(
crate::link::certificate::CertificateRefusal::ServiceKey.to_string(),
)
})?;
let certified = crate::link::certificate::verify(
offered.der,
key,
holder,
crate::link_handshake::MachineIdentity::new(offer.machine),
unix_seconds_now(),
bound,
)
.map_err(|error| OpenRefusal::Refused(error.to_string()))?;
until = Some(certified.not_after().saturating_add(1));
certified.machine()
};
Ok(PeerOffer {
machine,
first,
certificate,
certificate_required: recorded.is_none(),
until,
})
}
fn prove_standing(
runtime: &ConnectionRuntime,
host: &crate::link::LinkHost,
network: &str,
holder: &str,
peer: &mut PeerOffer<'_>,
link: &Link,
end: LinkAction,
) -> Result<(), OpenRefusal> {
let unavailable = || {
OpenRefusal::Refused(
crate::link::certificate::CertificateRefusal::StatusUnavailable.to_string(),
)
};
let standing = host.standing().ok_or_else(unavailable)?;
let offered = peer.certificate.as_ref().ok_or_else(unavailable)?;
let token = standing
.prove(
holder,
peer.machine,
offered.der,
offered.proof,
unix_seconds_now(),
)
.map_err(|error| OpenRefusal::Refused(error.to_string()))?;
peer.until = Some(
peer.until
.map_or_else(|| token.expires_at(), |until| until.min(token.expires_at())),
);
let (admitter, _) = runtime.lys_admitter().ok_or_else(unavailable)?;
let fresh = admitter
.admit(token.as_bytes(), unix_seconds_now())
.map_err(|_| {
OpenRefusal::Refused(crate::link::certificate::CertificateRefusal::Proof.to_string())
})?;
let binding = crate::auth_lys::SubjectBinding::link(
network.into(),
link.resource().id,
"net.link.open".into(),
)
.map_err(|error| OpenRefusal::Refused(error.to_string()))?;
fresh
.decide_link(&binding, end, unix_seconds_now())
.map_err(OpenRefusal::Pass)?;
Ok(())
}