Skip to main content

pim_protocol/
handshake_frame.rs

1//! Wire-level representation of the authenticated peer handshake.
2
3use bytes::{Buf, BufMut, BytesMut};
4
5use pim_core::{FrameCodec, PimError};
6
7/// Sub-type of handshake frame.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9#[repr(u8)]
10pub enum HandshakeFrameType {
11    /// Initiator sends its ephemeral key and signature.
12    Init = 0,
13    /// Responder sends its ephemeral key and signature.
14    Response = 1,
15    /// Final transcript confirmation MAC.
16    Confirm = 2,
17}
18
19impl HandshakeFrameType {
20    /// Decode a raw handshake subtype from the wire.
21    pub fn from_u8(v: u8) -> Result<Self, PimError> {
22        match v {
23            0 => Ok(Self::Init),
24            1 => Ok(Self::Response),
25            2 => Ok(Self::Confirm),
26            other => Err(PimError::Protocol(format!(
27                "unknown handshake type: {other}"
28            ))),
29        }
30    }
31}
32
33/// Wire representation of a handshake message.
34///
35/// For Init/Response: handshake_type(1) + sender_pub(32) + ephemeral_pub(32) + nonce(32) + signature(64) = 161
36/// For Confirm: handshake_type(1) + hmac(32) = 33
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub enum HandshakeWireFrame {
39    /// Init or response frame sharing the same binary layout.
40    InitOrResponse {
41        /// Whether this frame is an init or a response.
42        handshake_type: HandshakeFrameType,
43        /// Sender's Ed25519 public key.
44        sender_pub: [u8; 32],
45        /// Sender's ephemeral X25519 public key.
46        ephemeral_pub: [u8; 32],
47        /// Random nonce mixed into transcript-derived keys.
48        nonce: [u8; 32],
49        /// Signature over the ephemeral key and nonce.
50        signature: [u8; 64],
51    },
52    /// Final transcript-confirmation frame.
53    Confirm {
54        /// HMAC of the negotiated handshake transcript.
55        hmac: [u8; 32],
56    },
57}
58
59const INIT_RESPONSE_SIZE: usize = 1 + 32 + 32 + 32 + 64; // 161
60const CONFIRM_SIZE: usize = 1 + 32; // 33
61
62impl FrameCodec for HandshakeWireFrame {
63    fn encode(&self, buf: &mut BytesMut) {
64        match self {
65            HandshakeWireFrame::InitOrResponse {
66                handshake_type,
67                sender_pub,
68                ephemeral_pub,
69                nonce,
70                signature,
71            } => {
72                buf.put_u8(*handshake_type as u8);
73                buf.put_slice(sender_pub);
74                buf.put_slice(ephemeral_pub);
75                buf.put_slice(nonce);
76                buf.put_slice(signature);
77            }
78            HandshakeWireFrame::Confirm { hmac } => {
79                buf.put_u8(HandshakeFrameType::Confirm as u8);
80                buf.put_slice(hmac);
81            }
82        }
83    }
84
85    fn decode(buf: &mut BytesMut) -> Result<Self, PimError> {
86        if buf.is_empty() {
87            return Err(PimError::Protocol("handshake frame empty".into()));
88        }
89
90        let handshake_type = HandshakeFrameType::from_u8(buf[0])?;
91
92        match handshake_type {
93            HandshakeFrameType::Init | HandshakeFrameType::Response => {
94                if buf.len() < INIT_RESPONSE_SIZE {
95                    return Err(PimError::Protocol(format!(
96                        "handshake init/response too short: need {INIT_RESPONSE_SIZE}, have {}",
97                        buf.len()
98                    )));
99                }
100                let mut sender_pub = [0u8; 32];
101                sender_pub.copy_from_slice(&buf[1..33]);
102                let mut ephemeral_pub = [0u8; 32];
103                ephemeral_pub.copy_from_slice(&buf[33..65]);
104                let mut nonce = [0u8; 32];
105                nonce.copy_from_slice(&buf[65..97]);
106                let mut signature = [0u8; 64];
107                signature.copy_from_slice(&buf[97..161]);
108
109                buf.advance(INIT_RESPONSE_SIZE);
110
111                Ok(HandshakeWireFrame::InitOrResponse {
112                    handshake_type,
113                    sender_pub,
114                    ephemeral_pub,
115                    nonce,
116                    signature,
117                })
118            }
119            HandshakeFrameType::Confirm => {
120                if buf.len() < CONFIRM_SIZE {
121                    return Err(PimError::Protocol(format!(
122                        "handshake confirm too short: need {CONFIRM_SIZE}, have {}",
123                        buf.len()
124                    )));
125                }
126                let mut hmac = [0u8; 32];
127                hmac.copy_from_slice(&buf[1..33]);
128
129                buf.advance(CONFIRM_SIZE);
130
131                Ok(HandshakeWireFrame::Confirm { hmac })
132            }
133        }
134    }
135}
136
137#[cfg(test)]
138mod tests;