1use chacha20poly1305::{AeadInOut, KeyInit, XChaCha20Poly1305};
20use ctutils::CtEq;
21use zeroize::{Zeroize, Zeroizing};
22
23use crate::error::{ConfigError, DecodeError, EncodeError, KeyError, KeyringError};
24use crate::limits::Limits;
25use crate::message::{Message, Payload, put_payload};
26use crate::wire::{Count, Sink};
27
28pub const WIRE_VERSION: u8 = 1;
30
31pub const MAGIC: [u8; 2] = *b"kn";
33
34pub const MAX_LABEL_LEN: usize = 255;
37
38pub const ENCRYPTED_HEADER_LEN: usize = 32;
40pub const PLAINTEXT_HEADER_LEN: usize = 12;
42pub const TAG_LEN: usize = 16;
44pub const NONCE_LEN: usize = 24;
46pub const ENCRYPTED_OVERHEAD: usize = ENCRYPTED_HEADER_LEN + TAG_LEN;
48
49const FLAG_ENCRYPTED: u8 = 0b01;
50const FLAG_STREAM: u8 = 0b10;
51const FLAGS_RESERVED: u8 = !(FLAG_ENCRYPTED | FLAG_STREAM);
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
55pub enum PacketKind {
56 Datagram,
57 Stream,
58}
59
60#[derive(Clone)]
65pub struct Key(Box<[u8; 32]>);
66
67impl PartialEq for Key {
68 fn eq(&self, other: &Self) -> bool {
71 self.0.ct_eq(&*other.0).to_bool()
72 }
73}
74
75impl Eq for Key {}
76
77impl Key {
78 pub const LEN: usize = 32;
79
80 pub fn from_bytes(mut bytes: [u8; 32]) -> Self {
82 let key = Self(Box::new(bytes));
83 bytes.zeroize();
84 key
85 }
86
87 pub fn from_slice(bytes: &[u8]) -> Option<Self> {
89 if bytes.len() != Self::LEN {
90 return None;
91 }
92 let mut key = Self(Box::new([0; 32]));
93 key.0.copy_from_slice(bytes);
94 Some(key)
95 }
96
97 pub fn from_base64(text: &str) -> Result<Self, KeyError> {
100 let text = text.trim();
101 let digits = text.trim_end_matches('=');
102 if text.len() - digits.len() > 2 {
103 return Err(KeyError::NotBase64);
104 }
105 let mut key = Self(Box::new([0; 32]));
107 let mut len = 0;
108 let mut acc = 0u32;
109 let mut bits = 0;
110 let mut result = Ok(());
111 for c in digits.bytes() {
112 let v = match c {
113 b'A'..=b'Z' => c - b'A',
114 b'a'..=b'z' => c - b'a' + 26,
115 b'0'..=b'9' => c - b'0' + 52,
116 b'+' => 62,
117 b'/' => 63,
118 _ => {
119 result = Err(KeyError::NotBase64);
120 break;
121 }
122 };
123 acc = (acc << 6) | u32::from(v);
124 bits += 6;
125 if bits >= 8 {
126 bits -= 8;
127 if len == Self::LEN {
128 result = Err(KeyError::WrongLength);
129 break;
130 }
131 key.0[len] = (acc >> bits) as u8;
132 len += 1;
133 acc &= (1 << bits) - 1;
134 }
135 }
136 if result.is_ok() && (bits >= 6 || acc != 0) {
138 result = Err(KeyError::NotBase64);
139 }
140 if result.is_ok() && len != Self::LEN {
141 result = Err(KeyError::WrongLength);
142 }
143 acc.zeroize();
144 result.map(|()| key)
145 }
146
147 pub fn to_base64(&self) -> Zeroizing<String> {
151 const DIGITS: &[u8; 64] =
152 b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
153 let mut out = Zeroizing::new(String::with_capacity(44));
154 for chunk in self.0.chunks(3) {
155 let b = [
156 chunk[0],
157 *chunk.get(1).unwrap_or(&0),
158 *chunk.get(2).unwrap_or(&0),
159 ];
160 let mut group = u32::from_be_bytes([0, b[0], b[1], b[2]]);
161 for i in 0..4 {
162 if i <= chunk.len() {
163 out.push(DIGITS[(group >> 18) as usize & 63] as char);
164 } else {
165 out.push('=');
166 }
167 group <<= 6;
168 }
169 group.zeroize();
170 }
171 out
172 }
173
174 pub fn id(&self) -> u32 {
176 let hash = blake3::hash(&self.0[..]);
177 let b = hash.as_bytes();
178 u32::from_be_bytes([b[0], b[1], b[2], b[3]])
179 }
180
181 pub fn key_id(&self) -> KeyId {
183 KeyId(self.id())
184 }
185}
186
187#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
189pub struct KeyId(u32);
190
191impl KeyId {
192 pub const fn from_raw(raw: u32) -> Self {
193 Self(raw)
194 }
195
196 pub const fn to_raw(self) -> u32 {
197 self.0
198 }
199}
200
201impl core::fmt::Display for KeyId {
202 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
203 write!(f, "{:08x}", self.0)
204 }
205}
206
207impl core::fmt::Debug for KeyId {
208 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
209 write!(f, "KeyId({:08x})", self.0)
210 }
211}
212
213impl Drop for Key {
214 fn drop(&mut self) {
215 self.0.zeroize();
216 }
217}
218
219impl core::fmt::Debug for Key {
220 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
221 write!(f, "Key({:08x})", self.id())
222 }
223}
224
225enum Security {
226 Plaintext,
227 Encrypted(Keys),
228}
229
230const DUMMY_KEY_CONTEXT: &str = "kinship 2026-10-08 key for packets with an unknown key id";
232
233struct Keys {
236 keys: Vec<Key>,
238 ids: Vec<u32>,
240 dummy: Key,
244}
245
246impl Keys {
247 fn new(keys: Vec<Key>) -> Self {
249 let (ids, dummy) = Self::derive(&keys);
250 Self { keys, ids, dummy }
251 }
252
253 fn changed(&mut self) {
255 (self.ids, self.dummy) = Self::derive(&self.keys);
256 }
257
258 fn derive(keys: &[Key]) -> (Vec<u32>, Key) {
260 let ids = keys.iter().map(Key::id).collect();
261 let mut dummy = blake3::derive_key(DUMMY_KEY_CONTEXT, &keys[0].0[..]);
262 let key = Key::from_bytes(dummy);
263 dummy.zeroize();
264 (ids, key)
265 }
266}
267
268pub struct Codec {
273 label: Vec<u8>,
274 label_hash: [u8; 8],
275 limits: Limits,
276 security: Security,
277}
278
279impl Codec {
280 pub fn encrypted(label: &[u8], limits: Limits, keys: Vec<Key>) -> Result<Self, ConfigError> {
282 if keys.is_empty() {
283 return Err(ConfigError::NoKeys);
284 }
285 Self::new(label, limits, Security::Encrypted(Keys::new(keys)))
286 }
287
288 pub fn insecure_plaintext(label: &[u8], limits: Limits) -> Result<Self, ConfigError> {
291 Self::new(label, limits, Security::Plaintext)
292 }
293
294 fn new(label: &[u8], limits: Limits, security: Security) -> Result<Self, ConfigError> {
295 if label.len() > MAX_LABEL_LEN {
296 return Err(ConfigError::LabelTooLong);
297 }
298 let hash = blake3::hash(label);
299 let mut label_hash = [0u8; 8];
300 label_hash.copy_from_slice(&hash.as_bytes()[..8]);
301 let codec = Self {
302 label: label.to_vec(),
303 label_hash,
304 limits,
305 security,
306 };
307 let floor = codec.overhead() + 3;
309 if limits.udp_max_payload < floor || limits.max_stream_frame < floor {
310 return Err(ConfigError::LimitTooSmall);
311 }
312 Ok(codec)
313 }
314
315 pub fn set_keys(&mut self, keys: Vec<Key>) -> Result<(), ConfigError> {
317 if keys.is_empty() {
318 return Err(ConfigError::NoKeys);
319 }
320 self.security = Security::Encrypted(Keys::new(keys));
321 Ok(())
322 }
323
324 pub fn is_encrypted(&self) -> bool {
325 matches!(self.security, Security::Encrypted(_))
326 }
327
328 pub fn limits(&self) -> &Limits {
329 &self.limits
330 }
331
332 pub fn key_ids(&self) -> Vec<KeyId> {
334 match &self.security {
335 Security::Plaintext => Vec::new(),
336 Security::Encrypted(keys) => keys.ids.iter().map(|&id| KeyId(id)).collect(),
337 }
338 }
339
340 pub fn header_check(&self) -> HeaderCheck {
343 let mut label = [0; 4];
344 label.copy_from_slice(&self.label_hash[..4]);
345 HeaderCheck {
346 key_ids: match &self.security {
347 Security::Plaintext => None,
348 Security::Encrypted(keys) => Some(keys.ids.clone()),
349 },
350 label,
351 }
352 }
353
354 pub fn install_key(&mut self, key: Key) -> Result<(), KeyringError> {
357 let keys = self.keys_mut()?;
358 if !keys.keys.contains(&key) {
359 keys.keys.push(key);
360 keys.changed();
361 }
362 Ok(())
363 }
364
365 pub fn use_key(&mut self, key: &Key) -> Result<(), KeyringError> {
367 let keys = self.keys_mut()?;
368 let at = keys
369 .keys
370 .iter()
371 .position(|k| k == key)
372 .ok_or(KeyringError::NotInstalled)?;
373 keys.keys[..=at].rotate_right(1);
374 keys.changed();
375 Ok(())
376 }
377
378 pub fn remove_key(&mut self, key: &Key) -> Result<(), KeyringError> {
381 let keys = self.keys_mut()?;
382 match keys.keys.iter().position(|k| k == key) {
383 None => Ok(()),
384 Some(_) if keys.keys.len() == 1 => Err(KeyringError::LastKey),
385 Some(0) => Err(KeyringError::InUse),
386 Some(at) => {
387 keys.keys.remove(at);
388 keys.changed();
389 Ok(())
390 }
391 }
392 }
393
394 fn keys_mut(&mut self) -> Result<&mut Keys, KeyringError> {
395 match &mut self.security {
396 Security::Plaintext => Err(KeyringError::Plaintext),
397 Security::Encrypted(keys) => Ok(keys),
398 }
399 }
400
401 pub fn overhead(&self) -> usize {
403 match self.security {
404 Security::Plaintext => PLAINTEXT_HEADER_LEN,
405 Security::Encrypted(_) => ENCRYPTED_OVERHEAD,
406 }
407 }
408
409 pub fn max_payload_len(&self, kind: PacketKind) -> usize {
411 self.limit(kind) - self.overhead()
412 }
413
414 fn limit(&self, kind: PacketKind) -> usize {
415 match kind {
416 PacketKind::Datagram => self.limits.udp_max_payload,
417 PacketKind::Stream => self.limits.max_stream_frame,
418 }
419 }
420
421 pub fn seal(
427 &self,
428 kind: PacketKind,
429 msgs: &[Message<'_>],
430 nonce: &[u8; NONCE_LEN],
431 out: &mut Vec<u8>,
432 ) -> Result<(), EncodeError> {
433 if msgs.is_empty() {
434 return Err(EncodeError::Empty);
435 }
436 for m in msgs {
437 m.validate(&self.limits)?;
438 }
439 let mut payload = Count::default();
440 put_payload(&mut payload, msgs);
441 self.seal_with(kind, nonce, payload.0, out, |out| put_payload(out, msgs))
442 }
443
444 pub fn seal_stream_frame(
447 &self,
448 msgs: &[Message<'_>],
449 nonce: &[u8; NONCE_LEN],
450 out: &mut Vec<u8>,
451 ) -> Result<(), EncodeError> {
452 let start = out.len();
453 out.put(&[0; 4]);
454 if let Err(e) = self.seal(PacketKind::Stream, msgs, nonce, out) {
455 out.truncate(start);
456 return Err(e);
457 }
458 let len = (out.len() - start - 4) as u32;
459 out[start..start + 4].copy_from_slice(&len.to_be_bytes());
460 Ok(())
461 }
462
463 #[doc(hidden)]
466 pub fn seal_raw(
467 &self,
468 kind: PacketKind,
469 inner: &[u8],
470 nonce: &[u8; NONCE_LEN],
471 out: &mut Vec<u8>,
472 ) -> Result<(), EncodeError> {
473 self.seal_with(kind, nonce, inner.len(), out, |out| out.put(inner))
474 }
475
476 fn seal_with(
477 &self,
478 kind: PacketKind,
479 nonce: &[u8; NONCE_LEN],
480 payload_len: usize,
481 out: &mut Vec<u8>,
482 write_payload: impl FnOnce(&mut Vec<u8>),
483 ) -> Result<(), EncodeError> {
484 if payload_len > self.max_payload_len(kind) {
485 return Err(EncodeError::TooLarge);
486 }
487 let start = out.len();
488 let mut flags = if kind == PacketKind::Stream {
489 FLAG_STREAM
490 } else {
491 0
492 };
493 out.put(&MAGIC);
494 out.put_u8(WIRE_VERSION);
495 match &self.security {
496 Security::Plaintext => {
497 out.put_u8(flags);
498 out.put(&self.label_hash);
499 write_payload(out);
500 }
501 Security::Encrypted(keys) => {
502 let key = &keys.keys[0];
503 flags |= FLAG_ENCRYPTED;
504 out.put_u8(flags);
505 out.put(&keys.ids[0].to_be_bytes());
506 out.put(nonce);
507 write_payload(out);
508 let aad = Aad::new(&out[start..start + ENCRYPTED_HEADER_LEN], &self.label);
509 let cipher = XChaCha20Poly1305::new((&*key.0).into());
510 let body = &mut out[start + ENCRYPTED_HEADER_LEN..];
511 let tag = cipher
512 .encrypt_inout_detached(nonce.into(), aad.as_slice(), (&mut *body).into())
513 .map_err(|_| EncodeError::TooLarge);
515 match tag {
516 Ok(tag) => out.put(tag.as_slice()),
517 Err(e) => {
518 out.truncate(start);
519 return Err(e);
520 }
521 }
522 }
523 }
524 debug_assert_eq!(out.len() - start, payload_len + self.overhead());
525 Ok(())
526 }
527
528 pub fn open<'a>(
536 &self,
537 kind: PacketKind,
538 buf: &'a mut [u8],
539 ) -> Result<Payload<'a>, DecodeError> {
540 if buf.len() > self.limit(kind) {
541 return Err(DecodeError::TooLarge);
542 }
543 let encrypted = check_start(kind, buf)?;
544 let range = match (&self.security, encrypted) {
545 (Security::Plaintext, false) => {
546 let hash = buf
547 .get(4..PLAINTEXT_HEADER_LEN)
548 .ok_or(DecodeError::Truncated)?;
549 if hash != self.label_hash {
550 return Err(DecodeError::WrongCluster);
551 }
552 PLAINTEXT_HEADER_LEN..buf.len()
553 }
554 (Security::Encrypted(keys), true) => {
555 if buf.len() < ENCRYPTED_OVERHEAD {
556 return Err(DecodeError::Truncated);
557 }
558 let key_id = u32::from_be_bytes([buf[4], buf[5], buf[6], buf[7]]);
559 let nonce: [u8; NONCE_LEN] = buf[8..ENCRYPTED_HEADER_LEN]
560 .try_into()
561 .map_err(|_| DecodeError::Truncated)?;
562 let aad = Aad::new(&buf[..ENCRYPTED_HEADER_LEN], &self.label);
563 let tag_at = buf.len() - TAG_LEN;
564 let tag: [u8; TAG_LEN] = buf[tag_at..]
565 .try_into()
566 .map_err(|_| DecodeError::Truncated)?;
567 let body = &mut buf[ENCRYPTED_HEADER_LEN..tag_at];
568 let mut matched = false;
569 let mut opened = false;
570 let installed = keys.keys.iter().zip(&keys.ids);
571 for (key, _) in installed.filter(|&(_, &id)| id == key_id) {
572 matched = true;
573 let cipher = XChaCha20Poly1305::new((&*key.0).into());
574 if cipher
575 .decrypt_inout_detached(
576 (&nonce).into(),
577 aad.as_slice(),
578 (&mut *body).into(),
579 (&tag).into(),
580 )
581 .is_ok()
582 {
583 opened = true;
584 break;
585 }
586 }
587 if !matched {
588 let cipher = XChaCha20Poly1305::new((&*keys.dummy.0).into());
594 let _ = cipher.decrypt_inout_detached(
595 (&nonce).into(),
596 aad.as_slice(),
597 (&mut *body).into(),
598 (&tag).into(),
599 );
600 return Err(DecodeError::UnknownKey(key_id));
601 }
602 if !opened {
603 return Err(DecodeError::AuthFailed);
604 }
605 ENCRYPTED_HEADER_LEN..tag_at
606 }
607 _ => return Err(DecodeError::EncryptionMismatch),
608 };
609 Payload::parse(&buf[range], &self.limits)
610 }
611}
612
613fn check_start(kind: PacketKind, buf: &[u8]) -> Result<bool, DecodeError> {
616 let [m0, m1, version, flags, ..] = *buf else {
617 return Err(DecodeError::Truncated);
618 };
619 if [m0, m1] != MAGIC {
620 return Err(DecodeError::BadMagic);
621 }
622 if version == 0 || version > WIRE_VERSION {
623 return Err(DecodeError::UnsupportedVersion(version));
624 }
625 if flags & FLAGS_RESERVED != 0 {
626 return Err(DecodeError::ReservedFlags);
627 }
628 if (flags & FLAG_STREAM != 0) != (kind == PacketKind::Stream) {
629 return Err(DecodeError::WrongPacketKind);
630 }
631 Ok(flags & FLAG_ENCRYPTED != 0)
632}
633
634#[derive(Debug, Clone, PartialEq, Eq)]
644pub struct HeaderCheck {
645 key_ids: Option<Vec<u32>>,
647 label: [u8; 4],
649}
650
651impl HeaderCheck {
652 pub const LEN: usize = 8;
654
655 pub fn check(&self, kind: PacketKind, head: &[u8]) -> Result<(), DecodeError> {
658 let head = head.get(..Self::LEN).ok_or(DecodeError::Truncated)?;
659 let encrypted = check_start(kind, head)?;
660 let id = [head[4], head[5], head[6], head[7]];
661 match &self.key_ids {
662 None if encrypted => Err(DecodeError::EncryptionMismatch),
663 None if id != self.label => Err(DecodeError::WrongCluster),
664 Some(_) if !encrypted => Err(DecodeError::EncryptionMismatch),
665 Some(ids) if !ids.contains(&u32::from_be_bytes(id)) => {
666 Err(DecodeError::UnknownKey(u32::from_be_bytes(id)))
667 }
668 _ => Ok(()),
669 }
670 }
671}
672
673pub fn sealed_nonce(buf: &[u8]) -> Option<[u8; NONCE_LEN]> {
679 if buf.len() < ENCRYPTED_OVERHEAD || buf[..2] != MAGIC || buf[3] & FLAG_ENCRYPTED == 0 {
680 return None;
681 }
682 buf[8..ENCRYPTED_HEADER_LEN].try_into().ok()
683}
684
685struct Aad {
687 buf: [u8; ENCRYPTED_HEADER_LEN + MAX_LABEL_LEN],
688 len: usize,
689}
690
691impl Aad {
692 fn new(header: &[u8], label: &[u8]) -> Self {
693 let mut buf = [0u8; ENCRYPTED_HEADER_LEN + MAX_LABEL_LEN];
694 let len = header.len() + label.len();
695 buf[..header.len()].copy_from_slice(header);
696 buf[header.len()..len].copy_from_slice(label);
697 Self { buf, len }
698 }
699
700 fn as_slice(&self) -> &[u8] {
701 &self.buf[..self.len]
702 }
703}
704
705#[cfg(test)]
706mod tests {
707 use super::*;
708
709 fn key(n: u8) -> Key {
710 Key::from_bytes([n; 32])
711 }
712
713 fn keys(c: &Codec) -> &Keys {
714 match &c.security {
715 Security::Encrypted(keys) => keys,
716 Security::Plaintext => unreachable!("an encrypting codec"),
717 }
718 }
719
720 #[test]
721 fn keys_are_equal_only_when_every_byte_is() {
722 let a = key(7);
723 assert!(a == key(7));
724 for at in [0, 1, 16, 31] {
725 let mut bytes = [7; 32];
726 bytes[at] ^= 0x80;
727 assert!(a != Key::from_bytes(bytes), "byte {at}");
728 }
729 }
730
731 #[test]
732 fn key_bytes_stay_where_they_are_when_keys_move() {
733 let first = key(1);
734 let at = first.0.as_ptr();
735 let mut keys = vec![first];
736 keys.extend((2..40).map(key));
738 let moved = keys.swap_remove(0);
739 assert_eq!(moved.0.as_ptr(), at);
740 let text: Zeroizing<String> = moved.to_base64();
741 assert_eq!(
742 text.capacity(),
743 text.len(),
744 "written into its final allocation"
745 );
746 }
747
748 #[test]
749 fn ids_and_the_dummy_key_follow_every_change_of_keys() {
750 let mut c = Codec::encrypted(b"c", Limits::default(), vec![key(1)]).unwrap();
751 let first = keys(&c).dummy.clone();
752 assert_eq!(keys(&c).ids, [key(1).id()]);
753 c.install_key(key(2)).unwrap();
754 assert_eq!(keys(&c).ids, [key(1).id(), key(2).id()]);
755 assert!(keys(&c).dummy == first, "the sealing key did not change");
756 c.use_key(&key(2)).unwrap();
757 assert_eq!(keys(&c).ids, [key(2).id(), key(1).id()]);
758 assert!(keys(&c).dummy != first, "derived from the new sealing key");
759 c.remove_key(&key(1)).unwrap();
760 assert_eq!(keys(&c).ids, [key(2).id()]);
761 c.set_keys(vec![key(3)]).unwrap();
762 assert_eq!(keys(&c).ids, [key(3).id()]);
763 assert_eq!(c.key_ids(), [key(3).key_id()]);
764 }
765
766 #[test]
767 fn a_packet_with_an_unknown_key_id_is_refused_even_when_the_dummy_key_opens_it() {
768 let c = Codec::encrypted(b"c", Limits::default(), vec![key(1)]).unwrap();
769 let dummy = keys(&c).dummy.clone();
770 let forger = Codec::encrypted(b"c", Limits::default(), vec![dummy]).unwrap();
771 let mut pkt = Vec::new();
772 let ack = Message::Ack { seq: 1 };
773 forger
774 .seal(PacketKind::Datagram, &[ack], &[0; 24], &mut pkt)
775 .unwrap();
776 let id = keys(&forger).ids[0];
777 assert!(!keys(&c).ids.contains(&id));
778 let opened = c.open(PacketKind::Datagram, &mut pkt).map(drop);
779 assert_eq!(opened, Err(DecodeError::UnknownKey(id)));
780 }
781}