alpine/handshake/
client.rs

1use 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};
13use ed25519_dalek::{Signature, VerifyingKey};
14use tracing::{debug, info};
15
16/// Controller-side handshake driver implementing the ALPINE 1.0 flow.
17pub struct ClientHandshake<A, K>
18where
19    A: super::ChallengeAuthenticator + Send + Sync,
20    K: KeyExchange + Send + Sync,
21{
22    pub identity: DeviceIdentity,
23    pub capabilities: CapabilitySet,
24    pub authenticator: A,
25    pub key_exchange: K,
26    pub context: HandshakeContext,
27}
28
29#[async_trait]
30impl<A, K> HandshakeParticipant for ClientHandshake<A, K>
31where
32    A: super::ChallengeAuthenticator + Send + Sync,
33    K: KeyExchange + Send + Sync,
34{
35    async fn run<T: HandshakeTransport + Send>(
36        &self,
37        transport: &mut T,
38    ) -> Result<HandshakeOutcome, HandshakeError> {
39        let controller_nonce = self.context.client_nonce.clone();
40        let session_id = Uuid::new_v4();
41        let session_id_str = session_id.to_string();
42        info!(
43            "[ALPINE][HANDSHAKE] initiating client handshake session_id={} controller_nonce_len={} device_id={}",
44            session_id_str,
45            controller_nonce.len(),
46            self.identity.device_id
47        );
48
49        // 1) Controller -> device: session_init
50        let init = SessionInit {
51            message_type: MessageType::SessionInit,
52            controller_nonce: controller_nonce.clone(),
53            controller_pubkey: self.key_exchange.public_key(),
54            requested: self.capabilities.clone(),
55            session_id: session_id_str.clone(),
56        };
57        transport.send(HandshakeMessage::SessionInit(init)).await?;
58        info!(
59            "[ALPINE][HANDSHAKE][TX] SessionInit dispatched session_id={} controller_nonce={} controller_pubkey_len={} requested_caps={:?}",
60            session_id_str,
61            hex::encode(&controller_nonce),
62            self.key_exchange.public_key().len(),
63            self.capabilities
64        );
65
66        // 2) Device -> controller: session_ack
67        info!(
68            "[ALPINE][HANDSHAKE] awaiting SessionAck session_id={} timeout_hint_ms={}",
69            session_id_str,
70            self.context.recv_timeout.as_millis()
71        );
72        let ack = match transport.recv().await? {
73            HandshakeMessage::SessionAck(ack) => ack,
74            other => {
75                let encoded = serde_cbor::to_vec(&other).unwrap_or_default();
76                let first_bytes =
77                    hex::encode(&encoded.iter().take(32).cloned().collect::<Vec<_>>());
78                debug!(
79                    "[ALPINE][HANDSHAKE][RX][unexpected] expected=SessionAck actual={} payload_len={} first32={} state=awaiting_session_ack",
80                    message_label(&other),
81                    encoded.len(),
82                    first_bytes
83                );
84                return Err(HandshakeError::Protocol(format!(
85                    "expected SessionAck, got {:?}",
86                    other
87                )));
88            }
89        };
90        info!(
91            "[ALPINE][HANDSHAKE][RX] SessionAck fields session_id={} device_nonce={} device_pubkey_len={} device_identity_pubkey_len={} device_identity={:?} capabilities={:?} signature={}",
92            ack.session_id,
93            hex::encode(&ack.device_nonce),
94            ack.device_pubkey.len(),
95            ack.device_identity_pubkey.len(),
96            ack.device_identity,
97            ack.capabilities,
98            hex::encode(&ack.signature)
99        );
100        validate_ack(&ack, &session_id_str, &controller_nonce, &self.context)?;
101        info!(
102            "[ALPINE][HANDSHAKE][RX] SessionAck received session_id={} device_nonce_len={} device_id={}",
103            session_id_str,
104            ack.device_nonce.len(),
105            ack.device_identity.device_id
106        );
107
108        // 3) Verify device signature over the controller nonce using the device identity key.
109        println!(
110            "[ALPINE][DEBUG] handshake verify using pubkey={}",
111            hex::encode(&ack.device_identity_pubkey)
112        );
113        println!(
114            "[ALPINE][DEBUG] handshake verify controller_nonce={}",
115            hex::encode(&controller_nonce)
116        );
117        println!(
118            "[ALPINE][DEBUG] handshake verify signature={}",
119            hex::encode(&ack.signature)
120        );
121        let mut verified = false;
122        let mut tried_keys = Vec::new();
123        let mut candidates: Vec<&[u8]> = Vec::new();
124        if let Some(pk) = self.context.device_identity_pubkey.as_deref() {
125            candidates.push(pk);
126        }
127        if !ack.device_identity_pubkey.is_empty() {
128            candidates.push(ack.device_identity_pubkey.as_slice());
129        }
130        if candidates.is_empty() {
131            info!(
132                "[ALPINE][HANDSHAKE][VERIFY] no device identity pubkey provided; skipping signature check"
133            );
134            if ack.signature == controller_nonce {
135                verified = true;
136            }
137        }
138        for pk in candidates {
139            if pk.len() == 32 {
140                tried_keys.push(hex::encode(pk));
141                if let Ok(pk_bytes) = <&[u8; 32]>::try_from(pk) {
142                    if let Ok(device_identity_key) = VerifyingKey::from_bytes(pk_bytes) {
143                        if let Ok(signature) = Signature::from_slice(&ack.signature) {
144                            if device_identity_key
145                                .verify_strict(&controller_nonce, &signature)
146                                .is_ok()
147                            {
148                                info!(
149                                    "[ALPINE][HANDSHAKE][VERIFY] SessionAck signature validated with pubkey={}",
150                                    hex::encode(pk)
151                                );
152                                verified = true;
153                                break;
154                            }
155                        }
156                    }
157                }
158            }
159        }
160        if !verified {
161            println!(
162                "[ALPINE][DEBUG] handshake verification failed; tried identity keys: {:?}",
163                tried_keys
164            );
165            return Err(HandshakeError::Authentication(
166                "device signature validation failed: identity pubkey missing or invalid".into(),
167            ));
168        }
169
170        // 4) Derive shared keys (HKDF over concatenated nonces).
171        let mut salt = controller_nonce.clone();
172        salt.extend_from_slice(&ack.device_nonce);
173        let peer_key = if ack.device_pubkey.len() == 32 {
174            ack.device_pubkey.clone()
175        } else {
176            info!(
177                "[ALPINE][HANDSHAKE][VERIFY] device_pubkey missing; using zero key for Phase 3 compatibility"
178            );
179            vec![0u8; 32]
180        };
181        let keys = self
182            .key_exchange
183            .derive_keys(&peer_key, &salt)
184            .map_err(|e| HandshakeError::Authentication(format!("{}", e)))?;
185
186        // 5) Controller -> device: session_ready (MAC proves key possession).
187        let mac = compute_mac(
188            &keys,
189            0,
190            session_id_str.as_bytes(),
191            ack.device_nonce.as_slice(),
192        )
193        .map_err(|e| HandshakeError::Authentication(e.to_string()))?;
194        let ready = SessionReady {
195            message_type: MessageType::SessionReady,
196            session_id: session_id_str.clone(),
197            mac,
198        };
199        let ready_mac_hex = hex::encode(&ready.mac);
200        transport
201            .send(HandshakeMessage::SessionReady(ready))
202            .await?;
203        info!(
204            "[ALPINE][HANDSHAKE][TX] SessionReady sent session_id={} mac={}",
205            session_id_str, ready_mac_hex
206        );
207
208        // 6) Device -> controller: session_complete
209        let complete = match transport.recv().await? {
210            HandshakeMessage::SessionComplete(c) => c,
211            other => {
212                let encoded = serde_cbor::to_vec(&other).unwrap_or_default();
213                let first_bytes =
214                    hex::encode(&encoded.iter().take(32).cloned().collect::<Vec<_>>());
215                debug!(
216                    "[ALPINE][HANDSHAKE][RX][unexpected] expected=SessionComplete actual={} payload_len={} first32={} state=awaiting_session_complete",
217                    message_label(&other),
218                    encoded.len(),
219                    first_bytes
220                );
221                return Err(HandshakeError::Protocol(format!(
222                    "expected SessionComplete, got {:?}",
223                    other
224                )));
225            }
226        };
227        info!(
228            "[ALPINE][HANDSHAKE][RX] SessionComplete fields session_id={} ok={} error={:?}",
229            complete.session_id, complete.ok, complete.error
230        );
231        if !complete.ok {
232            return Err(HandshakeError::Authentication(
233                "device rejected session_ready".into(),
234            ));
235        }
236
237        let established = SessionEstablished {
238            session_id: session_id_str,
239            controller_nonce,
240            device_nonce: ack.device_nonce,
241            capabilities: ack.capabilities,
242            device_identity: ack.device_identity,
243        };
244
245        Ok(HandshakeOutcome { established, keys })
246    }
247}
248
249fn validate_ack(
250    ack: &SessionAck,
251    session_id: &str,
252    controller_nonce: &[u8],
253    context: &HandshakeContext,
254) -> Result<(), HandshakeError> {
255    if ack.session_id != session_id {
256        return Err(HandshakeError::Protocol(
257            "session_id mismatch between init and ack".into(),
258        ));
259    }
260
261    if ack.device_nonce.len() != controller_nonce.len() {
262        return Err(HandshakeError::Protocol(
263            "device nonce length mismatch".into(),
264        ));
265    }
266
267    if let Some(expected) = &context.expected_controller {
268        if expected != session_id {
269            return Err(HandshakeError::Authentication(
270                "controller identity rejected".into(),
271            ));
272        }
273    }
274
275    Ok(())
276}
277
278fn message_label(msg: &HandshakeMessage) -> &'static str {
279    match msg {
280        HandshakeMessage::SessionInit(_) => "SessionInit",
281        HandshakeMessage::SessionAck(_) => "SessionAck",
282        HandshakeMessage::SessionReady(_) => "SessionReady",
283        HandshakeMessage::SessionComplete(_) => "SessionComplete",
284        HandshakeMessage::SessionEstablished(_) => "SessionEstablished",
285        HandshakeMessage::Keepalive(_) => "Keepalive",
286        HandshakeMessage::Control(_) => "Control",
287        HandshakeMessage::Ack(_) => "Ack",
288    }
289}