1use chacha20::{cipher::StreamCipher, ChaCha20};
2use chacha20poly1305::{AeadInPlace, ChaCha20Poly1305, ChaChaPoly1305, KeyInit};
3use x25519_dalek::{PublicKey, StaticSecret};
4
5const INITIAL_ENCRYPTION_KEY_INFO: &str = "initial_encryption_key";
7const SERVER_EPHEMERAL_PUBLIC_KEY_MAC_KEY_INFO: &str = "server_ephemeral_public_key_mac_key";
8
9const S2C_STREAM_KEY_INFO: &str = "s2c_stream_key";
11const C2S_STREAM_KEY_INFO: &str = "c2s_stream_key";
12
13pub struct MorfPeer {
14 tx_cipher: Option<ChaCha20Poly1305>,
15 rx_cipher: Option<ChaCha20Poly1305>,
16 client_handshake_state: Option<ClientHandshakeState>,
17 tx_counter: u16,
18 rx_counter: u16,
19}
20
21struct ClientHandshakeState {
22 ephemeral_secret: StaticSecret,
23 server_ephemeral_key_mac_key: [u8; 32],
24}
25
26#[derive(Clone)]
27pub struct UnauthenticatedClientHandshake {
28 pub device_static_public_key_hash: [u8; 16],
29
30 pub server_ephemeral_public_key: PublicKey,
31 pub server_ephemeral_secret_key: StaticSecret,
32 pub client_ephemeral_public_key: PublicKey,
33}
34
35impl UnauthenticatedClientHandshake {
36 pub fn authenticate(self, device_static_public_key: &PublicKey, peer: &mut MorfPeer) {
37 let mut session_key_material = [0u8; 64];
38 session_key_material[0..32].copy_from_slice(
39 self
40 .server_ephemeral_secret_key
41 .diffie_hellman(device_static_public_key)
42 .as_bytes(),
43 );
44 session_key_material[32..64].copy_from_slice(
45 self
46 .server_ephemeral_secret_key
47 .diffie_hellman(&self.client_ephemeral_public_key)
48 .as_bytes(),
49 );
50
51 let tx_stream_key: [u8; 32] = blake3::derive_key(S2C_STREAM_KEY_INFO, &session_key_material);
52 let rx_stream_key: [u8; 32] = blake3::derive_key(C2S_STREAM_KEY_INFO, &session_key_material);
53
54 assert!(peer.tx_cipher.is_none() && peer.rx_cipher.is_none());
55 assert!(peer.client_handshake_state.is_none());
56
57 peer.tx_cipher = Some(ChaChaPoly1305::new(&tx_stream_key.into()));
58 peer.rx_cipher = Some(ChaChaPoly1305::new(&rx_stream_key.into()));
59 }
60}
61
62impl MorfPeer {
63 pub fn client_initiate_handshake(
64 ephemeral_secret: [u8; 32],
65 server_public_key: &PublicKey,
66 device_static_public_key: &PublicKey,
67 ) -> (Self, impl AsMut<[u8]> + AsRef<[u8]>) {
68 let ephemeral_secret = StaticSecret::from(ephemeral_secret);
69 let ephemeral_public = PublicKey::from(&ephemeral_secret);
70 let early_key_material = ephemeral_secret.diffie_hellman(server_public_key);
71
72 let initial_encryption_key: [u8; 32] =
73 blake3::derive_key(INITIAL_ENCRYPTION_KEY_INFO, early_key_material.as_bytes());
74 let mut init_cipher = <ChaCha20 as chacha20::cipher::KeyIvInit>::new(
75 &initial_encryption_key.into(),
76 &[0u8; 12].into(),
77 );
78
79 let device_static_public_key_hash =
80 &mut (<[u8; 32]>::from(blake3::hash(device_static_public_key.as_bytes())))[..16];
81 init_cipher.apply_keystream(device_static_public_key_hash);
82
83 let mut output = [0u8; 49];
84 output[0] = 3;
85 output[1..33].copy_from_slice(ephemeral_public.as_bytes());
86 output[33..49].copy_from_slice(&device_static_public_key_hash);
87
88 let server_ephemeral_key_mac_key = blake3::derive_key(
89 SERVER_EPHEMERAL_PUBLIC_KEY_MAC_KEY_INFO,
90 early_key_material.as_bytes(),
91 );
92
93 (
94 Self {
95 tx_cipher: None,
96 rx_cipher: None,
97 client_handshake_state: Some(ClientHandshakeState {
98 ephemeral_secret,
99 server_ephemeral_key_mac_key,
100 }),
101 tx_counter: 0,
102 rx_counter: 0,
103 },
104 output,
105 )
106 }
107
108 pub fn server_accept_handshake(
109 ephemeral_secret: [u8; 32],
110 static_secret: &StaticSecret,
111 packet: &[u8],
112 ) -> Result<
113 (
114 Self,
115 UnauthenticatedClientHandshake,
116 impl AsMut<[u8]> + AsRef<[u8]>,
117 ),
118 (),
119 > {
120 if packet.len() != 49 || packet[0] != 3 {
121 return Err(());
122 }
123
124 let client_ephemeral_public_key =
125 PublicKey::from(<[u8; 32]>::try_from(&packet[1..33]).unwrap());
126 let mut device_static_public_key_hash: [u8; 16] = packet[33..49].try_into().unwrap();
127
128 let ephemeral_secret = StaticSecret::from(ephemeral_secret);
129 let ephemeral_public = PublicKey::from(&ephemeral_secret);
130
131 let early_key_material = static_secret.diffie_hellman(&client_ephemeral_public_key);
132
133 let mut init_cipher = <ChaCha20 as chacha20::cipher::KeyIvInit>::new(
134 &blake3::derive_key(INITIAL_ENCRYPTION_KEY_INFO, early_key_material.as_bytes()).into(),
135 &[0u8; 12].into(),
136 );
137
138 init_cipher.apply_keystream(&mut device_static_public_key_hash);
139
140 let server_ephemeral_key_mac_key = blake3::derive_key(
142 SERVER_EPHEMERAL_PUBLIC_KEY_MAC_KEY_INFO,
143 early_key_material.as_bytes(),
144 );
145 let mut output = [0u8; 49];
146 output[0] = 1;
147 output[1..33].copy_from_slice(ephemeral_public.as_bytes());
148 output[33..49].copy_from_slice(
149 &blake3::keyed_hash(&server_ephemeral_key_mac_key, ephemeral_public.as_bytes()).as_bytes()
150 [..16],
151 );
152
153 Ok((
154 Self {
155 tx_cipher: None,
156 rx_cipher: None,
157 client_handshake_state: None,
158 tx_counter: 0,
159 rx_counter: 0,
160 },
161 UnauthenticatedClientHandshake {
162 device_static_public_key_hash,
163 server_ephemeral_public_key: ephemeral_public,
164 server_ephemeral_secret_key: ephemeral_secret,
165 client_ephemeral_public_key,
166 },
167 output,
168 ))
169 }
170
171 pub fn client_finalize_handshake(
172 &mut self,
173 device_static_secret_key: &StaticSecret,
174 packet: &[u8],
175 ) -> Result<(), ()> {
176 if packet.len() != 49 || packet[0] != 1 {
177 return Err(());
178 }
179
180 let Some(st) = &self.client_handshake_state else {
181 return Err(());
182 };
183
184 if !constant_time_eq::constant_time_eq(
185 &blake3::keyed_hash(&st.server_ephemeral_key_mac_key, &packet[1..33]).as_bytes()[..16],
186 &packet[33..49],
187 ) {
188 return Err(());
189 }
190
191 let server_ephemeral_public_key =
194 PublicKey::from(<[u8; 32]>::try_from(&packet[1..33]).unwrap());
195
196 let mut session_key_material = [0u8; 64];
197 session_key_material[0..32].copy_from_slice(
198 device_static_secret_key
199 .diffie_hellman(&server_ephemeral_public_key)
200 .as_bytes(),
201 );
202 session_key_material[32..64].copy_from_slice(
203 st.ephemeral_secret
204 .diffie_hellman(&server_ephemeral_public_key)
205 .as_bytes(),
206 );
207
208 let tx_stream_key: [u8; 32] = blake3::derive_key(C2S_STREAM_KEY_INFO, &session_key_material);
209 let rx_stream_key: [u8; 32] = blake3::derive_key(S2C_STREAM_KEY_INFO, &session_key_material);
210
211 self.tx_cipher = Some(ChaChaPoly1305::new(&tx_stream_key.into()));
212 self.rx_cipher = Some(ChaChaPoly1305::new(&rx_stream_key.into()));
213 self.client_handshake_state = None;
214
215 Ok(())
216 }
217
218 pub fn unseal<'a>(&mut self, packet: &'a mut [u8]) -> Result<&'a mut [u8], ()> {
219 let Some(rx_cipher) = &self.rx_cipher else {
220 return Err(());
221 };
222 if packet.len() < 19 || packet[0] != 2 {
223 return Err(());
224 }
225
226 let packet_len = packet.len();
227
228 let untrusted_counter = u16::from_be_bytes(packet[1..3].try_into().unwrap());
229 let tag = <[u8; 16]>::try_from(&packet[packet_len - 16..]).unwrap();
230 let content = &mut packet[3..packet_len - 16];
231
232 if untrusted_counter <= self.rx_counter {
233 return Err(());
234 }
235
236 let mut nonce = [0u8; 12];
237 nonce[10..12].copy_from_slice(&untrusted_counter.to_be_bytes());
238
239 rx_cipher
240 .decrypt_in_place_detached(&nonce.into(), &[], content, &tag.into())
241 .map_err(|_| ())?;
242
243 self.rx_counter = untrusted_counter;
244 Ok(content)
245 }
246
247 pub fn seal(&mut self, packet: &mut [u8]) -> Result<([u8; 3], [u8; 16]), ()> {
248 let Some(tx_cipher) = &self.tx_cipher else {
249 return Err(());
250 };
251
252 let next_counter = match self.tx_counter.checked_add(1) {
253 Some(next_counter) => next_counter,
254 None => return Err(()),
255 };
256 self.tx_counter = next_counter;
257
258 let mut nonce = [0u8; 12];
259 nonce[10..12].copy_from_slice(&self.tx_counter.to_be_bytes());
260 let tag = tx_cipher
261 .encrypt_in_place_detached(&nonce.into(), &[], packet)
262 .unwrap();
263
264 let mut prefix = [0u8; 3];
265 prefix[0] = 2;
266 prefix[1..3].copy_from_slice(&self.tx_counter.to_be_bytes());
267 Ok((prefix, tag.into()))
268 }
269}