Skip to content

Commit fa13744

Browse files
committed
Implement encryption of event / command payloads.
This implements the encryption of event and command payloads, to avoid the exposure of possible sensitive data (e.g. configuration parameters). This also adds a new root_key configuration, which will be used to derrive the keys for mic calculation and encryption. In case the signing_key configuration is present, then this key will be used for mic calculation (for backwards compatibility).
1 parent 2419a0a commit fa13744

18 files changed

+394
-41
lines changed

src/aes128.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
use std::fmt;
22
use std::str::FromStr;
33

4+
use aes::{
5+
cipher::{generic_array::GenericArray, BlockEncrypt, KeyInit},
6+
Aes128, Block,
7+
};
48
use anyhow::{Error, Result};
59
use serde::{
610
de::{self, Visitor},
@@ -95,3 +99,26 @@ impl Visitor<'_> for Aes128KeyVisitor {
9599
Aes128Key::from_str(value).map_err(|e| E::custom(format!("{}", e)))
96100
}
97101
}
102+
103+
pub fn get_signing_key(key: Aes128Key) -> Aes128Key {
104+
let b: [u8; 16] = [0; 16];
105+
get_key(key, b)
106+
}
107+
108+
pub fn get_encryption_key(key: Aes128Key) -> Aes128Key {
109+
let mut b: [u8; 16] = [0; 16];
110+
b[0] = 0x01;
111+
get_key(key, b)
112+
}
113+
114+
fn get_key(key: Aes128Key, b: [u8; 16]) -> Aes128Key {
115+
let key_bytes = key.to_bytes();
116+
let key = GenericArray::from_slice(&key_bytes);
117+
let cipher = Aes128::new(key);
118+
119+
let mut b = b;
120+
let block = Block::from_mut_slice(&mut b);
121+
cipher.encrypt_block(block);
122+
123+
Aes128Key(b)
124+
}

