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 replay;
36
37pub trait AesCipher {
39 fn encrypt_block(&self, block: &mut [u8; 16]);
41 fn decrypt_block(&self, block: &mut [u8; 16]);
43}
44
45pub trait AesProvider {
47 type Cipher: AesCipher;
49
50 fn new_cipher(&self, key: &[u8; 16]) -> Self::Cipher;
52}
53
54pub trait Sha256Provider {
56 fn hash(&self, data: &[&[u8]]) -> [u8; 32];
58 fn hmac(&self, key: &[u8], data: &[&[u8]]) -> [u8; 32];
60}
61
62#[derive(Clone, Zeroize, ZeroizeOnDrop)]
64pub struct SharedSecret(pub [u8; 32]);
65
66pub trait NodeIdentity {
68 type Error;
69
70 fn public_key(&self) -> &PublicKey;
72
73 fn hint(&self) -> umsh_core::NodeHint {
75 self.public_key().hint()
76 }
77
78 async fn sign(&self, message: &[u8]) -> Result<[u8; 64], Self::Error>;
80 async fn agree(&self, peer: &PublicKey) -> Result<SharedSecret, Self::Error>;
82}
83
84#[derive(Clone, Debug, PartialEq, Eq)]
86pub enum CryptoError {
87 InvalidPublicKey,
88 InvalidSharedSecret,
89 InvalidPacket,
90 AuthenticationFailed,
91}
92
93pub const MAX_CHANNEL_NAME_LEN: usize = 64;
100
101#[derive(Clone, Copy, Debug, PartialEq, Eq)]
104pub enum ChannelNameError {
105 NotAscii,
108 TooLong,
110}
111
112#[derive(Clone, Zeroize, ZeroizeOnDrop)]
114pub struct PairwiseKeys {
115 pub k_enc: [u8; 16],
116 pub k_mic: [u8; 16],
117}
118
119#[derive(Clone)]
121pub struct DerivedChannelKeys {
122 pub k_enc: [u8; 16],
123 pub k_mic: [u8; 16],
124 pub channel_id: ChannelId,
125}
126
127impl Zeroize for DerivedChannelKeys {
128 fn zeroize(&mut self) {
129 self.k_enc.zeroize();
130 self.k_mic.zeroize();
131 }
132}
133
134impl Drop for DerivedChannelKeys {
135 fn drop(&mut self) {
136 self.zeroize();
137 }
138}
139
140pub struct CmacState<C: AesCipher> {
142 cipher: C,
143 state: [u8; 16],
144 buffer: [u8; 16],
145 pos: usize,
146 k1: [u8; 16],
147 k2: [u8; 16],
148}
149
150impl<C: AesCipher> CmacState<C> {
151 pub fn new(cipher: C) -> Self {
153 let mut l = [0u8; 16];
154 cipher.encrypt_block(&mut l);
155 let k1 = dbl(&l);
156 let k2 = dbl(&k1);
157 Self {
158 cipher,
159 state: [0u8; 16],
160 buffer: [0u8; 16],
161 pos: 0,
162 k1,
163 k2,
164 }
165 }
166
167 pub fn update(&mut self, mut data: &[u8]) {
169 while !data.is_empty() {
170 let space = 16 - self.pos;
171 let take = space.min(data.len());
172 self.buffer[self.pos..self.pos + take].copy_from_slice(&data[..take]);
173 self.pos += take;
174 data = &data[take..];
175 if self.pos == 16 && !data.is_empty() {
176 self.process_buffer();
177 }
178 }
179 }
180
181 pub fn finalize(self) -> [u8; 16] {
183 let this = self;
184 let mut last = [0u8; 16];
185 if this.pos == 16 {
186 last.copy_from_slice(&this.buffer);
187 xor_in_place(&mut last, &this.k1);
188 } else {
189 last[..this.pos].copy_from_slice(&this.buffer[..this.pos]);
190 last[this.pos] = 0x80;
191 xor_in_place(&mut last, &this.k2);
192 }
193
194 xor_in_place(&mut last, &this.state);
195 this.cipher.encrypt_block(&mut last);
196 last
197 }
198
199 fn process_buffer(&mut self) {
200 let mut block = self.buffer;
201 xor_in_place(&mut block, &self.state);
202 self.cipher.encrypt_block(&mut block);
203 self.state = block;
204 self.buffer = [0u8; 16];
205 self.pos = 0;
206 }
207}
208
209pub struct CryptoEngine<A: AesProvider, S: Sha256Provider> {
211 aes: A,
212 sha: S,
213}
214
215impl<A: AesProvider, S: Sha256Provider> CryptoEngine<A, S> {
216 pub fn new(aes: A, sha: S) -> Self {
218 Self { aes, sha }
219 }
220
221 pub fn derive_pairwise_keys(&self, shared_secret: &SharedSecret) -> PairwiseKeys {
223 let mut okm = [0u8; 32];
224 self.hkdf(
225 &shared_secret.0,
226 b"UMSH-PAIRWISE-SALT",
227 b"UMSH-UNICAST-V1",
228 &mut okm,
229 );
230 let mut keys = PairwiseKeys {
231 k_enc: [0u8; 16],
232 k_mic: [0u8; 16],
233 };
234 keys.k_enc.copy_from_slice(&okm[..16]);
235 keys.k_mic.copy_from_slice(&okm[16..32]);
236 okm.zeroize();
237 keys
238 }
239
240 pub fn derive_channel_id(&self, channel_key: &ChannelKey) -> ChannelId {
242 let mut out = [0u8; 2];
243 self.hkdf(&channel_key.0, b"UMSH-CHAN-ID", b"", &mut out);
244 ChannelId(out)
245 }
246
247 pub fn derive_channel_tint(&self, channel_key: &ChannelKey) -> [u8; 3] {
255 let mut out = [0u8; 3];
256 self.hkdf(&channel_key.0, b"UMSH-CHAN-ID", b"", &mut out);
257 out
258 }
259
260 pub fn derive_channel_tag(&self, channel_key: &ChannelKey) -> ChannelTag {
266 let mut out = [0u8; 16];
267 self.hkdf(&channel_key.0, b"UMSH-CHAN-ID", b"", &mut out);
268 ChannelTag(out)
269 }
270
271 pub fn derive_channel_keys(&self, channel_key: &ChannelKey) -> DerivedChannelKeys {
273 let channel_id = self.derive_channel_id(channel_key);
274 let mut info = [0u8; 15];
275 info[..13].copy_from_slice(b"UMSH-MCAST-V1");
276 info[13..15].copy_from_slice(&channel_id.0);
277 let mut okm = [0u8; 32];
278 self.hkdf(&channel_key.0, b"UMSH-MCAST-SALT", &info, &mut okm);
279 let mut derived = DerivedChannelKeys {
280 k_enc: [0u8; 16],
281 k_mic: [0u8; 16],
282 channel_id,
283 };
284 derived.k_enc.copy_from_slice(&okm[..16]);
285 derived.k_mic.copy_from_slice(&okm[16..32]);
286 okm.zeroize();
287 derived
288 }
289
290 pub fn derive_blind_keys(
292 &self,
293 pairwise: &PairwiseKeys,
294 channel: &DerivedChannelKeys,
295 ) -> PairwiseKeys {
296 let mut keys = PairwiseKeys {
297 k_enc: [0u8; 16],
298 k_mic: [0u8; 16],
299 };
300 for (dst, (left, right)) in keys
301 .k_enc
302 .iter_mut()
303 .zip(pairwise.k_enc.iter().zip(channel.k_enc.iter()))
304 {
305 *dst = left ^ right;
306 }
307 for (dst, (left, right)) in keys
308 .k_mic
309 .iter_mut()
310 .zip(pairwise.k_mic.iter().zip(channel.k_mic.iter()))
311 {
312 *dst = left ^ right;
313 }
314 keys
315 }
316
317 pub fn derive_named_channel_key(&self, name: &str) -> Result<ChannelKey, ChannelNameError> {
327 let bytes = name.as_bytes();
328 if bytes.len() > MAX_CHANNEL_NAME_LEN {
329 return Err(ChannelNameError::TooLong);
330 }
331 if !name.is_ascii() {
332 return Err(ChannelNameError::NotAscii);
333 }
334 let mut canonical = [0u8; MAX_CHANNEL_NAME_LEN];
335 for (dst, byte) in canonical.iter_mut().zip(bytes) {
336 *dst = byte.to_ascii_lowercase();
337 }
338 Ok(ChannelKey(
339 self.sha
340 .hmac(b"UMSH-CHANNEL-V1", &[&canonical[..bytes.len()]]),
341 ))
342 }
343
344 pub fn seal_packet(
346 &self,
347 packet: &mut UnsealedPacket<'_>,
348 keys: &PairwiseKeys,
349 ) -> Result<usize, CryptoError> {
350 let header = packet.header().map_err(|_| CryptoError::InvalidPacket)?;
351 let sec_info = header.sec_info.ok_or(CryptoError::InvalidPacket)?;
352 let full_mac = {
353 let bytes = packet.as_bytes();
354 let mut cmac = self.cmac_state(&keys.k_mic);
355 feed_aad(&header, bytes, |chunk| cmac.update(chunk));
356 cmac.update(packet.body());
357 cmac.finalize()
358 };
359
360 let mic_len = sec_info
361 .scf
362 .mic_size()
363 .map_err(|_| CryptoError::InvalidPacket)?
364 .byte_len();
365 packet.mic_slot()[..mic_len].copy_from_slice(&full_mac[..mic_len]);
366
367 if sec_info.scf.encrypted() {
368 let iv = self.build_ctr_iv(
369 &full_mac[..mic_len],
370 &packet.as_bytes()[packet.sec_info_range()],
371 );
372 self.aes_ctr(&keys.k_enc, &iv, packet.body_mut());
373 }
374
375 Ok(mic_len)
376 }
377
378 pub fn seal_blind_packet(
380 &self,
381 packet: &mut UnsealedPacket<'_>,
382 blind_keys: &PairwiseKeys,
383 channel_keys: &DerivedChannelKeys,
384 ) -> Result<usize, CryptoError> {
385 let header = packet.header().map_err(|_| CryptoError::InvalidPacket)?;
386 match header.packet_type() {
387 PacketType::BlindUnicast | PacketType::BlindUnicastAckReq => {}
388 _ => return Err(CryptoError::InvalidPacket),
389 }
390
391 let sec_info = header.sec_info.ok_or(CryptoError::InvalidPacket)?;
392 let blind_addr_range = packet
393 .blind_addr_range()
394 .ok_or(CryptoError::InvalidPacket)?;
395 let full_mac = {
396 let bytes = packet.as_bytes();
397 let mut cmac = self.cmac_state(&blind_keys.k_mic);
398 feed_aad(&header, bytes, |chunk| cmac.update(chunk));
399 cmac.update(packet.body());
400 cmac.finalize()
401 };
402
403 let mic_len = sec_info
404 .scf
405 .mic_size()
406 .map_err(|_| CryptoError::InvalidPacket)?
407 .byte_len();
408 let iv = self.build_ctr_iv(
409 &full_mac[..mic_len],
410 &packet.as_bytes()[packet.sec_info_range()],
411 );
412 packet.mic_slot()[..mic_len].copy_from_slice(&full_mac[..mic_len]);
413 if sec_info.scf.encrypted() {
414 self.aes_ctr(&blind_keys.k_enc, &iv, packet.body_mut());
415 self.aes_ctr(
416 &channel_keys.k_enc,
417 &iv,
418 &mut packet.as_bytes_mut()[blind_addr_range],
419 );
420 }
421 Ok(mic_len)
422 }
423
424 pub fn open_packet(
426 &self,
427 buf: &mut [u8],
428 header: &PacketHeader,
429 keys: &PairwiseKeys,
430 ) -> Result<Range<usize>, CryptoError> {
431 let sec_info = header.sec_info.ok_or(CryptoError::InvalidPacket)?;
432 let mut mic = [0u8; 16];
433 let mic_len = header.mic_range.end - header.mic_range.start;
434 mic[..mic_len].copy_from_slice(&buf[header.mic_range.clone()]);
435 if sec_info.scf.encrypted() {
436 let iv = self.build_ctr_iv(&mic[..mic_len], &buf[sec_info_bytes_range(header)?]);
437 self.aes_ctr(&keys.k_enc, &iv, &mut buf[header.body_range.clone()]);
438 }
439
440 let full_mac = {
441 let mut cmac = self.cmac_state(&keys.k_mic);
442 feed_aad(header, buf, |chunk| cmac.update(chunk));
443 cmac.update(&buf[header.body_range.clone()]);
444 cmac.finalize()
445 };
446 if !constant_time_eq(&mic[..mic_len], &full_mac[..mic_len]) {
447 return Err(CryptoError::AuthenticationFailed);
448 }
449
450 let body_range = match (header.packet_type(), header.source) {
451 (PacketType::Multicast, SourceAddrRef::Encrypted { len, .. }) => {
452 (header.body_range.start + len)..header.body_range.end
453 }
454 _ => header.body_range.clone(),
455 };
456 Ok(body_range)
457 }
458
459 pub fn decrypt_blind_addr(
461 &self,
462 buf: &mut [u8],
463 header: &PacketHeader,
464 channel_keys: &DerivedChannelKeys,
465 ) -> Result<(umsh_core::NodeHint, SourceAddrRef), CryptoError> {
466 match header.source {
467 SourceAddrRef::Encrypted { offset, len } => {
468 let addr_start = offset.checked_sub(3).ok_or(CryptoError::InvalidPacket)?;
469 let addr_end = addr_start + 3 + len;
470 let iv = self.build_ctr_iv(
471 &buf[header.mic_range.clone()],
472 &buf[sec_info_bytes_range(header)?],
473 );
474 self.aes_ctr(&channel_keys.k_enc, &iv, &mut buf[addr_start..addr_end]);
475 let dst = umsh_core::NodeHint([
476 buf[addr_start],
477 buf[addr_start + 1],
478 buf[addr_start + 2],
479 ]);
480 let source = if len == 3 {
481 SourceAddrRef::Hint(umsh_core::NodeHint([
482 buf[addr_start + 3],
483 buf[addr_start + 4],
484 buf[addr_start + 5],
485 ]))
486 } else if len == 32 {
487 SourceAddrRef::FullKeyAt {
488 offset: addr_start + 3,
489 }
490 } else {
491 return Err(CryptoError::InvalidPacket);
492 };
493 Ok((dst, source))
494 }
495 SourceAddrRef::Hint(hint) => {
496 let dst = header.dst.ok_or(CryptoError::InvalidPacket)?;
497 Ok((dst, SourceAddrRef::Hint(hint)))
498 }
499 SourceAddrRef::FullKeyAt { offset } => {
500 let dst = header.dst.ok_or(CryptoError::InvalidPacket)?;
501 let _ = buf
502 .get(offset..offset + 32)
503 .ok_or(CryptoError::InvalidPacket)?;
504 Ok((dst, SourceAddrRef::FullKeyAt { offset }))
505 }
506 SourceAddrRef::None => Err(CryptoError::InvalidPacket),
507 }
508 }
509
510 pub fn compute_ack_trailer(&self, full_cmac: &[u8; 16], k_enc: &[u8; 16]) -> [u8; 8] {
526 let cipher = self.aes.new_cipher(k_enc);
527 let mut block = *full_cmac;
528 cipher.encrypt_block(&mut block);
529 let mut trailer = [0u8; 8];
530 trailer[..4].copy_from_slice(&full_cmac[..4]); trailer[4..].copy_from_slice(&block[..4]); trailer
533 }
534
535 pub fn cmac_state(&self, key: &[u8; 16]) -> CmacState<A::Cipher> {
537 CmacState::new(self.aes.new_cipher(key))
538 }
539
540 pub fn aes_cmac(&self, key: &[u8; 16], data: &[&[u8]]) -> [u8; 16] {
542 let mut state = self.cmac_state(key);
543 for chunk in data {
544 state.update(chunk);
545 }
546 state.finalize()
547 }
548
549 pub fn aes_ctr(&self, key: &[u8; 16], iv: &[u8; 16], data: &mut [u8]) {
551 let cipher = self.aes.new_cipher(key);
552 let mut counter = *iv;
553 for chunk in data.chunks_mut(16) {
554 let mut stream = counter;
555 cipher.encrypt_block(&mut stream);
556 for (dst, src) in chunk.iter_mut().zip(stream.iter()) {
557 *dst ^= *src;
558 }
559 increment_counter(&mut counter);
560 }
561 }
562
563 pub fn build_ctr_iv(&self, mic: &[u8], sec_info_bytes: &[u8]) -> [u8; 16] {
570 let mut iv = [0u8; 16];
571 let mut written = 0usize;
572 for byte in mic.iter().chain(sec_info_bytes.iter()).take(16) {
573 iv[written] = *byte;
574 written += 1;
575 }
576 iv
577 }
578
579 pub fn hkdf(&self, ikm: &[u8], salt: &[u8], info: &[u8], okm: &mut [u8]) {
581 let prk = self.sha.hmac(salt, &[ikm]);
582 let mut previous = [0u8; 32];
583 let mut previous_len = 0usize;
584 let mut written = 0usize;
585 let mut counter = 1u8;
586 while written < okm.len() {
587 let next = self
588 .sha
589 .hmac(&prk, &[&previous[..previous_len], info, &[counter]]);
590 let take = (okm.len() - written).min(next.len());
591 okm[written..written + take].copy_from_slice(&next[..take]);
592 previous = next;
593 previous_len = 32;
594 written += take;
595 counter = counter.wrapping_add(1);
596 }
597 }
598}
599
600fn xor_in_place(dst: &mut [u8; 16], rhs: &[u8; 16]) {
601 for (left, right) in dst.iter_mut().zip(rhs.iter()) {
602 *left ^= *right;
603 }
604}
605
606fn dbl(block: &[u8; 16]) -> [u8; 16] {
607 let mut out = [0u8; 16];
608 let mut carry = 0u8;
609 for (index, byte) in block.iter().enumerate().rev() {
610 out[index] = (byte << 1) | carry;
611 carry = byte >> 7;
612 }
613 if block[0] & 0x80 != 0 {
614 out[15] ^= 0x87;
615 }
616 out
617}
618
619fn sec_info_bytes_range(header: &PacketHeader) -> Result<Range<usize>, CryptoError> {
624 let sec_info = header.sec_info.ok_or(CryptoError::InvalidPacket)?;
625 header
626 .options_range
627 .start
628 .checked_sub(sec_info.wire_len())
629 .map(|start| start..header.options_range.start)
630 .ok_or(CryptoError::InvalidPacket)
631}
632
633fn increment_counter(counter: &mut [u8; 16]) {
634 for byte in counter.iter_mut().rev() {
635 let (next, carry) = byte.overflowing_add(1);
636 *byte = next;
637 if !carry {
638 break;
639 }
640 }
641}
642
643fn constant_time_eq(left: &[u8], right: &[u8]) -> bool {
644 let mut diff = left.len() ^ right.len();
645 let max_len = left.len().max(right.len());
646 for index in 0..max_len {
647 let lhs = left.get(index).copied().unwrap_or(0);
648 let rhs = right.get(index).copied().unwrap_or(0);
649 diff |= usize::from(lhs ^ rhs);
650 }
651 diff == 0
652}
653
654#[cfg(feature = "software-crypto")]
655pub mod software {
656 use aes::cipher::{BlockDecrypt, BlockEncrypt, KeyInit, generic_array::GenericArray};
657 use curve25519_dalek::{edwards::CompressedEdwardsY, montgomery::MontgomeryPoint};
658 use ed25519_dalek::{Signer, SigningKey};
659 use rand_core::CryptoRngCore;
660 use sha2::{Digest, Sha256, Sha512};
661
662 use super::*;
663
664 pub struct SoftwareAes;
665
666 pub struct SoftwareAesCipher(aes::Aes128);
667
668 impl AesCipher for SoftwareAesCipher {
669 fn encrypt_block(&self, block: &mut [u8; 16]) {
670 self.0.encrypt_block(GenericArray::from_mut_slice(block));
671 }
672
673 fn decrypt_block(&self, block: &mut [u8; 16]) {
674 self.0.decrypt_block(GenericArray::from_mut_slice(block));
675 }
676 }
677
678 impl AesProvider for SoftwareAes {
679 type Cipher = SoftwareAesCipher;
680
681 fn new_cipher(&self, key: &[u8; 16]) -> Self::Cipher {
682 SoftwareAesCipher(aes::Aes128::new(GenericArray::from_slice(key)))
683 }
684 }
685
686 pub struct SoftwareSha256;
687
688 impl Sha256Provider for SoftwareSha256 {
689 fn hash(&self, data: &[&[u8]]) -> [u8; 32] {
690 let mut hasher = Sha256::new();
691 for chunk in data {
692 hasher.update(chunk);
693 }
694 hasher.finalize().into()
695 }
696
697 fn hmac(&self, key: &[u8], data: &[&[u8]]) -> [u8; 32] {
698 const BLOCK_LEN: usize = 64;
699 let mut key_block = [0u8; BLOCK_LEN];
700 if key.len() > BLOCK_LEN {
701 key_block[..32].copy_from_slice(&self.hash(&[key]));
702 } else {
703 key_block[..key.len()].copy_from_slice(key);
704 }
705
706 let mut ipad = [0x36u8; BLOCK_LEN];
707 let mut opad = [0x5Cu8; BLOCK_LEN];
708 for index in 0..BLOCK_LEN {
709 ipad[index] ^= key_block[index];
710 opad[index] ^= key_block[index];
711 }
712
713 let mut inner = Sha256::new();
714 inner.update(ipad);
715 for chunk in data {
716 inner.update(chunk);
717 }
718 let inner_hash = inner.finalize();
719
720 let mut outer = Sha256::new();
721 outer.update(opad);
722 outer.update(inner_hash);
723 outer.finalize().into()
724 }
725 }
726
727 #[derive(Clone)]
731 pub struct SoftwareIdentity {
732 secret: SigningKey,
733 public: PublicKey,
734 }
735
736 impl SoftwareIdentity {
737 pub fn generate(rng: &mut impl CryptoRngCore) -> Self {
738 let secret = SigningKey::generate(rng);
739 let public = PublicKey(secret.verifying_key().to_bytes());
740 Self { secret, public }
741 }
742
743 pub fn from_secret_bytes(bytes: &[u8; 32]) -> Self {
744 let secret = SigningKey::from_bytes(bytes);
745 let public = PublicKey(secret.verifying_key().to_bytes());
746 Self { secret, public }
747 }
748
749 pub fn shared_secret_with(&self, peer: &PublicKey) -> Result<SharedSecret, CryptoError> {
750 let local = signing_key_to_x25519(&self.secret);
751 let remote = public_key_to_x25519(peer)?;
752 let shared = local.diffie_hellman(&remote);
753 let bytes = shared.to_bytes();
754 if bytes.iter().all(|byte| *byte == 0) {
755 return Err(CryptoError::InvalidSharedSecret);
756 }
757 Ok(SharedSecret(bytes))
758 }
759 }
760
761 impl NodeIdentity for SoftwareIdentity {
762 type Error = CryptoError;
763
764 fn public_key(&self) -> &PublicKey {
765 &self.public
766 }
767
768 async fn sign(&self, message: &[u8]) -> Result<[u8; 64], Self::Error> {
769 Ok(self.secret.sign(message).to_bytes())
770 }
771
772 async fn agree(&self, peer: &PublicKey) -> Result<SharedSecret, Self::Error> {
773 self.shared_secret_with(peer)
774 }
775 }
776
777 pub type SoftwareCryptoEngine = CryptoEngine<SoftwareAes, SoftwareSha256>;
778
779 fn signing_key_to_x25519(secret: &SigningKey) -> x25519_dalek::StaticSecret {
780 let digest = Sha512::digest(secret.to_bytes());
781 let mut scalar = [0u8; 32];
782 scalar.copy_from_slice(&digest[..32]);
783 scalar[0] &= 248;
784 scalar[31] &= 127;
785 scalar[31] |= 64;
786 x25519_dalek::StaticSecret::from(scalar)
787 }
788
789 fn public_key_to_x25519(public: &PublicKey) -> Result<x25519_dalek::PublicKey, CryptoError> {
790 let compressed = CompressedEdwardsY(public.0);
791 let edwards = compressed
792 .decompress()
793 .ok_or(CryptoError::InvalidPublicKey)?;
794 let mont: MontgomeryPoint = edwards.to_montgomery();
795 Ok(x25519_dalek::PublicKey::from(mont.to_bytes()))
796 }
797
798 pub fn is_valid_ed25519_public_key(pk: &PublicKey) -> bool {
802 CompressedEdwardsY(pk.0).decompress().is_some()
803 }
804
805 pub fn verify_ed25519_signature(pk: &PublicKey, message: &[u8], signature: &[u8; 64]) -> bool {
809 let Ok(key) = ed25519_dalek::VerifyingKey::from_bytes(&pk.0) else {
810 return false;
811 };
812 key.verify_strict(message, &ed25519_dalek::Signature::from_bytes(signature))
813 .is_ok()
814 }
815}
816
817#[cfg(feature = "software-crypto")]
818pub use software::*;
819
820#[cfg(test)]
821mod tests {
822 use crate::{constant_time_eq, dbl};
823
824 #[cfg(feature = "software-crypto")]
825 use super::{software::*, *};
826 #[cfg(feature = "software-crypto")]
827 use umsh_core::{MicSize, NodeHint, PacketBuilder, PacketHeader, PublicKey};
828
829 #[cfg(feature = "software-crypto")]
830 #[test]
831 fn pairwise_hkdf_is_stable() {
832 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
833 let keys = engine.derive_pairwise_keys(&SharedSecret([7u8; 32]));
834 assert_ne!(keys.k_enc, [0u8; 16]);
835 assert_ne!(keys.k_mic, [0u8; 16]);
836 }
837
838 #[cfg(feature = "software-crypto")]
839 #[test]
840 fn aes_cmac_matches_rfc4493_example_2() {
841 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
842 let key = hex_16("2b7e151628aed2a6abf7158809cf4f3c");
843 let msg = hex_vec("6bc1bee22e409f96e93d7e117393172a");
844 let expected = hex_16("070a16b46b4d4144f79bdd9dd04a287c");
845 assert_eq!(engine.aes_cmac(&key, &[&msg]), expected);
846 }
847
848 #[cfg(feature = "software-crypto")]
849 #[test]
850 fn aes_cmac_matches_rfc4493_example_1_empty() {
851 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
852 let key = hex_16("2b7e151628aed2a6abf7158809cf4f3c");
853 let expected = hex_16("bb1d6929e95937287fa37d129b756746");
854 assert_eq!(engine.aes_cmac(&key, &[&[]]), expected);
855 }
856
857 #[cfg(feature = "software-crypto")]
858 #[test]
859 fn aes_cmac_matches_rfc4493_example_3_64b() {
860 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
861 let key = hex_16("2b7e151628aed2a6abf7158809cf4f3c");
862 let msg = hex_vec(
863 "6bc1bee22e409f96e93d7e117393172a\
864 ae2d8a571e03ac9c9eb76fac45af8e51\
865 30c81c46a35ce411e5fbc1191a0a52ef\
866 f69f2445df4f9b17ad2b417be66c3710",
867 );
868 let expected = hex_16("51f0bebf7e3b9d92fc49741779363cfe");
869 assert_eq!(engine.aes_cmac(&key, &[&msg]), expected);
870 }
871
872 #[cfg(feature = "software-crypto")]
873 #[test]
874 fn aes_cmac_matches_rfc4493_example_4_40b() {
875 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
876 let key = hex_16("2b7e151628aed2a6abf7158809cf4f3c");
877 let msg = hex_vec(
878 "6bc1bee22e409f96e93d7e117393172a\
879 ae2d8a571e03ac9c9eb76fac45af8e51\
880 30c81c46a35ce411",
881 );
882 let expected = hex_16("dfa66747de9ae63030ca32611497c827");
883 assert_eq!(engine.aes_cmac(&key, &[&msg]), expected);
884 }
885
886 #[cfg(feature = "software-crypto")]
888 #[test]
889 fn aes_ctr_matches_nist_sp800_38a_block_1() {
890 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
891 let key = hex_16("2b7e151628aed2a6abf7158809cf4f3c");
892 let iv = hex_16("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
893 let mut data = hex_vec("6bc1bee22e409f96e93d7e117393172a");
894 engine.aes_ctr(&key, &iv, &mut data);
895 assert_eq!(data, hex_vec("874d6191b620e3261bef6864990db6ce"));
896 }
897
898 #[cfg(feature = "software-crypto")]
901 #[test]
902 fn aes_ctr_matches_nist_sp800_38a_4_blocks() {
903 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
904 let key = hex_16("2b7e151628aed2a6abf7158809cf4f3c");
905 let iv = hex_16("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
906 let mut data = hex_vec(
907 "6bc1bee22e409f96e93d7e117393172a\
908 ae2d8a571e03ac9c9eb76fac45af8e51\
909 30c81c46a35ce411e5fbc1191a0a52ef\
910 f69f2445df4f9b17ad2b417be66c3710",
911 );
912 let expected = hex_vec(
913 "874d6191b620e3261bef6864990db6ce\
914 9806f66b7970fdff8617187bb9fffdff\
915 5ae4df3edbd5d35e5b4f09020db03eab\
916 1e031dda2fbe03d1792170a0f3009cee",
917 );
918 engine.aes_ctr(&key, &iv, &mut data);
919 assert_eq!(data, expected);
920 }
921
922 #[cfg(feature = "software-crypto")]
924 #[test]
925 fn aes_ctr_decrypt_matches_nist_sp800_38a() {
926 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
927 let key = hex_16("2b7e151628aed2a6abf7158809cf4f3c");
928 let iv = hex_16("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
929 let mut data = hex_vec(
930 "874d6191b620e3261bef6864990db6ce\
931 9806f66b7970fdff8617187bb9fffdff\
932 5ae4df3edbd5d35e5b4f09020db03eab\
933 1e031dda2fbe03d1792170a0f3009cee",
934 );
935 let expected = hex_vec(
936 "6bc1bee22e409f96e93d7e117393172a\
937 ae2d8a571e03ac9c9eb76fac45af8e51\
938 30c81c46a35ce411e5fbc1191a0a52ef\
939 f69f2445df4f9b17ad2b417be66c3710",
940 );
941 engine.aes_ctr(&key, &iv, &mut data);
942 assert_eq!(data, expected);
943 }
944
945 #[cfg(feature = "software-crypto")]
947 #[test]
948 fn aes_ctr_partial_block() {
949 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
950 let key = hex_16("2b7e151628aed2a6abf7158809cf4f3c");
951 let iv = hex_16("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
952 let mut data = hex_vec("6bc1bee22e");
954 engine.aes_ctr(&key, &iv, &mut data);
955 assert_eq!(data, hex_vec("874d6191b6"));
957 engine.aes_ctr(&key, &iv, &mut data);
959 assert_eq!(data, hex_vec("6bc1bee22e"));
960 }
961
962 #[cfg(feature = "software-crypto")]
963 #[test]
964 fn hmac_sha256_matches_rfc4231_case_1() {
965 let sha = SoftwareSha256;
966 let key = [0x0bu8; 20];
967 let expected = hex_32("b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7");
968 assert_eq!(sha.hmac(&key, &[b"Hi There"]), expected);
969 }
970
971 #[cfg(feature = "software-crypto")]
972 #[test]
973 fn hkdf_matches_rfc5869_case_1() {
974 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
975 let ikm = [0x0bu8; 22];
976 let salt = hex_vec("000102030405060708090a0b0c");
977 let info = hex_vec("f0f1f2f3f4f5f6f7f8f9");
978 let mut okm = [0u8; 42];
979 engine.hkdf(&ikm, &salt, &info, &mut okm);
980 assert_eq!(
981 okm.to_vec(),
982 hex_vec(
983 "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865"
984 )
985 );
986 }
987
988 #[test]
989 fn cmac_doubling_matches_rfc4493_subkey_generation() {
990 let l = hex_16("7df76b0c1ab899b33e42f047b91b546f");
991 let expected_k1 = hex_16("fbeed618357133667c85e08f7236a8de");
992 let expected_k2 = hex_16("f7ddac306ae266ccf90bc11ee46d513b");
993 let k1 = dbl(&l);
994 let k2 = dbl(&k1);
995 assert_eq!(k1, expected_k1);
996 assert_eq!(k2, expected_k2);
997 }
998
999 #[test]
1000 fn constant_time_eq_rejects_mismatch() {
1001 assert!(constant_time_eq(&[1, 2, 3], &[1, 2, 3]));
1002 assert!(!constant_time_eq(&[1, 2, 3], &[1, 2, 4]));
1003 assert!(!constant_time_eq(&[1, 2, 3], &[1, 2, 3, 4]));
1004 }
1005
1006 #[cfg(feature = "software-crypto")]
1007 #[test]
1008 fn unicast_seal_and_open_round_trip() {
1009 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1010 let keys = engine.derive_pairwise_keys(&SharedSecret([9u8; 32]));
1011 let src = PublicKey([0xA1; 32]);
1012 let dst = NodeHint([0xC3, 0xD4, 0x25]);
1013 let mut buf = [0u8; 128];
1014 let mut packet = PacketBuilder::new(&mut buf)
1015 .unicast(dst)
1016 .source_full(&src)
1017 .frame_counter(1)
1018 .encrypted()
1019 .mic_size(MicSize::Mic16)
1020 .payload(b"hello")
1021 .build()
1022 .unwrap();
1023
1024 engine.seal_packet(&mut packet, &keys).unwrap();
1025 let header = PacketHeader::parse(packet.as_bytes()).unwrap();
1026 let mut wire = packet.as_bytes().to_vec();
1027 let range = engine.open_packet(&mut wire, &header, &keys).unwrap();
1028 assert_eq!(&wire[range], b"hello");
1029 }
1030
1031 #[cfg(feature = "software-crypto")]
1037 #[test]
1038 fn short_mic_encrypted_unicast_round_trip() {
1039 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1040 let keys = engine.derive_pairwise_keys(&SharedSecret([9u8; 32]));
1041 let mut buf = [0u8; 128];
1042 let mut packet = PacketBuilder::new(&mut buf)
1043 .unicast(NodeHint([0xC3, 0xD4, 0x25]))
1044 .source_hint(NodeHint([0xA1, 0xA1, 0xA1]))
1045 .frame_counter(7)
1046 .encrypted()
1047 .mic_size(MicSize::Mic8)
1048 .payload(b"short mic")
1049 .build()
1050 .unwrap();
1051 engine.seal_packet(&mut packet, &keys).unwrap();
1052 let header = PacketHeader::parse(packet.as_bytes()).unwrap();
1053 let mut wire = packet.as_bytes().to_vec();
1054 let range = engine.open_packet(&mut wire, &header, &keys).unwrap();
1055 assert_eq!(&wire[range], b"short mic");
1056 }
1057
1058 #[cfg(feature = "software-crypto")]
1063 #[test]
1064 fn short_mic_blind_unicast_round_trip() {
1065 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1066 let channel_key = ChannelKey([0x5A; 32]);
1067 let channel_keys = engine.derive_channel_keys(&channel_key);
1068 let pairwise = engine.derive_pairwise_keys(&SharedSecret([9u8; 32]));
1069 let blind_keys = engine.derive_blind_keys(&pairwise, &channel_keys);
1070 let dst = NodeHint([0xC3, 0xD4, 0x25]);
1071 let src = NodeHint([0xA1, 0xA1, 0xA1]);
1072
1073 let mut buf = [0u8; 128];
1074 let mut packet = PacketBuilder::new(&mut buf)
1075 .blind_unicast(channel_keys.channel_id, dst)
1076 .source_hint(src)
1077 .frame_counter(7)
1078 .ack_requested()
1079 .encrypted()
1080 .mic_size(MicSize::Mic8)
1081 .payload(b"blind")
1082 .build()
1083 .unwrap();
1084 engine
1085 .seal_blind_packet(&mut packet, &blind_keys, &channel_keys)
1086 .unwrap();
1087
1088 let header = PacketHeader::parse(packet.as_bytes()).unwrap();
1089 let mut wire = packet.as_bytes().to_vec();
1090 let (decrypted_dst, decrypted_src) = engine
1091 .decrypt_blind_addr(&mut wire, &header, &channel_keys)
1092 .unwrap();
1093 assert_eq!(decrypted_dst, dst);
1094 assert_eq!(decrypted_src, SourceAddrRef::Hint(src));
1095 let range = engine.open_packet(&mut wire, &header, &blind_keys).unwrap();
1096 assert_eq!(&wire[range], b"blind");
1097 }
1098
1099 #[cfg(feature = "software-crypto")]
1103 #[test]
1104 fn compute_ack_trailer_is_deterministic() {
1105 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1106 let key = hex_16("2b7e151628aed2a6abf7158809cf4f3c");
1107 let cmac = hex_16("070a16b46b4d4144f79bdd9dd04a287c"); let t1 = engine.compute_ack_trailer(&cmac, &key);
1109 let t2 = engine.compute_ack_trailer(&cmac, &key);
1110 assert_eq!(t1, t2);
1111 assert_eq!(t1.len(), 8);
1112 assert_eq!(t1[..4], cmac[..4]);
1114 let cipher = SoftwareAes.new_cipher(&key);
1116 let mut block = cmac;
1117 cipher.encrypt_block(&mut block);
1118 assert_eq!(t1[4..], block[..4]);
1119 }
1120
1121 #[cfg(feature = "software-crypto")]
1124 #[test]
1125 fn compute_ack_trailer_differs_for_different_cmacs() {
1126 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1127 let key = hex_16("2b7e151628aed2a6abf7158809cf4f3c");
1128 let cmac_a = hex_16("070a16b46b4d4144f79bdd9dd04a287c");
1129 let cmac_b = hex_16("51f0bebf7e3b9d92fc49741779363cfe");
1130 let t_a = engine.compute_ack_trailer(&cmac_a, &key);
1131 let t_b = engine.compute_ack_trailer(&cmac_b, &key);
1132 assert_ne!(t_a, t_b);
1133 assert_ne!(t_a, [0u8; 8]);
1134 assert_ne!(t_b, [0u8; 8]);
1135 }
1136
1137 #[cfg(feature = "software-crypto")]
1138 #[test]
1139 fn encrypted_multicast_round_trip_preserves_source_prefix() {
1140 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1141 let channel_key = ChannelKey([0x5Au8; 32]);
1142 let derived = engine.derive_channel_keys(&channel_key);
1143 let src = PublicKey([0xA1; 32]);
1144 let mut buf = [0u8; 160];
1145 let mut packet = PacketBuilder::new(&mut buf)
1146 .multicast(derived.channel_id)
1147 .source_hint(src.hint())
1148 .frame_counter(5)
1149 .encrypted()
1150 .mic_size(MicSize::Mic16)
1151 .payload(b"hello")
1152 .build()
1153 .unwrap();
1154
1155 let multicast_keys = PairwiseKeys {
1156 k_enc: derived.k_enc,
1157 k_mic: derived.k_mic,
1158 };
1159 engine.seal_packet(&mut packet, &multicast_keys).unwrap();
1160 let header = PacketHeader::parse(packet.as_bytes()).unwrap();
1161 let mut wire = packet.as_bytes().to_vec();
1162 let range = engine
1163 .open_packet(&mut wire, &header, &multicast_keys)
1164 .unwrap();
1165 assert_eq!(
1166 &wire[header.body_range.start..header.body_range.start + 3],
1167 &src.hint().0
1168 );
1169 assert_eq!(&wire[range], b"hello");
1170 }
1171
1172 #[cfg(feature = "software-crypto")]
1173 #[test]
1174 fn blind_unicast_round_trip_recovers_addresses_and_payload() {
1175 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1176 let shared = SharedSecret([0x33u8; 32]);
1177 let pairwise = engine.derive_pairwise_keys(&shared);
1178 let channel_key = ChannelKey([0x5Au8; 32]);
1179 let channel = engine.derive_channel_keys(&channel_key);
1180 let blind_keys = engine.derive_blind_keys(&pairwise, &channel);
1181 let src = PublicKey([
1182 0xA1, 0xB2, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
1183 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C,
1184 0x1D, 0x1E, 0x1F, 0x20,
1185 ]);
1186 let dst = NodeHint([0xC3, 0xD4, 0x25]);
1187 let mut buf = [0u8; 160];
1188 let mut packet = PacketBuilder::new(&mut buf)
1189 .blind_unicast(channel.channel_id, dst)
1190 .source_full(&src)
1191 .frame_counter(5)
1192 .mic_size(MicSize::Mic16)
1193 .payload(b"hello")
1194 .build()
1195 .unwrap();
1196
1197 engine
1198 .seal_blind_packet(&mut packet, &blind_keys, &channel)
1199 .unwrap();
1200 let header = PacketHeader::parse(packet.as_bytes()).unwrap();
1201 let mut wire = packet.as_bytes().to_vec();
1202 let (decoded_dst, decoded_src) = engine
1203 .decrypt_blind_addr(&mut wire, &header, &channel)
1204 .unwrap();
1205 let range = engine.open_packet(&mut wire, &header, &blind_keys).unwrap();
1206
1207 assert_eq!(decoded_dst, dst);
1208 assert_eq!(
1209 decoded_src,
1210 SourceAddrRef::FullKeyAt {
1211 offset: header.body_range.start - 32
1212 }
1213 );
1214 assert_eq!(
1215 &wire[header.body_range.start - 32..header.body_range.start],
1216 &src.0
1217 );
1218 assert_eq!(&wire[range], b"hello");
1219 }
1220
1221 #[cfg(feature = "software-crypto")]
1222 #[test]
1223 fn software_identity_agreement_is_symmetric() {
1224 let alice = SoftwareIdentity::from_secret_bytes(&[
1225 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E,
1226 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C,
1227 0x2D, 0x2E, 0x2F, 0x30,
1228 ]);
1229 let bob = SoftwareIdentity::from_secret_bytes(&[
1230 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E,
1231 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C,
1232 0x4D, 0x4E, 0x4F, 0x50,
1233 ]);
1234
1235 let ab = alice.shared_secret_with(bob.public_key()).unwrap();
1236 let ba = bob.shared_secret_with(alice.public_key()).unwrap();
1237 assert_eq!(ab.0, ba.0);
1238 }
1239
1240 #[cfg(feature = "software-crypto")]
1241 #[test]
1242 fn channel_tint_extends_the_channel_identifier() {
1243 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1244 let key = ChannelKey([0x5Au8; 32]);
1245 let tint = engine.derive_channel_tint(&key);
1246 assert_eq!(&tint[..2], &engine.derive_channel_id(&key).0[..]);
1249 assert_eq!(tint, engine.derive_channel_tint(&key));
1250 assert_ne!(tint, engine.derive_channel_tint(&ChannelKey([0x5Bu8; 32])));
1251 }
1252
1253 #[cfg(feature = "software-crypto")]
1254 #[test]
1255 fn channel_tag_extends_the_channel_identifier_and_tint() {
1256 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1257 let key = ChannelKey([0x5Au8; 32]);
1258 let tag = engine.derive_channel_tag(&key);
1259 assert_eq!(tag.channel_id(), engine.derive_channel_id(&key));
1262 assert_eq!(&tag.0[..3], &engine.derive_channel_tint(&key)[..]);
1263 assert_eq!(tag, engine.derive_channel_tag(&key));
1264 assert_ne!(tag, engine.derive_channel_tag(&ChannelKey([0x5Bu8; 32])));
1265 }
1266
1267 #[cfg(feature = "software-crypto")]
1270 #[test]
1271 fn derive_named_channel_key_is_deterministic() {
1272 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1273 let k1 = engine.derive_named_channel_key("lobby").unwrap();
1274 let k2 = engine.derive_named_channel_key("lobby").unwrap();
1275 let k3 = engine.derive_named_channel_key("other").unwrap();
1276 assert_eq!(k1, k2);
1277 assert_ne!(k1, k3);
1278 assert_ne!(k1, ChannelKey([0u8; 32]));
1279 }
1280
1281 #[cfg(feature = "software-crypto")]
1282 #[test]
1283 fn derive_named_channel_key_folds_ascii_case() {
1284 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1285 let lower = engine.derive_named_channel_key("public").unwrap();
1286 assert_eq!(engine.derive_named_channel_key("Public").unwrap(), lower);
1287 assert_eq!(engine.derive_named_channel_key("PUBLIC").unwrap(), lower);
1288 assert_eq!(
1290 engine.derive_named_channel_key("Mesh-42!").unwrap(),
1291 engine.derive_named_channel_key("mesh-42!").unwrap()
1292 );
1293 }
1294
1295 #[cfg(feature = "software-crypto")]
1296 #[test]
1297 fn derive_named_channel_key_rejects_invalid_names() {
1298 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1299 assert_eq!(
1300 engine.derive_named_channel_key("caf\u{e9}"),
1301 Err(ChannelNameError::NotAscii)
1302 );
1303 let too_long = "x".repeat(MAX_CHANNEL_NAME_LEN + 1);
1304 assert_eq!(
1305 engine.derive_named_channel_key(&too_long),
1306 Err(ChannelNameError::TooLong)
1307 );
1308 let at_limit = "x".repeat(MAX_CHANNEL_NAME_LEN);
1310 assert!(engine.derive_named_channel_key(&at_limit).is_ok());
1311 }
1312
1313 #[cfg(feature = "software-crypto")]
1316 #[test]
1317 fn software_identity_generate_produces_valid_key() {
1318 use rand_core::{CryptoRng, RngCore};
1319
1320 struct CounterRng(u64);
1323 impl RngCore for CounterRng {
1324 fn next_u32(&mut self) -> u32 {
1325 self.next_u64() as u32
1326 }
1327 fn next_u64(&mut self) -> u64 {
1328 self.0 = self.0.wrapping_add(1);
1329 self.0
1330 }
1331 fn fill_bytes(&mut self, dest: &mut [u8]) {
1332 for chunk in dest.chunks_mut(8) {
1333 let bytes = self.next_u64().to_le_bytes();
1334 chunk.copy_from_slice(&bytes[..chunk.len()]);
1335 }
1336 }
1337 fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
1338 self.fill_bytes(dest);
1339 Ok(())
1340 }
1341 }
1342 impl CryptoRng for CounterRng {}
1343
1344 let mut rng1 = CounterRng(0);
1345 let mut rng2 = CounterRng(1000);
1346 let id1 = SoftwareIdentity::generate(&mut rng1);
1347 let id2 = SoftwareIdentity::generate(&mut rng2);
1348 assert_ne!(id1.public_key(), id2.public_key());
1349 let ss1 = id1.shared_secret_with(id2.public_key()).unwrap();
1350 let ss2 = id2.shared_secret_with(id1.public_key()).unwrap();
1351 assert_eq!(ss1.0, ss2.0);
1352 }
1353
1354 #[cfg(feature = "software-crypto")]
1357 #[test]
1358 fn seal_blind_packet_rejects_non_blind_packet() {
1359 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1360 let shared = SharedSecret([0x11u8; 32]);
1361 let pairwise = engine.derive_pairwise_keys(&shared);
1362 let channel_key = ChannelKey([0x5Au8; 32]);
1363 let channel = engine.derive_channel_keys(&channel_key);
1364 let blind_keys = engine.derive_blind_keys(&pairwise, &channel);
1365 let src = PublicKey([0xA1u8; 32]);
1366 let dst = NodeHint([0xC3, 0xD4, 0x25]);
1367 let mut buf = [0u8; 128];
1368 let mut packet = PacketBuilder::new(&mut buf)
1370 .unicast(dst)
1371 .source_full(&src)
1372 .frame_counter(1)
1373 .encrypted()
1374 .mic_size(MicSize::Mic16)
1375 .payload(b"test")
1376 .build()
1377 .unwrap();
1378 assert!(matches!(
1379 engine.seal_blind_packet(&mut packet, &blind_keys, &channel),
1380 Err(CryptoError::InvalidPacket)
1381 ));
1382 }
1383
1384 #[cfg(feature = "software-crypto")]
1387 #[test]
1388 fn open_packet_fails_on_tampered_ciphertext() {
1389 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1390 let keys = engine.derive_pairwise_keys(&SharedSecret([9u8; 32]));
1391 let src = PublicKey([0xA1; 32]);
1392 let dst = NodeHint([0xC3, 0xD4, 0x25]);
1393 let mut buf = [0u8; 128];
1394 let mut packet = PacketBuilder::new(&mut buf)
1395 .unicast(dst)
1396 .source_full(&src)
1397 .frame_counter(1)
1398 .encrypted()
1399 .mic_size(MicSize::Mic16)
1400 .payload(b"hello")
1401 .build()
1402 .unwrap();
1403 engine.seal_packet(&mut packet, &keys).unwrap();
1404
1405 let mut wire = packet.as_bytes().to_vec();
1407 let header = PacketHeader::parse(&wire).unwrap();
1408 wire[header.body_range.start] ^= 0x01;
1409
1410 assert!(matches!(
1411 engine.open_packet(&mut wire, &header, &keys),
1412 Err(CryptoError::AuthenticationFailed)
1413 ));
1414 }
1415
1416 #[cfg(feature = "software-crypto")]
1419 #[test]
1420 fn decrypt_blind_addr_encrypted_hint_source() {
1421 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
1422 let shared = SharedSecret([0x33u8; 32]);
1423 let pairwise = engine.derive_pairwise_keys(&shared);
1424 let channel_key = ChannelKey([0x5Au8; 32]);
1425 let channel = engine.derive_channel_keys(&channel_key);
1426 let blind_keys = engine.derive_blind_keys(&pairwise, &channel);
1427 let src = PublicKey([0xA1u8; 32]);
1428 let dst = NodeHint([0xC3, 0xD4, 0x25]);
1429 let mut buf = [0u8; 128];
1430 let mut packet = PacketBuilder::new(&mut buf)
1432 .blind_unicast(channel.channel_id, dst)
1433 .source_hint(src.hint())
1434 .frame_counter(5)
1435 .encrypted()
1436 .mic_size(MicSize::Mic16)
1437 .payload(b"hi")
1438 .build()
1439 .unwrap();
1440 engine
1441 .seal_blind_packet(&mut packet, &blind_keys, &channel)
1442 .unwrap();
1443 let mut wire = packet.as_bytes().to_vec();
1444 let header = PacketHeader::parse(&wire).unwrap();
1445 let (decoded_dst, decoded_src) = engine
1446 .decrypt_blind_addr(&mut wire, &header, &channel)
1447 .unwrap();
1448 assert_eq!(decoded_dst, dst);
1449 assert_eq!(decoded_src, SourceAddrRef::Hint(src.hint()));
1450 }
1451
1452 #[cfg(feature = "software-crypto")]
1455 #[test]
1456 fn aes_decrypt_block_inverts_encrypt_block() {
1457 let key = hex_16("2b7e151628aed2a6abf7158809cf4f3c");
1458 let cipher = SoftwareAes.new_cipher(&key);
1459 let original = hex_16("6bc1bee22e409f96e93d7e117393172a");
1460 let mut block = original;
1461 cipher.encrypt_block(&mut block);
1462 assert_ne!(block, original);
1463 cipher.decrypt_block(&mut block);
1464 assert_eq!(block, original);
1465 }
1466
1467 #[cfg(feature = "software-crypto")]
1470 #[test]
1471 fn sign_and_agree_via_trait() {
1472 use core::future::Future;
1473 use core::pin::pin;
1474 use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
1475
1476 fn block_on<F: Future>(future: F) -> F::Output {
1477 let raw = RawWaker::new(
1478 core::ptr::null(),
1479 &RawWakerVTable::new(|_| panic!(), |_| {}, |_| {}, |_| {}),
1480 );
1481 let waker = unsafe { Waker::from_raw(raw) };
1482 let mut ctx = Context::from_waker(&waker);
1483 match pin!(future).poll(&mut ctx) {
1484 Poll::Ready(v) => v,
1485 Poll::Pending => panic!("future unexpectedly pending"),
1486 }
1487 }
1488
1489 let alice = SoftwareIdentity::from_secret_bytes(&[0x11u8; 32]);
1490 let bob = SoftwareIdentity::from_secret_bytes(&[0x22u8; 32]);
1491
1492 let sig = block_on(alice.sign(b"test message")).unwrap();
1493 assert_eq!(sig.len(), 64);
1494
1495 let shared = block_on(alice.agree(bob.public_key())).unwrap();
1496 assert_ne!(shared.0, [0u8; 32]);
1497 }
1498
1499 #[cfg(feature = "software-crypto")]
1502 #[test]
1503 fn node_identity_hint_matches_public_key_hint() {
1504 let id = SoftwareIdentity::from_secret_bytes(&[0x11u8; 32]);
1505 assert_eq!(NodeIdentity::hint(&id), id.public_key().hint());
1507 }
1508
1509 #[test]
1512 fn hex_vec_accepts_uppercase() {
1513 assert_eq!(hex_vec("DEADBEEF"), hex_vec("deadbeef"));
1514 assert_eq!(hex_vec("0A1B2C3D"), hex_vec("0a1b2c3d"));
1515 }
1516
1517 fn hex_vec(input: &str) -> std::vec::Vec<u8> {
1518 assert_eq!(input.len() % 2, 0);
1519 let mut out = std::vec::Vec::with_capacity(input.len() / 2);
1520 let bytes = input.as_bytes();
1521 for index in (0..bytes.len()).step_by(2) {
1522 out.push((decode_hex(bytes[index]) << 4) | decode_hex(bytes[index + 1]));
1523 }
1524 out
1525 }
1526
1527 fn hex_16(input: &str) -> [u8; 16] {
1528 let bytes = hex_vec(input);
1529 let mut out = [0u8; 16];
1530 out.copy_from_slice(&bytes);
1531 out
1532 }
1533
1534 #[cfg(feature = "software-crypto")]
1535 fn hex_32(input: &str) -> [u8; 32] {
1536 let bytes = hex_vec(input);
1537 let mut out = [0u8; 32];
1538 out.copy_from_slice(&bytes);
1539 out
1540 }
1541
1542 fn decode_hex(byte: u8) -> u8 {
1543 match byte {
1544 b'0'..=b'9' => byte - b'0',
1545 b'a'..=b'f' => byte - b'a' + 10,
1546 b'A'..=b'F' => byte - b'A' + 10,
1547 _ => panic!("invalid hex"),
1548 }
1549 }
1550
1551 #[cfg(feature = "software-crypto")]
1552 #[test]
1553 fn is_valid_ed25519_public_key_accepts_real_key() {
1554 let identity = SoftwareIdentity::from_secret_bytes(&[0x11; 32]);
1557 assert!(is_valid_ed25519_public_key(identity.public_key()));
1558 }
1559
1560 #[cfg(feature = "software-crypto")]
1561 #[test]
1562 fn is_valid_ed25519_public_key_rejects_non_curve_bytes() {
1563 let mut bytes = [0u8; 32];
1569 bytes[0] = 2;
1570 let bogus = PublicKey(bytes);
1571 assert!(!is_valid_ed25519_public_key(&bogus));
1572 }
1573
1574 #[cfg(feature = "software-crypto")]
1575 #[test]
1576 fn is_valid_ed25519_public_key_handles_all_zero() {
1577 let zero = PublicKey([0u8; 32]);
1582 assert!(is_valid_ed25519_public_key(&zero));
1583 }
1584}