Skip to main content

foctet_core/
control.rs

1use crate::{
2    CoreError,
3    auth::{HANDSHAKE_AUTH_ED25519, HANDSHAKE_AUTH_NONE, HandshakeAuth},
4};
5
6const CONTROL_PREFIX: [u8; 4] = *b"FCTL";
7const CONTROL_VERSION: u8 = 0;
8
9/// Control message type discriminator for Draft v0 control payloads.
10#[derive(Clone, Copy, Debug, Eq, PartialEq)]
11#[repr(u8)]
12pub enum ControlMessageKind {
13    /// Initiator ephemeral key + session salt.
14    ClientHello = 1,
15    /// Responder ephemeral key.
16    ServerHello = 2,
17    /// Rekey notification carrying salt and key-id transition.
18    Rekey = 3,
19    /// Generic protocol error.
20    Error = 255,
21}
22
23/// Control payload schema transported in encrypted control frames.
24#[derive(Clone, Debug, Eq, PartialEq)]
25pub enum ControlMessage {
26    /// First handshake message from initiator.
27    ClientHello {
28        /// Initiator ephemeral public key.
29        eph_public: [u8; 32],
30        /// Session salt used for initial key derivation.
31        session_salt: [u8; 32],
32        /// Transcript binding hash.
33        transcript_binding: [u8; 32],
34        /// Optional identity authentication for the handshake transcript.
35        auth: Option<HandshakeAuth>,
36    },
37    /// Handshake response from responder.
38    ServerHello {
39        /// Responder ephemeral public key.
40        eph_public: [u8; 32],
41        /// Transcript binding hash.
42        transcript_binding: [u8; 32],
43        /// Optional identity authentication for the handshake transcript.
44        auth: Option<HandshakeAuth>,
45    },
46    /// Rekey event message.
47    Rekey {
48        /// Previous key identifier.
49        old_key_id: u8,
50        /// New key identifier.
51        new_key_id: u8,
52        /// Salt value used for rekey derivation.
53        rekey_salt: [u8; 32],
54        /// Transcript binding hash.
55        transcript_binding: [u8; 32],
56    },
57    /// Generic control error code.
58    Error {
59        /// Protocol-defined error code.
60        code: u16,
61    },
62}
63
64impl ControlMessage {
65    /// Returns the message-kind discriminator.
66    pub fn kind(&self) -> ControlMessageKind {
67        match self {
68            Self::ClientHello { .. } => ControlMessageKind::ClientHello,
69            Self::ServerHello { .. } => ControlMessageKind::ServerHello,
70            Self::Rekey { .. } => ControlMessageKind::Rekey,
71            Self::Error { .. } => ControlMessageKind::Error,
72        }
73    }
74
75    /// Encodes control payload into wire bytes.
76    pub fn encode(&self) -> Vec<u8> {
77        let mut out = Vec::with_capacity(4 + 1 + 1 + 128);
78        out.extend_from_slice(&CONTROL_PREFIX);
79        out.push(CONTROL_VERSION);
80        out.push(self.kind() as u8);
81
82        match self {
83            Self::ClientHello {
84                eph_public,
85                session_salt,
86                transcript_binding,
87                auth,
88            } => {
89                out.extend_from_slice(eph_public);
90                out.extend_from_slice(session_salt);
91                out.extend_from_slice(transcript_binding);
92                encode_handshake_auth(&mut out, auth);
93            }
94            Self::ServerHello {
95                eph_public,
96                transcript_binding,
97                auth,
98            } => {
99                out.extend_from_slice(eph_public);
100                out.extend_from_slice(transcript_binding);
101                encode_handshake_auth(&mut out, auth);
102            }
103            Self::Rekey {
104                old_key_id,
105                new_key_id,
106                rekey_salt,
107                transcript_binding,
108            } => {
109                out.push(*old_key_id);
110                out.push(*new_key_id);
111                out.extend_from_slice(rekey_salt);
112                out.extend_from_slice(transcript_binding);
113            }
114            Self::Error { code } => {
115                out.extend_from_slice(&code.to_be_bytes());
116            }
117        }
118
119        out
120    }
121
122    /// Decodes control payload from wire bytes.
123    pub fn decode(bytes: &[u8]) -> Result<Self, CoreError> {
124        if bytes.len() < 6 {
125            return Err(CoreError::InvalidControlMessage);
126        }
127        if bytes[0..4] != CONTROL_PREFIX {
128            return Err(CoreError::InvalidControlMessage);
129        }
130        if bytes[4] != CONTROL_VERSION {
131            return Err(CoreError::InvalidControlMessage);
132        }
133
134        let kind = bytes[5];
135        let body = &bytes[6..];
136
137        match kind {
138            x if x == ControlMessageKind::ClientHello as u8 => {
139                if body.len() != 96 && body.len() != 97 && body.len() != 193 {
140                    return Err(CoreError::InvalidControlMessage);
141                }
142                let mut eph_public = [0u8; 32];
143                eph_public.copy_from_slice(&body[0..32]);
144                let mut session_salt = [0u8; 32];
145                session_salt.copy_from_slice(&body[32..64]);
146                let mut transcript_binding = [0u8; 32];
147                transcript_binding.copy_from_slice(&body[64..96]);
148                let auth = decode_handshake_auth(&body[96..])?;
149                Ok(Self::ClientHello {
150                    eph_public,
151                    session_salt,
152                    transcript_binding,
153                    auth,
154                })
155            }
156            x if x == ControlMessageKind::ServerHello as u8 => {
157                if body.len() != 64 && body.len() != 65 && body.len() != 161 {
158                    return Err(CoreError::InvalidControlMessage);
159                }
160                let mut eph_public = [0u8; 32];
161                eph_public.copy_from_slice(&body[0..32]);
162                let mut transcript_binding = [0u8; 32];
163                transcript_binding.copy_from_slice(&body[32..64]);
164                let auth = decode_handshake_auth(&body[64..])?;
165                Ok(Self::ServerHello {
166                    eph_public,
167                    transcript_binding,
168                    auth,
169                })
170            }
171            x if x == ControlMessageKind::Rekey as u8 => {
172                if body.len() != 66 {
173                    return Err(CoreError::InvalidControlMessage);
174                }
175                let old_key_id = body[0];
176                let new_key_id = body[1];
177                let mut rekey_salt = [0u8; 32];
178                rekey_salt.copy_from_slice(&body[2..34]);
179                let mut transcript_binding = [0u8; 32];
180                transcript_binding.copy_from_slice(&body[34..66]);
181                Ok(Self::Rekey {
182                    old_key_id,
183                    new_key_id,
184                    rekey_salt,
185                    transcript_binding,
186                })
187            }
188            x if x == ControlMessageKind::Error as u8 => {
189                if body.len() != 2 {
190                    return Err(CoreError::InvalidControlMessage);
191                }
192                let mut code_bytes = [0u8; 2];
193                code_bytes.copy_from_slice(body);
194                Ok(Self::Error {
195                    code: u16::from_be_bytes(code_bytes),
196                })
197            }
198            _ => Err(CoreError::InvalidControlMessage),
199        }
200    }
201}
202
203fn encode_handshake_auth(out: &mut Vec<u8>, auth: &Option<HandshakeAuth>) {
204    match auth {
205        Some(auth) => {
206            out.push(HANDSHAKE_AUTH_ED25519);
207            out.extend_from_slice(&auth.identity_public_key);
208            out.extend_from_slice(&auth.signature);
209        }
210        None => out.push(HANDSHAKE_AUTH_NONE),
211    }
212}
213
214fn decode_handshake_auth(bytes: &[u8]) -> Result<Option<HandshakeAuth>, CoreError> {
215    if bytes.is_empty() {
216        return Ok(None);
217    }
218
219    match bytes[0] {
220        HANDSHAKE_AUTH_NONE if bytes.len() == 1 => Ok(None),
221        HANDSHAKE_AUTH_ED25519 if bytes.len() == 1 + HandshakeAuth::encoded_len() => {
222            let mut identity_public_key = [0u8; 32];
223            identity_public_key.copy_from_slice(&bytes[1..33]);
224            let mut signature = [0u8; 64];
225            signature.copy_from_slice(&bytes[33..97]);
226            Ok(Some(HandshakeAuth {
227                identity_public_key,
228                signature,
229            }))
230        }
231        _ => Err(CoreError::InvalidControlMessage),
232    }
233}
234
235#[cfg(test)]
236mod tests {
237    use super::*;
238
239    #[test]
240    fn control_roundtrip() {
241        let msg = ControlMessage::Rekey {
242            old_key_id: 1,
243            new_key_id: 2,
244            rekey_salt: [7u8; 32],
245            transcript_binding: [9u8; 32],
246        };
247        let encoded = msg.encode();
248        let decoded = ControlMessage::decode(&encoded).expect("decode");
249        assert_eq!(decoded, msg);
250    }
251}