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
93#[derive(Debug, PartialEq, Eq)]
95pub enum LinkHandshakeError {
96 InvalidIdentity,
98 PeerNotTheExpectedMachine,
101 Framing(String),
103 Setup(String),
105 NotFinished,
107}
108
109impl std::fmt::Display for LinkHandshakeError {
110 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111 match self {
112 Self::InvalidIdentity => formatter.write_str("machine identity is not an Ed25519 key"),
113 Self::PeerNotTheExpectedMachine => {
114 formatter.write_str("peer is not the machine this link was opened with")
115 }
116 Self::Framing(detail) => write!(formatter, "noise framing refused: {detail}"),
117 Self::Setup(detail) => write!(formatter, "noise setup refused: {detail}"),
118 Self::NotFinished => formatter.write_str("handshake has not finished"),
119 }
120 }
121}
122
123impl std::error::Error for LinkHandshakeError {}
124
125fn classify(error: &snow::Error) -> LinkHandshakeError {
126 match error {
127 snow::Error::Decrypt => LinkHandshakeError::PeerNotTheExpectedMachine,
128 snow::Error::Input => LinkHandshakeError::Framing("input outside bounds".to_owned()),
129 other => LinkHandshakeError::Framing(other.to_string()),
130 }
131}
132
133pub struct LinkHandshake {
135 state: snow::HandshakeState,
136 peer: MachineIdentity,
137}
138
139impl std::fmt::Debug for LinkHandshake {
140 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141 formatter
142 .debug_struct("LinkHandshake")
143 .field("peer", &self.peer)
144 .field("finished", &self.state.is_handshake_finished())
145 .finish()
146 }
147}
148
149impl LinkHandshake {
150 fn build(
151 local: &LocalMachine,
152 peer: MachineIdentity,
153 initiator: bool,
154 ) -> Result<Self, LinkHandshakeError> {
155 let params: snow::params::NoiseParams = LINK_NOISE_PATTERN
156 .parse()
157 .map_err(|error: snow::Error| LinkHandshakeError::Setup(error.to_string()))?;
158 let secret = local.x25519_secret();
159 let remote = peer.x25519_public()?;
160 let builder = snow::Builder::new(params)
161 .local_private_key(&secret)
162 .map_err(|error| LinkHandshakeError::Setup(error.to_string()))?
163 .remote_public_key(&remote)
164 .map_err(|error| LinkHandshakeError::Setup(error.to_string()))?;
165 let state = if initiator {
166 builder.build_initiator()
167 } else {
168 builder.build_responder()
169 }
170 .map_err(|error| LinkHandshakeError::Setup(error.to_string()))?;
171 Ok(Self { state, peer })
172 }
173
174 pub fn initiator(
179 local: &LocalMachine,
180 peer: MachineIdentity,
181 ) -> Result<Self, LinkHandshakeError> {
182 Self::build(local, peer, true)
183 }
184
185 pub fn responder(
190 local: &LocalMachine,
191 peer: MachineIdentity,
192 ) -> Result<Self, LinkHandshakeError> {
193 Self::build(local, peer, false)
194 }
195
196 pub fn write(&mut self, out: &mut [u8]) -> Result<usize, LinkHandshakeError> {
201 self.state
202 .write_message(&[], out)
203 .map_err(|error| classify(&error))
204 }
205
206 pub fn read(&mut self, message: &[u8]) -> Result<(), LinkHandshakeError> {
212 let mut payload = [0_u8; NOISE_OVERHEAD];
213 self.state
214 .read_message(message, &mut payload)
215 .map(|_| ())
216 .map_err(|error| classify(&error))
217 }
218
219 #[must_use]
221 pub fn is_finished(&self) -> bool {
222 self.state.is_handshake_finished()
223 }
224
225 pub fn finish(self) -> Result<LinkTransport, LinkHandshakeError> {
230 if !self.state.is_handshake_finished() {
231 return Err(LinkHandshakeError::NotFinished);
232 }
233 let transport = self
234 .state
235 .into_transport_mode()
236 .map_err(|error| LinkHandshakeError::Setup(error.to_string()))?;
237 Ok(LinkTransport {
238 transport,
239 peer: self.peer,
240 })
241 }
242}
243
244pub struct LinkTransport {
246 transport: snow::TransportState,
247 peer: MachineIdentity,
248}
249
250impl std::fmt::Debug for LinkTransport {
251 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
252 formatter
253 .debug_struct("LinkTransport")
254 .field("peer", &self.peer)
255 .finish_non_exhaustive()
256 }
257}
258
259impl LinkTransport {
260 #[must_use]
262 pub fn sending_nonce(&self) -> u64 {
263 self.transport.sending_nonce()
264 }
265
266 pub fn set_receiving_nonce(&mut self, nonce: u64) {
268 self.transport.set_receiving_nonce(nonce);
269 }
270
271 #[must_use]
273 pub const fn peer(&self) -> &MachineIdentity {
274 &self.peer
275 }
276
277 pub fn seal(&mut self, payload: &[u8], out: &mut [u8]) -> Result<usize, LinkHandshakeError> {
282 self.transport
283 .write_message(payload, out)
284 .map_err(|error| classify(&error))
285 }
286
287 pub fn open(&mut self, frame: &[u8], out: &mut [u8]) -> Result<usize, LinkHandshakeError> {
293 self.transport
294 .read_message(frame, out)
295 .map_err(|error| classify(&error))
296 }
297}