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};
13
14/// Controller-side handshake driver implementing the ALPINE 1.0 flow.
15pub 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        // 1) Controller -> device: session_init
41        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        // 2) Device -> controller: session_ack
51        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        // 3) Verify device signature over the controller nonce.
63        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        // 4) Derive shared keys (HKDF over concatenated nonces).
73        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        // 5) Controller -> device: session_ready (MAC proves key possession).
81        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        // 6) Device -> controller: session_complete
93        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}