1use tracing::*;
2
3use crate::frame::{
4 self, ChallengeData, ClientFinishData, ClientHelloData, ClientQueryData, FrameBody,
5 GoodbyeData, ServerDumpData, ServerHelloData,
6};
7use crate::traits::AuthConfig;
8use crate::{auth, peer, topic};
9
10#[derive(Clone, Debug)]
12pub enum Action {
13 SendFrame(frame::FrameBody),
15
16 Ready(ReadyData),
18
19 Abort(GoodbyeData),
21
22 Exit(GoodbyeData),
24
25 SetRemoteAgent(String),
27
28 SetRemoteIdent(peer::Identity),
30
31 HappyClose,
33}
34
35#[derive(Copy, Clone, Debug, Eq, PartialEq)]
37pub enum StateKind {
38 WaitHello,
39 WaitFinish,
40 Ready,
41 Fail,
42}
43
44#[derive(Clone)]
46pub enum ClientState<A: AuthConfig> {
47 WaitServerHello {
48 auth: A,
49 protocol: topic::Topic,
50 sent_chal: Option<ChallengeData>,
51 },
52 Ready {
53 auth: A,
54 },
55 Fail,
56}
57
58impl<A: AuthConfig> ClientState<A> {
59 pub fn auth(&self) -> Option<&A> {
60 match self {
61 Self::WaitServerHello { auth, .. } => Some(auth),
62 Self::Ready { auth } => Some(auth),
63 _ => None,
64 }
65 }
66
67 pub fn kind(&self) -> StateKind {
68 match self {
69 Self::WaitServerHello { .. } => StateKind::WaitHello,
70 Self::Ready { .. } => StateKind::Ready,
71 Self::Fail => StateKind::Fail,
72 }
73 }
74}
75
76#[derive(Clone, Debug)]
78pub enum ServerState<A: AuthConfig> {
79 WaitClientHello {
80 auth: A,
81 },
82 WaitClientFinish {
83 auth: A,
84 protocol: topic::Topic,
85 client_chal: Option<ChallengeData>,
86 server_chal: Option<ChallengeData>,
87 },
88 Ready {
89 auth: A,
90 },
91 Fail,
92}
93
94impl<A: AuthConfig> ServerState<A> {
95 pub fn init(auth: A) -> Self {
98 Self::WaitClientHello { auth }
99 }
100
101 pub fn auth(&self) -> Option<&A> {
102 match self {
103 Self::WaitClientHello { auth, .. } => Some(auth),
104 Self::WaitClientFinish { auth, .. } => Some(auth),
105 Self::Ready { auth } => Some(auth),
106 _ => None,
107 }
108 }
109
110 pub fn kind(&self) -> StateKind {
111 match self {
112 Self::WaitClientHello { .. } => StateKind::WaitHello,
113 Self::WaitClientFinish { .. } => StateKind::WaitFinish,
114 Self::Ready { .. } => StateKind::Ready,
115 Self::Fail => StateKind::Fail,
116 }
117 }
118}
119
120#[derive(Clone, Debug)]
121pub struct ReadyData {
122 protocol: topic::Topic,
123}
124
125impl ReadyData {
126 pub fn new(protocol: topic::Topic) -> Self {
127 Self { protocol }
128 }
129
130 pub fn protocol(&self) -> topic::Topic {
131 self.protocol
132 }
133}
134
135#[derive(Clone, Debug)]
136pub struct ClientMeta {
137 agent: String,
138}
139
140impl ClientMeta {
141 pub fn new(agent: String) -> Self {
142 Self { agent }
143 }
144
145 pub fn create_query_frame(&self) -> FrameBody {
146 FrameBody::ClientQuery(ClientQueryData::new(self.agent.clone()))
147 }
148}
149
150#[derive(Clone, Debug)]
151pub struct ServerMeta {
152 agent: String,
153 protocols: Vec<topic::Topic>,
154}
155
156impl ServerMeta {
157 pub fn add_protocol(&mut self, proto: topic::Topic) {
158 self.protocols.push(proto);
159 }
160}
161
162impl ServerMeta {
163 pub fn new(agent: String, protocols: Vec<topic::Topic>) -> Self {
164 Self { agent, protocols }
165 }
166}
167
168pub fn exec_client_init<A: AuthConfig>(
170 protocol: topic::Topic,
171 auth: A,
172 meta: &ClientMeta,
173) -> (ClientState<A>, Vec<Action>) {
174 let sent_chal = if auth.get_intent().should_exchange_chals() {
176 let chal = auth::gen_challenge();
177 Some(chal)
178 } else {
179 None
180 };
181
182 let hello = ClientHelloData::new(meta.agent.clone(), protocol, sent_chal);
183 let frame = FrameBody::ClientHello(hello);
184 (
185 ClientState::WaitServerHello {
186 auth,
187 protocol,
188 sent_chal,
189 },
190 vec![Action::SendFrame(frame)],
191 )
192}
193
194pub fn exec_client_inp<A: AuthConfig>(
196 state: &ClientState<A>,
197 inp: &FrameBody,
198 _meta: &ClientMeta,
199) -> (ClientState<A>, Vec<Action>) {
200 use ClientState::*;
201 match (state, inp) {
202 (
203 WaitServerHello {
204 auth,
205 protocol,
206 sent_chal,
207 },
208 FrameBody::ServerHello(shello),
209 ) => {
210 let mut actions = Vec::new();
211 actions.push(Action::SetRemoteAgent(shello.agent().to_owned()));
212
213 let intent = auth.get_intent();
214 let should_sign = intent.should_sign(frame::Side::Client);
215 let exp_resp = intent.should_sign(frame::Side::Server);
216 let client_chal = sent_chal.as_ref();
217 let server_chal = shello.challenge();
218 let server_resp = shello.response();
219
220 let mut sent_resp = None;
221
222 if intent.should_exchange_chals() {
224 let (Some(client_chal), Some(server_chal)) = (client_chal, server_chal) else {
225 warn!("missing expected challenge");
227 let goodbye = GoodbyeData::new(-104, "expected chal".to_string());
228 return (ClientState::Fail, vec![Action::Abort(goodbye)]);
229 };
230
231 if exp_resp {
232 let Some(server_resp) = server_resp else {
233 warn!("missing expected response to challenge");
235 let goodbye = GoodbyeData::new(-103, "expected chal resp".to_string());
236 return (ClientState::Fail, vec![Action::Abort(goodbye)]);
237 };
238
239 match auth.verify_response(
241 client_chal,
242 server_chal,
243 frame::Side::Server,
244 server_resp,
245 ) {
246 Ok(Some(id)) => {
247 trace!(%id, "verified identity");
248 actions.push(Action::SetRemoteIdent(id));
249 }
250 Ok(None) => { }
251 Err(e) => {
252 warn!(err = %e, "server sent bad challenge response");
254 let goodbye = GoodbyeData::new(-102, "auth error".to_string());
255 return (ClientState::Fail, vec![Action::Abort(goodbye)]);
256 }
257 }
258 }
259
260 if should_sign {
262 match auth.sign_challenge(client_chal, server_chal, frame::Side::Client) {
263 Ok(Some(resp)) => {
264 sent_resp = Some(resp);
265 }
266 Ok(None) => {
267 warn!("intent said we should sign but generated no response");
268 }
269 Err(e) => {
270 error!(err = %e, "could not generate response");
271 let goodbye = GoodbyeData::new(-101, "internal error".to_string());
272 return (ClientState::Fail, vec![Action::Abort(goodbye)]);
273 }
274 }
275 }
276 }
277
278 let cfinish = ClientFinishData::new(sent_resp);
279 let frame = FrameBody::ClientFinish(cfinish);
280 let rd = ReadyData::new(*protocol);
281 actions.push(Action::SendFrame(frame));
282 actions.push(Action::Ready(rd));
283 (Ready { auth: auth.clone() }, actions)
284 }
285
286 (WaitServerHello { .. }, FrameBody::Goodbye(gd)) => {
287 (ClientState::Fail, vec![Action::Exit(gd.clone())])
289 }
290
291 _ => {
292 let goodbye = GoodbyeData::new(-101, "idk what you're doing".to_string());
293 (state.clone(), vec![Action::Abort(goodbye)])
294 }
295 }
296}
297
298pub fn exec_server_inp<A: AuthConfig>(
300 state: &ServerState<A>,
301 inp: &FrameBody,
302 meta: &ServerMeta,
303) -> (ServerState<A>, Vec<Action>) {
304 use ServerState::*;
305 match (state, inp) {
306 (WaitClientHello { .. }, FrameBody::ClientQuery(_query)) => {
307 let dump = ServerDumpData::new(meta.agent.clone(), meta.protocols.clone());
308 let frame = FrameBody::ServerDump(dump);
309 (state.clone(), vec![Action::SendFrame(frame)])
310 }
311
312 (WaitClientHello { auth }, FrameBody::ClientHello(chello)) => {
313 let mut actions = Vec::new();
314 actions.push(Action::SetRemoteAgent(chello.agent().to_owned()));
315
316 let protocol = chello.protocol();
317
318 if !meta.protocols.contains(&protocol) {
320 debug!(%protocol, "connection wanted unsupported protocol");
321 let goodbye = GoodbyeData::new(-100, "unsupported protocol".to_string());
322 return (ServerState::Fail, vec![Action::Abort(goodbye)]);
323 }
324
325 let intent = auth.get_intent();
327 let mut sending_chal = None;
328 let mut sending_resp = None;
329
330 if intent.should_exchange_chals() {
331 let Some(client_chal) = chello.challenge() else {
332 let goodbye = GoodbyeData::new(-104, "expected chal".to_string());
334 return (ServerState::Fail, vec![Action::Abort(goodbye)]);
335 };
336
337 let server_chal = auth::gen_challenge();
338 sending_chal = Some(server_chal);
339
340 if intent.should_sign(frame::Side::Server) {
341 match auth.sign_challenge(client_chal, &server_chal, frame::Side::Server) {
342 Ok(Some(resp)) => {
343 sending_resp = Some(resp);
344 }
345 Ok(None) => {
346 warn!("intent said we should sign but generated no response");
347 }
348 Err(e) => {
349 error!(err = %e, "could not generate response");
350 let goodbye = GoodbyeData::new(-101, "internal error".to_string());
351 return (ServerState::Fail, vec![Action::Abort(goodbye)]);
352 }
353 }
354 }
355 }
356
357 let shello = ServerHelloData::new(meta.agent.clone(), sending_resp, sending_chal);
358 let frame = FrameBody::ServerHello(shello);
359 actions.push(Action::SendFrame(frame));
360
361 (
362 ServerState::WaitClientFinish {
363 auth: auth.clone(),
364 protocol,
365 client_chal: chello.challenge().cloned(),
366 server_chal: sending_chal,
367 },
368 actions,
369 )
370 }
371
372 (
373 WaitClientFinish {
374 auth,
375 protocol,
376 client_chal,
377 server_chal,
378 },
379 FrameBody::ClientFinish(cfinish),
380 ) => {
381 let mut actions = Vec::new();
382
383 let intent = auth.get_intent();
385 if intent.should_exchange_chals() && intent.should_sign(frame::Side::Client) {
386 let (Some(client_chal), Some(server_chal)) = (client_chal, server_chal) else {
387 error!("in an inconsistent state! didn't have local expected challenges!");
388 let goodbye = GoodbyeData::new(-101, "internal error".to_string());
389 return (ServerState::Fail, vec![Action::Abort(goodbye)]);
390 };
391
392 let Some(client_resp) = cfinish.response() else {
393 warn!("missing expected response to challenge");
395 let goodbye = GoodbyeData::new(-103, "expected chal resp".to_string());
396 return (ServerState::Fail, vec![Action::Abort(goodbye)]);
397 };
398
399 match auth.verify_response(
400 client_chal,
401 server_chal,
402 frame::Side::Client,
403 client_resp,
404 ) {
405 Ok(Some(id)) => {
406 trace!(%id, "verified identity");
407 actions.push(Action::SetRemoteIdent(id))
408 }
409 Ok(None) => {
410 }
412 Err(e) => {
413 warn!(err = %e, "new client sent bad challenge response");
415 let goodbye = GoodbyeData::new(-102, "auth error".to_string());
416 return (ServerState::Fail, vec![Action::Abort(goodbye)]);
417 }
418 }
419 }
420
421 let rd = ReadyData::new(*protocol);
423 actions.push(Action::Ready(rd));
424 (ServerState::Ready { auth: auth.clone() }, actions)
425 }
426
427 _ => {
428 let goodbye = GoodbyeData::new(-101, "idk what you're doing".to_string());
429 (ServerState::Fail, vec![Action::Abort(goodbye)])
430 }
431 }
432}