diff --git a/srsgnb/hdr/stack/rrc/rrc_nr_config.h b/srsgnb/hdr/stack/rrc/rrc_nr_config.h index d064fc3b6..4167a22a5 100644 --- a/srsgnb/hdr/stack/rrc/rrc_nr_config.h +++ b/srsgnb/hdr/stack/rrc/rrc_nr_config.h @@ -53,6 +53,9 @@ struct rrc_nr_cfg_t { rrc_cell_list_nr_t cell_list; bool is_standalone; + std::array eea_preference_list; + std::array eia_preference_list; + std::string log_name = "RRC-NR"; std::string log_level; uint32_t log_hex_limit; diff --git a/srsgnb/hdr/stack/rrc/rrc_nr_security_context.h b/srsgnb/hdr/stack/rrc/rrc_nr_security_context.h new file mode 100644 index 000000000..aa7009d82 --- /dev/null +++ b/srsgnb/hdr/stack/rrc/rrc_nr_security_context.h @@ -0,0 +1,74 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2021 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ + +#ifndef SRSRAN_RRC_NR_SECURITY_CONTEXT_H +#define SRSRAN_RRC_NR_SECURITY_CONTEXT_H + +#include "srsgnb/hdr/stack/rrc/rrc_nr_config.h" +#include "srsran/asn1/ngap.h" +#include "srsran/interfaces/gnb_interfaces.h" +#include "srsran/srslog/srslog.h" + +namespace srsgnb { + +class nr_security_context +{ +public: + explicit nr_security_context(const srsenb::rrc_nr_cfg_t& cfg_) : + cfg(&cfg_), logger(srslog::fetch_basic_logger("RRC_NR")) + {} + + explicit nr_security_context(const nr_security_context& other) : logger(srslog::fetch_basic_logger("RRC_NR")) + { + cfg = other.cfg; + k_gnb_present = other.k_gnb_present; + security_capabilities = other.security_capabilities; + std::copy(other.k_gnb, other.k_gnb + 32, k_gnb); + sec_cfg = other.sec_cfg; + ncc = other.ncc; + } + + nr_security_context& operator=(const nr_security_context& other) + { + cfg = other.cfg; + k_gnb_present = other.k_gnb_present; + security_capabilities = other.security_capabilities; + std::copy(other.k_gnb, other.k_gnb + 32, k_gnb); + sec_cfg = other.sec_cfg; + ncc = other.ncc; + return *this; + } + + bool set_security_capabilities(const asn1::ngap_nr::ue_security_cap_s& caps); + void set_security_key(const asn1::fixed_bitstring<256, false, true>& key); + void set_ncc(uint8_t ncc_) { ncc = ncc_; } + + asn1::rrc_nr::security_algorithm_cfg_s get_security_algorithm_cfg() const; + const srsran::as_security_config_t& get_as_sec_cfg() const { return sec_cfg; } + uint8_t get_ncc() const { return ncc; } + bool is_as_sec_cfg_valid() const { return k_gnb_present; } + + void regenerate_keys_handover(uint32_t new_pci, uint32_t new_dl_earfcn); + +private: + void generate_as_keys(); + + srslog::basic_logger& logger; + const srsenb::rrc_nr_cfg_t* cfg = nullptr; + bool k_gnb_present = false; + asn1::ngap_nr::ue_security_cap_s security_capabilities = {}; + uint8_t k_gnb[32] = {}; // Provided by MME + srsran::as_security_config_t sec_cfg = {}; + uint8_t ncc = 0; +}; +} // namespace srsgnb +#endif diff --git a/srsgnb/hdr/stack/rrc/rrc_nr_ue.h b/srsgnb/hdr/stack/rrc/rrc_nr_ue.h index e27c06cf9..9eb5aecff 100644 --- a/srsgnb/hdr/stack/rrc/rrc_nr_ue.h +++ b/srsgnb/hdr/stack/rrc/rrc_nr_ue.h @@ -14,6 +14,7 @@ #define SRSRAN_RRC_NR_UE_H #include "rrc_nr.h" +#include "rrc_nr_security_context.h" namespace srsenb { @@ -43,6 +44,11 @@ public: void get_metrics(rrc_ue_metrics_t& ue_metrics) { ue_metrics = {}; /*TODO fill RRC metrics*/ }; // setters + void set_security_key(const asn1::fixed_bitstring<256, false, true>& key) { sec_ctx.set_security_key(key); } + void set_security_capabilities(const asn1::ngap_nr::ue_security_cap_s& caps) + { + sec_ctx.set_security_capabilities(caps); + } void deactivate_bearers(); @@ -170,6 +176,9 @@ private: const uint32_t drb1_lcid = 4; + // Security helper + srsgnb::nr_security_context sec_ctx; + // SA specific variables struct ctxt_t { uint64_t setup_ue_id = -1; diff --git a/srsgnb/src/stack/rrc/CMakeLists.txt b/srsgnb/src/stack/rrc/CMakeLists.txt index 8161ba69c..d2c57ff9a 100644 --- a/srsgnb/src/stack/rrc/CMakeLists.txt +++ b/srsgnb/src/stack/rrc/CMakeLists.txt @@ -10,10 +10,10 @@ set(SOURCES rrc_nr_config_utils.cc) add_library(srsgnb_rrc_config_utils STATIC ${SOURCES}) target_link_libraries(srsgnb_rrc_config_utils srsran_phy) -set(SOURCES rrc_nr.cc rrc_nr_ue.cc cell_asn1_config.cc) +set(SOURCES rrc_nr.cc rrc_nr_ue.cc rrc_nr_security_context.cc cell_asn1_config.cc) add_library(srsgnb_rrc STATIC ${SOURCES}) target_link_libraries(srsgnb_rrc srsgnb_rrc_config_utils) include_directories(${PROJECT_SOURCE_DIR}) -add_subdirectory(test) \ No newline at end of file +add_subdirectory(test) diff --git a/srsgnb/src/stack/rrc/rrc_nr.cc b/srsgnb/src/stack/rrc/rrc_nr.cc index 377937816..4e654ba32 100644 --- a/srsgnb/src/stack/rrc/rrc_nr.cc +++ b/srsgnb/src/stack/rrc/rrc_nr.cc @@ -538,6 +538,15 @@ void rrc_nr::notify_pdcp_integrity_error(uint16_t rnti, uint32_t lcid) {} int rrc_nr::ue_set_security_cfg_key(uint16_t rnti, const asn1::fixed_bitstring<256, false, true>& key) { + logger.debug("Setting securtiy key for rnti=0x%x", rnti); + auto ue_it = users.find(rnti); + + if (ue_it == users.end()) { + logger.error("Trying to set key for non-existing rnti=0x%x", rnti); + return SRSRAN_ERROR; + } + ue& u = *ue_it->second; + u.set_security_key(key); return SRSRAN_SUCCESS; } int rrc_nr::ue_set_bitrates(uint16_t rnti, const asn1::ngap_nr::ue_aggregate_maximum_bit_rate_s& rates) @@ -550,6 +559,15 @@ int rrc_nr::set_aggregate_max_bitrate(uint16_t rnti, const asn1::ngap_nr::ue_agg } int rrc_nr::ue_set_security_cfg_capabilities(uint16_t rnti, const asn1::ngap_nr::ue_security_cap_s& caps) { + logger.debug("Setting securtiy capabilites for rnti=0x%x", rnti); + auto ue_it = users.find(rnti); + + if (ue_it == users.end()) { + logger.error("Trying to set security capabilities for non-existing rnti=0x%x", rnti); + return SRSRAN_ERROR; + } + ue& u = *ue_it->second; + u.set_security_capabilities(caps); return SRSRAN_SUCCESS; } int rrc_nr::start_security_mode_procedure(uint16_t rnti) @@ -609,9 +627,9 @@ void rrc_nr::sgnb_addition_request(uint16_t eutra_rnti, const sgnb_addition_req_ uecfg.carriers[0].cc = 0; uecfg.ue_bearers[0].direction = mac_lc_ch_cfg_t::BOTH; srsran::phy_cfg_nr_default_t::reference_cfg_t ref_args{}; - ref_args.duplex = cfg.cell_list[0].duplex_mode == SRSRAN_DUPLEX_MODE_TDD - ? srsran::phy_cfg_nr_default_t::reference_cfg_t::R_DUPLEX_TDD_CUSTOM_6_4 - : srsran::phy_cfg_nr_default_t::reference_cfg_t::R_DUPLEX_FDD; + ref_args.duplex = cfg.cell_list[0].duplex_mode == SRSRAN_DUPLEX_MODE_TDD + ? srsran::phy_cfg_nr_default_t::reference_cfg_t::R_DUPLEX_TDD_CUSTOM_6_4 + : srsran::phy_cfg_nr_default_t::reference_cfg_t::R_DUPLEX_FDD; uecfg.phy_cfg = srsran::phy_cfg_nr_default_t{ref_args}; uecfg.phy_cfg.csi = {}; // disable CSI until RA is complete diff --git a/srsgnb/src/stack/rrc/rrc_nr_security_context.cc b/srsgnb/src/stack/rrc/rrc_nr_security_context.cc new file mode 100644 index 000000000..d56f4384f --- /dev/null +++ b/srsgnb/src/stack/rrc/rrc_nr_security_context.cc @@ -0,0 +1,197 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2021 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ + +#include "srsgnb/hdr/stack/rrc/rrc_nr_security_context.h" +#include "srsran/asn1/obj_id_cmp_utils.h" +#include "srsran/asn1/rrc_utils.h" + +namespace srsgnb { + +asn1::rrc_nr::security_algorithm_cfg_s nr_security_context::get_security_algorithm_cfg() const +{ + asn1::rrc_nr::security_algorithm_cfg_s ret; + // TODO: select these based on UE capabilities and preference order + ret.integrity_prot_algorithm = (asn1::rrc_nr::integrity_prot_algorithm_e::options)sec_cfg.integ_algo; + ret.ciphering_algorithm = (asn1::rrc_nr::ciphering_algorithm_e::options)sec_cfg.cipher_algo; + return ret; +} + +bool nr_security_context::set_security_capabilities(const asn1::ngap_nr::ue_security_cap_s& caps) +{ + security_capabilities = caps; + + // Selects security algorithms (cipher_algo and integ_algo) based on capabilities and config preferences + // Each position in the bitmap represents an encryption algorithm: + // “all bits equal to 0” – UE supports no other algorithm than EEA0, + // “first bit” – 128-EEA1, + // “second bit” – 128-EEA2, + // “third bit” – 128-EEA3, + // other bits reserved for future use. Value ‘1’ indicates support and value + // ‘0’ indicates no support of the algorithm. + // Algorithms are defined in TS 33.401 [15]. + // Note: information missing + + bool enc_algo_found = false; + bool integ_algo_found = false; + + for (auto& cipher_item : cfg->eea_preference_list) { + auto& v = security_capabilities.nrencryption_algorithms; + switch (cipher_item) { + case srsran::CIPHERING_ALGORITHM_ID_EEA0: + // “all bits equal to 0” – UE supports no other algorithm than EEA0, + // specification does not cover the case in which EEA0 is supported with other algorithms + // just assume that EEA0 is always supported even this can not be explicity signaled by S1AP + sec_cfg.cipher_algo = srsran::CIPHERING_ALGORITHM_ID_EEA0; + enc_algo_found = true; + logger.info("Selected EEA0 as RRC encryption algorithm"); + break; + case srsran::CIPHERING_ALGORITHM_ID_128_EEA1: + // “first bit” – 128-EEA1, + if (v.get(v.length() - srsran::CIPHERING_ALGORITHM_ID_128_EEA1)) { + sec_cfg.cipher_algo = srsran::CIPHERING_ALGORITHM_ID_128_EEA1; + enc_algo_found = true; + logger.info("Selected EEA1 as RRC encryption algorithm"); + break; + } else { + logger.info("Failed to selected EEA1 as RRC encryption algorithm, due to unsupported algorithm"); + } + break; + case srsran::CIPHERING_ALGORITHM_ID_128_EEA2: + // “second bit” – 128-EEA2, + if (v.get(v.length() - srsran::CIPHERING_ALGORITHM_ID_128_EEA2)) { + sec_cfg.cipher_algo = srsran::CIPHERING_ALGORITHM_ID_128_EEA2; + enc_algo_found = true; + logger.info("Selected EEA2 as RRC encryption algorithm"); + break; + } else { + logger.info("Failed to selected EEA2 as RRC encryption algorithm, due to unsupported algorithm"); + } + break; + case srsran::CIPHERING_ALGORITHM_ID_128_EEA3: + // “third bit” – 128-EEA3, + if (v.get(v.length() - srsran::CIPHERING_ALGORITHM_ID_128_EEA3)) { + sec_cfg.cipher_algo = srsran::CIPHERING_ALGORITHM_ID_128_EEA3; + enc_algo_found = true; + logger.info("Selected EEA3 as RRC encryption algorithm"); + break; + } else { + logger.info("Failed to selected EEA2 as RRC encryption algorithm, due to unsupported algorithm"); + } + break; + default: + enc_algo_found = false; + break; + } + if (enc_algo_found) { + break; + } + } + + for (auto& eia_enum : cfg->eia_preference_list) { + auto& v = security_capabilities.nrintegrity_protection_algorithms; + switch (eia_enum) { + case srsran::INTEGRITY_ALGORITHM_ID_EIA0: + // Null integrity is not supported + logger.info("Skipping EIA0 as RRC integrity algorithm. Null integrity is not supported."); + sec_cfg.integ_algo = srsran::INTEGRITY_ALGORITHM_ID_EIA0; + integ_algo_found = true; + break; + case srsran::INTEGRITY_ALGORITHM_ID_128_EIA1: + // “first bit” – 128-EIA1, + if (v.get(v.length() - srsran::INTEGRITY_ALGORITHM_ID_128_EIA1)) { + sec_cfg.integ_algo = srsran::INTEGRITY_ALGORITHM_ID_128_EIA1; + integ_algo_found = true; + logger.info("Selected EIA1 as RRC integrity algorithm."); + } else { + logger.info("Failed to selected EIA1 as RRC encryption algorithm, due to unsupported algorithm"); + } + break; + case srsran::INTEGRITY_ALGORITHM_ID_128_EIA2: + // “second bit” – 128-EIA2, + if (v.get(v.length() - srsran::INTEGRITY_ALGORITHM_ID_128_EIA2)) { + sec_cfg.integ_algo = srsran::INTEGRITY_ALGORITHM_ID_128_EIA2; + integ_algo_found = true; + logger.info("Selected EIA2 as RRC integrity algorithm."); + } else { + logger.info("Failed to selected EIA2 as RRC encryption algorithm, due to unsupported algorithm"); + } + break; + case srsran::INTEGRITY_ALGORITHM_ID_128_EIA3: + // “third bit” – 128-EIA3, + if (v.get(v.length() - srsran::INTEGRITY_ALGORITHM_ID_128_EIA3)) { + sec_cfg.integ_algo = srsran::INTEGRITY_ALGORITHM_ID_128_EIA3; + integ_algo_found = true; + logger.info("Selected EIA3 as RRC integrity algorithm."); + } else { + logger.info("Failed to selected EIA3 as RRC encryption algorithm, due to unsupported algorithm"); + } + break; + default: + integ_algo_found = false; + break; + } + + if (integ_algo_found) { + break; + } + } + + if (not integ_algo_found || not enc_algo_found) { + logger.error("Did not find a matching integrity or encryption algorithm with the UE"); + return false; + } + return true; +} + +void nr_security_context::set_security_key(const asn1::fixed_bitstring<256, false, true>& key) +{ + k_gnb_present = true; + for (uint32_t i = 0; i < key.nof_octets(); ++i) { + k_gnb[i] = key.data()[key.nof_octets() - 1 - i]; + } + logger.info(k_gnb, 32, "Key gNodeB (k_gnb)"); + + generate_as_keys(); +} + +void nr_security_context::generate_as_keys() +{ + // Generate K_rrc_enc and K_rrc_int + srsran::security_generate_k_nr_rrc( + k_gnb, sec_cfg.cipher_algo, sec_cfg.integ_algo, sec_cfg.k_rrc_enc.data(), sec_cfg.k_rrc_int.data()); + + // Generate K_up_enc and K_up_int + security_generate_k_nr_up( + k_gnb, sec_cfg.cipher_algo, sec_cfg.integ_algo, sec_cfg.k_up_enc.data(), sec_cfg.k_up_int.data()); + + logger.info(k_gnb, 32, "K_gNB (k_gnb)"); + logger.info(sec_cfg.k_rrc_enc.data(), 32, "RRC Encryption Key (k_rrc_enc)"); + logger.info(sec_cfg.k_rrc_int.data(), 32, "RRC Integrity Key (k_rrc_int)"); + logger.info(sec_cfg.k_up_enc.data(), 32, "UP Encryption Key (k_up_enc)"); + logger.info(sec_cfg.k_up_int.data(), 32, "UP Encryption Key (k_up_enc)"); +} + +void nr_security_context::regenerate_keys_handover(uint32_t new_pci, uint32_t new_dl_earfcn) +{ + logger.info("Regenerating KeNB with PCI=0x%02x, DL-EARFCN=%d", new_pci, new_dl_earfcn); + logger.info(k_gnb, 32, "Old K_eNB (k_enb)"); + // Generate K_enb* + uint8_t k_gnb_star[32]; + srsran::security_generate_k_enb_star(k_gnb, new_pci, new_dl_earfcn, k_gnb_star); + + // K_enb becomes K_enb* + memcpy(k_gnb, k_gnb_star, 32); + + generate_as_keys(); +} + +} // namespace srsgnb diff --git a/srsgnb/src/stack/rrc/rrc_nr_ue.cc b/srsgnb/src/stack/rrc/rrc_nr_ue.cc index d4a33bcc5..2e9ba8930 100644 --- a/srsgnb/src/stack/rrc/rrc_nr_ue.cc +++ b/srsgnb/src/stack/rrc/rrc_nr_ue.cc @@ -27,7 +27,7 @@ Every function in UE class is called from a mutex environment thus does not need extra protection. *******************************************************************************/ rrc_nr::ue::ue(rrc_nr* parent_, uint16_t rnti_, const sched_nr_ue_cfg_t& uecfg_, bool start_msg3_timer) : - parent(parent_), logger(parent_->logger), rnti(rnti_), uecfg(uecfg_) + parent(parent_), logger(parent_->logger), rnti(rnti_), uecfg(uecfg_), sec_ctx(parent->cfg) { if (not parent->cfg.is_standalone) { // Add the final PDCCH config in case of NSA