1#![allow(async_fn_in_trait)]
2#![cfg_attr(not(feature = "std"), no_std)]
3
4use core::ops::Range;
28
29use umsh_core::{
30 ChannelId, ChannelKey, ChannelTag, PacketHeader, PacketType, PublicKey, SourceAddrRef,
31 UnsealedPacket, feed_aad,
32};
33use zeroize::{Zeroize, ZeroizeOnDrop};
34
35pub mod pool;
36pub mod replay;
37
38pub trait AesCipher {
40 fn encrypt_block(&self, block: &mut [u8; 16]);
42 fn decrypt_block(&self, block: &mut [u8; 16]);
44}
45
46impl<C: AesCipher + ?Sized> AesCipher for &C {
47 fn encrypt_block(&self, block: &mut [u8; 16]) {
48 (**self).encrypt_block(block);
49 }
50
51 fn decrypt_block(&self, block: &mut [u8; 16]) {
52 (**self).decrypt_block(block);
53 }
54}
55
56pub trait AesProvider {
58 type Cipher: AesCipher;
60
61 fn new_cipher(&self, key: &[u8; 32]) -> Self::Cipher;
63}
64
65pub trait Sha256Provider {
67 fn hash(&self, data: &[&[u8]]) -> [u8; 32];
69 fn hmac(&self, key: &[u8], data: &[&[u8]]) -> [u8; 32];
71}
72
73#[derive(Clone, Zeroize, ZeroizeOnDrop)]
75pub struct SharedSecret(pub [u8; 32]);
76
77pub trait NodeIdentity {
79 type Error;
80
81 fn public_key(&self) -> &PublicKey;
83
84 fn hint(&self) -> umsh_core::NodeHint {
86 self.public_key().hint()
87 }
88
89 async fn sign(&self, message: &[u8]) -> Result<[u8; 64], Self::Error>;
91 async fn agree(&self, peer: &PublicKey) -> Result<SharedSecret, Self::Error>;
93}
94
95#[derive(Clone, Debug, PartialEq, Eq)]
97pub enum CryptoError {
98 InvalidPublicKey,
99 InvalidSharedSecret,
100 InvalidPacket,
101 AuthenticationFailed,
102}
103
104pub const MAX_CHANNEL_NAME_LEN: usize = 64;
111
112#[derive(Clone, Copy, Debug, PartialEq, Eq)]
115pub enum ChannelNameError {
116 NotAscii,
119 TooLong,
121}
122
123#[derive(Clone, Zeroize, ZeroizeOnDrop)]
125pub struct PairwiseKeys {
126 pub k_enc: [u8; 32],
127 pub k_mic: [u8; 32],
128}
129
130#[derive(Clone)]
132pub struct DerivedChannelKeys {
133 pub k_enc: [u8; 32],
134 pub k_mic: [u8; 32],
135 pub channel_id: ChannelId,
136}
137
138impl Zeroize for DerivedChannelKeys {
139 fn zeroize(&mut self) {
140 self.k_enc.zeroize();
141 self.k_mic.zeroize();
142 }
143}
144
145impl Drop for DerivedChannelKeys {
146 fn drop(&mut self) {
147 self.zeroize();
148 }
149}
150
151pub struct CmacState<C: AesCipher> {
153 cipher: C,
154 state: [u8; 16],
155 buffer: [u8; 16],
156 pos: usize,
157 k1: [u8; 16],
158 k2: [u8; 16],
159}
160
161impl<C: AesCipher> CmacState<C> {
162 pub fn new(cipher: C) -> Self {
164 let mut l = [0u8; 16];
165 cipher.encrypt_block(&mut l);
166 let k1 = dbl(&l);
167 let k2 = dbl(&k1);
168 Self {
169 cipher,
170 state: [0u8; 16],
171 buffer: [0u8; 16],
172 pos: 0,
173 k1,
174 k2,
175 }
176 }
177
178 pub fn update(&mut self, mut data: &[u8]) {
180 while !data.is_empty() {
181 let space = 16 - self.pos;
182 let take = space.min(data.len());
183 self.buffer[self.pos..self.pos + take].copy_from_slice(&data[..take]);
184 self.pos += take;
185 data = &data[take..];
186 if self.pos == 16 && !data.is_empty() {
187 self.process_buffer();
188 }
189 }
190 }
191
192 pub fn finalize(self) -> [u8; 16] {
194 let this = self;
195 let mut last = [0u8; 16];
196 if this.pos == 16 {
197 last.copy_from_slice(&this.buffer);
198 xor_in_place(&mut last, &this.k1);
199 } else {
200 last[..this.pos].copy_from_slice(&this.buffer[..this.pos]);
201 last[this.pos] = 0x80;
202 xor_in_place(&mut last, &this.k2);
203 }
204
205 xor_in_place(&mut last, &this.state);
206 this.cipher.encrypt_block(&mut last);
207 last
208 }
209
210 fn process_buffer(&mut self) {
211 let mut block = self.buffer;
212 xor_in_place(&mut block, &self.state);
213 self.cipher.encrypt_block(&mut block);
214 self.state = block;
215 self.buffer = [0u8; 16];
216 self.pos = 0;
217 }
218}
219
220pub struct S2vState<'a, C: AesCipher> {
228 cipher: &'a C,
229 d: [u8; 16],
230}
231
232impl<'a, C: AesCipher> S2vState<'a, C> {
233 pub fn new(cipher: &'a C) -> Self {
235 let mut cmac = CmacState::new(cipher);
236 cmac.update(&[0u8; 16]);
237 Self {
238 cipher,
239 d: cmac.finalize(),
240 }
241 }
242
243 pub fn push_component(&mut self, feed: impl FnOnce(&mut CmacState<&'a C>)) {
246 let mut cmac = CmacState::new(self.cipher);
247 feed(&mut cmac);
248 let mac = cmac.finalize();
249 self.d = dbl(&self.d);
250 xor_in_place(&mut self.d, &mac);
251 }
252
253 pub fn finalize(self, last: &[u8]) -> [u8; 16] {
258 let mut cmac = CmacState::new(self.cipher);
259 if last.len() >= 16 {
260 let split = last.len() - 16;
261 cmac.update(&last[..split]);
262 let mut tail = [0u8; 16];
263 tail.copy_from_slice(&last[split..]);
264 xor_in_place(&mut tail, &self.d);
265 cmac.update(&tail);
266 } else {
267 let mut block = dbl(&self.d);
268 for (dst, byte) in block.iter_mut().zip(last) {
269 *dst ^= *byte;
270 }
271 block[last.len()] ^= 0x80;
272 cmac.update(&block);
273 }
274 cmac.finalize()
275 }
276}
277
278pub struct CryptoEngine<A: AesProvider, S: Sha256Provider> {
280 aes: A,
281 sha: S,
282}
283
284impl<A: AesProvider, S: Sha256Provider> CryptoEngine<A, S> {
285 pub fn new(aes: A, sha: S) -> Self {
287 Self { aes, sha }
288 }
289
290 pub fn derive_pairwise_keys(&self, shared_secret: &SharedSecret) -> PairwiseKeys {
296 let mut okm = [0u8; 64];
297 self.hkdf(
298 &shared_secret.0,
299 b"UMSH-PAIRWISE-SALT",
300 b"UMSH-UNICAST-V2",
301 &mut okm,
302 );
303 let mut keys = PairwiseKeys {
304 k_enc: [0u8; 32],
305 k_mic: [0u8; 32],
306 };
307 keys.k_mic.copy_from_slice(&okm[..32]);
308 keys.k_enc.copy_from_slice(&okm[32..64]);
309 okm.zeroize();
310 keys
311 }
312
313 pub fn derive_channel_id(&self, channel_key: &ChannelKey) -> ChannelId {
315 let mut out = [0u8; 2];
316 self.hkdf(&channel_key.0, b"UMSH-CHAN-ID", b"", &mut out);
317 ChannelId(out)
318 }
319
320 pub fn derive_channel_tint(&self, channel_key: &ChannelKey) -> [u8; 3] {
328 let mut out = [0u8; 3];
329 self.hkdf(&channel_key.0, b"UMSH-CHAN-ID", b"", &mut out);
330 out
331 }
332
333 pub fn derive_channel_tag(&self, channel_key: &ChannelKey) -> ChannelTag {
339 let mut out = [0u8; 16];
340 self.hkdf(&channel_key.0, b"UMSH-CHAN-ID", b"", &mut out);
341 ChannelTag(out)
342 }
343
344 pub fn derive_channel_keys(&self, channel_key: &ChannelKey) -> DerivedChannelKeys {
350 let channel_id = self.derive_channel_id(channel_key);
351 let mut info = [0u8; 15];
352 info[..13].copy_from_slice(b"UMSH-MCAST-V2");
353 info[13..15].copy_from_slice(&channel_id.0);
354 let mut okm = [0u8; 64];
355 self.hkdf(&channel_key.0, b"UMSH-MCAST-SALT", &info, &mut okm);
356 let mut derived = DerivedChannelKeys {
357 k_enc: [0u8; 32],
358 k_mic: [0u8; 32],
359 channel_id,
360 };
361 derived.k_mic.copy_from_slice(&okm[..32]);
362 derived.k_enc.copy_from_slice(&okm[32..64]);
363 okm.zeroize();
364 derived
365 }
366
367 pub fn derive_blind_keys(
369 &self,
370 pairwise: &PairwiseKeys,
371 channel: &DerivedChannelKeys,
372 ) -> PairwiseKeys {
373 let mut keys = PairwiseKeys {
374 k_enc: [0u8; 32],
375 k_mic: [0u8; 32],
376 };
377 for (dst, (left, right)) in keys
378 .k_enc
379 .iter_mut()
380 .zip(pairwise.k_enc.iter().zip(channel.k_enc.iter()))
381 {
382 *dst = left ^ right;
383 }
384 for (dst, (left, right)) in keys
385 .k_mic
386 .iter_mut()
387 .zip(pairwise.k_mic.iter().zip(channel.k_mic.iter()))
388 {
389 *dst = left ^ right;
390 }
391 keys
392 }
393
394 pub fn derive_named_channel_key(&self, name: &str) -> Result<ChannelKey, ChannelNameError> {
404 let bytes = name.as_bytes();
405 if bytes.len() > MAX_CHANNEL_NAME_LEN {
406 return Err(ChannelNameError::TooLong);
407 }
408 if !name.is_ascii() {
409 return Err(ChannelNameError::NotAscii);
410 }
411 let mut canonical = [0u8; MAX_CHANNEL_NAME_LEN];
412 for (dst, byte) in canonical.iter_mut().zip(bytes) {
413 *dst = byte.to_ascii_lowercase();
414 }
415 Ok(ChannelKey(
416 self.sha
417 .hmac(b"UMSH-CHANNEL-V1", &[&canonical[..bytes.len()]]),
418 ))
419 }
420
421 pub fn seal_packet(
423 &self,
424 packet: &mut UnsealedPacket<'_>,
425 keys: &PairwiseKeys,
426 ) -> Result<usize, CryptoError> {
427 let header = packet.header().map_err(|_| CryptoError::InvalidPacket)?;
428 let sec_info = header.sec_info.ok_or(CryptoError::InvalidPacket)?;
429 let full_mac = {
430 let bytes = packet.as_bytes();
431 self.s2v_tag(
432 &keys.k_mic,
433 |cmac| feed_aad(&header, bytes, |chunk| cmac.update(chunk)),
434 packet.body(),
435 )
436 };
437
438 let mic_len = sec_info
439 .scf
440 .mic_size()
441 .map_err(|_| CryptoError::InvalidPacket)?
442 .byte_len();
443 packet.mic_slot()[..mic_len].copy_from_slice(&full_mac[..mic_len]);
444
445 if sec_info.scf.encrypted() {
446 let iv = self.build_ctr_iv(
447 &full_mac[..mic_len],
448 &packet.as_bytes()[packet.sec_info_range()],
449 );
450 self.aes_ctr(&keys.k_enc, &iv, packet.body_mut());
451 }
452
453 Ok(mic_len)
454 }
455
456 pub fn seal_blind_packet(
458 &self,
459 packet: &mut UnsealedPacket<'_>,
460 blind_keys: &PairwiseKeys,
461 channel_keys: &DerivedChannelKeys,
462 ) -> Result<usize, CryptoError> {
463 let header = packet.header().map_err(|_| CryptoError::InvalidPacket)?;
464 match header.packet_type() {
465 PacketType::BlindUnicast | PacketType::BlindUnicastAckReq => {}
466 _ => return Err(CryptoError::InvalidPacket),
467 }
468
469 let sec_info = header.sec_info.ok_or(CryptoError::InvalidPacket)?;
470 let blind_addr_range = packet
471 .blind_addr_range()
472 .ok_or(CryptoError::InvalidPacket)?;
473 let full_mac = {
474 let bytes = packet.as_bytes();
475 self.s2v_tag(
476 &blind_keys.k_mic,
477 |cmac| feed_aad(&header, bytes, |chunk| cmac.update(chunk)),
478 packet.body(),
479 )
480 };
481
482 let mic_len = sec_info
483 .scf
484 .mic_size()
485 .map_err(|_| CryptoError::InvalidPacket)?
486 .byte_len();
487 let iv = self.build_ctr_iv(
488 &full_mac[..mic_len],
489 &packet.as_bytes()[packet.sec_info_range()],
490 );
491 packet.mic_slot()[..mic_len].copy_from_slice(&full_mac[..mic_len]);
492 if sec_info.scf.encrypted() {
493 self.aes_ctr(&blind_keys.k_enc, &iv, packet.body_mut());
494 self.aes_ctr(
495 &channel_keys.k_enc,
496 &iv,
497 &mut packet.as_bytes_mut()[blind_addr_range],
498 );
499 }
500 Ok(mic_len)
501 }
502
503 pub fn open_packet(
505 &self,
506 buf: &mut [u8],
507 header: &PacketHeader,
508 keys: &PairwiseKeys,
509 ) -> Result<Range<usize>, CryptoError> {
510 let sec_info = header.sec_info.ok_or(CryptoError::InvalidPacket)?;
511 let mut mic = [0u8; 16];
512 let mic_len = header.mic_range.end - header.mic_range.start;
513 mic[..mic_len].copy_from_slice(&buf[header.mic_range.clone()]);
514 if sec_info.scf.encrypted() {
515 let iv = self.build_ctr_iv(&mic[..mic_len], &buf[sec_info_bytes_range(header)?]);
516 self.aes_ctr(&keys.k_enc, &iv, &mut buf[header.body_range.clone()]);
517 }
518
519 let full_mac = self.s2v_tag(
520 &keys.k_mic,
521 |cmac| feed_aad(header, buf, |chunk| cmac.update(chunk)),
522 &buf[header.body_range.clone()],
523 );
524 if !constant_time_eq(&mic[..mic_len], &full_mac[..mic_len]) {
525 return Err(CryptoError::AuthenticationFailed);
526 }
527
528 let body_range = match (header.packet_type(), header.source) {
529 (PacketType::Multicast, SourceAddrRef::Encrypted { len, .. }) => {
530 (header.body_range.start + len)..header.body_range.end
531 }
532 _ => header.body_range.clone(),
533 };
534 Ok(body_range)
535 }
536
537 pub fn decrypt_blind_addr(
539 &self,
540 buf: &mut [u8],
541 header: &PacketHeader,
542 channel_keys: &DerivedChannelKeys,
543 ) -> Result<(umsh_core::NodeHint, SourceAddrRef), CryptoError> {
544 match header.source {
545 SourceAddrRef::Encrypted { offset, len } => {
546 let addr_start = offset.checked_sub(3).ok_or(CryptoError::InvalidPacket)?;
547 let addr_end = addr_start + 3 + len;
548 let iv = self.build_ctr_iv(
549 &buf[header.mic_range.clone()],
550 &buf[sec_info_bytes_range(header)?],
551 );
552 self.aes_ctr(&channel_keys.k_enc, &iv, &mut buf[addr_start..addr_end]);
553 let dst = umsh_core::NodeHint([
554 buf[addr_start],
555 buf[addr_start + 1],
556 buf[addr_start + 2],
557 ]);
558 let source = if len == 3 {
559 SourceAddrRef::Hint(umsh_core::NodeHint([
560 buf[addr_start + 3],
561 buf[addr_start + 4],
562 buf[addr_start + 5],
563 ]))
564 } else if len == 32 {
565 SourceAddrRef::FullKeyAt {
566 offset: addr_start + 3,
567 }
568 } else {
569 return Err(CryptoError::InvalidPacket);
570 };
571 Ok((dst, source))
572 }
573 SourceAddrRef::Hint(hint) => {
574 let dst = header.dst.ok_or(CryptoError::InvalidPacket)?;
575 Ok((dst, SourceAddrRef::Hint(hint)))
576 }
577 SourceAddrRef::FullKeyAt { offset } => {
578 let dst = header.dst.ok_or(CryptoError::InvalidPacket)?;
579 let _ = buf
580 .get(offset..offset + 32)
581 .ok_or(CryptoError::InvalidPacket)?;
582 Ok((dst, SourceAddrRef::FullKeyAt { offset }))
583 }
584 SourceAddrRef::None => Err(CryptoError::InvalidPacket),
585 }
586 }
587
588 pub fn s2v_tag(
593 &self,
594 k_mic: &[u8; 32],
595 aad: impl FnOnce(&mut CmacState<&A::Cipher>),
596 plaintext: &[u8],
597 ) -> [u8; 16] {
598 let cipher = self.aes.new_cipher(k_mic);
599 let mut s2v = S2vState::new(&cipher);
600 s2v.push_component(aad);
601 s2v.finalize(plaintext)
602 }
603
604 pub fn compute_ack_trailer(&self, full_tag: &[u8; 16], k_enc: &[u8; 32]) -> [u8; 8] {
620 let cipher = self.aes.new_cipher(k_enc);
621 let mut block = *full_tag;
622 cipher.encrypt_block(&mut block);
623 let mut trailer = [0u8; 8];
624 trailer[..4].copy_from_slice(&full_tag[..4]); trailer[4..].copy_from_slice(&block[..4]); trailer
627 }
628
629 pub fn cmac_state(&self, key: &[u8; 32]) -> CmacState<A::Cipher> {
631 CmacState::new(self.aes.new_cipher(key))
632 }
633
634 pub fn aes_cmac(&self, key: &[u8; 32], data: &[&[u8]]) -> [u8; 16] {
636 let mut state = self.cmac_state(key);
637 for chunk in data {
638 state.update(chunk);
639 }
640 state.finalize()
641 }
642
643 pub fn aes_ctr(&self, key: &[u8; 32], iv: &[u8; 16], data: &mut [u8]) {
645 aes_ctr_with_cipher(&self.aes.new_cipher(key), iv, data);
646 }
647
648 pub fn build_ctr_iv(&self, mic: &[u8], sec_info_bytes: &[u8]) -> [u8; 16] {
657 let mut iv = [0u8; 16];
658 let mut written = 0usize;
659 for byte in mic.iter().chain(sec_info_bytes.iter()).take(16) {
660 iv[written] = *byte;
661 written += 1;
662 }
663 iv[8] &= 0x7F;
664 iv[12] &= 0x7F;
665 iv
666 }
667
668 pub fn hkdf(&self, ikm: &[u8], salt: &[u8], info: &[u8], okm: &mut [u8]) {
670 hkdf_sha256(&self.sha, ikm, salt, info, okm);
671 }
672}
673
674pub fn hkdf_sha256<S: Sha256Provider>(
678 sha: &S,
679 ikm: &[u8],
680 salt: &[u8],
681 info: &[u8],
682 okm: &mut [u8],
683) {
684 let prk = sha.hmac(salt, &[ikm]);
685 let mut previous = [0u8; 32];
686 let mut previous_len = 0usize;
687 let mut written = 0usize;
688 let mut counter = 1u8;
689 while written < okm.len() {
690 let next = sha.hmac(&prk, &[&previous[..previous_len], info, &[counter]]);
691 let take = (okm.len() - written).min(next.len());
692 okm[written..written + take].copy_from_slice(&next[..take]);
693 previous = next;
694 previous_len = 32;
695 written += take;
696 counter = counter.wrapping_add(1);
697 }
698}
699
700fn aes_ctr_with_cipher<C: AesCipher>(cipher: &C, iv: &[u8; 16], data: &mut [u8]) {
701 let mut counter = *iv;
702 for chunk in data.chunks_mut(16) {
703 let mut stream = counter;
704 cipher.encrypt_block(&mut stream);
705 for (dst, src) in chunk.iter_mut().zip(stream.iter()) {
706 *dst ^= *src;
707 }
708 increment_counter(&mut counter);
709 }
710}
711
712fn xor_in_place(dst: &mut [u8; 16], rhs: &[u8; 16]) {
713 for (left, right) in dst.iter_mut().zip(rhs.iter()) {
714 *left ^= *right;
715 }
716}
717
718fn dbl(block: &[u8; 16]) -> [u8; 16] {
719 let mut out = [0u8; 16];
720 let mut carry = 0u8;
721 for (index, byte) in block.iter().enumerate().rev() {
722 out[index] = (byte << 1) | carry;
723 carry = byte >> 7;
724 }
725 if block[0] & 0x80 != 0 {
726 out[15] ^= 0x87;
727 }
728 out
729}
730
731fn sec_info_bytes_range(header: &PacketHeader) -> Result<Range<usize>, CryptoError> {
736 let sec_info = header.sec_info.ok_or(CryptoError::InvalidPacket)?;
737 header
738 .options_range
739 .start
740 .checked_sub(sec_info.wire_len())
741 .map(|start| start..header.options_range.start)
742 .ok_or(CryptoError::InvalidPacket)
743}
744
745fn increment_counter(counter: &mut [u8; 16]) {
746 for byte in counter.iter_mut().rev() {
747 let (next, carry) = byte.overflowing_add(1);
748 *byte = next;
749 if !carry {
750 break;
751 }
752 }
753}
754
755fn constant_time_eq(left: &[u8], right: &[u8]) -> bool {
756 let mut diff = left.len() ^ right.len();
757 let max_len = left.len().max(right.len());
758 for index in 0..max_len {
759 let lhs = left.get(index).copied().unwrap_or(0);
760 let rhs = right.get(index).copied().unwrap_or(0);
761 diff |= usize::from(lhs ^ rhs);
762 }
763 diff == 0
764}
765
766#[cfg(feature = "software-crypto")]
767pub mod software {
768 use aes::cipher::{BlockDecrypt, BlockEncrypt, KeyInit, generic_array::GenericArray};
769 use curve25519_dalek::{edwards::CompressedEdwardsY, montgomery::MontgomeryPoint};
770 use ed25519_dalek::{Signer, SigningKey};
771 use rand_core::CryptoRngCore;
772 use sha2::{Digest, Sha256, Sha512};
773
774 use super::*;
775
776 pub struct SoftwareAes;
777
778 pub struct SoftwareAesCipher(aes::Aes256);
779
780 impl AesCipher for SoftwareAesCipher {
781 fn encrypt_block(&self, block: &mut [u8; 16]) {
782 self.0.encrypt_block(GenericArray::from_mut_slice(block));
783 }
784
785 fn decrypt_block(&self, block: &mut [u8; 16]) {
786 self.0.decrypt_block(GenericArray::from_mut_slice(block));
787 }
788 }
789
790 impl AesProvider for SoftwareAes {
791 type Cipher = SoftwareAesCipher;
792
793 fn new_cipher(&self, key: &[u8; 32]) -> Self::Cipher {
794 SoftwareAesCipher(aes::Aes256::new(GenericArray::from_slice(key)))
795 }
796 }
797
798 pub struct SoftwareSha256;
799
800 impl Sha256Provider for SoftwareSha256 {
801 fn hash(&self, data: &[&[u8]]) -> [u8; 32] {
802 let mut hasher = Sha256::new();
803 for chunk in data {
804 hasher.update(chunk);
805 }
806 hasher.finalize().into()
807 }
808
809 fn hmac(&self, key: &[u8], data: &[&[u8]]) -> [u8; 32] {
810 const BLOCK_LEN: usize = 64;
811 let mut key_block = [0u8; BLOCK_LEN];
812 if key.len() > BLOCK_LEN {
813 key_block[..32].copy_from_slice(&self.hash(&[key]));
814 } else {
815 key_block[..key.len()].copy_from_slice(key);
816 }
817
818 let mut ipad = [0x36u8; BLOCK_LEN];
819 let mut opad = [0x5Cu8; BLOCK_LEN];
820 for index in 0..BLOCK_LEN {
821 ipad[index] ^= key_block[index];
822 opad[index] ^= key_block[index];
823 }
824
825 let mut inner = Sha256::new();
826 inner.update(ipad);
827 for chunk in data {
828 inner.update(chunk);
829 }
830 let inner_hash = inner.finalize();
831
832 let mut outer = Sha256::new();
833 outer.update(opad);
834 outer.update(inner_hash);
835 outer.finalize().into()
836 }
837 }
838
839 #[derive(Clone)]
843 pub struct SoftwareIdentity {
844 secret: SigningKey,
845 public: PublicKey,
846 }
847
848 impl SoftwareIdentity {
849 pub fn generate(rng: &mut impl CryptoRngCore) -> Self {
850 let secret = SigningKey::generate(rng);
851 let public = PublicKey(secret.verifying_key().to_bytes());
852 Self { secret, public }
853 }
854
855 pub fn from_secret_bytes(bytes: &[u8; 32]) -> Self {
856 let secret = SigningKey::from_bytes(bytes);
857 let public = PublicKey(secret.verifying_key().to_bytes());
858 Self { secret, public }
859 }
860
861 pub fn shared_secret_with(&self, peer: &PublicKey) -> Result<SharedSecret, CryptoError> {
862 let local = signing_key_to_x25519(&self.secret);
863 let remote = public_key_to_x25519(peer)?;
864 let shared = local.diffie_hellman(&remote);
865 let bytes = shared.to_bytes();
866 if bytes.iter().all(|byte| *byte == 0) {
867 return Err(CryptoError::InvalidSharedSecret);
868 }
869 Ok(SharedSecret(bytes))
870 }
871 }
872
873 impl NodeIdentity for SoftwareIdentity {
874 type Error = CryptoError;
875
876 fn public_key(&self) -> &PublicKey {
877 &self.public
878 }
879
880 async fn sign(&self, message: &[u8]) -> Result<[u8; 64], Self::Error> {
881 Ok(self.secret.sign(message).to_bytes())
882 }
883
884 async fn agree(&self, peer: &PublicKey) -> Result<SharedSecret, Self::Error> {
885 self.shared_secret_with(peer)
886 }
887 }
888
889 pub type SoftwareCryptoEngine = CryptoEngine<SoftwareAes, SoftwareSha256>;
890
891 fn signing_key_to_x25519(secret: &SigningKey) -> x25519_dalek::StaticSecret {
892 let digest = Sha512::digest(secret.to_bytes());
893 let mut scalar = [0u8; 32];
894 scalar.copy_from_slice(&digest[..32]);
895 scalar[0] &= 248;
896 scalar[31] &= 127;
897 scalar[31] |= 64;
898 x25519_dalek::StaticSecret::from(scalar)
899 }
900
901 fn public_key_to_x25519(public: &PublicKey) -> Result<x25519_dalek::PublicKey, CryptoError> {
902 let compressed = CompressedEdwardsY(public.0);
903 let edwards = compressed
904 .decompress()
905 .ok_or(CryptoError::InvalidPublicKey)?;
906 let mont: MontgomeryPoint = edwards.to_montgomery();
907 Ok(x25519_dalek::PublicKey::from(mont.to_bytes()))
908 }
909
910 pub fn is_valid_ed25519_public_key(pk: &PublicKey) -> bool {
914 CompressedEdwardsY(pk.0).decompress().is_some()
915 }
916
917 pub fn verify_ed25519_signature(pk: &PublicKey, message: &[u8], signature: &[u8; 64]) -> bool {
921 let Ok(key) = ed25519_dalek::VerifyingKey::from_bytes(&pk.0) else {
922 return false;
923 };
924 key.verify_strict(message, &ed25519_dalek::Signature::from_bytes(signature))
925 .is_ok()
926 }
927}
928
929#[cfg(feature = "software-crypto")]
930pub use software::*;
931
932#[cfg(test)]
933mod tests {
934 use crate::{constant_time_eq, dbl};
935
936 #[cfg(feature = "software-crypto")]
937 use super::{software::*, *};
938 #[cfg(feature = "software-crypto")]
939 use umsh_core::{MicSize, NodeHint, PacketBuilder, PacketHeader, PublicKey};
940
941 #[cfg(feature = "software-crypto")]
942 #[test]
943 fn pairwise_hkdf_is_stable() {
944 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
945 let keys = engine.derive_pairwise_keys(&SharedSecret([7u8; 32]));
946 assert_ne!(keys.k_enc, [0u8; 32]);
947 assert_ne!(keys.k_mic, [0u8; 32]);
948 }
949
950 #[cfg(feature = "software-crypto")]
953 #[test]
954 fn pairwise_okm_is_the_rfc5297_key() {
955 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
956 let shared = SharedSecret([9u8; 32]);
957 let keys = engine.derive_pairwise_keys(&shared);
958 let mut okm = [0u8; 64];
959 engine.hkdf(
960 &shared.0,
961 b"UMSH-PAIRWISE-SALT",
962 b"UMSH-UNICAST-V2",
963 &mut okm,
964 );
965 assert_eq!(keys.k_mic, okm[..32]);
966 assert_eq!(keys.k_enc, okm[32..]);
967 }
968
969 #[cfg(feature = "software-crypto")]
971 const NIST_AES256_KEY: &str =
972 "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4";
973
974 #[cfg(feature = "software-crypto")]
975 #[test]
976 fn aes_cmac_matches_nist_aes256_example_2() {
977 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
978 let key = hex_32(NIST_AES256_KEY);
979 let msg = hex_vec("6bc1bee22e409f96e93d7e117393172a");
980 let expected = hex_16("28a7023f452e8f82bd4bf28d8c37c35c");
981 assert_eq!(engine.aes_cmac(&key, &[&msg]), expected);
982 }
983
984 #[cfg(feature = "software-crypto")]
985 #[test]
986 fn aes_cmac_matches_nist_aes256_example_1_empty() {
987 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
988 let key = hex_32(NIST_AES256_KEY);
989 let expected = hex_16("028962f61b7bf89efc6b551f4667d983");
990 assert_eq!(engine.aes_cmac(&key, &[&[]]), expected);
991 }
992
993 #[cfg(feature = "software-crypto")]
994 #[test]
995 fn aes_cmac_matches_nist_aes256_example_4_64b() {
996 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
997 let key = hex_32(NIST_AES256_KEY);
998 let msg = hex_vec(
999 "6bc1bee22e409f96e93d7e117393172a\
1000 ae2d8a571e03ac9c9eb76fac45af8e51\
1001 30c81c46a35ce411e5fbc1191a0a52ef\
1002 f69f2445df4f9b17ad2b417be66c3710",
1003 );
1004 let expected = hex_16("e1992190549f6ed5696a2c056c315410");
1005 assert_eq!(engine.aes_cmac(&key, &[&msg]), expected);
1006 }
1007
1008 #[cfg(feature = "software-crypto")]
1009 #[test]
1010 fn aes_cmac_matches_nist_aes256_example_3_40b() {
1011 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1012 let key = hex_32(NIST_AES256_KEY);
1013 let msg = hex_vec(
1014 "6bc1bee22e409f96e93d7e117393172a\
1015 ae2d8a571e03ac9c9eb76fac45af8e51\
1016 30c81c46a35ce411",
1017 );
1018 let expected = hex_16("aaf3d8f1de5640c232f5b169b9c911e6");
1019 assert_eq!(engine.aes_cmac(&key, &[&msg]), expected);
1020 }
1021
1022 #[cfg(feature = "software-crypto")]
1024 #[test]
1025 fn aes_ctr_matches_nist_sp800_38a_block_1() {
1026 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1027 let key = hex_32(NIST_AES256_KEY);
1028 let iv = hex_16("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
1029 let mut data = hex_vec("6bc1bee22e409f96e93d7e117393172a");
1030 engine.aes_ctr(&key, &iv, &mut data);
1031 assert_eq!(data, hex_vec("601ec313775789a5b7a7f504bbf3d228"));
1032 }
1033
1034 #[cfg(feature = "software-crypto")]
1037 #[test]
1038 fn aes_ctr_matches_nist_sp800_38a_4_blocks() {
1039 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1040 let key = hex_32(NIST_AES256_KEY);
1041 let iv = hex_16("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
1042 let mut data = hex_vec(
1043 "6bc1bee22e409f96e93d7e117393172a\
1044 ae2d8a571e03ac9c9eb76fac45af8e51\
1045 30c81c46a35ce411e5fbc1191a0a52ef\
1046 f69f2445df4f9b17ad2b417be66c3710",
1047 );
1048 let expected = hex_vec(
1049 "601ec313775789a5b7a7f504bbf3d228\
1050 f443e3ca4d62b59aca84e990cacaf5c5\
1051 2b0930daa23de94ce87017ba2d84988d\
1052 dfc9c58db67aada613c2dd08457941a6",
1053 );
1054 engine.aes_ctr(&key, &iv, &mut data);
1055 assert_eq!(data, expected);
1056 }
1057
1058 #[cfg(feature = "software-crypto")]
1060 #[test]
1061 fn aes_ctr_decrypt_matches_nist_sp800_38a() {
1062 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1063 let key = hex_32(NIST_AES256_KEY);
1064 let iv = hex_16("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
1065 let mut data = hex_vec(
1066 "601ec313775789a5b7a7f504bbf3d228\
1067 f443e3ca4d62b59aca84e990cacaf5c5\
1068 2b0930daa23de94ce87017ba2d84988d\
1069 dfc9c58db67aada613c2dd08457941a6",
1070 );
1071 let expected = hex_vec(
1072 "6bc1bee22e409f96e93d7e117393172a\
1073 ae2d8a571e03ac9c9eb76fac45af8e51\
1074 30c81c46a35ce411e5fbc1191a0a52ef\
1075 f69f2445df4f9b17ad2b417be66c3710",
1076 );
1077 engine.aes_ctr(&key, &iv, &mut data);
1078 assert_eq!(data, expected);
1079 }
1080
1081 #[cfg(feature = "software-crypto")]
1083 #[test]
1084 fn aes_ctr_partial_block() {
1085 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1086 let key = hex_32(NIST_AES256_KEY);
1087 let iv = hex_16("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
1088 let mut data = hex_vec("6bc1bee22e");
1090 engine.aes_ctr(&key, &iv, &mut data);
1091 assert_eq!(data, hex_vec("601ec31377"));
1093 engine.aes_ctr(&key, &iv, &mut data);
1095 assert_eq!(data, hex_vec("6bc1bee22e"));
1096 }
1097
1098 #[cfg(feature = "software-crypto")]
1103 struct Aes128TestCipher(aes::Aes128);
1104
1105 #[cfg(feature = "software-crypto")]
1106 impl Aes128TestCipher {
1107 fn new(key: &[u8; 16]) -> Self {
1108 use aes::cipher::KeyInit;
1109 Self(aes::Aes128::new(
1110 aes::cipher::generic_array::GenericArray::from_slice(key),
1111 ))
1112 }
1113 }
1114
1115 #[cfg(feature = "software-crypto")]
1116 impl AesCipher for Aes128TestCipher {
1117 fn encrypt_block(&self, block: &mut [u8; 16]) {
1118 use aes::cipher::BlockEncrypt;
1119 self.0
1120 .encrypt_block(aes::cipher::generic_array::GenericArray::from_mut_slice(
1121 block,
1122 ));
1123 }
1124
1125 fn decrypt_block(&self, block: &mut [u8; 16]) {
1126 use aes::cipher::BlockDecrypt;
1127 self.0
1128 .decrypt_block(aes::cipher::generic_array::GenericArray::from_mut_slice(
1129 block,
1130 ));
1131 }
1132 }
1133
1134 #[cfg(feature = "software-crypto")]
1138 #[test]
1139 fn rfc5297_a1_deterministic_aes_siv() {
1140 let k1 = hex_16("fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0");
1141 let k2 = hex_16("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
1142 let ad = hex_vec("101112131415161718191a1b1c1d1e1f2021222324252627");
1143 let plaintext = hex_vec("112233445566778899aabbccddee");
1144
1145 let s2v_cipher = Aes128TestCipher::new(&k1);
1146 let mut s2v = S2vState::new(&s2v_cipher);
1147 s2v.push_component(|cmac| cmac.update(&ad));
1148 let v = s2v.finalize(&plaintext);
1149 assert_eq!(v, hex_16("85632d07c6e8f37f950acd320a2ecc93"));
1150
1151 let mut q = v;
1152 q[8] &= 0x7F;
1153 q[12] &= 0x7F;
1154 let ctr_cipher = Aes128TestCipher::new(&k2);
1155 let mut data = plaintext.clone();
1156 aes_ctr_with_cipher(&ctr_cipher, &q, &mut data);
1157 assert_eq!(data, hex_vec("40c02b9690c4dc04daef7f6afe5c"));
1158 }
1159
1160 #[cfg(feature = "software-crypto")]
1164 #[test]
1165 fn rfc5297_a2_nonce_based_aes_siv() {
1166 let k1 = hex_16("7f7e7d7c7b7a79787776757473727170");
1167 let k2 = hex_16("404142434445464748494a4b4c4d4e4f");
1168 let ad1 = hex_vec(
1169 "00112233445566778899aabbccddeeff\
1170 deaddadadeaddadaffeeddccbbaa9988\
1171 7766554433221100",
1172 );
1173 let ad2 = hex_vec("102030405060708090a0");
1174 let nonce = hex_vec("09f911029d74e35bd84156c5635688c0");
1175 let plaintext = hex_vec(
1176 "7468697320697320736f6d6520706c61\
1177 696e7465787420746f20656e63727970\
1178 74207573696e67205349562d414553",
1179 );
1180
1181 let s2v_cipher = Aes128TestCipher::new(&k1);
1182 let mut s2v = S2vState::new(&s2v_cipher);
1183 s2v.push_component(|cmac| cmac.update(&ad1));
1184 s2v.push_component(|cmac| cmac.update(&ad2));
1185 s2v.push_component(|cmac| cmac.update(&nonce));
1186 let v = s2v.finalize(&plaintext);
1187 assert_eq!(v, hex_16("7bdb6e3b432667eb06f4d14bff2fbd0f"));
1188
1189 let mut q = v;
1190 q[8] &= 0x7F;
1191 q[12] &= 0x7F;
1192 let ctr_cipher = Aes128TestCipher::new(&k2);
1193 let mut data = plaintext.clone();
1194 aes_ctr_with_cipher(&ctr_cipher, &q, &mut data);
1195 assert_eq!(
1196 data,
1197 hex_vec(
1198 "cb900f2fddbe404326601965c889bf17\
1199 dba77ceb094fa663b7a3f748ba8af829\
1200 ea64ad544a272e9c485b62a3fd5c0d"
1201 )
1202 );
1203 }
1204
1205 #[cfg(feature = "software-crypto")]
1208 #[test]
1209 fn build_ctr_iv_masks_rfc5297_bits() {
1210 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1211 let mic = [0xFFu8; 16];
1212 let sec_info = [0xFFu8; 7];
1213 for mic_len in [4usize, 8, 12, 16] {
1214 let iv = engine.build_ctr_iv(&mic[..mic_len], &sec_info);
1215 assert_eq!(iv[8] & 0x80, 0, "mic_len {mic_len}");
1216 assert_eq!(iv[12] & 0x80, 0, "mic_len {mic_len}");
1217 }
1218 }
1219
1220 #[cfg(feature = "software-crypto")]
1221 #[test]
1222 fn hmac_sha256_matches_rfc4231_case_1() {
1223 let sha = SoftwareSha256;
1224 let key = [0x0bu8; 20];
1225 let expected = hex_32("b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7");
1226 assert_eq!(sha.hmac(&key, &[b"Hi There"]), expected);
1227 }
1228
1229 #[cfg(feature = "software-crypto")]
1230 #[test]
1231 fn hkdf_matches_rfc5869_case_1() {
1232 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1233 let ikm = [0x0bu8; 22];
1234 let salt = hex_vec("000102030405060708090a0b0c");
1235 let info = hex_vec("f0f1f2f3f4f5f6f7f8f9");
1236 let mut okm = [0u8; 42];
1237 engine.hkdf(&ikm, &salt, &info, &mut okm);
1238 assert_eq!(
1239 okm.to_vec(),
1240 hex_vec(
1241 "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865"
1242 )
1243 );
1244 }
1245
1246 #[test]
1247 fn cmac_doubling_matches_rfc4493_subkey_generation() {
1248 let l = hex_16("7df76b0c1ab899b33e42f047b91b546f");
1249 let expected_k1 = hex_16("fbeed618357133667c85e08f7236a8de");
1250 let expected_k2 = hex_16("f7ddac306ae266ccf90bc11ee46d513b");
1251 let k1 = dbl(&l);
1252 let k2 = dbl(&k1);
1253 assert_eq!(k1, expected_k1);
1254 assert_eq!(k2, expected_k2);
1255 }
1256
1257 #[test]
1258 fn constant_time_eq_rejects_mismatch() {
1259 assert!(constant_time_eq(&[1, 2, 3], &[1, 2, 3]));
1260 assert!(!constant_time_eq(&[1, 2, 3], &[1, 2, 4]));
1261 assert!(!constant_time_eq(&[1, 2, 3], &[1, 2, 3, 4]));
1262 }
1263
1264 #[cfg(feature = "software-crypto")]
1265 #[test]
1266 fn unicast_seal_and_open_round_trip() {
1267 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1268 let keys = engine.derive_pairwise_keys(&SharedSecret([9u8; 32]));
1269 let src = PublicKey([0xA1; 32]);
1270 let dst = NodeHint([0xC3, 0xD4, 0x25]);
1271 let mut buf = [0u8; 128];
1272 let mut packet = PacketBuilder::new(&mut buf)
1273 .unicast(dst)
1274 .source_full(&src)
1275 .frame_counter(1)
1276 .encrypted()
1277 .mic_size(MicSize::Mic16)
1278 .payload(b"hello")
1279 .build()
1280 .unwrap();
1281
1282 engine.seal_packet(&mut packet, &keys).unwrap();
1283 let header = PacketHeader::parse(packet.as_bytes()).unwrap();
1284 let mut wire = packet.as_bytes().to_vec();
1285 let range = engine.open_packet(&mut wire, &header, &keys).unwrap();
1286 assert_eq!(&wire[range], b"hello");
1287 }
1288
1289 #[cfg(feature = "software-crypto")]
1294 #[test]
1295 fn mic16_packet_matches_rfc5297_aes_siv_crate() {
1296 use aes_siv::{KeyInit, siv::Aes256Siv};
1297
1298 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1299 let keys = engine.derive_pairwise_keys(&SharedSecret([9u8; 32]));
1300 let src = PublicKey([0xA1; 32]);
1301 let dst = NodeHint([0xC3, 0xD4, 0x25]);
1302 for payload in [&b"hello, RFC 5297 - a plaintext spanning blocks"[..], b""] {
1303 let mut buf = [0u8; 128];
1304 let mut packet = PacketBuilder::new(&mut buf)
1305 .unicast(dst)
1306 .source_full(&src)
1307 .frame_counter(7)
1308 .encrypted()
1309 .mic_size(MicSize::Mic16)
1310 .payload(payload)
1311 .build()
1312 .unwrap();
1313 engine.seal_packet(&mut packet, &keys).unwrap();
1314
1315 let wire = packet.as_bytes().to_vec();
1316 let header = PacketHeader::parse(&wire).unwrap();
1317 let mut aad = std::vec::Vec::new();
1318 feed_aad(&header, &wire, |chunk| aad.extend_from_slice(chunk));
1319
1320 let mut siv_key = [0u8; 64];
1321 siv_key[..32].copy_from_slice(&keys.k_mic);
1322 siv_key[32..].copy_from_slice(&keys.k_enc);
1323 let mut siv = Aes256Siv::new((&siv_key).into());
1324 let sealed = siv.encrypt([&aad], payload).unwrap();
1325
1326 assert_eq!(sealed[..16], wire[header.mic_range.clone()]);
1329 assert_eq!(sealed[16..], wire[header.body_range.clone()]);
1330 }
1331 }
1332
1333 #[cfg(feature = "software-crypto")]
1336 #[test]
1337 fn short_mic_tag_matches_rfc5297_s2v() {
1338 use aes_siv::{KeyInit, siv::Aes256Siv};
1339
1340 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1341 let keys = engine.derive_pairwise_keys(&SharedSecret([11u8; 32]));
1342 let payload = b"short-tag payload";
1343 let src = PublicKey([0xB2; 32]);
1344 let dst = NodeHint([0x10, 0x20, 0x30]);
1345 let mut buf = [0u8; 128];
1346 let mut packet = PacketBuilder::new(&mut buf)
1347 .unicast(dst)
1348 .source_full(&src)
1349 .frame_counter(3)
1350 .encrypted()
1351 .mic_size(MicSize::Mic4)
1352 .payload(payload)
1353 .build()
1354 .unwrap();
1355
1356 let full_tag = {
1357 let header = packet.header().unwrap();
1358 let bytes = packet.as_bytes();
1359 engine.s2v_tag(
1360 &keys.k_mic,
1361 |cmac| feed_aad(&header, bytes, |chunk| cmac.update(chunk)),
1362 packet.body(),
1363 )
1364 };
1365 let mut aad = std::vec::Vec::new();
1366 {
1367 let header = packet.header().unwrap();
1368 feed_aad(&header, packet.as_bytes(), |chunk| {
1369 aad.extend_from_slice(chunk)
1370 });
1371 }
1372 engine.seal_packet(&mut packet, &keys).unwrap();
1373
1374 let mut siv_key = [0u8; 64];
1375 siv_key[..32].copy_from_slice(&keys.k_mic);
1376 siv_key[32..].copy_from_slice(&keys.k_enc);
1377 let mut siv = Aes256Siv::new((&siv_key).into());
1378 let sealed = siv.encrypt([&aad], payload).unwrap();
1379 assert_eq!(sealed[..16], full_tag);
1380
1381 let header = PacketHeader::parse(packet.as_bytes()).unwrap();
1383 assert_eq!(packet.as_bytes()[header.mic_range.clone()], full_tag[..4]);
1384 }
1385
1386 #[cfg(feature = "software-crypto")]
1392 #[test]
1393 fn short_mic_encrypted_unicast_round_trip() {
1394 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1395 let keys = engine.derive_pairwise_keys(&SharedSecret([9u8; 32]));
1396 let mut buf = [0u8; 128];
1397 let mut packet = PacketBuilder::new(&mut buf)
1398 .unicast(NodeHint([0xC3, 0xD4, 0x25]))
1399 .source_hint(NodeHint([0xA1, 0xA1, 0xA1]))
1400 .frame_counter(7)
1401 .encrypted()
1402 .mic_size(MicSize::Mic8)
1403 .payload(b"short mic")
1404 .build()
1405 .unwrap();
1406 engine.seal_packet(&mut packet, &keys).unwrap();
1407 let header = PacketHeader::parse(packet.as_bytes()).unwrap();
1408 let mut wire = packet.as_bytes().to_vec();
1409 let range = engine.open_packet(&mut wire, &header, &keys).unwrap();
1410 assert_eq!(&wire[range], b"short mic");
1411 }
1412
1413 #[cfg(feature = "software-crypto")]
1418 #[test]
1419 fn short_mic_blind_unicast_round_trip() {
1420 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1421 let channel_key = ChannelKey([0x5A; 32]);
1422 let channel_keys = engine.derive_channel_keys(&channel_key);
1423 let pairwise = engine.derive_pairwise_keys(&SharedSecret([9u8; 32]));
1424 let blind_keys = engine.derive_blind_keys(&pairwise, &channel_keys);
1425 let dst = NodeHint([0xC3, 0xD4, 0x25]);
1426 let src = NodeHint([0xA1, 0xA1, 0xA1]);
1427
1428 let mut buf = [0u8; 128];
1429 let mut packet = PacketBuilder::new(&mut buf)
1430 .blind_unicast(channel_keys.channel_id, dst)
1431 .source_hint(src)
1432 .frame_counter(7)
1433 .ack_requested()
1434 .encrypted()
1435 .mic_size(MicSize::Mic8)
1436 .payload(b"blind")
1437 .build()
1438 .unwrap();
1439 engine
1440 .seal_blind_packet(&mut packet, &blind_keys, &channel_keys)
1441 .unwrap();
1442
1443 let header = PacketHeader::parse(packet.as_bytes()).unwrap();
1444 let mut wire = packet.as_bytes().to_vec();
1445 let (decrypted_dst, decrypted_src) = engine
1446 .decrypt_blind_addr(&mut wire, &header, &channel_keys)
1447 .unwrap();
1448 assert_eq!(decrypted_dst, dst);
1449 assert_eq!(decrypted_src, SourceAddrRef::Hint(src));
1450 let range = engine.open_packet(&mut wire, &header, &blind_keys).unwrap();
1451 assert_eq!(&wire[range], b"blind");
1452 }
1453
1454 #[cfg(feature = "software-crypto")]
1458 #[test]
1459 fn compute_ack_trailer_is_deterministic() {
1460 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1461 let key = hex_32(NIST_AES256_KEY);
1462 let tag = hex_16("28a7023f452e8f82bd4bf28d8c37c35c");
1463 let t1 = engine.compute_ack_trailer(&tag, &key);
1464 let t2 = engine.compute_ack_trailer(&tag, &key);
1465 assert_eq!(t1, t2);
1466 assert_eq!(t1.len(), 8);
1467 assert_eq!(t1[..4], tag[..4]);
1469 let cipher = SoftwareAes.new_cipher(&key);
1471 let mut block = tag;
1472 cipher.encrypt_block(&mut block);
1473 assert_eq!(t1[4..], block[..4]);
1474 }
1475
1476 #[cfg(feature = "software-crypto")]
1479 #[test]
1480 fn compute_ack_trailer_differs_for_different_tags() {
1481 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1482 let key = hex_32(NIST_AES256_KEY);
1483 let tag_a = hex_16("28a7023f452e8f82bd4bf28d8c37c35c");
1484 let tag_b = hex_16("aaf3d8f1de5640c232f5b169b9c911e6");
1485 let t_a = engine.compute_ack_trailer(&tag_a, &key);
1486 let t_b = engine.compute_ack_trailer(&tag_b, &key);
1487 assert_ne!(t_a, t_b);
1488 assert_ne!(t_a, [0u8; 8]);
1489 assert_ne!(t_b, [0u8; 8]);
1490 }
1491
1492 #[cfg(feature = "software-crypto")]
1493 #[test]
1494 fn encrypted_multicast_round_trip_preserves_source_prefix() {
1495 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1496 let channel_key = ChannelKey([0x5Au8; 32]);
1497 let derived = engine.derive_channel_keys(&channel_key);
1498 let src = PublicKey([0xA1; 32]);
1499 let mut buf = [0u8; 160];
1500 let mut packet = PacketBuilder::new(&mut buf)
1501 .multicast(derived.channel_id)
1502 .source_hint(src.hint())
1503 .frame_counter(5)
1504 .encrypted()
1505 .mic_size(MicSize::Mic16)
1506 .payload(b"hello")
1507 .build()
1508 .unwrap();
1509
1510 let multicast_keys = PairwiseKeys {
1511 k_enc: derived.k_enc,
1512 k_mic: derived.k_mic,
1513 };
1514 engine.seal_packet(&mut packet, &multicast_keys).unwrap();
1515 let header = PacketHeader::parse(packet.as_bytes()).unwrap();
1516 let mut wire = packet.as_bytes().to_vec();
1517 let range = engine
1518 .open_packet(&mut wire, &header, &multicast_keys)
1519 .unwrap();
1520 assert_eq!(
1521 &wire[header.body_range.start..header.body_range.start + 3],
1522 &src.hint().0
1523 );
1524 assert_eq!(&wire[range], b"hello");
1525 }
1526
1527 #[cfg(feature = "software-crypto")]
1528 #[test]
1529 fn blind_unicast_round_trip_recovers_addresses_and_payload() {
1530 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1531 let shared = SharedSecret([0x33u8; 32]);
1532 let pairwise = engine.derive_pairwise_keys(&shared);
1533 let channel_key = ChannelKey([0x5Au8; 32]);
1534 let channel = engine.derive_channel_keys(&channel_key);
1535 let blind_keys = engine.derive_blind_keys(&pairwise, &channel);
1536 let src = PublicKey([
1537 0xA1, 0xB2, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
1538 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C,
1539 0x1D, 0x1E, 0x1F, 0x20,
1540 ]);
1541 let dst = NodeHint([0xC3, 0xD4, 0x25]);
1542 let mut buf = [0u8; 160];
1543 let mut packet = PacketBuilder::new(&mut buf)
1544 .blind_unicast(channel.channel_id, dst)
1545 .source_full(&src)
1546 .frame_counter(5)
1547 .mic_size(MicSize::Mic16)
1548 .payload(b"hello")
1549 .build()
1550 .unwrap();
1551
1552 engine
1553 .seal_blind_packet(&mut packet, &blind_keys, &channel)
1554 .unwrap();
1555 let header = PacketHeader::parse(packet.as_bytes()).unwrap();
1556 let mut wire = packet.as_bytes().to_vec();
1557 let (decoded_dst, decoded_src) = engine
1558 .decrypt_blind_addr(&mut wire, &header, &channel)
1559 .unwrap();
1560 let range = engine.open_packet(&mut wire, &header, &blind_keys).unwrap();
1561
1562 assert_eq!(decoded_dst, dst);
1563 assert_eq!(
1564 decoded_src,
1565 SourceAddrRef::FullKeyAt {
1566 offset: header.body_range.start - 32
1567 }
1568 );
1569 assert_eq!(
1570 &wire[header.body_range.start - 32..header.body_range.start],
1571 &src.0
1572 );
1573 assert_eq!(&wire[range], b"hello");
1574 }
1575
1576 #[cfg(feature = "software-crypto")]
1577 #[test]
1578 fn software_identity_agreement_is_symmetric() {
1579 let alice = SoftwareIdentity::from_secret_bytes(&[
1580 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E,
1581 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C,
1582 0x2D, 0x2E, 0x2F, 0x30,
1583 ]);
1584 let bob = SoftwareIdentity::from_secret_bytes(&[
1585 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E,
1586 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C,
1587 0x4D, 0x4E, 0x4F, 0x50,
1588 ]);
1589
1590 let ab = alice.shared_secret_with(bob.public_key()).unwrap();
1591 let ba = bob.shared_secret_with(alice.public_key()).unwrap();
1592 assert_eq!(ab.0, ba.0);
1593 }
1594
1595 #[cfg(feature = "software-crypto")]
1596 #[test]
1597 fn channel_tint_extends_the_channel_identifier() {
1598 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1599 let key = ChannelKey([0x5Au8; 32]);
1600 let tint = engine.derive_channel_tint(&key);
1601 assert_eq!(&tint[..2], &engine.derive_channel_id(&key).0[..]);
1604 assert_eq!(tint, engine.derive_channel_tint(&key));
1605 assert_ne!(tint, engine.derive_channel_tint(&ChannelKey([0x5Bu8; 32])));
1606 }
1607
1608 #[cfg(feature = "software-crypto")]
1609 #[test]
1610 fn channel_tag_extends_the_channel_identifier_and_tint() {
1611 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1612 let key = ChannelKey([0x5Au8; 32]);
1613 let tag = engine.derive_channel_tag(&key);
1614 assert_eq!(tag.channel_id(), engine.derive_channel_id(&key));
1617 assert_eq!(&tag.0[..3], &engine.derive_channel_tint(&key)[..]);
1618 assert_eq!(tag, engine.derive_channel_tag(&key));
1619 assert_ne!(tag, engine.derive_channel_tag(&ChannelKey([0x5Bu8; 32])));
1620 }
1621
1622 #[cfg(feature = "software-crypto")]
1625 #[test]
1626 fn derive_named_channel_key_is_deterministic() {
1627 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1628 let k1 = engine.derive_named_channel_key("lobby").unwrap();
1629 let k2 = engine.derive_named_channel_key("lobby").unwrap();
1630 let k3 = engine.derive_named_channel_key("other").unwrap();
1631 assert_eq!(k1, k2);
1632 assert_ne!(k1, k3);
1633 assert_ne!(k1, ChannelKey([0u8; 32]));
1634 }
1635
1636 #[cfg(feature = "software-crypto")]
1637 #[test]
1638 fn derive_named_channel_key_folds_ascii_case() {
1639 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1640 let lower = engine.derive_named_channel_key("public").unwrap();
1641 assert_eq!(engine.derive_named_channel_key("Public").unwrap(), lower);
1642 assert_eq!(engine.derive_named_channel_key("PUBLIC").unwrap(), lower);
1643 assert_eq!(
1645 engine.derive_named_channel_key("Mesh-42!").unwrap(),
1646 engine.derive_named_channel_key("mesh-42!").unwrap()
1647 );
1648 }
1649
1650 #[cfg(feature = "software-crypto")]
1651 #[test]
1652 fn derive_named_channel_key_rejects_invalid_names() {
1653 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1654 assert_eq!(
1655 engine.derive_named_channel_key("caf\u{e9}"),
1656 Err(ChannelNameError::NotAscii)
1657 );
1658 let too_long = "x".repeat(MAX_CHANNEL_NAME_LEN + 1);
1659 assert_eq!(
1660 engine.derive_named_channel_key(&too_long),
1661 Err(ChannelNameError::TooLong)
1662 );
1663 let at_limit = "x".repeat(MAX_CHANNEL_NAME_LEN);
1665 assert!(engine.derive_named_channel_key(&at_limit).is_ok());
1666 }
1667
1668 #[cfg(feature = "software-crypto")]
1671 #[test]
1672 fn software_identity_generate_produces_valid_key() {
1673 use rand_core::{CryptoRng, RngCore};
1674
1675 struct CounterRng(u64);
1678 impl RngCore for CounterRng {
1679 fn next_u32(&mut self) -> u32 {
1680 self.next_u64() as u32
1681 }
1682 fn next_u64(&mut self) -> u64 {
1683 self.0 = self.0.wrapping_add(1);
1684 self.0
1685 }
1686 fn fill_bytes(&mut self, dest: &mut [u8]) {
1687 for chunk in dest.chunks_mut(8) {
1688 let bytes = self.next_u64().to_le_bytes();
1689 chunk.copy_from_slice(&bytes[..chunk.len()]);
1690 }
1691 }
1692 fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
1693 self.fill_bytes(dest);
1694 Ok(())
1695 }
1696 }
1697 impl CryptoRng for CounterRng {}
1698
1699 let mut rng1 = CounterRng(0);
1700 let mut rng2 = CounterRng(1000);
1701 let id1 = SoftwareIdentity::generate(&mut rng1);
1702 let id2 = SoftwareIdentity::generate(&mut rng2);
1703 assert_ne!(id1.public_key(), id2.public_key());
1704 let ss1 = id1.shared_secret_with(id2.public_key()).unwrap();
1705 let ss2 = id2.shared_secret_with(id1.public_key()).unwrap();
1706 assert_eq!(ss1.0, ss2.0);
1707 }
1708
1709 #[cfg(feature = "software-crypto")]
1712 #[test]
1713 fn seal_blind_packet_rejects_non_blind_packet() {
1714 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1715 let shared = SharedSecret([0x11u8; 32]);
1716 let pairwise = engine.derive_pairwise_keys(&shared);
1717 let channel_key = ChannelKey([0x5Au8; 32]);
1718 let channel = engine.derive_channel_keys(&channel_key);
1719 let blind_keys = engine.derive_blind_keys(&pairwise, &channel);
1720 let src = PublicKey([0xA1u8; 32]);
1721 let dst = NodeHint([0xC3, 0xD4, 0x25]);
1722 let mut buf = [0u8; 128];
1723 let mut packet = PacketBuilder::new(&mut buf)
1725 .unicast(dst)
1726 .source_full(&src)
1727 .frame_counter(1)
1728 .encrypted()
1729 .mic_size(MicSize::Mic16)
1730 .payload(b"test")
1731 .build()
1732 .unwrap();
1733 assert!(matches!(
1734 engine.seal_blind_packet(&mut packet, &blind_keys, &channel),
1735 Err(CryptoError::InvalidPacket)
1736 ));
1737 }
1738
1739 #[cfg(feature = "software-crypto")]
1742 #[test]
1743 fn open_packet_fails_on_tampered_ciphertext() {
1744 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1745 let keys = engine.derive_pairwise_keys(&SharedSecret([9u8; 32]));
1746 let src = PublicKey([0xA1; 32]);
1747 let dst = NodeHint([0xC3, 0xD4, 0x25]);
1748 let mut buf = [0u8; 128];
1749 let mut packet = PacketBuilder::new(&mut buf)
1750 .unicast(dst)
1751 .source_full(&src)
1752 .frame_counter(1)
1753 .encrypted()
1754 .mic_size(MicSize::Mic16)
1755 .payload(b"hello")
1756 .build()
1757 .unwrap();
1758 engine.seal_packet(&mut packet, &keys).unwrap();
1759
1760 let mut wire = packet.as_bytes().to_vec();
1762 let header = PacketHeader::parse(&wire).unwrap();
1763 wire[header.body_range.start] ^= 0x01;
1764
1765 assert!(matches!(
1766 engine.open_packet(&mut wire, &header, &keys),
1767 Err(CryptoError::AuthenticationFailed)
1768 ));
1769 }
1770
1771 #[cfg(feature = "software-crypto")]
1774 #[test]
1775 fn decrypt_blind_addr_encrypted_hint_source() {
1776 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1777 let shared = SharedSecret([0x33u8; 32]);
1778 let pairwise = engine.derive_pairwise_keys(&shared);
1779 let channel_key = ChannelKey([0x5Au8; 32]);
1780 let channel = engine.derive_channel_keys(&channel_key);
1781 let blind_keys = engine.derive_blind_keys(&pairwise, &channel);
1782 let src = PublicKey([0xA1u8; 32]);
1783 let dst = NodeHint([0xC3, 0xD4, 0x25]);
1784 let mut buf = [0u8; 128];
1785 let mut packet = PacketBuilder::new(&mut buf)
1787 .blind_unicast(channel.channel_id, dst)
1788 .source_hint(src.hint())
1789 .frame_counter(5)
1790 .encrypted()
1791 .mic_size(MicSize::Mic16)
1792 .payload(b"hi")
1793 .build()
1794 .unwrap();
1795 engine
1796 .seal_blind_packet(&mut packet, &blind_keys, &channel)
1797 .unwrap();
1798 let mut wire = packet.as_bytes().to_vec();
1799 let header = PacketHeader::parse(&wire).unwrap();
1800 let (decoded_dst, decoded_src) = engine
1801 .decrypt_blind_addr(&mut wire, &header, &channel)
1802 .unwrap();
1803 assert_eq!(decoded_dst, dst);
1804 assert_eq!(decoded_src, SourceAddrRef::Hint(src.hint()));
1805 }
1806
1807 #[cfg(feature = "software-crypto")]
1810 #[test]
1811 fn aes_decrypt_block_inverts_encrypt_block() {
1812 let key = hex_32(NIST_AES256_KEY);
1813 let cipher = SoftwareAes.new_cipher(&key);
1814 let original = hex_16("6bc1bee22e409f96e93d7e117393172a");
1815 let mut block = original;
1816 cipher.encrypt_block(&mut block);
1817 assert_ne!(block, original);
1818 cipher.decrypt_block(&mut block);
1819 assert_eq!(block, original);
1820 }
1821
1822 #[cfg(feature = "software-crypto")]
1825 #[test]
1826 fn sign_and_agree_via_trait() {
1827 use core::future::Future;
1828 use core::pin::pin;
1829 use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
1830
1831 fn block_on<F: Future>(future: F) -> F::Output {
1832 let raw = RawWaker::new(
1833 core::ptr::null(),
1834 &RawWakerVTable::new(|_| panic!(), |_| {}, |_| {}, |_| {}),
1835 );
1836 let waker = unsafe { Waker::from_raw(raw) };
1837 let mut ctx = Context::from_waker(&waker);
1838 match pin!(future).poll(&mut ctx) {
1839 Poll::Ready(v) => v,
1840 Poll::Pending => panic!("future unexpectedly pending"),
1841 }
1842 }
1843
1844 let alice = SoftwareIdentity::from_secret_bytes(&[0x11u8; 32]);
1845 let bob = SoftwareIdentity::from_secret_bytes(&[0x22u8; 32]);
1846
1847 let sig = block_on(alice.sign(b"test message")).unwrap();
1848 assert_eq!(sig.len(), 64);
1849
1850 let shared = block_on(alice.agree(bob.public_key())).unwrap();
1851 assert_ne!(shared.0, [0u8; 32]);
1852 }
1853
1854 #[cfg(feature = "software-crypto")]
1857 #[test]
1858 fn node_identity_hint_matches_public_key_hint() {
1859 let id = SoftwareIdentity::from_secret_bytes(&[0x11u8; 32]);
1860 assert_eq!(NodeIdentity::hint(&id), id.public_key().hint());
1862 }
1863
1864 #[test]
1867 fn hex_vec_accepts_uppercase() {
1868 assert_eq!(hex_vec("DEADBEEF"), hex_vec("deadbeef"));
1869 assert_eq!(hex_vec("0A1B2C3D"), hex_vec("0a1b2c3d"));
1870 }
1871
1872 fn hex_vec(input: &str) -> std::vec::Vec<u8> {
1873 assert_eq!(input.len() % 2, 0);
1874 let mut out = std::vec::Vec::with_capacity(input.len() / 2);
1875 let bytes = input.as_bytes();
1876 for index in (0..bytes.len()).step_by(2) {
1877 out.push((decode_hex(bytes[index]) << 4) | decode_hex(bytes[index + 1]));
1878 }
1879 out
1880 }
1881
1882 fn hex_16(input: &str) -> [u8; 16] {
1883 let bytes = hex_vec(input);
1884 let mut out = [0u8; 16];
1885 out.copy_from_slice(&bytes);
1886 out
1887 }
1888
1889 #[cfg(feature = "software-crypto")]
1890 fn hex_32(input: &str) -> [u8; 32] {
1891 let bytes = hex_vec(input);
1892 let mut out = [0u8; 32];
1893 out.copy_from_slice(&bytes);
1894 out
1895 }
1896
1897 fn decode_hex(byte: u8) -> u8 {
1898 match byte {
1899 b'0'..=b'9' => byte - b'0',
1900 b'a'..=b'f' => byte - b'a' + 10,
1901 b'A'..=b'F' => byte - b'A' + 10,
1902 _ => panic!("invalid hex"),
1903 }
1904 }
1905
1906 #[cfg(feature = "software-crypto")]
1907 #[test]
1908 fn is_valid_ed25519_public_key_accepts_real_key() {
1909 let identity = SoftwareIdentity::from_secret_bytes(&[0x11; 32]);
1912 assert!(is_valid_ed25519_public_key(identity.public_key()));
1913 }
1914
1915 #[cfg(feature = "software-crypto")]
1916 #[test]
1917 fn is_valid_ed25519_public_key_rejects_non_curve_bytes() {
1918 let mut bytes = [0u8; 32];
1924 bytes[0] = 2;
1925 let bogus = PublicKey(bytes);
1926 assert!(!is_valid_ed25519_public_key(&bogus));
1927 }
1928
1929 #[cfg(feature = "software-crypto")]
1930 #[test]
1931 fn is_valid_ed25519_public_key_handles_all_zero() {
1932 let zero = PublicKey([0u8; 32]);
1937 assert!(is_valid_ed25519_public_key(&zero));
1938 }
1939}