1use aes::Aes128;
20use aes::cipher::{Array, BlockCipherDecrypt, BlockCipherEncrypt, KeyInit};
21
22use crate::types::{
23 AppEui, AppKey, AppNonce, AppSKey, DevAddr, DevEui, DevNonce, FNwkSIntKey, JSEncKey, JSIntKey, NetId, NwkKey,
24 NwkSEncKey, NwkSKey, RootWorSKey, SNwkSIntKey, WorSEncKey, WorSIntKey,
25};
26
27pub fn aes_ecb_encrypt(block: &[u8; 16], key: &[u8; 16]) -> [u8; 16] {
57 let cipher = Aes128::new(&Array::from(*key));
58 let mut buf = Array::from(*block);
59 cipher.encrypt_block(&mut buf);
60 buf.into()
61}
62
63pub fn aes_ecb_decrypt(block: &[u8; 16], key: &[u8; 16]) -> [u8; 16] {
69 let cipher = Aes128::new(&Array::from(*key));
70 let mut buf = Array::from(*block);
71 cipher.decrypt_block(&mut buf);
72 buf.into()
73}
74
75#[derive(Debug, Clone)]
85pub struct SessionKeys10 {
86 pub app_s_key: AppSKey,
88 pub nwk_s_key: NwkSKey,
90}
91
92impl SessionKeys10 {
93 #[allow(clippy::trivially_copy_pass_by_ref)]
116 pub fn derive(app_key: &AppKey, net_id: &NetId, app_nonce: &AppNonce, dev_nonce: &DevNonce) -> Self {
117 let app_s_key = AppSKey::new(derive_session_key_10(0x02, app_key, net_id, app_nonce, dev_nonce));
118 let nwk_s_key = NwkSKey::new(derive_session_key_10(0x01, app_key, net_id, app_nonce, dev_nonce));
119 Self { app_s_key, nwk_s_key }
120 }
121}
122
123#[allow(clippy::trivially_copy_pass_by_ref)]
124fn derive_session_key_10(
125 prefix: u8,
126 app_key: &AppKey,
127 net_id: &NetId,
128 app_nonce: &AppNonce,
129 dev_nonce: &DevNonce,
130) -> [u8; 16] {
131 let mut block = [0u8; 16];
132 block[0] = prefix;
133 let mut n = *app_nonce.as_bytes();
134 n.reverse();
135 block[1..4].copy_from_slice(&n);
136 let mut id = *net_id.as_bytes();
137 id.reverse();
138 block[4..7].copy_from_slice(&id);
139 let mut dn = *dev_nonce.as_bytes();
140 dn.reverse();
141 block[7..9].copy_from_slice(&dn);
142 aes_ecb_encrypt(&block, app_key.as_bytes())
143}
144
145#[derive(Debug, Clone)]
152pub struct SessionKeys11 {
153 pub app_s_key: AppSKey,
155 pub f_nwk_s_int_key: FNwkSIntKey,
158 pub s_nwk_s_int_key: SNwkSIntKey,
161 pub nwk_s_enc_key: NwkSEncKey,
164}
165
166impl SessionKeys11 {
167 #[allow(clippy::trivially_copy_pass_by_ref)]
188 pub fn derive(
189 app_key: &AppKey,
190 nwk_key: &NwkKey,
191 join_eui: &AppEui,
192 app_nonce: &AppNonce,
193 dev_nonce: &DevNonce,
194 ) -> Self {
195 let app_s_key = AppSKey::new(derive_session_key_11(
196 0x02,
197 app_key.as_bytes(),
198 join_eui,
199 app_nonce,
200 dev_nonce,
201 ));
202 let f_nwk_s_int_key = FNwkSIntKey::new(derive_session_key_11(
203 0x01,
204 nwk_key.as_bytes(),
205 join_eui,
206 app_nonce,
207 dev_nonce,
208 ));
209 let s_nwk_s_int_key = SNwkSIntKey::new(derive_session_key_11(
210 0x03,
211 nwk_key.as_bytes(),
212 join_eui,
213 app_nonce,
214 dev_nonce,
215 ));
216 let nwk_s_enc_key = NwkSEncKey::new(derive_session_key_11(
217 0x04,
218 nwk_key.as_bytes(),
219 join_eui,
220 app_nonce,
221 dev_nonce,
222 ));
223 Self {
224 app_s_key,
225 f_nwk_s_int_key,
226 s_nwk_s_int_key,
227 nwk_s_enc_key,
228 }
229 }
230}
231
232#[allow(clippy::trivially_copy_pass_by_ref)]
233fn derive_session_key_11(
234 prefix: u8,
235 key: &[u8; 16],
236 join_eui: &AppEui,
237 app_nonce: &AppNonce,
238 dev_nonce: &DevNonce,
239) -> [u8; 16] {
240 let mut block = [0u8; 16];
241 block[0] = prefix;
242 let mut n = *app_nonce.as_bytes();
243 n.reverse();
244 block[1..4].copy_from_slice(&n);
245 let mut e = *join_eui.as_bytes();
246 e.reverse();
247 block[4..12].copy_from_slice(&e);
248 let mut dn = *dev_nonce.as_bytes();
249 dn.reverse();
250 block[12..14].copy_from_slice(&dn);
251 aes_ecb_encrypt(&block, key)
252}
253
254#[derive(Debug, Clone)]
259pub struct JoinServerKeys {
260 pub js_int_key: JSIntKey,
262 pub js_enc_key: JSEncKey,
265}
266
267impl JoinServerKeys {
268 #[allow(clippy::trivially_copy_pass_by_ref)]
281 pub fn derive(nwk_key: &NwkKey, dev_eui: &DevEui) -> Self {
282 let mut block = [0u8; 16];
283 block[0] = 0x06;
284 let mut e = *dev_eui.as_bytes();
285 e.reverse();
286 block[1..9].copy_from_slice(&e);
287 let js_int_key = JSIntKey::new(aes_ecb_encrypt(&block, nwk_key.as_bytes()));
288 block[0] = 0x05;
289 let js_enc_key = JSEncKey::new(aes_ecb_encrypt(&block, nwk_key.as_bytes()));
290 Self { js_int_key, js_enc_key }
291 }
292}
293
294#[derive(Debug, Clone)]
300pub struct WorSessionKeys {
301 pub wor_s_int_key: WorSIntKey,
303 pub wor_s_enc_key: WorSEncKey,
305}
306
307pub struct WorKeys;
325
326impl WorKeys {
327 pub fn root(nwk_s_key: &NwkSKey) -> RootWorSKey {
331 let mut block = [0u8; 16];
332 block[0] = 0x01;
333 RootWorSKey::new(aes_ecb_encrypt(&block, nwk_s_key.as_bytes()))
334 }
335
336 #[allow(clippy::trivially_copy_pass_by_ref)]
342 pub fn session(root: &RootWorSKey, dev_addr: &DevAddr) -> WorSessionKeys {
343 let mut block = [0u8; 16];
344 block[0] = 0x01;
345 let mut a = *dev_addr.as_bytes();
346 a.reverse();
347 block[1..5].copy_from_slice(&a);
348 let wor_s_int_key = WorSIntKey::new(aes_ecb_encrypt(&block, root.as_bytes()));
349 block[0] = 0x02;
350 let wor_s_enc_key = WorSEncKey::new(aes_ecb_encrypt(&block, root.as_bytes()));
351 WorSessionKeys {
352 wor_s_int_key,
353 wor_s_enc_key,
354 }
355 }
356}
357
358impl crate::codec::Data {
359 pub fn decrypt_payload(
388 &self,
389 app_s_key: &AppSKey,
390 nwk_s_key: &NwkSKey,
391 f_cnt_msb: u16,
392 ) -> crate::Result<alloc::vec::Vec<u8>> {
393 let cipher = self.frm_payload.as_deref().unwrap_or(&[]);
394 let key = if self.f_port == Some(0) {
395 nwk_s_key.as_bytes()
396 } else {
397 app_s_key.as_bytes()
398 };
399 payload_crypt(cipher, key, self.direction, self.dev_addr, self.f_cnt_32(f_cnt_msb))
400 }
401
402 pub fn encrypt_payload(
416 &self,
417 plaintext: &[u8],
418 app_s_key: &AppSKey,
419 nwk_s_key: &NwkSKey,
420 f_cnt_msb: u16,
421 ) -> crate::Result<alloc::vec::Vec<u8>> {
422 let key = if self.f_port == Some(0) {
423 nwk_s_key.as_bytes()
424 } else {
425 app_s_key.as_bytes()
426 };
427 payload_crypt(plaintext, key, self.direction, self.dev_addr, self.f_cnt_32(f_cnt_msb))
428 }
429}
430
431fn payload_crypt(
432 input: &[u8],
433 key: &[u8; 16],
434 direction: crate::types::Direction,
435 dev_addr: DevAddr,
436 f_cnt_32: u32,
437) -> crate::Result<alloc::vec::Vec<u8>> {
438 let block_count = input.len().div_ceil(16);
442 if block_count > 255 {
443 return Err(crate::Error::PayloadTooLarge(input.len()));
444 }
445 let dir_byte = u8::from(!matches!(direction, crate::types::Direction::Uplink));
446 let mut out = alloc::vec::Vec::with_capacity(input.len());
447 let mut addr = *dev_addr.as_bytes();
448 addr.reverse();
449 for (i_chunk, chunk) in input.chunks(16).enumerate() {
450 let mut ai = [0u8; 16];
451 ai[0] = 0x01;
452 ai[5] = dir_byte;
453 ai[6..10].copy_from_slice(&addr);
454 ai[10..14].copy_from_slice(&f_cnt_32.to_le_bytes());
455 ai[15] = u8::try_from(i_chunk + 1).map_err(|_| crate::Error::PayloadTooLarge(input.len()))?;
457 let s = aes_ecb_encrypt(&ai, key);
458 for (j, b) in chunk.iter().enumerate() {
459 out.push(b ^ s[j]);
460 }
461 }
462 Ok(out)
463}
464
465impl crate::codec::Data {
466 pub fn decrypt_fopts(
480 &self,
481 nwk_s_enc_key: &crate::types::NwkSEncKey,
482 f_cnt_msb: u16,
483 ) -> crate::Result<alloc::vec::Vec<u8>> {
484 Ok(fopts_crypt(
485 &self.f_opts,
486 nwk_s_enc_key.as_bytes(),
487 self.direction,
488 self.dev_addr,
489 self.f_port,
490 self.f_cnt_32(f_cnt_msb),
491 ))
492 }
493
494 pub fn encrypt_fopts(
503 &self,
504 nwk_s_enc_key: &crate::types::NwkSEncKey,
505 f_cnt_msb: u16,
506 ) -> crate::Result<alloc::vec::Vec<u8>> {
507 Ok(fopts_crypt(
508 &self.f_opts,
509 nwk_s_enc_key.as_bytes(),
510 self.direction,
511 self.dev_addr,
512 self.f_port,
513 self.f_cnt_32(f_cnt_msb),
514 ))
515 }
516}
517
518impl crate::codec::JoinAccept {
519 pub fn decrypt_from_wire(ciphertext: &[u8], app_key: &AppKey) -> crate::Result<alloc::vec::Vec<u8>> {
548 join_accept_transform(ciphertext, app_key, aes_ecb_encrypt)
549 }
550
551 pub fn encrypt_for_wire(plaintext: &[u8], app_key: &AppKey) -> crate::Result<alloc::vec::Vec<u8>> {
563 join_accept_transform(plaintext, app_key, aes_ecb_decrypt)
564 }
565}
566
567fn join_accept_transform(
568 input: &[u8],
569 app_key: &AppKey,
570 op: fn(&[u8; 16], &[u8; 16]) -> [u8; 16],
571) -> crate::Result<alloc::vec::Vec<u8>> {
572 if input.len() != 17 && input.len() != 33 {
573 return Err(crate::Error::InvalidJoinAcceptLength(input.len()));
574 }
575 let mut out = alloc::vec::Vec::with_capacity(input.len());
576 out.push(input[0]);
577 for chunk in input[1..].chunks(16) {
578 let mut block = [0u8; 16];
579 block.copy_from_slice(chunk);
580 out.extend_from_slice(&op(&block, app_key.as_bytes()));
581 }
582 Ok(out)
583}
584
585fn fopts_crypt(
586 input: &[u8],
587 key: &[u8; 16],
588 direction: crate::types::Direction,
589 dev_addr: DevAddr,
590 f_port: Option<u8>,
591 f_cnt_32: u32,
592) -> alloc::vec::Vec<u8> {
593 let is_downlink = matches!(direction, crate::types::Direction::Downlink);
594 let dir_byte = u8::from(is_downlink);
595 let a_f_cnt_down = is_downlink && f_port.is_some_and(|p| p > 0);
596
597 let mut ai = [0u8; 16];
598 ai[0] = 0x01;
599 ai[4] = if a_f_cnt_down { 0x02 } else { 0x01 };
600 ai[5] = dir_byte;
601 let mut addr = *dev_addr.as_bytes();
602 addr.reverse();
603 ai[6..10].copy_from_slice(&addr);
604 ai[10..14].copy_from_slice(&f_cnt_32.to_le_bytes());
605 ai[15] = 0x01;
606 let s = aes_ecb_encrypt(&ai, key);
607
608 input.iter().enumerate().map(|(i, b)| b ^ s[i]).collect()
609}
610
611#[cfg(test)]
612mod tests {
613 use super::*;
614
615 #[test]
617 fn aes_ecb_encrypt_nist_vector() {
618 let key = [
619 0x2bu8, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c,
620 ];
621 let plaintext = [
622 0x32u8, 0x43, 0xf6, 0xa8, 0x88, 0x5a, 0x30, 0x8d, 0x31, 0x31, 0x98, 0xa2, 0xe0, 0x37, 0x07, 0x34,
623 ];
624 let expected = [
625 0x39u8, 0x25, 0x84, 0x1d, 0x02, 0xdc, 0x09, 0xfb, 0xdc, 0x11, 0x85, 0x97, 0x19, 0x6a, 0x0b, 0x32,
626 ];
627 assert_eq!(aes_ecb_encrypt(&plaintext, &key), expected);
628 }
629
630 #[test]
631 fn session_keys_10_distinct() {
632 let app_key = AppKey::new([
633 0x2bu8, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c,
634 ]);
635 let net_id = NetId::new([0x00, 0x00, 0x01]);
636 let app_nonce = AppNonce::new([0xC1, 0xD5, 0xEC]);
637 let dev_nonce = DevNonce::new([0xC8, 0xF8]);
638 let keys = SessionKeys10::derive(&app_key, &net_id, &app_nonce, &dev_nonce);
639 assert_ne!(keys.app_s_key.as_bytes(), keys.nwk_s_key.as_bytes());
640 let keys2 = SessionKeys10::derive(&app_key, &net_id, &app_nonce, &dev_nonce);
642 assert_eq!(keys.app_s_key.as_bytes(), keys2.app_s_key.as_bytes());
643 assert_eq!(keys.nwk_s_key.as_bytes(), keys2.nwk_s_key.as_bytes());
644 }
645
646 #[test]
647 fn session_keys_11_distinct() {
648 let app_key = AppKey::new([0x11u8; 16]);
649 let nwk_key = NwkKey::new([0x22u8; 16]);
650 let join_eui = AppEui::new([0x33u8; 8]);
651 let app_nonce = AppNonce::new([0x44, 0x55, 0x66]);
652 let dev_nonce = DevNonce::new([0x77, 0x88]);
653 let k = SessionKeys11::derive(&app_key, &nwk_key, &join_eui, &app_nonce, &dev_nonce);
654 assert_ne!(k.app_s_key.as_bytes(), k.f_nwk_s_int_key.as_bytes());
655 assert_ne!(k.f_nwk_s_int_key.as_bytes(), k.s_nwk_s_int_key.as_bytes());
656 assert_ne!(k.s_nwk_s_int_key.as_bytes(), k.nwk_s_enc_key.as_bytes());
657 }
658
659 #[test]
660 fn js_keys_distinct() {
661 let nwk_key = NwkKey::new([0x42u8; 16]);
662 let dev_eui = DevEui::new([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]);
663 let k = JoinServerKeys::derive(&nwk_key, &dev_eui);
664 assert_ne!(k.js_int_key.as_bytes(), k.js_enc_key.as_bytes());
665 }
666
667 #[test]
668 fn wor_root_key_deterministic() {
669 let nwk = NwkSKey::new([0x00u8; 16]);
670 let r1 = WorKeys::root(&nwk);
671 let r2 = WorKeys::root(&nwk);
672 assert_eq!(r1.as_bytes(), r2.as_bytes());
673 }
674
675 #[test]
676 fn wor_session_keys_distinct() {
677 let nwk = NwkSKey::new([0x33u8; 16]);
678 let root = WorKeys::root(&nwk);
679 let dev = DevAddr::new([0x01, 0x02, 0x03, 0x04]);
680 let s = WorKeys::session(&root, &dev);
681 assert_ne!(s.wor_s_int_key.as_bytes(), s.wor_s_enc_key.as_bytes());
682 }
683
684 use crate::codec::LoraPacket;
685 use alloc::vec::Vec;
686
687 fn hex_to_vec(s: &str) -> Vec<u8> {
688 (0..s.len())
689 .step_by(2)
690 .map(|i| u8::from_str_radix(&s[i..i + 2], 16).expect("valid hex"))
691 .collect()
692 }
693
694 fn hex_to_arr_16(s: &str) -> [u8; 16] {
695 let mut arr = [0u8; 16];
696 for (i, byte) in (0..s.len()).step_by(2).enumerate() {
697 arr[i] = u8::from_str_radix(&s[byte..byte + 2], 16).unwrap();
698 }
699 arr
700 }
701
702 #[test]
704 fn decrypt_payload_test_text() {
705 let bytes = hex_to_vec("40f17dbe4900020001954378762b11ff0d");
706 let packet = LoraPacket::from_wire(&bytes).unwrap();
707 let data = packet.as_data().unwrap();
708 let app_s_key = AppSKey::new(hex_to_arr_16("ec925802ae430ca77fd3dd73cb2cc588"));
709 let nwk_s_key = NwkSKey::new([0u8; 16]);
710 let plain = data.decrypt_payload(&app_s_key, &nwk_s_key, 0).unwrap();
711 assert_eq!(plain, b"test");
712 }
713
714 #[test]
716 fn encrypt_then_decrypt_round_trip() {
717 let bytes = hex_to_vec("40f17dbe4900020001954378762b11ff0d");
718 let packet = LoraPacket::from_wire(&bytes).unwrap();
719 let data = packet.as_data().unwrap();
720 let app_s_key = AppSKey::new(hex_to_arr_16("ec925802ae430ca77fd3dd73cb2cc588"));
721 let nwk_s_key = NwkSKey::new([0u8; 16]);
722 let plain = b"hello world!";
723 let ct = data.encrypt_payload(plain, &app_s_key, &nwk_s_key, 0).unwrap();
724 assert_ne!(ct, plain);
725 let mut clone = data.clone();
726 clone.frm_payload = Some(ct);
727 let decrypted = clone.decrypt_payload(&app_s_key, &nwk_s_key, 0).unwrap();
728 assert_eq!(decrypted, plain);
729 }
730
731 #[test]
735 fn encrypt_fopts_1_1_vector() {
736 use crate::codec::Data;
737 use crate::types::{Direction, FCtrl, NwkSEncKey};
738
739 let data = Data {
740 direction: Direction::Downlink,
741 confirmed: false,
742 dev_addr: DevAddr::new([0x01, 0x02, 0x03, 0x04]),
743 f_ctrl: FCtrl(0x03),
744 f_cnt: [0x00, 0x00],
745 f_opts: alloc::vec![0x02, 0x07, 0x01],
746 f_port: Some(1),
747 frm_payload: Some(alloc::vec![0x01, 0x02, 0x03, 0x04]),
748 };
749 let nwk_s_enc_key = NwkSEncKey::new([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0]);
750 let encrypted = data.encrypt_fopts(&nwk_s_enc_key, 0).unwrap();
751 assert_eq!(encrypted, [0x22, 0xac, 0x0a]);
752
753 let mut clone = data;
754 clone.f_opts = encrypted;
755 let decrypted = clone.decrypt_fopts(&nwk_s_enc_key, 0).unwrap();
756 assert_eq!(decrypted, [0x02, 0x07, 0x01]);
757 }
758
759 #[test]
762 fn join_accept_encrypt_zero_app_key() {
763 let app_key = AppKey::new([0u8; 16]);
764 let plaintext = hex_to_vec("20000000000000000000000000f86f0a91");
765 let encrypted = crate::codec::JoinAccept::encrypt_for_wire(&plaintext, &app_key).unwrap();
766 let expected = hex_to_vec("20e3de108795f776b8037610ef7869b5b3");
767 assert_eq!(encrypted, expected);
768 }
769
770 #[test]
771 fn payload_crypt_rejects_oversize() {
772 use crate::codec::Data;
776 use crate::types::{Direction, FCtrl};
777
778 let huge_plaintext = alloc::vec![0u8; 16 * 256];
779 let data = Data {
780 direction: Direction::Uplink,
781 confirmed: false,
782 dev_addr: DevAddr::new([0u8; 4]),
783 f_ctrl: FCtrl(0),
784 f_cnt: [0, 0],
785 f_opts: alloc::vec![],
786 f_port: Some(1),
787 frm_payload: None,
788 };
789 let app_s_key = AppSKey::new([0u8; 16]);
790 let nwk_s_key = NwkSKey::new([0u8; 16]);
791 let err = data
792 .encrypt_payload(&huge_plaintext, &app_s_key, &nwk_s_key, 0)
793 .unwrap_err();
794 assert!(matches!(err, crate::Error::PayloadTooLarge(n) if n == 16 * 256));
795
796 let max_plaintext = alloc::vec![0u8; 16 * 255];
798 let ok = data.encrypt_payload(&max_plaintext, &app_s_key, &nwk_s_key, 0).unwrap();
799 assert_eq!(ok.len(), 16 * 255);
800 }
801
802 #[test]
805 fn join_accept_decrypt_round_trip() {
806 let app_key = AppKey::new([0u8; 16]);
807 let plaintext = hex_to_vec("20000000000000000000000000f86f0a91");
808 let encrypted = crate::codec::JoinAccept::encrypt_for_wire(&plaintext, &app_key).unwrap();
809 let decrypted = crate::codec::JoinAccept::decrypt_from_wire(&encrypted, &app_key).unwrap();
810 assert_eq!(decrypted, plaintext);
811 }
812}