alpine/handshake/
mod.rs

1use async_trait::async_trait;
2use rand::{rngs::OsRng, RngCore};
3use serde::{Deserialize, Serialize};
4use std::time::Duration;
5use thiserror::Error;
6
7use crate::crypto::{KeyExchangeAlgorithm, SessionKeys};
8use crate::messages::{
9    Acknowledge, ControlEnvelope, Keepalive, SessionAck, SessionComplete, SessionEstablished,
10    SessionInit, SessionReady,
11};
12
13pub mod client;
14pub mod keepalive;
15pub mod server;
16pub mod transport;
17
18/// Transport abstraction used during the ALNP handshake.
19#[async_trait]
20pub trait HandshakeTransport {
21    async fn send(&mut self, msg: HandshakeMessage) -> Result<(), HandshakeError>;
22    async fn recv(&mut self) -> Result<HandshakeMessage, HandshakeError>;
23}
24
25/// Minimal message envelope for the handshake pipeline.
26#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
27#[serde(untagged)]
28pub enum HandshakeMessage {
29    SessionInit(SessionInit),
30    SessionAck(SessionAck),
31    SessionReady(SessionReady),
32    SessionComplete(SessionComplete),
33    SessionEstablished(SessionEstablished),
34    Keepalive(Keepalive),
35    Control(ControlEnvelope),
36    Ack(Acknowledge),
37}
38
39/// Context shared between handshake participants.
40#[derive(Debug, Clone)]
41pub struct HandshakeContext {
42    pub key_algorithm: KeyExchangeAlgorithm,
43    pub expected_controller: Option<String>,
44    pub required_firmware_rev: Option<String>,
45    pub client_nonce: Vec<u8>,
46    pub device_identity_pubkey: Option<Vec<u8>>,
47    pub recv_timeout: Duration,
48    pub debug_cbor: bool,
49}
50
51impl HandshakeContext {
52    pub fn with_client_nonce(mut self, nonce: Vec<u8>) -> Self {
53        self.client_nonce = nonce;
54        self
55    }
56
57    pub fn with_device_identity_pubkey(mut self, pubkey: Vec<u8>) -> Self {
58        self.device_identity_pubkey = Some(pubkey);
59        self
60    }
61
62    pub fn with_recv_timeout(mut self, timeout: Duration) -> Self {
63        self.recv_timeout = timeout;
64        self
65    }
66
67    pub fn with_debug_cbor(mut self, enabled: bool) -> Self {
68        self.debug_cbor = enabled;
69        self
70    }
71}
72
73impl Default for HandshakeContext {
74    fn default() -> Self {
75        Self {
76            key_algorithm: KeyExchangeAlgorithm::X25519,
77            expected_controller: None,
78            required_firmware_rev: None,
79            client_nonce: new_nonce().to_vec(),
80            device_identity_pubkey: None,
81            recv_timeout: Duration::from_millis(7000),
82            debug_cbor: false,
83        }
84    }
85}
86
87#[derive(Debug, Error)]
88pub enum HandshakeError {
89    #[error("transport error: {0}")]
90    Transport(String),
91    #[error("protocol violation: {0}")]
92    Protocol(String),
93    #[error("authentication failed: {0}")]
94    Authentication(String),
95    #[error("unsupported capability: {0}")]
96    Capability(String),
97}
98
99/// Generates a cryptographic nonce for challenge/response.
100pub fn new_nonce() -> [u8; 32] {
101    let mut bytes = [0u8; 32];
102    OsRng.fill_bytes(&mut bytes);
103    bytes
104}
105
106/// Shared behavior between controller and node handshake roles.
107#[async_trait]
108pub trait HandshakeParticipant {
109    async fn run<T: HandshakeTransport + Send>(
110        &self,
111        transport: &mut T,
112    ) -> Result<HandshakeOutcome, HandshakeError>;
113}
114
115/// Minimal authenticator stub for challenge validation.
116pub trait ChallengeAuthenticator {
117    fn sign_challenge(&self, nonce: &[u8]) -> Vec<u8>;
118    fn verify_challenge(&self, nonce: &[u8], signature: &[u8]) -> bool;
119    fn identity_verifying_key(&self) -> Option<Vec<u8>> {
120        None
121    }
122}
123
124/// Output returned by handshake drivers.
125#[derive(Debug, Clone)]
126pub struct HandshakeOutcome {
127    pub established: SessionEstablished,
128    pub keys: SessionKeys,
129}