Skip to content

Commit eff371f

Browse files
committed
Add 4 byte MIC signature to each mesh payload.
1 parent e6d4fe2 commit eff371f

14 files changed

+365
-84
lines changed

Cargo.lock

Lines changed: 59 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
futures = "0.3"
4141
prost-types = "0.12"
4242
zmq = "0.10"
43+
cmac = { version = "0.7" }
44+
aes = { version = "0.8" }
4345

4446
[dev-dependencies]
4547
zeromq = "0.3.5"
@@ -52,24 +54,24 @@
5254
codegen-units = 1
5355
panic = "abort"
5456

55-
# Debian packaging.
56-
[package.metadata.deb]
57-
assets = [
58-
[
59-
"target/release/chirpstack-gateway-mesh",
60-
"usr/bin/",
61-
"755",
62-
],
63-
[
64-
"configuration/*.toml",
65-
"etc/chirpstack-gateway-mesh/",
66-
"640",
67-
],
68-
]
69-
conf-files = [
70-
"/etc/chirpstack-gateway-mesh/chirpstack-gateway-mesh.toml",
71-
"/etc/chirpstack-gateway-mesh/region_eu868.toml",
72-
"/etc/chirpstack-gateway-mesh/region_us915.toml",
73-
]
74-
maintainer-scripts = "packaging/debian/"
75-
systemd-units = { enable = true }
57+
# Debian packaging.
58+
[package.metadata.deb]
59+
assets = [
60+
[
61+
"target/release/chirpstack-gateway-mesh",
62+
"usr/bin/",
63+
"755",
64+
],
65+
[
66+
"configuration/*.toml",
67+
"etc/chirpstack-gateway-mesh/",
68+
"640",
69+
],
70+
]
71+
conf-files = [
72+
"/etc/chirpstack-gateway-mesh/chirpstack-gateway-mesh.toml",
73+
"/etc/chirpstack-gateway-mesh/region_eu868.toml",
74+
"/etc/chirpstack-gateway-mesh/region_us915.toml",
75+
]
76+
maintainer-scripts = "packaging/debian/"
77+
systemd-units = { enable = true }

