1use crate::framing::{MAX_FRAME_SIZE, read_frame, write_frame};
22use crate::transport::PeerCredentials;
23use tokio::io::{AsyncRead, AsyncWrite};
24
25pub use crate::noise_keys::{
27 ZeroizingKeypair, create_keys_dir, generate_keypair, read_bus_public_key,
28 read_bus_public_key_from, read_daemon_keypair, read_daemon_public_key,
29 set_runtime_dir_override, write_bus_keypair, write_daemon_keypair,
30};
31
32pub(crate) const NOISE_PARAMS: &str = "Noise_IK_25519_ChaChaPoly_BLAKE2s";
34
35const MAX_NOISE_PLAINTEXT: usize = 65535 - 16;
37
38const HANDSHAKE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
40
41fn build_prologue(local: &PeerCredentials, remote: &PeerCredentials) -> Vec<u8> {
51 let (first, second) = if local.pid <= remote.pid {
53 (local, remote)
54 } else {
55 (remote, local)
56 };
57 format!(
58 "PDS-IPC-v1:{}:{}:{}:{}",
59 first.pid, first.uid, second.pid, second.uid
60 )
61 .into_bytes()
62}
63
64pub struct NoiseTransport {
74 state: snow::TransportState,
75}
76
77impl NoiseTransport {
78 pub async fn write_encrypted_frame<W: AsyncWrite + Unpin>(
93 &mut self,
94 writer: &mut W,
95 payload: &[u8],
96 ) -> core_types::Result<()> {
97 if payload.len() > MAX_FRAME_SIZE as usize {
98 return Err(core_types::Error::Ipc(format!(
99 "payload size {} exceeds maximum {}",
100 payload.len(),
101 MAX_FRAME_SIZE
102 )));
103 }
104
105 let chunk_count = if payload.is_empty() {
107 1 } else {
109 payload.len().div_ceil(MAX_NOISE_PLAINTEXT)
110 };
111
112 let count_bytes = u32::try_from(chunk_count)
115 .map_err(|_| core_types::Error::Ipc("too many chunks".into()))?;
116 write_frame(writer, &count_bytes.to_be_bytes()).await?;
117
118 let mut enc_buf = vec![0u8; MAX_NOISE_PLAINTEXT + 16];
121
122 for chunk_idx in 0..chunk_count {
123 let start = chunk_idx * MAX_NOISE_PLAINTEXT;
124 let end = (start + MAX_NOISE_PLAINTEXT).min(payload.len());
125 let chunk = &payload[start..end];
126
127 let len = self
128 .state
129 .write_message(chunk, &mut enc_buf)
130 .map_err(|e| core_types::Error::Ipc(format!("Noise encrypt failed: {e}")))?;
131
132 write_frame(writer, &enc_buf[..len]).await?;
133 }
134
135 Ok(())
136 }
137
138 pub async fn read_encrypted_frame<R: AsyncRead + Unpin>(
148 &mut self,
149 reader: &mut R,
150 ) -> core_types::Result<Vec<u8>> {
151 let count_frame = read_frame(reader).await?;
153 if count_frame.len() != 4 {
154 return Err(core_types::Error::Ipc(format!(
155 "invalid chunk count header: expected 4 bytes, got {}",
156 count_frame.len()
157 )));
158 }
159 let chunk_count = u32::from_be_bytes([
160 count_frame[0],
161 count_frame[1],
162 count_frame[2],
163 count_frame[3],
164 ]) as usize;
165
166 let max_chunks = (MAX_FRAME_SIZE as usize).div_ceil(MAX_NOISE_PLAINTEXT);
168 if chunk_count > max_chunks {
169 return Err(core_types::Error::Ipc(format!(
170 "chunk count {chunk_count} exceeds maximum {max_chunks}"
171 )));
172 }
173
174 let mut payload = Vec::with_capacity(chunk_count * MAX_NOISE_PLAINTEXT);
175 let mut dec_buf = vec![0u8; MAX_NOISE_PLAINTEXT];
176
177 for _ in 0..chunk_count {
178 let ciphertext = read_frame(reader).await?;
179 let len = self
180 .state
181 .read_message(&ciphertext, &mut dec_buf)
182 .map_err(|e| core_types::Error::Ipc(format!("Noise decrypt failed: {e}")))?;
183 payload.extend_from_slice(&dec_buf[..len]);
184 }
185
186 zeroize::Zeroize::zeroize(&mut dec_buf);
188
189 if payload.len() > MAX_FRAME_SIZE as usize {
190 return Err(core_types::Error::Ipc(format!(
191 "decrypted payload {} exceeds maximum {}",
192 payload.len(),
193 MAX_FRAME_SIZE
194 )));
195 }
196
197 Ok(payload)
198 }
199
200 #[must_use]
202 pub fn is_initiator(&self) -> bool {
203 self.state.is_initiator()
204 }
205
206 #[must_use]
208 pub fn remote_static(&self) -> Option<&[u8]> {
209 self.state.get_remote_static()
210 }
211}
212
213pub async fn server_handshake<R, W>(
229 reader: &mut R,
230 writer: &mut W,
231 server_keypair: &snow::Keypair,
232 local_creds: &PeerCredentials,
233 remote_creds: &PeerCredentials,
234) -> core_types::Result<NoiseTransport>
235where
236 R: AsyncRead + Unpin,
237 W: AsyncWrite + Unpin,
238{
239 let prologue = build_prologue(local_creds, remote_creds);
240
241 let mut handshake = snow::Builder::new(
242 NOISE_PARAMS
243 .parse()
244 .map_err(|e| core_types::Error::Platform(format!("invalid Noise params: {e}")))?,
245 )
246 .local_private_key(&server_keypair.private)
247 .map_err(|e| core_types::Error::Ipc(format!("Noise builder error: {e}")))?
248 .prologue(&prologue)
249 .map_err(|e| core_types::Error::Ipc(format!("Noise prologue error: {e}")))?
250 .build_responder()
251 .map_err(|e| core_types::Error::Ipc(format!("Noise responder build failed: {e}")))?;
252
253 tokio::time::timeout(HANDSHAKE_TIMEOUT, async {
254 let msg1 = read_frame(reader).await?;
258 let mut payload_buf = vec![0u8; 65535];
259 handshake
260 .read_message(&msg1, &mut payload_buf)
261 .map_err(|e| {
262 core_types::Error::Ipc(format!("Noise handshake msg1 read failed: {e}"))
263 })?;
264
265 let mut msg2_buf = vec![0u8; 65535];
267 let msg2_len = handshake.write_message(&[], &mut msg2_buf).map_err(|e| {
268 core_types::Error::Ipc(format!("Noise handshake msg2 write failed: {e}"))
269 })?;
270 write_frame(writer, &msg2_buf[..msg2_len]).await?;
271
272 let transport = handshake
274 .into_transport_mode()
275 .map_err(|e| core_types::Error::Ipc(format!("Noise transport mode failed: {e}")))?;
276
277 tracing::info!("Noise IK handshake completed (server)");
278 Ok(NoiseTransport { state: transport })
279 })
280 .await
281 .map_err(|_| core_types::Error::Ipc("Noise handshake timed out".into()))?
282}
283
284pub async fn client_handshake<R, W>(
299 reader: &mut R,
300 writer: &mut W,
301 server_public_key: &[u8; 32],
302 client_keypair: &snow::Keypair,
303 local_creds: &PeerCredentials,
304 remote_creds: &PeerCredentials,
305) -> core_types::Result<NoiseTransport>
306where
307 R: AsyncRead + Unpin,
308 W: AsyncWrite + Unpin,
309{
310 let prologue = build_prologue(local_creds, remote_creds);
311
312 let mut handshake = snow::Builder::new(
313 NOISE_PARAMS
314 .parse()
315 .map_err(|e| core_types::Error::Platform(format!("invalid Noise params: {e}")))?,
316 )
317 .local_private_key(&client_keypair.private)
318 .map_err(|e| core_types::Error::Ipc(format!("Noise builder error: {e}")))?
319 .remote_public_key(server_public_key)
320 .map_err(|e| core_types::Error::Ipc(format!("Noise remote key error: {e}")))?
321 .prologue(&prologue)
322 .map_err(|e| core_types::Error::Ipc(format!("Noise prologue error: {e}")))?
323 .build_initiator()
324 .map_err(|e| core_types::Error::Ipc(format!("Noise initiator build failed: {e}")))?;
325
326 tokio::time::timeout(HANDSHAKE_TIMEOUT, async {
327 let mut msg1_buf = vec![0u8; 65535];
331 let msg1_len = handshake.write_message(&[], &mut msg1_buf).map_err(|e| {
332 core_types::Error::Ipc(format!("Noise handshake msg1 write failed: {e}"))
333 })?;
334 write_frame(writer, &msg1_buf[..msg1_len]).await?;
335
336 let msg2 = read_frame(reader).await?;
338 let mut payload_buf = vec![0u8; 65535];
339 handshake
340 .read_message(&msg2, &mut payload_buf)
341 .map_err(|e| {
342 core_types::Error::Ipc(format!("Noise handshake msg2 read failed: {e}"))
343 })?;
344
345 let transport = handshake
347 .into_transport_mode()
348 .map_err(|e| core_types::Error::Ipc(format!("Noise transport mode failed: {e}")))?;
349
350 tracing::info!("Noise IK handshake completed (client)");
351 Ok(NoiseTransport { state: transport })
352 })
353 .await
354 .map_err(|_| core_types::Error::Ipc("Noise handshake timed out".into()))?
355}
356
357#[cfg(test)]
358mod tests {
359 use super::*;
360
361 #[test]
362 fn prologue_canonical_ordering() {
363 let a = PeerCredentials {
364 pid: 100,
365 uid: 1000,
366 };
367 let b = PeerCredentials {
368 pid: 200,
369 uid: 1000,
370 };
371
372 assert_eq!(build_prologue(&a, &b), build_prologue(&b, &a));
374
375 let p = String::from_utf8(build_prologue(&a, &b)).unwrap();
377 assert_eq!(p, "PDS-IPC-v1:100:1000:200:1000");
378 }
379
380 #[tokio::test]
381 async fn handshake_and_transport_roundtrip() {
382 let server_kp = generate_keypair().unwrap();
384 let client_kp = generate_keypair().unwrap();
385
386 let server_pub: [u8; 32] = server_kp.public().try_into().unwrap();
387
388 let server_creds = PeerCredentials { pid: 1, uid: 1000 };
390 let client_creds = PeerCredentials { pid: 2, uid: 1000 };
391
392 let (client_stream, server_stream) = tokio::io::duplex(65536);
394
395 let (mut client_reader, mut client_writer) = tokio::io::split(client_stream);
396 let (mut server_reader, mut server_writer) = tokio::io::split(server_stream);
397
398 let (client_result, server_result) = tokio::join!(
400 client_handshake(
401 &mut client_reader,
402 &mut client_writer,
403 &server_pub,
404 client_kp.as_inner(),
405 &client_creds,
406 &server_creds,
407 ),
408 server_handshake(
409 &mut server_reader,
410 &mut server_writer,
411 server_kp.as_inner(),
412 &server_creds,
413 &client_creds,
414 ),
415 );
416
417 let mut client_transport = client_result.unwrap();
418 let mut server_transport = server_result.unwrap();
419
420 let plaintext = b"hello encrypted world";
422
423 client_transport
424 .write_encrypted_frame(&mut client_writer, plaintext)
425 .await
426 .unwrap();
427
428 let decrypted = server_transport
429 .read_encrypted_frame(&mut server_reader)
430 .await
431 .unwrap();
432
433 assert_eq!(decrypted, plaintext);
434
435 let response = b"acknowledged";
437 server_transport
438 .write_encrypted_frame(&mut server_writer, response)
439 .await
440 .unwrap();
441
442 let decrypted_response = client_transport
443 .read_encrypted_frame(&mut client_reader)
444 .await
445 .unwrap();
446
447 assert_eq!(decrypted_response, response);
448 }
449
450 #[tokio::test]
451 async fn large_frame_chunking() {
452 let server_kp = generate_keypair().unwrap();
454 let client_kp = generate_keypair().unwrap();
455 let server_pub: [u8; 32] = server_kp.public().try_into().unwrap();
456
457 let server_creds = PeerCredentials { pid: 10, uid: 1000 };
458 let client_creds = PeerCredentials { pid: 20, uid: 1000 };
459
460 let (client_stream, server_stream) = tokio::io::duplex(1024 * 1024);
461 let (mut cr, mut cw) = tokio::io::split(client_stream);
462 let (mut sr, mut sw) = tokio::io::split(server_stream);
463
464 let (mut ct, mut st) = tokio::join!(
465 async {
466 client_handshake(
467 &mut cr,
468 &mut cw,
469 &server_pub,
470 client_kp.as_inner(),
471 &client_creds,
472 &server_creds,
473 )
474 .await
475 .unwrap()
476 },
477 async {
478 server_handshake(
479 &mut sr,
480 &mut sw,
481 server_kp.as_inner(),
482 &server_creds,
483 &client_creds,
484 )
485 .await
486 .unwrap()
487 },
488 );
489
490 let large_payload = vec![0xABu8; 200 * 1024];
492
493 ct.write_encrypted_frame(&mut cw, &large_payload)
494 .await
495 .unwrap();
496 let decrypted = st.read_encrypted_frame(&mut sr).await.unwrap();
497 assert_eq!(decrypted, large_payload);
498 }
499
500 #[tokio::test]
501 async fn prologue_mismatch_fails_handshake() {
502 let server_kp = generate_keypair().unwrap();
503 let client_kp = generate_keypair().unwrap();
504 let server_pub: [u8; 32] = server_kp.public().try_into().unwrap();
505
506 let server_creds = PeerCredentials { pid: 1, uid: 1000 };
508 let client_creds_real = PeerCredentials { pid: 2, uid: 1000 };
509 let client_creds_fake = PeerCredentials { pid: 99, uid: 1000 };
510
511 let (client_stream, server_stream) = tokio::io::duplex(65536);
512 let (mut cr, mut cw) = tokio::io::split(client_stream);
513 let (mut sr, mut sw) = tokio::io::split(server_stream);
514
515 let (client_result, server_result) = tokio::join!(
516 client_handshake(
518 &mut cr,
519 &mut cw,
520 &server_pub,
521 client_kp.as_inner(),
522 &client_creds_real,
523 &server_creds,
524 ),
525 server_handshake(
527 &mut sr,
528 &mut sw,
529 server_kp.as_inner(),
530 &server_creds,
531 &client_creds_fake,
532 ),
533 );
534
535 assert!(
537 client_result.is_err() || server_result.is_err(),
538 "prologue mismatch should cause handshake failure"
539 );
540 }
541
542 #[tokio::test]
543 async fn empty_payload_roundtrip() {
544 let server_kp = generate_keypair().unwrap();
545 let client_kp = generate_keypair().unwrap();
546 let server_pub: [u8; 32] = server_kp.public().try_into().unwrap();
547
548 let sc = PeerCredentials { pid: 1, uid: 1000 };
549 let cc = PeerCredentials { pid: 2, uid: 1000 };
550
551 let (cs, ss) = tokio::io::duplex(65536);
552 let (mut cr, mut cw) = tokio::io::split(cs);
553 let (mut sr, mut sw) = tokio::io::split(ss);
554
555 let (mut ct, mut st) = tokio::join!(
556 async {
557 client_handshake(
558 &mut cr,
559 &mut cw,
560 &server_pub,
561 client_kp.as_inner(),
562 &cc,
563 &sc,
564 )
565 .await
566 .unwrap()
567 },
568 async {
569 server_handshake(&mut sr, &mut sw, server_kp.as_inner(), &sc, &cc)
570 .await
571 .unwrap()
572 },
573 );
574
575 ct.write_encrypted_frame(&mut cw, b"").await.unwrap();
576 let decrypted = st.read_encrypted_frame(&mut sr).await.unwrap();
577 assert!(decrypted.is_empty());
578 }
579}