1use alloc::vec::Vec;
2
3use rns_crypto::ed25519::{Ed25519PrivateKey, Ed25519PublicKey};
4use rns_crypto::hkdf::hkdf;
5use rns_crypto::x25519::{X25519PrivateKey, X25519PublicKey};
6
7use super::types::{LinkError, LinkId, LinkMode};
8use crate::constants::{LINK_ECPUBSIZE, LINK_MODE_BYTEMASK, LINK_MTU_BYTEMASK, LINK_MTU_SIZE};
9use crate::hash::truncated_hash;
10
11pub fn compute_link_id(hashable_part: &[u8], extra_bytes_len: usize) -> LinkId {
16 let end = if extra_bytes_len > 0 && hashable_part.len() > extra_bytes_len {
17 hashable_part.len() - extra_bytes_len
18 } else {
19 hashable_part.len()
20 };
21 truncated_hash(&hashable_part[..end])
22}
23
24pub fn build_signalling_bytes(mtu: u32, mode: LinkMode) -> [u8; 3] {
29 let mode_bits = ((mode.mode_byte() << 5) & LINK_MODE_BYTEMASK) as u32;
30 let signalling_value = (mtu & LINK_MTU_BYTEMASK) + (mode_bits << 16);
31 let bytes = signalling_value.to_be_bytes();
32 [bytes[1], bytes[2], bytes[3]]
33}
34
35pub fn parse_signalling_bytes(bytes: &[u8; 3]) -> Result<(u32, LinkMode), LinkError> {
37 let mtu = ((bytes[0] as u32) << 16) | ((bytes[1] as u32) << 8) | (bytes[2] as u32);
38 let mode_byte = (bytes[0] & LINK_MODE_BYTEMASK) >> 5;
39 let mtu_val = mtu & LINK_MTU_BYTEMASK;
40 let mode = LinkMode::from_byte(mode_byte)?;
41 Ok((mtu_val, mode))
42}
43
44pub fn build_linkrequest_data(
46 pub_bytes: &[u8; 32],
47 sig_pub_bytes: &[u8; 32],
48 mtu: Option<u32>,
49 mode: LinkMode,
50) -> Vec<u8> {
51 let mut data = Vec::with_capacity(LINK_ECPUBSIZE + LINK_MTU_SIZE);
52 data.extend_from_slice(pub_bytes);
53 data.extend_from_slice(sig_pub_bytes);
54 if let Some(mtu_val) = mtu {
55 let sig_bytes = build_signalling_bytes(mtu_val, mode);
56 data.extend_from_slice(&sig_bytes);
57 }
58 data
59}
60
61#[allow(clippy::type_complexity)]
63pub fn parse_linkrequest_data(
64 data: &[u8],
65) -> Result<([u8; 32], [u8; 32], Option<u32>, LinkMode), LinkError> {
66 if data.len() != LINK_ECPUBSIZE && data.len() != LINK_ECPUBSIZE + LINK_MTU_SIZE {
67 return Err(LinkError::InvalidData);
68 }
69
70 let mut x25519_pub = [0u8; 32];
71 let mut ed25519_pub = [0u8; 32];
72 x25519_pub.copy_from_slice(&data[..32]);
73 ed25519_pub.copy_from_slice(&data[32..64]);
74
75 if data.len() == LINK_ECPUBSIZE + LINK_MTU_SIZE {
76 let mut sig_bytes = [0u8; 3];
77 sig_bytes.copy_from_slice(&data[LINK_ECPUBSIZE..LINK_ECPUBSIZE + LINK_MTU_SIZE]);
78 let (mtu, mode) = parse_signalling_bytes(&sig_bytes)?;
79 Ok((x25519_pub, ed25519_pub, Some(mtu), mode))
80 } else {
81 Ok((x25519_pub, ed25519_pub, None, LinkMode::Aes256Cbc))
82 }
83}
84
85pub fn build_lrproof(
89 link_id: &LinkId,
90 pub_bytes: &[u8; 32],
91 sig_pub_bytes: &[u8; 32],
92 sig_prv: &Ed25519PrivateKey,
93 mtu: Option<u32>,
94 mode: LinkMode,
95) -> Vec<u8> {
96 let signalling_bytes = if let Some(mtu_val) = mtu {
97 build_signalling_bytes(mtu_val, mode).to_vec()
98 } else {
99 Vec::new()
100 };
101
102 let mut signed_data = Vec::with_capacity(16 + 32 + 32 + signalling_bytes.len());
103 signed_data.extend_from_slice(link_id);
104 signed_data.extend_from_slice(pub_bytes);
105 signed_data.extend_from_slice(sig_pub_bytes);
106 signed_data.extend_from_slice(&signalling_bytes);
107
108 let signature = sig_prv.sign(&signed_data);
109
110 let mut proof_data = Vec::with_capacity(64 + 32 + signalling_bytes.len());
111 proof_data.extend_from_slice(&signature);
112 proof_data.extend_from_slice(pub_bytes);
113 proof_data.extend_from_slice(&signalling_bytes);
114
115 proof_data
116}
117
118pub fn validate_lrproof(
123 proof_data: &[u8],
124 link_id: &LinkId,
125 peer_sig_pub: &Ed25519PublicKey,
126 peer_sig_pub_bytes: &[u8; 32],
127) -> Result<([u8; 32], Option<u32>, LinkMode), LinkError> {
128 let sig_len = 64;
129 let pub_len = 32;
130
131 if proof_data.len() != sig_len + pub_len
132 && proof_data.len() != sig_len + pub_len + LINK_MTU_SIZE
133 {
134 return Err(LinkError::InvalidData);
135 }
136
137 let mut signature = [0u8; 64];
138 signature.copy_from_slice(&proof_data[..sig_len]);
139
140 let mut peer_pub = [0u8; 32];
141 peer_pub.copy_from_slice(&proof_data[sig_len..sig_len + pub_len]);
142
143 let signalling_bytes = &proof_data[sig_len + pub_len..];
144
145 let (mtu, mode) = if signalling_bytes.len() == LINK_MTU_SIZE {
146 let mut sb = [0u8; 3];
147 sb.copy_from_slice(signalling_bytes);
148 let (m, md) = parse_signalling_bytes(&sb)?;
149 (Some(m), md)
150 } else {
151 (None, LinkMode::Aes256Cbc)
152 };
153
154 let mut signed_data = Vec::with_capacity(16 + 32 + 32 + signalling_bytes.len());
155 signed_data.extend_from_slice(link_id);
156 signed_data.extend_from_slice(&peer_pub);
157 signed_data.extend_from_slice(peer_sig_pub_bytes);
158 signed_data.extend_from_slice(signalling_bytes);
159
160 if peer_sig_pub.verify(&signature, &signed_data) {
161 Ok((peer_pub, mtu, mode))
162 } else {
163 Err(LinkError::InvalidSignature)
164 }
165}
166
167pub fn derive_session_key(
173 shared_key: &[u8; 32],
174 link_id: &LinkId,
175 mode: LinkMode,
176) -> Result<Vec<u8>, LinkError> {
177 let length = mode.derived_key_length();
178 hkdf(length, shared_key, Some(link_id), None).map_err(|_| LinkError::CryptoError)
179}
180
181pub fn perform_key_exchange(
183 prv: &X25519PrivateKey,
184 peer_pub_bytes: &[u8; 32],
185 link_id: &LinkId,
186 mode: LinkMode,
187) -> Result<Vec<u8>, LinkError> {
188 let peer_pub = X25519PublicKey::from_bytes(peer_pub_bytes);
189 let shared_key = prv.exchange(&peer_pub);
190 derive_session_key(&shared_key, link_id, mode)
191}
192
193pub fn pack_rtt(rtt: f64) -> Vec<u8> {
195 let mut data = Vec::with_capacity(9);
196 data.push(0xcb);
197 data.extend_from_slice(&rtt.to_be_bytes());
198 data
199}
200
201pub fn unpack_rtt(data: &[u8]) -> Option<f64> {
206 if data.len() == 9 && data[0] == 0xcb {
207 let mut bytes = [0u8; 8];
208 bytes.copy_from_slice(&data[1..9]);
209 let rtt = f64::from_be_bytes(bytes);
210 (rtt.is_finite() && rtt >= 0.0).then_some(rtt)
211 } else {
212 None
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219 use rns_crypto::FixedRng;
220
221 #[test]
222 fn test_signalling_bytes_roundtrip() {
223 let mtu = 500u32;
224 let mode = LinkMode::Aes256Cbc;
225 let bytes = build_signalling_bytes(mtu, mode);
226 let (parsed_mtu, parsed_mode) = parse_signalling_bytes(&bytes).unwrap();
227 assert_eq!(parsed_mtu, mtu);
228 assert_eq!(parsed_mode, mode);
229 }
230
231 #[test]
232 fn test_signalling_bytes_aes128() {
233 let mtu = 1234u32;
234 let mode = LinkMode::Aes128Cbc;
235 let bytes = build_signalling_bytes(mtu, mode);
236 let (parsed_mtu, parsed_mode) = parse_signalling_bytes(&bytes).unwrap();
237 assert_eq!(parsed_mtu, mtu);
238 assert_eq!(parsed_mode, mode);
239 }
240
241 #[test]
242 fn test_signalling_bytes_max_mtu() {
243 let mtu = LINK_MTU_BYTEMASK; let mode = LinkMode::Aes256Cbc;
245 let bytes = build_signalling_bytes(mtu, mode);
246 let (parsed_mtu, parsed_mode) = parse_signalling_bytes(&bytes).unwrap();
247 assert_eq!(parsed_mtu, mtu);
248 assert_eq!(parsed_mode, mode);
249 }
250
251 #[test]
252 fn test_linkrequest_data_roundtrip() {
253 let pub_bytes = [0xAAu8; 32];
254 let sig_pub_bytes = [0xBBu8; 32];
255 let data =
256 build_linkrequest_data(&pub_bytes, &sig_pub_bytes, Some(500), LinkMode::Aes256Cbc);
257 assert_eq!(data.len(), LINK_ECPUBSIZE + LINK_MTU_SIZE);
258
259 let (p, s, mtu, mode) = parse_linkrequest_data(&data).unwrap();
260 assert_eq!(p, pub_bytes);
261 assert_eq!(s, sig_pub_bytes);
262 assert_eq!(mtu, Some(500));
263 assert_eq!(mode, LinkMode::Aes256Cbc);
264 }
265
266 #[test]
267 fn test_linkrequest_data_no_mtu() {
268 let pub_bytes = [0xAAu8; 32];
269 let sig_pub_bytes = [0xBBu8; 32];
270 let data = build_linkrequest_data(&pub_bytes, &sig_pub_bytes, None, LinkMode::Aes256Cbc);
271 assert_eq!(data.len(), LINK_ECPUBSIZE);
272
273 let (p, s, mtu, mode) = parse_linkrequest_data(&data).unwrap();
274 assert_eq!(p, pub_bytes);
275 assert_eq!(s, sig_pub_bytes);
276 assert_eq!(mtu, None);
277 assert_eq!(mode, LinkMode::Aes256Cbc); }
279
280 #[test]
281 fn test_linkrequest_data_invalid_size() {
282 let data = [0u8; 10];
283 assert_eq!(parse_linkrequest_data(&data), Err(LinkError::InvalidData));
284 }
285
286 #[test]
287 fn test_compute_link_id_no_extra() {
288 let hashable = [0x42u8; 40];
289 let id = compute_link_id(&hashable, 0);
290 assert_eq!(id.len(), 16);
291 }
292
293 #[test]
294 fn test_compute_link_id_with_extra() {
295 let hashable = [0x42u8; 43]; let id_with_extra = compute_link_id(&hashable, 3);
297 let id_base = compute_link_id(&hashable[..40], 0);
298 assert_eq!(id_with_extra, id_base);
299 }
300
301 #[test]
302 fn test_lrproof_sign_verify() {
303 let mut rng = FixedRng::new(&[0x11; 64]);
304 let sig_prv = Ed25519PrivateKey::generate(&mut rng);
305 let sig_pub = sig_prv.public_key();
306 let sig_pub_bytes = sig_pub.public_bytes();
307
308 let mut rng2 = FixedRng::new(&[0x22; 64]);
309 let x_prv = rns_crypto::x25519::X25519PrivateKey::generate(&mut rng2);
310 let pub_bytes = x_prv.public_key().public_bytes();
311
312 let link_id: LinkId = [0xAA; 16];
313 let mtu = Some(500u32);
314 let mode = LinkMode::Aes256Cbc;
315
316 let proof = build_lrproof(&link_id, &pub_bytes, &sig_pub_bytes, &sig_prv, mtu, mode);
317
318 let result = validate_lrproof(&proof, &link_id, &sig_pub, &sig_pub_bytes);
319 assert!(result.is_ok());
320 let (peer_pub, parsed_mtu, parsed_mode) = result.unwrap();
321 assert_eq!(peer_pub, pub_bytes);
322 assert_eq!(parsed_mtu, mtu);
323 assert_eq!(parsed_mode, mode);
324 }
325
326 #[test]
327 fn test_lrproof_wrong_link_id() {
328 let mut rng = FixedRng::new(&[0x11; 64]);
329 let sig_prv = Ed25519PrivateKey::generate(&mut rng);
330 let sig_pub = sig_prv.public_key();
331 let sig_pub_bytes = sig_pub.public_bytes();
332
333 let pub_bytes = [0x33u8; 32];
334 let link_id: LinkId = [0xAA; 16];
335 let wrong_id: LinkId = [0xBB; 16];
336
337 let proof = build_lrproof(
338 &link_id,
339 &pub_bytes,
340 &sig_pub_bytes,
341 &sig_prv,
342 None,
343 LinkMode::Aes256Cbc,
344 );
345 let result = validate_lrproof(&proof, &wrong_id, &sig_pub, &sig_pub_bytes);
346 assert_eq!(result, Err(LinkError::InvalidSignature));
347 }
348
349 #[test]
350 fn test_derive_session_key_aes128() {
351 let shared = [0x42u8; 32];
352 let link_id = [0xAA; 16];
353 let key = derive_session_key(&shared, &link_id, LinkMode::Aes128Cbc).unwrap();
354 assert_eq!(key.len(), 32);
355 }
356
357 #[test]
358 fn test_derive_session_key_aes256() {
359 let shared = [0x42u8; 32];
360 let link_id = [0xAA; 16];
361 let key = derive_session_key(&shared, &link_id, LinkMode::Aes256Cbc).unwrap();
362 assert_eq!(key.len(), 64);
363 }
364
365 #[test]
366 fn test_rtt_pack_unpack() {
367 let rtt = 0.123456789;
368 let packed = pack_rtt(rtt);
369 assert_eq!(packed.len(), 9);
370 assert_eq!(packed[0], 0xcb);
371 let unpacked = unpack_rtt(&packed).unwrap();
372 assert_eq!(unpacked, rtt);
373 }
374
375 #[test]
376 fn test_rtt_unpack_invalid() {
377 assert_eq!(unpack_rtt(&[0xcb, 0x00]), None);
378 assert_eq!(unpack_rtt(&[0xca, 0, 0, 0, 0, 0, 0, 0, 0]), None);
379 }
380
381 #[test]
382 fn test_rtt_pack_zero() {
383 let packed = pack_rtt(0.0);
384 let unpacked = unpack_rtt(&packed).unwrap();
385 assert_eq!(unpacked, 0.0);
386 }
387
388 #[test]
389 fn unpack_rtt_rejects_non_finite_and_negative_values() {
390 for bad in [
391 f64::NAN,
392 f64::INFINITY,
393 f64::NEG_INFINITY,
394 -1.0,
395 -f64::MIN_POSITIVE,
396 ] {
397 let mut data = vec![0xcb];
398 data.extend_from_slice(&bad.to_be_bytes());
399 assert_eq!(unpack_rtt(&data), None, "rtt {bad}");
400 }
401 assert_eq!(unpack_rtt(&pack_rtt(0.0)), Some(0.0));
402 assert_eq!(unpack_rtt(&pack_rtt(-0.0)), Some(-0.0));
403 assert_eq!(unpack_rtt(&pack_rtt(2.5)), Some(2.5));
404 }
405}