shell.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{ pkgs ? import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/nixos-23.11.tar.gz") {} }:
1+
{ pkgs ? import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/nixos-24.05.tar.gz") {} }:
22

33
pkgs.mkShell {
44
buildInputs = [

src/aes128.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
use std::fmt;
2+
use std::str::FromStr;
3+
4+
use anyhow::{Error, Result};
5+
use serde::{
6+
de::{self, Visitor},
7+
Deserialize, Deserializer, Serialize, Serializer,
8+
};
9+
10+
#[derive(Copy, Clone, PartialEq, Eq, Default)]
11+
pub struct Aes128Key([u8; 16]);
12+
13+
impl Aes128Key {
14+
pub fn null() -> Self {
15+
Aes128Key([0; 16])
16+
}
17+
18+
pub fn from_slice(b: &[u8]) -> Result<Self, Error> {
19+
if b.len() != 16 {
20+
return Err(anyhow!("16 bytes are expected"));
21+
}
22+
23+
let mut bb: [u8; 16] = [0; 16];
24+
bb.copy_from_slice(b);
25+
26+
Ok(Aes128Key(bb))
27+
}
28+
29+
pub fn from_bytes(b: [u8; 16]) -> Self {
30+
Aes128Key(b)
31+
}
32+
33+
pub fn to_bytes(&self) -> [u8; 16] {
34+
self.0
35+
}
36+
37+
pub fn to_vec(&self) -> Vec<u8> {
38+
self.0.to_vec()
39+
}
40+
}
41+
42+
impl fmt::Display for Aes128Key {
43+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44+
write!(f, "{}", hex::encode(self.0))
45+
}
46+
}
47+
48+
impl fmt::Debug for Aes128Key {
49+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50+
write!(f, "{}", hex::encode(self.0))
51+
}
52+
}
53+
54+
impl FromStr for Aes128Key {
55+
type Err = Error;
56+
57+
fn from_str(s: &str) -> Result<Self, Self::Err> {
58+
let mut bytes: [u8; 16] = [0; 16];
59+
hex::decode_to_slice(s, &mut bytes)?;
60+
Ok(Aes128Key(bytes))
61+
}
62+
}
63+
64+
impl Serialize for Aes128Key {
65+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
66+
where
67+
S: Serializer,
68+
{
69+
serializer.serialize_str(&self.to_string())
70+
}
71+
}
72+
73+
impl<'de> Deserialize<'de> for Aes128Key {
74+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
75+
where
76+
D: Deserializer<'de>,
77+
{
78+
deserializer.deserialize_str(Aes128KeyVisitor)
79+
}
80+
}
81+
82+
struct Aes128KeyVisitor;
83+
84+
impl<'de> Visitor<'de> for Aes128KeyVisitor {
85+
type Value = Aes128Key;
86+
87+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
88+
formatter.write_str("A hex encoded AES key of 128 bit is expected")
89+
}
90+
91+
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
92+
where
93+
E: de::Error,
94+
{
95+
Aes128Key::from_str(value).map_err(|e| E::custom(format!("{}", e)))
96+
}
97+
}

src/cmd/configfile.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ pub fn run() {
2525
2626
# Mesh configuration.
2727
[mesh]
28+
# Signing key (AES128, HEX encoded).
29+
#
30+
# This key is used to sign and validate each mesh packet. This key must be
31+
# configured on every Border / Relay gateway equally.
32+
signing_key="{{ mesh.signing_key }}"
2833
2934
# Border Gateway.
3035
#

src/config.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ use once_cell::sync::OnceCell;
66
use serde::de::Error;
77
use serde::{Deserialize, Deserializer, Serialize, Serializer};
88

9+
use crate::aes128::Aes128Key;
10+
911
static CONFIG: OnceCell<Mutex<Arc<Configuration>>> = OnceCell::new();
1012

1113
#[derive(Serialize, Deserialize, Default)]
@@ -48,6 +50,7 @@ impl Default for Logging {
4850
#[derive(Serialize, Deserialize)]
4951
#[serde(default)]
5052
pub struct Mesh {
53+
pub signing_key: Aes128Key,
5154
pub frequencies: Vec<u32>,
5255
pub data_rate: DataRate,
5356
pub tx_power: i32,
@@ -61,6 +64,7 @@ pub struct Mesh {
6164
impl Default for Mesh {
6265
fn default() -> Self {
6366
Mesh {
67+
signing_key: Aes128Key::null(),
6468
frequencies: vec![868100000, 868300000, 868500000],
6569
data_rate: DataRate {
6670
modulation: Modulation::LORA,

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#[macro_use]
22
extern crate anyhow;
33

4+
pub mod aes128;
45
pub mod backend;
56
pub mod cache;
67
pub mod cmd;

src/mesh.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,12 @@ pub async fn handle_uplink(border_gateway: bool, pl: gw::UplinkFrame) -> Result<
3636

3737
// Handle Proprietary LoRaWAN payload (mesh encapsulated).
3838
pub async fn handle_mesh(border_gateway: bool, pl: gw::UplinkFrame) -> Result<()> {
39+
let conf = config::get();
3940
let packet = MeshPacket::from_slice(&pl.phy_payload)?;
41+
if !packet.validate_mic(conf.mesh.signing_key)? {
42+
warn!("Dropping packet, invalid MIC, mesh_packet: {}", packet);
43+
return Ok(());
44+
}
4045

4146
// If we can't add the packet to the cache, it means we have already seen the packet and we can
4247
// drop it.
@@ -205,6 +210,7 @@ async fn relay_mesh_packet(_: &gw::UplinkFrame, mut packet: MeshPacket) -> Resul
205210

206211
// Increment hop count.
207212
packet.mhdr.hop_count += 1;
213+
packet.set_mic(conf.mesh.signing_key)?;
208214

209215
if packet.mhdr.hop_count > conf.mesh.max_hop_count {
210216
return Err(anyhow!("Max hop count exceeded"));
@@ -256,7 +262,7 @@ async fn relay_uplink_lora_packet(pl: &gw::UplinkFrame) -> Result<()> {
256262
.as_ref()
257263
.ok_or_else(|| anyhow!("modulation is None"))?;
258264

259-
let packet = MeshPacket {
265+
let mut packet = MeshPacket {
260266
mhdr: MHDR {
261267
payload_type: PayloadType::Uplink,
262268
hop_count: 1,
@@ -272,7 +278,9 @@ async fn relay_uplink_lora_packet(pl: &gw::UplinkFrame) -> Result<()> {
272278
relay_id: backend::get_relay_id().await?,
273279
phy_payload: pl.phy_payload.clone(),
274280
}),
281+
mic: None,
275282
};
283+
packet.set_mic(conf.mesh.signing_key)?;
276284

277285
let pl = gw::DownlinkFrame {
278286
downlink_id: random(),
@@ -345,7 +353,7 @@ async fn relay_downlink_lora_packet(pl: &gw::DownlinkFrame) -> Result<gw::Downli
345353
.get(CTX_PREFIX.len()..CTX_PREFIX.len() + 6)
346354
.ok_or_else(|| anyhow!("context does not contain enough bytes"))?;
347355

348-
let packet = packets::MeshPacket {
356+
let mut packet = packets::MeshPacket {
349357
mhdr: packets::MHDR {
350358
payload_type: packets::PayloadType::Downlink,
351359
hop_count: 1,
@@ -369,7 +377,9 @@ async fn relay_downlink_lora_packet(pl: &gw::DownlinkFrame) -> Result<gw::Downli
369377
delay,
370378
},
371379
}),
380+
mic: None,
372381
};
382+
packet.set_mic(conf.mesh.signing_key)?;
373383

374384
let pl = gw::DownlinkFrame {
375385
downlink_id: pl.downlink_id,

0 commit comments

Comments
 (0)