From 52a08acf8123e444286e5581a6a5dfedfc1c9b1a Mon Sep 17 00:00:00 2001 From: Orne Brocaar Date: Mon, 27 May 2024 11:20:30 +0100 Subject: [PATCH] Add passive_roaming_validate_mic option. This option does two things: 1) In case the passive-roaming agreement is not state-less, it will trigger the validation of MIC (this was already implemented at the roaming-session retrieval, but never used). 2) On PRStartAns, it will return the NwkSKey / FNwkSIntKey to the requester (such that the MIC validation can be performed). For state-less passive-roaming, it is recommended to leave this option set to false, such that no session-keys are exposed. --- chirpstack/src/api/backend/mod.rs | 5 +++-- chirpstack/src/backend/roaming.rs | 19 +++++++++++++++++++ chirpstack/src/cmd/configfile.rs | 15 +++++++++++++++ chirpstack/src/config.rs | 2 ++ chirpstack/src/test/class_a_pr_test.rs | 1 + chirpstack/src/uplink/data_fns.rs | 1 + chirpstack/src/uplink/join_fns.rs | 1 + 7 files changed, 42 insertions(+), 2 deletions(-) diff --git a/chirpstack/src/api/backend/mod.rs b/chirpstack/src/api/backend/mod.rs index 7008a357..28d46272 100644 --- a/chirpstack/src/api/backend/mod.rs +++ b/chirpstack/src/api/backend/mod.rs @@ -293,6 +293,7 @@ async fn _handle_pr_start_req_data( let region_common_name = CommonName::from_str(&pl.ul_meta_data.rf_region)?; let region_config_id = region::get_region_config_id(region_common_name)?; let dr = pl.ul_meta_data.data_rate.unwrap_or_default(); + let validate_mic = roaming::get_passive_roaming_validate_mic(sender_id)?; let mut ufs = UplinkFrameSet { uplink_set_id: Uuid::new_v4(), @@ -318,7 +319,7 @@ async fn _handle_pr_start_req_data( let kek_label = roaming::get_passive_roaming_kek_label(sender_id)?; let ds = d.get_device_session()?; - let nwk_s_key = if ds.mac_version().to_string().starts_with("1.0") { + let nwk_s_key = if validate_mic && ds.mac_version().to_string().starts_with("1.0") { Some(keywrap::wrap( &kek_label, AES128Key::from_slice(&ds.nwk_s_enc_key)?, @@ -327,7 +328,7 @@ async fn _handle_pr_start_req_data( None }; - let f_nwk_s_int_key = if ds.mac_version().to_string().starts_with("1.0") { + let f_nwk_s_int_key = if validate_mic && ds.mac_version().to_string().starts_with("1.0") { None } else { Some(keywrap::wrap( diff --git a/chirpstack/src/backend/roaming.rs b/chirpstack/src/backend/roaming.rs index fac6067f..2f6b45b0 100644 --- a/chirpstack/src/backend/roaming.rs +++ b/chirpstack/src/backend/roaming.rs @@ -155,6 +155,25 @@ pub fn get_passive_roaming_kek_label(net_id: NetID) -> Result { )) } +pub fn get_passive_roaming_validate_mic(net_id: NetID) -> Result { + let conf = config::get(); + + for s in &conf.roaming.servers { + if s.net_id == net_id { + return Ok(s.passive_roaming_validate_mic); + } + } + + if conf.roaming.default.enabled { + return Ok(conf.roaming.default.passive_roaming_validate_mic); + } + + Err(anyhow!( + "Passive-roaming mic-check for net_id {} does not exist", + net_id + )) +} + pub fn is_enabled() -> bool { let conf = config::get(); conf.roaming.default.enabled || !conf.roaming.servers.is_empty() diff --git a/chirpstack/src/cmd/configfile.rs b/chirpstack/src/cmd/configfile.rs index e0b6257e..e1e8d3c6 100644 --- a/chirpstack/src/cmd/configfile.rs +++ b/chirpstack/src/cmd/configfile.rs @@ -802,6 +802,13 @@ pub fn run() { # # If set, the session-keys will be encrypted using the given KEK. passive_roaming_kek_label="{{roaming.default.passive_roaming_kek_label}}" + + # Passive-roaming validate MIC. + # + # If set ChirpStack will validate the MIC (for non-stateless roaming + # agreements). As well it means it will expose the NwkSKey / FNwkSIntKey + # on PRStartAns. + passive_roaming_validate_mic={{roaming.default.passive_roaming_validate_mic}} # Server. # @@ -846,6 +853,13 @@ pub fn run() { # # # # If set, the session-keys will be encrypted using the given KEK. # passive_roaming_kek_label="" + + # # Passive-roaming validate MIC. + # # + # # If set ChirpStack will validate the MIC (for non-stateless roaming + # # agreements). As well it means it will expose the NwkSKey / FNwkSIntKey + # # on PRStartAns. + # passive_roaming_validate_mic=false # # # Server. # # @@ -878,6 +892,7 @@ pub fn run() { async_timeout="{{ this.async_timeout }}" passive_roaming_lifetime="{{ this.passive_roaming_lifetime }}" passive_roaming_kek_label="{{ this.passive_roaming_kek_label }}" + passive_roaming_validate_mic={{ this.passive_roaming_validate_mic }} server="{{ this.server }}" use_target_role_suffix="{{ this.use_target_role_suffix }}" ca_cert="{{ this.ca_cert }}" diff --git a/chirpstack/src/config.rs b/chirpstack/src/config.rs index 1b331576..a776939d 100644 --- a/chirpstack/src/config.rs +++ b/chirpstack/src/config.rs @@ -487,6 +487,7 @@ pub struct RoamingServer { #[serde(with = "humantime_serde")] pub passive_roaming_lifetime: Duration, pub passive_roaming_kek_label: String, + pub passive_roaming_validate_mic: bool, pub server: String, pub use_target_role_suffix: bool, pub ca_cert: String, @@ -504,6 +505,7 @@ pub struct RoamingServerDefault { #[serde(with = "humantime_serde")] pub passive_roaming_lifetime: Duration, pub passive_roaming_kek_label: String, + pub passive_roaming_validate_mic: bool, pub server: String, pub use_target_role_suffix: bool, pub ca_cert: String, diff --git a/chirpstack/src/test/class_a_pr_test.rs b/chirpstack/src/test/class_a_pr_test.rs index 156b3413..3aecfdb8 100644 --- a/chirpstack/src/test/class_a_pr_test.rs +++ b/chirpstack/src/test/class_a_pr_test.rs @@ -182,6 +182,7 @@ async fn test_sns_uplink() { // Set roaming agreement. conf.roaming.servers.push(config::RoamingServer { net_id: NetID::from_str("000202").unwrap(), + passive_roaming_validate_mic: true, server: fns_mock.url("/"), ..Default::default() }); diff --git a/chirpstack/src/uplink/data_fns.rs b/chirpstack/src/uplink/data_fns.rs index 9d98a0a7..7de35060 100644 --- a/chirpstack/src/uplink/data_fns.rs +++ b/chirpstack/src/uplink/data_fns.rs @@ -200,6 +200,7 @@ impl Data { session_id: sess_id.as_bytes().to_vec(), net_id: net_id.to_vec(), dev_addr: self.mac_payload.fhdr.devaddr.to_vec(), + validate_mic: roaming::get_passive_roaming_validate_mic(net_id)?, lifetime: { let lt = pr_start_ans.lifetime.unwrap_or_default() as i64; if lt == 0 { diff --git a/chirpstack/src/uplink/join_fns.rs b/chirpstack/src/uplink/join_fns.rs index fd07ef05..cf3fd372 100644 --- a/chirpstack/src/uplink/join_fns.rs +++ b/chirpstack/src/uplink/join_fns.rs @@ -172,6 +172,7 @@ impl JoinRequest { let sess = internal::PassiveRoamingDeviceSession { session_id: sess_id.as_bytes().to_vec(), net_id: self.home_net_id.unwrap().to_vec(), + validate_mic: roaming::get_passive_roaming_validate_mic(self.home_net_id.unwrap())?, dev_addr: pr_start_ans.dev_addr.clone(), dev_eui: self.join_request.dev_eui.to_vec(), lifetime: {