alpine/handshake/
client.rs1use async_trait::async_trait;
2use uuid::Uuid;
3
4use super::{
5 HandshakeContext, HandshakeError, HandshakeMessage, HandshakeOutcome, HandshakeParticipant,
6 HandshakeTransport,
7};
8use crate::crypto::{compute_mac, KeyExchange};
9use crate::messages::{
10 CapabilitySet, DeviceIdentity, MessageType, SessionAck, SessionEstablished, SessionInit,
11 SessionReady,
12};
13
14pub struct ClientHandshake<A, K>
16where
17 A: super::ChallengeAuthenticator + Send + Sync,
18 K: KeyExchange + Send + Sync,
19{
20 pub identity: DeviceIdentity,
21 pub capabilities: CapabilitySet,
22 pub authenticator: A,
23 pub key_exchange: K,
24 pub context: HandshakeContext,
25}
26
27#[async_trait]
28impl<A, K> HandshakeParticipant for ClientHandshake<A, K>
29where
30 A: super::ChallengeAuthenticator + Send + Sync,
31 K: KeyExchange + Send + Sync,
32{
33 async fn run<T: HandshakeTransport + Send>(
34 &self,
35 transport: &mut T,
36 ) -> Result<HandshakeOutcome, HandshakeError> {
37 let controller_nonce = super::new_nonce().to_vec();
38 let session_id = Uuid::new_v4();
39
40 let init = SessionInit {
42 message_type: MessageType::SessionInit,
43 controller_nonce: controller_nonce.clone(),
44 controller_pubkey: self.key_exchange.public_key(),
45 requested: self.capabilities.clone(),
46 session_id,
47 };
48 transport.send(HandshakeMessage::SessionInit(init)).await?;
49
50 let ack = match transport.recv().await? {
52 HandshakeMessage::SessionAck(ack) => ack,
53 other => {
54 return Err(HandshakeError::Protocol(format!(
55 "expected SessionAck, got {:?}",
56 other
57 )))
58 }
59 };
60 validate_ack(&ack, session_id, &controller_nonce, &self.context)?;
61
62 let sig_valid = self
64 .authenticator
65 .verify_challenge(&controller_nonce, &ack.signature);
66 if !sig_valid {
67 return Err(HandshakeError::Authentication(
68 "device signature validation failed".into(),
69 ));
70 }
71
72 let mut salt = controller_nonce.clone();
74 salt.extend_from_slice(&ack.device_nonce);
75 let keys = self
76 .key_exchange
77 .derive_keys(&ack.device_pubkey, &salt)
78 .map_err(|e| HandshakeError::Authentication(format!("{}", e)))?;
79
80 let mac = compute_mac(&keys, 0, session_id.as_bytes(), ack.device_nonce.as_slice())
82 .map_err(|e| HandshakeError::Authentication(e.to_string()))?;
83 let ready = SessionReady {
84 message_type: MessageType::SessionReady,
85 session_id,
86 mac,
87 };
88 transport
89 .send(HandshakeMessage::SessionReady(ready))
90 .await?;
91
92 let complete = match transport.recv().await? {
94 HandshakeMessage::SessionComplete(c) => c,
95 other => {
96 return Err(HandshakeError::Protocol(format!(
97 "expected SessionComplete, got {:?}",
98 other
99 )))
100 }
101 };
102 if !complete.ok {
103 return Err(HandshakeError::Authentication(
104 "device rejected session_ready".into(),
105 ));
106 }
107
108 let established = SessionEstablished {
109 session_id,
110 controller_nonce,
111 device_nonce: ack.device_nonce,
112 capabilities: ack.capabilities,
113 device_identity: ack.device_identity,
114 };
115
116 Ok(HandshakeOutcome { established, keys })
117 }
118}
119
120fn validate_ack(
121 ack: &SessionAck,
122 session_id: Uuid,
123 controller_nonce: &[u8],
124 context: &HandshakeContext,
125) -> Result<(), HandshakeError> {
126 if ack.session_id != session_id {
127 return Err(HandshakeError::Protocol(
128 "session_id mismatch between init and ack".into(),
129 ));
130 }
131
132 if ack.device_nonce.len() != controller_nonce.len() {
133 return Err(HandshakeError::Protocol(
134 "device nonce length mismatch".into(),
135 ));
136 }
137
138 if let Some(expected) = &context.expected_controller {
139 if expected != &session_id.to_string() {
140 return Err(HandshakeError::Authentication(
141 "controller identity rejected".into(),
142 ));
143 }
144 }
145
146 Ok(())
147}