src/cmd/configfile.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,19 @@ pub fn run() {
2424
2525
# Mesh configuration.
2626
[mesh]
27-
# Signing key (AES128, HEX encoded).
27+
# Mesh root key (AES128, HEX encoded).
28+
#
29+
# This key is used to derive the signing and encryption keys. The same key
30+
# must be configured on every Border and Relay gateway.
31+
root_key="{{ mesh.root_key }}"
32+
33+
# Signing key (AES128, HEX encoded) (deprecated).
2834
#
2935
# This key is used to sign and validate each mesh packet. This key must be
3036
# configured on every Border / Relay gateway equally.
37+
#
38+
# Deprecation note: If set, the signing key will not be derrived from the
39+
# above root_key, but this key will be used.
3140
signing_key="{{ mesh.signing_key }}"
3241
3342
# Border Gateway.

src/commands.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ pub async fn execute_commands(pl: &packets::CommandPayload) -> Result<Vec<packet
5252
for cmd in &pl.commands {
5353
let resp = match cmd {
5454
packets::Command::Proprietary((t, v)) => execute_proprietary(*t, v).await,
55+
packets::Command::Encrypted(_) => panic!("Commands must be decrypted first"),
5556
};
5657

5758
match resp {

src/config.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ impl Default for Logging {
5858
#[derive(Serialize, Deserialize)]
5959
#[serde(default)]
6060
pub struct Mesh {
61+
pub root_key: Aes128Key,
6162
pub signing_key: Aes128Key,
6263
pub frequencies: Vec<u32>,
6364
pub data_rate: DataRate,
@@ -72,6 +73,7 @@ pub struct Mesh {
7273
impl Default for Mesh {
7374
fn default() -> Self {
7475
Mesh {
76+
root_key: Aes128Key::null(),
7577
signing_key: Aes128Key::null(),
7678
frequencies: vec![868100000, 868300000, 868500000],
7779
data_rate: DataRate {

src/events.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use tokio::process::Command;
99
use tokio::sync::OnceCell;
1010
use tokio::time::sleep;
1111

12+
use crate::aes128::{get_encryption_key, get_signing_key, Aes128Key};
1213
use crate::backend;
1314
use crate::config::{self, Configuration};
1415
use crate::helpers;
@@ -133,7 +134,8 @@ pub async fn send_events(events: Vec<packets::Event>) -> Result<()> {
133134
}),
134135
mic: None,
135136
};
136-
packet.set_mic(conf.mesh.signing_key)?;
137+
packet.encrypt(get_encryption_key(Aes128Key::null()))?;
138+
packet.set_mic(get_signing_key(conf.mesh.signing_key))?;
137139

138140
let pl = gw::DownlinkFrame {
139141
downlink_id: random(),

src/mesh.rs

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use log::{info, trace, warn};
88
use rand::random;
99

1010
use crate::{
11+
aes128::{get_encryption_key, get_signing_key, Aes128Key},
1112
backend,
1213
cache::{Cache, PayloadCache},
1314
commands,
@@ -39,8 +40,12 @@ pub async fn handle_uplink(border_gateway: bool, pl: &gw::UplinkFrame) -> Result
3940
// Handle Proprietary LoRaWAN payload (mesh encapsulated).
4041
pub async fn handle_mesh(border_gateway: bool, pl: &gw::UplinkFrame) -> Result<()> {
4142
let conf = config::get();
42-
let packet = MeshPacket::from_slice(&pl.phy_payload)?;
43-
if !packet.validate_mic(conf.mesh.signing_key)? {
43+
let mut packet = MeshPacket::from_slice(&pl.phy_payload)?;
44+
if !packet.validate_mic(if conf.mesh.signing_key != Aes128Key::null() {
45+
conf.mesh.signing_key
46+
} else {
47+
get_signing_key(conf.mesh.root_key)
48+
})? {
4449
warn!("Dropping packet, invalid MIC, mesh_packet: {}", packet);
4550
return Ok(());
4651
}
@@ -55,6 +60,9 @@ pub async fn handle_mesh(border_gateway: bool, pl: &gw::UplinkFrame) -> Result<(
5560
return Ok(());
5661
};
5762

63+
// Decrypt the packet (in case it contains an encrypted payload).
64+
packet.decrypt(get_encryption_key(conf.mesh.root_key))?;
65+
5866
match border_gateway {
5967
// Proxy relayed uplink
6068
true => match packet.mhdr.payload_type {
@@ -113,7 +121,12 @@ pub async fn send_mesh_command(pl: gw::MeshCommand) -> Result<()> {
113121
}),
114122
mic: None,
115123
};
116-
packet.set_mic(conf.mesh.signing_key)?;
124+
packet.encrypt(get_encryption_key(conf.mesh.root_key))?;
125+
packet.set_mic(if conf.mesh.signing_key != Aes128Key::null() {
126+
conf.mesh.signing_key
127+
} else {
128+
get_signing_key(conf.mesh.root_key)
129+
})?;
117130

118131
let pl = gw::DownlinkFrame {
119132
downlink_id: random(),
@@ -267,6 +280,7 @@ async fn proxy_event_mesh_packet(pl: &gw::UplinkFrame, packet: MeshPacket) -> Re
267280
payload: v.1.clone(),
268281
})
269282
}
283+
Event::Encrypted(_) => panic!("Events must be decrypted first"),
270284
}),
271285
})
272286
.collect(),
@@ -375,9 +389,16 @@ async fn relay_mesh_packet(pl: &gw::UplinkFrame, mut packet: MeshPacket) -> Resu
375389
// Increment hop count.
376390
packet.mhdr.hop_count += 1;
377391

392+
// Encrypt.
393+
packet.encrypt(get_encryption_key(conf.mesh.root_key))?;
394+
378395
// We need to re-set the MIC as we have changed the payload by incrementing
379396
// the hop count (and in casee of heartbeat, we have modified the Relay path).
380-
packet.set_mic(conf.mesh.signing_key)?;
397+
packet.set_mic(if conf.mesh.signing_key != Aes128Key::null() {
398+
conf.mesh.signing_key
399+
} else {
400+
get_signing_key(conf.mesh.root_key)
401+
})?;
381402

382403
if packet.mhdr.hop_count > conf.mesh.max_hop_count {
383404
return Err(anyhow!("Max hop count exceeded"));
@@ -447,7 +468,11 @@ async fn relay_uplink_lora_packet(pl: &gw::UplinkFrame) -> Result<()> {
447468
}),
448469
mic: None,
449470
};
450-
packet.set_mic(conf.mesh.signing_key)?;
471+
packet.set_mic(if conf.mesh.signing_key != Aes128Key::null() {
472+
conf.mesh.signing_key
473+
} else {
474+
get_signing_key(conf.mesh.root_key)
475+
})?;
451476

452477
let pl = gw::DownlinkFrame {
453478
downlink_id: random(),
@@ -546,7 +571,11 @@ async fn relay_downlink_lora_packet(pl: &gw::DownlinkFrame) -> Result<gw::Downli
546571
}),
547572
mic: None,
548573
};
549-
packet.set_mic(conf.mesh.signing_key)?;
574+
packet.set_mic(if conf.mesh.signing_key != Aes128Key::null() {
575+
conf.mesh.signing_key
576+
} else {
577+
get_signing_key(conf.mesh.root_key)
578+
})?;
550579

551580
let pl = gw::DownlinkFrame {
552581
downlink_id: pl.downlink_id,

0 commit comments

Comments
 (0)