liminal_server/
link_handshake.rs1use ed25519_dalek::VerifyingKey;
12use lys_core::keys::Ed25519Identity;
13
14pub const LINK_NOISE_PATTERN: &str = "Noise_KK_25519_ChaChaPoly_BLAKE2s";
16pub const NOISE_OVERHEAD: usize = 96;
18pub const NOISE_MAX_MESSAGE: usize = 65_535;
20
21#[derive(Clone, Copy, PartialEq, Eq)]
23pub struct MachineIdentity {
24 ed25519_public: [u8; 32],
25}
26
27impl std::fmt::Debug for MachineIdentity {
28 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 formatter
30 .debug_struct("MachineIdentity")
31 .field("ed25519_public", &"32 bytes")
32 .finish()
33 }
34}
35
36impl MachineIdentity {
37 #[must_use]
39 pub const fn new(ed25519_public: [u8; 32]) -> Self {
40 Self { ed25519_public }
41 }
42
43 #[must_use]
45 pub const fn ed25519_public(&self) -> &[u8; 32] {
46 &self.ed25519_public
47 }
48
49 pub fn x25519_public(&self) -> Result<[u8; 32], LinkHandshakeError> {
55 VerifyingKey::from_bytes(&self.ed25519_public)
56 .map(|key| key.to_montgomery().to_bytes())
57 .map_err(|_| LinkHandshakeError::InvalidIdentity)
58 }
59}
60
61pub struct LocalMachine {
63 identity: Ed25519Identity,
64}
65
66impl std::fmt::Debug for LocalMachine {
67 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68 formatter
69 .debug_struct("LocalMachine")
70 .field("identity", &self.machine())
71 .finish()
72 }
73}
74
75impl LocalMachine {
76 #[must_use]
78 pub const fn new(identity: Ed25519Identity) -> Self {
79 Self { identity }
80 }
81
82 #[must_use]
84 pub fn machine(&self) -> MachineIdentity {
85 MachineIdentity::new(self.identity.public_key_bytes())
86 }
87
88 fn x25519_secret(&self) -> [u8; 32] {
89 self.identity.x25519_static_secret().to_bytes()
90 }
91
92 pub(crate) fn certificate_proof(
98 &self,
99 agent: &str,
100 path: &str,
101 body: &[u8],
102 ) -> Result<String, crate::link::certificate::CertificateRefusal> {
103 use crate::link::certificate::CertificateRefusal;
104 use std::io::Read;
105 let mut nonce = [0; 16];
106 std::fs::File::open("/dev/urandom")
107 .and_then(|mut file| file.read_exact(&mut nonce))
108 .map_err(|_| CertificateRefusal::StatusUnavailable)?;
109 let at = std::time::SystemTime::now()
110 .duration_since(std::time::UNIX_EPOCH)
111 .map_err(|_| CertificateRefusal::StatusUnavailable)?;
112 let at =
113 u64::try_from(at.as_millis()).map_err(|_| CertificateRefusal::StatusUnavailable)?;
114 Ok(crate::link_pass::cose::header(
115 &self.identity,
116 agent,
117 path,
118 body,
119 at,
120 &hex::encode(nonce),
121 ))
122 }
123}
124
125#[derive(Debug, PartialEq, Eq)]
127pub enum LinkHandshakeError {
128 InvalidIdentity,
130 PeerNotTheExpectedMachine,
133 Framing(String),
135 Setup(String),
137 NotFinished,
139}
140
141impl std::fmt::Display for LinkHandshakeError {
142 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143 match self {
144 Self::InvalidIdentity => formatter.write_str("machine identity is not an Ed25519 key"),
145 Self::PeerNotTheExpectedMachine => {
146 formatter.write_str("peer is not the machine this link was opened with")
147 }
148 Self::Framing(detail) => write!(formatter, "noise framing refused: {detail}"),
149 Self::Setup(detail) => write!(formatter, "noise setup refused: {detail}"),
150 Self::NotFinished => formatter.write_str("handshake has not finished"),
151 }
152 }
153}
154
155impl std::error::Error for LinkHandshakeError {}
156
157fn classify(error: &snow::Error) -> LinkHandshakeError {
158 match error {
159 snow::Error::Decrypt => LinkHandshakeError::PeerNotTheExpectedMachine,
160 snow::Error::Input => LinkHandshakeError::Framing("input outside bounds".to_owned()),
161 other => LinkHandshakeError::Framing(other.to_string()),
162 }
163}
164
165pub struct LinkHandshake {
167 state: snow::HandshakeState,
168 peer: MachineIdentity,
169}
170
171impl std::fmt::Debug for LinkHandshake {
172 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173 formatter
174 .debug_struct("LinkHandshake")
175 .field("peer", &self.peer)
176 .field("finished", &self.state.is_handshake_finished())
177 .finish()
178 }
179}
180
181impl LinkHandshake {
182 fn build(
183 local: &LocalMachine,
184 peer: MachineIdentity,
185 initiator: bool,
186 ) -> Result<Self, LinkHandshakeError> {
187 let params: snow::params::NoiseParams = LINK_NOISE_PATTERN
188 .parse()
189 .map_err(|error: snow::Error| LinkHandshakeError::Setup(error.to_string()))?;
190 let secret = local.x25519_secret();
191 let remote = peer.x25519_public()?;
192 let builder = snow::Builder::new(params)
193 .local_private_key(&secret)
194 .map_err(|error| LinkHandshakeError::Setup(error.to_string()))?
195 .remote_public_key(&remote)
196 .map_err(|error| LinkHandshakeError::Setup(error.to_string()))?;
197 let state = if initiator {
198 builder.build_initiator()
199 } else {
200 builder.build_responder()
201 }
202 .map_err(|error| LinkHandshakeError::Setup(error.to_string()))?;
203 Ok(Self { state, peer })
204 }
205
206 pub fn initiator(
211 local: &LocalMachine,
212 peer: MachineIdentity,
213 ) -> Result<Self, LinkHandshakeError> {
214 Self::build(local, peer, true)
215 }
216
217 pub fn responder(
222 local: &LocalMachine,
223 peer: MachineIdentity,
224 ) -> Result<Self, LinkHandshakeError> {
225 Self::build(local, peer, false)
226 }
227
228 pub fn write(&mut self, out: &mut [u8]) -> Result<usize, LinkHandshakeError> {
233 self.state
234 .write_message(&[], out)
235 .map_err(|error| classify(&error))
236 }
237
238 pub fn read(&mut self, message: &[u8]) -> Result<(), LinkHandshakeError> {
244 let mut payload = [0_u8; NOISE_OVERHEAD];
245 self.state
246 .read_message(message, &mut payload)
247 .map(|_| ())
248 .map_err(|error| classify(&error))
249 }
250
251 #[must_use]
253 pub fn is_finished(&self) -> bool {
254 self.state.is_handshake_finished()
255 }
256
257 pub fn finish(self) -> Result<LinkTransport, LinkHandshakeError> {
262 if !self.state.is_handshake_finished() {
263 return Err(LinkHandshakeError::NotFinished);
264 }
265 let transport = self
266 .state
267 .into_transport_mode()
268 .map_err(|error| LinkHandshakeError::Setup(error.to_string()))?;
269 Ok(LinkTransport {
270 transport,
271 peer: self.peer,
272 })
273 }
274}
275
276pub struct LinkTransport {
278 transport: snow::TransportState,
279 peer: MachineIdentity,
280}
281
282impl std::fmt::Debug for LinkTransport {
283 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
284 formatter
285 .debug_struct("LinkTransport")
286 .field("peer", &self.peer)
287 .finish_non_exhaustive()
288 }
289}
290
291impl LinkTransport {
292 #[must_use]
294 pub fn sending_nonce(&self) -> u64 {
295 self.transport.sending_nonce()
296 }
297
298 pub fn set_receiving_nonce(&mut self, nonce: u64) {
300 self.transport.set_receiving_nonce(nonce);
301 }
302
303 #[must_use]
305 pub const fn peer(&self) -> &MachineIdentity {
306 &self.peer
307 }
308
309 pub fn seal(&mut self, payload: &[u8], out: &mut [u8]) -> Result<usize, LinkHandshakeError> {
314 self.transport
315 .write_message(payload, out)
316 .map_err(|error| classify(&error))
317 }
318
319 pub fn open(&mut self, frame: &[u8], out: &mut [u8]) -> Result<usize, LinkHandshakeError> {
325 self.transport
326 .read_message(frame, out)
327 .map_err(|error| classify(&error))
328 }
329}