Compare commits

...

5 Commits

Author SHA1 Message Date
Vadim Yanitskiy 2df561cfa2 srsue/extnas: write NAS frames to the given PCAP file 2022-01-31 17:18:02 +06:00
Vadim Yanitskiy fbb2ed3aa9 srsue/extnas: implement encryption and signing of NAS messages 2022-01-31 17:18:02 +06:00
Vadim Yanitskiy 19ed00e9bb srsue/extnas: move EEA/EIA API from 'nas' to 'nas_base'
This way 'nas_ext' would also be able to use it.
2022-01-31 17:18:02 +06:00
Vadim Yanitskiy b594b15e79 srsue/extnas: handle (U)SIM GenAuthResp.Request 2022-01-31 17:18:02 +06:00
Vadim Yanitskiy 27344af7f0 srsue/rrctl: make codec::enc_hdr() return proto::msg_hdr 2022-01-31 17:18:02 +06:00
6 changed files with 249 additions and 50 deletions

View File

@ -66,6 +66,31 @@ protected:
// PCAP
srslte::nas_pcap* pcap = nullptr;
// Security context
uint8_t k_nas_enc[32] = { };
uint8_t k_nas_int[32] = { };
struct nas_sec_ctxt {
uint8_t ksi;
uint8_t k_asme[32];
uint32_t tx_count;
uint32_t rx_count;
uint32_t k_enb_count;
srslte::CIPHERING_ALGORITHM_ID_ENUM cipher_algo;
srslte::INTEGRITY_ALGORITHM_ID_ENUM integ_algo;
LIBLTE_MME_EPS_MOBILE_ID_GUTI_STRUCT guti;
} ctxt = { };
void integrity_generate(uint8_t* key_128, uint32_t count, uint8_t direction,
uint8_t* msg, uint32_t msg_len, uint8_t* mac);
bool integrity_check(srslte::byte_buffer_t* pdu);
void cipher_encrypt(srslte::byte_buffer_t* pdu);
void cipher_decrypt(srslte::byte_buffer_t* pdu);
void set_k_enb_count(uint32_t count);
uint32_t get_k_enb_count();
};
class nas : public nas_base
@ -83,7 +108,6 @@ public:
bool paging(srslte::s_tmsi_t* ue_identity) override;
void set_barring(srslte::barring_t barring) override;
void write_pdu(uint32_t lcid, srslte::unique_byte_buffer_t pdu) override;
uint32_t get_k_enb_count() override;
bool is_attached() override;
bool get_k_asme(uint8_t* k_asme_, uint32_t n) override;
uint32_t get_ipv4_addr() override;
@ -113,18 +137,6 @@ private:
std::vector<srslte::plmn_id_t> known_plmns;
// Security context
struct nas_sec_ctxt {
uint8_t ksi;
uint8_t k_asme[32];
uint32_t tx_count;
uint32_t rx_count;
uint32_t k_enb_count;
srslte::CIPHERING_ALGORITHM_ID_ENUM cipher_algo;
srslte::INTEGRITY_ALGORITHM_ID_ENUM integ_algo;
LIBLTE_MME_EPS_MOBILE_ID_GUTI_STRUCT guti;
};
typedef enum { DEFAULT_EPS_BEARER = 0, DEDICATED_EPS_BEARER } eps_bearer_type_t;
typedef struct {
@ -139,7 +151,6 @@ private:
bool have_guti = false;
bool have_ctxt = false;
nas_sec_ctxt ctxt = {};
bool auth_request = false;
uint8_t current_sec_hdr = LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS;
@ -175,8 +186,6 @@ private:
// Security
bool eia_caps[8] = {};
bool eea_caps[8] = {};
uint8_t k_nas_enc[32] = {};
uint8_t k_nas_int[32] = {};
// Airplane mode simulation
typedef enum { DISABLED = 0, ENABLED } airplane_mode_state_t;
@ -187,16 +196,9 @@ private:
bool running = false;
void
integrity_generate(uint8_t* key_128, uint32_t count, uint8_t direction, uint8_t* msg, uint32_t msg_len, uint8_t* mac);
bool integrity_check(srslte::byte_buffer_t* pdu);
void cipher_encrypt(srslte::byte_buffer_t* pdu);
void cipher_decrypt(srslte::byte_buffer_t* pdu);
int apply_security_config(srslte::unique_byte_buffer_t& pdu, uint8_t sec_hdr_type);
void reset_security_context();
void set_k_enb_count(uint32_t count);
bool check_cap_replay(LIBLTE_MME_UE_SECURITY_CAPABILITIES_STRUCT* caps);
void select_plmn();

View File

@ -58,7 +58,6 @@ public:
bool paging(srslte::s_tmsi_t* ue_identity);
void set_barring(srslte::barring_t barring);
void write_pdu(uint32_t lcid, srslte::unique_byte_buffer_t pdu);
uint32_t get_k_enb_count();
bool is_attached();
bool get_k_asme(uint8_t* k_asme_, uint32_t n);
uint32_t get_ipv4_addr();
@ -89,6 +88,11 @@ private:
void handle_rrctl_conn_release(rrctl::proto::msg_disc disc, const uint8_t* msg, size_t len);
void handle_rrctl_data(rrctl::proto::msg_disc disc, const uint8_t* msg, size_t len);
void handle_rrctl_param(rrctl::proto::msg_disc disc, const uint8_t* msg, size_t len);
void handle_rrctl_sec_mode(rrctl::proto::msg_disc disc, const uint8_t* msg, size_t len);
void handle_rrctl_ext_usim(const uint8_t* msg, size_t len);
void handle_usim_gen_auth_resp_req(const struct rrctl::proto::ext_usim_msg* msg, size_t len);
void handle_usim_gen_nas_keys_req(const struct rrctl::proto::ext_usim_msg* msg, size_t len);
void rrctl_send_confirm(rrctl::proto::msg_type type);
void rrctl_send_error(rrctl::proto::msg_type type);

View File

@ -228,10 +228,10 @@ public:
explicit error(const std::string& msg) : std::runtime_error(msg) {};
};
void enc_hdr(srslte::byte_buffer_t& buf,
proto::msg_type type,
proto::msg_disc disc,
uint16_t len = 0);
struct proto::msg_hdr* enc_hdr(srslte::byte_buffer_t& buf,
proto::msg_type type,
proto::msg_disc disc,
uint16_t len = 0);
const uint8_t* dec_hdr(const srslte::byte_buffer_t& buf,
proto::msg_type& type,
proto::msg_disc& disc,

View File

@ -713,14 +713,14 @@ void nas::write_pdu(uint32_t lcid, unique_byte_buffer_t pdu)
}
}
void nas::set_k_enb_count(uint32_t count)
void nas_base::set_k_enb_count(uint32_t count)
{
// UL count for RRC key derivation depends on UL Count of the Attach Request or Service Request.
// On the case of an Authentication Request, the UL count used to generate K_enb must be reset to zero.
ctxt.k_enb_count = count;
}
uint32_t nas::get_k_enb_count()
uint32_t nas_base::get_k_enb_count()
{
return ctxt.k_enb_count;
}
@ -768,12 +768,12 @@ void nas_base::start_pcap(srslte::nas_pcap* pcap_)
* Security
******************************************************************************/
void nas::integrity_generate(uint8_t* key_128,
uint32_t count,
uint8_t direction,
uint8_t* msg,
uint32_t msg_len,
uint8_t* mac)
void nas_base::integrity_generate(uint8_t* key_128,
uint32_t count,
uint8_t direction,
uint8_t* msg,
uint32_t msg_len,
uint8_t* mac)
{
switch (ctxt.integ_algo) {
case INTEGRITY_ALGORITHM_ID_EIA0:
@ -812,7 +812,7 @@ void nas::integrity_generate(uint8_t* key_128,
// This function depends to a valid k_nas_int.
// This key is generated in the security mode command.
bool nas::integrity_check(byte_buffer_t* pdu)
bool nas_base::integrity_check(byte_buffer_t* pdu)
{
if (pdu == nullptr) {
nas_log->error("Invalid PDU\n");
@ -866,7 +866,7 @@ bool nas::integrity_check(byte_buffer_t* pdu)
}
}
void nas::cipher_encrypt(byte_buffer_t* pdu)
void nas_base::cipher_encrypt(byte_buffer_t* pdu)
{
byte_buffer_t pdu_tmp;
switch (ctxt.cipher_algo) {
@ -908,7 +908,7 @@ void nas::cipher_encrypt(byte_buffer_t* pdu)
}
}
void nas::cipher_decrypt(byte_buffer_t* pdu)
void nas_base::cipher_decrypt(byte_buffer_t* pdu)
{
byte_buffer_t tmp_pdu;
switch (ctxt.cipher_algo) {

View File

@ -84,7 +84,11 @@ void nas_ext::init(usim_interface_nas* usim_, rrc_interface_nas* rrc_, gw_interf
handle_rrctl_param(disc, payload, length);
break;
case rrctl::proto::RRCTL_SEC_MODE:
handle_rrctl_sec_mode(disc, payload, length);
break;
case rrctl::proto::RRCTL_EXT_USIM:
handle_rrctl_ext_usim(payload, length);
break;
case rrctl::proto::RRCTL_CONN_RELEASE:
default:
nas_log->warning("%s is not handled\n", desc.c_str());
@ -113,7 +117,9 @@ void nas_ext::rrctl_send_error(rrctl::proto::msg_type type)
void nas_ext::handle_rrctl_reset(rrctl::proto::msg_disc disc, const uint8_t* msg, size_t len)
{
// TODO: do we need to reset anything?
/* Reset security state (no EEA, no EIA) */
memset(&ctxt, 0x00, sizeof(ctxt));
rrctl_send_confirm(rrctl::proto::RRCTL_RESET);
}
@ -160,10 +166,16 @@ void nas_ext::handle_rrctl_conn_establish(rrctl::proto::msg_disc disc, const uin
return;
}
set_k_enb_count(ctxt.tx_count);
ctxt.tx_count++;
// Allocate a new NAS PDU on heap
unique_byte_buffer_t nas_pdu = srslte::allocate_unique_buffer(*pool, true);
nas_pdu->append_bytes(pdu, pdu_len);
if (pcap != nullptr)
pcap->write_nas(nas_pdu->msg, nas_pdu->N_bytes);
rrc->connection_request(cause, std::move(nas_pdu));
}
@ -179,7 +191,18 @@ void nas_ext::handle_rrctl_data(rrctl::proto::msg_disc disc, const uint8_t* msg,
unique_byte_buffer_t nas_pdu = srslte::allocate_unique_buffer(*pool, true);
nas_pdu->append_bytes(msg, len);
if (pcap != nullptr)
pcap->write_nas(nas_pdu->msg, nas_pdu->N_bytes);
// Apply pre-configured EEA algorythm (if enabled)
cipher_encrypt(nas_pdu.get());
// Apply pre-configured EIA algorythm (if enabled)
integrity_generate(&k_nas_int[16], ctxt.tx_count, SECURITY_DIRECTION_UPLINK,
&nas_pdu->msg[5], nas_pdu->N_bytes - 5, &nas_pdu->msg[1]);
rrc->write_sdu(std::move(nas_pdu));
ctxt.tx_count++;
rrctl_send_confirm(rrctl::proto::RRCTL_DATA);
}
@ -213,6 +236,159 @@ void nas_ext::handle_rrctl_param(rrctl::proto::msg_disc disc, const uint8_t* msg
}
}
void nas_ext::handle_rrctl_sec_mode(rrctl::proto::msg_disc disc, const uint8_t* msg, size_t len)
{
const struct rrctl::proto::msg_sec_mode_req* req;
req = reinterpret_cast<const struct rrctl::proto::msg_sec_mode_req*> (msg);
if (len < sizeof(*req)) { /* XXX: mandatory fields only */
nas_log->error("Received malformed PARAM.req (len=%zu)\n", len);
rrctl_send_error(rrctl::proto::RRCTL_SEC_MODE);
return;
}
/* Skip the header */
len -= sizeof(*req);
nas_log->info("Rx SecurityMode.req (EEA%u, EIA%u)\n", req->eea, req->eia);
ctxt.cipher_algo = static_cast<srslte::CIPHERING_ALGORITHM_ID_ENUM> (req->eea);
ctxt.integ_algo = static_cast<srslte::INTEGRITY_ALGORITHM_ID_ENUM> (req->eia);
if (req->flags & SEC_MODE_F_RESET_RX_CTR)
ctxt.rx_count = 0;
if (req->flags & SEC_MODE_F_RESET_TX_CTR)
ctxt.tx_count = 0;
if (req->eea != 0x00 or req->eia != 0x00) {
/* Ensure that Kasme is present */
if (len != sizeof(ctxt.k_asme)) {
nas_log->error("Kasme is expected, but not present");
rrctl_send_error(rrctl::proto::RRCTL_SEC_MODE);
return;
}
memcpy(&ctxt.k_asme[0], &req->k_asme[0], sizeof(ctxt.k_asme));
/* Derive both Knas_enc and Knas_int from Kasme */
/* NOTE: how is this related to (U)SIM at all?!? */
usim->generate_nas_keys(&ctxt.k_asme[0], // in
&k_nas_enc[0], // out
&k_nas_int[0], // out
ctxt.cipher_algo,
ctxt.integ_algo);
}
rrctl_send_confirm(rrctl::proto::RRCTL_SEC_MODE);
}
void nas_ext::handle_rrctl_ext_usim(const uint8_t* _msg, size_t len)
{
enum rrctl::proto::ext_usim_msg_type msg_type;
const struct rrctl::proto::ext_usim_msg* msg;
std::string msg_desc;
if (len < 4) { /* XXX: type + padding */
nas_log->error("Received too short (U)SIM specific message (len=%zu)\n", len);
rrctl_send_error(rrctl::proto::RRCTL_EXT_USIM);
return;
}
/* Skip the header */
len -= 4;
msg = reinterpret_cast<const struct rrctl::proto::ext_usim_msg*> (_msg);
msg_type = static_cast<rrctl::proto::ext_usim_msg_type> (msg->type);
switch (msg_type) {
case rrctl::proto::EXT_USIM_GEN_AUTH_RESP:
handle_usim_gen_auth_resp_req(msg, len);
break;
case rrctl::proto::EXT_USIM_RAW_APDU:
case rrctl::proto::EXT_USIM_READ_FILE:
case rrctl::proto::EXT_USIM_UPDATE_FILE:
case rrctl::proto::EXT_USIM_RESERVED:
default:
nas_log->warning("(U)SIM specific message 0x%02x is not handled\n", msg->type);
}
}
void nas_ext::handle_usim_gen_auth_resp_req(const struct rrctl::proto::ext_usim_msg* msg, size_t len)
{
const struct rrctl::proto::ext_usim_gen_auth_resp_req* req;
struct rrctl::proto::ext_usim_gen_auth_resp_rsp* rsp;
struct rrctl::proto::ext_usim_msg* rsp_msg;
std::pair<uint16_t, uint16_t> mcc_mnc;
struct rrctl::proto::msg_hdr* hdr;
srslte::byte_buffer_t pdu;
/* Allocate the response message in advance */
hdr = rrctl::codec::enc_hdr(pdu, rrctl::proto::RRCTL_EXT_USIM, rrctl::proto::RRCTL_CNF, 4);
rsp_msg = reinterpret_cast<struct rrctl::proto::ext_usim_msg*> (&pdu.msg[pdu.N_bytes]);
rsp_msg->type = msg->type;
pdu.N_bytes += 4;
if (len < sizeof(*req)) {
nas_log->error("Received too short (U)SIM GenAuthResp.req (len=%zu)\n", len);
hdr->disc = (uint8_t) rrctl::proto::RRCTL_ERR;
iface->write(pdu);
return;
}
rsp = &rsp_msg->u.gen_auth_resp_rsp;
req = &msg->u.gen_auth_resp_req;
// Parse PLMN ID
try {
/* HACK: generalize this function */
rrctl::codec::dec_plmn_select_req(mcc_mnc, &req->mcc[0], len);
} catch (const rrctl::codec::error& e) {
nas_log->warning("Failed to parse MCC/MNC: %s\n", e.what());
hdr->disc = (uint8_t) rrctl::proto::RRCTL_ERR;
iface->write(pdu);
return;
}
uint8_t k_asme[32];
uint8_t res[16];
int res_len;
/* NOTE: this is a blocking call => no other RRCTL messages can be processed in parallel?
* FIXME: the authors of srsUE apparently are not aware of 'const', so we have to cast. */
auth_result_t auth_res = usim->generate_authentication_response((uint8_t*) &req->rand[0],
(uint8_t*) &req->autn[0],
mcc_mnc.first, mcc_mnc.second,
&res[0], &res_len, &k_asme[0]);
switch (auth_res) {
case AUTH_OK:
nas_log->info("Authentication vector has been generated successfully\n");
set_k_enb_count(0);
pdu.N_bytes += sizeof(*rsp);
pdu.append_bytes(&k_asme[0], sizeof(k_asme));
pdu.append_bytes(&res[0], res_len);
rsp->res_len = (uint8_t) res_len;
break;
case AUTH_SYNCH_FAILURE:
nas_log->warning("Synchronization is required to generate an authentication vector\n");
pdu.N_bytes += sizeof(*rsp);
pdu.append_bytes(&res[0], res_len);
rsp->res_len = (uint8_t) res_len;
rsp->out_of_sync = true;
break;
case AUTH_FAILED:
default:
nas_log->warning("Could not generate an authentication vector\n");
hdr->disc = (uint8_t) rrctl::proto::RRCTL_ERR;
}
hdr->len = htons(pdu.N_bytes - sizeof(*hdr));
iface->write(pdu);
}
void nas_ext::get_metrics(nas_metrics_t* m)
{
nas_metrics_t metrics = {};
@ -274,19 +450,34 @@ bool nas_ext::paging(srslte::s_tmsi_t* ue_identity)
void nas_ext::write_pdu(uint32_t lcid, srslte::unique_byte_buffer_t pdu)
{
srslte::byte_buffer_t msg;
uint8 pd, sec_hdr_type;
nas_log->info_hex(pdu->msg, pdu->N_bytes, "Received DL %s PDU from RRC\n", rrc->get_rb_name(lcid).c_str());
// Parse the message security header
liblte_mme_parse_msg_sec_header((LIBLTE_BYTE_MSG_STRUCT*) pdu.get(), &pd, &sec_hdr_type);
switch (sec_hdr_type) {
case LIBLTE_MME_SECURITY_HDR_TYPE_INTEGRITY_AND_CIPHERED_WITH_NEW_EPS_SECURITY_CONTEXT:
case LIBLTE_MME_SECURITY_HDR_TYPE_INTEGRITY_WITH_NEW_EPS_SECURITY_CONTEXT:
case LIBLTE_MME_SECURITY_HDR_TYPE_SERVICE_REQUEST:
case LIBLTE_MME_SECURITY_HDR_TYPE_PLAIN_NAS:
case LIBLTE_MME_SECURITY_HDR_TYPE_INTEGRITY:
break;
case LIBLTE_MME_SECURITY_HDR_TYPE_INTEGRITY_AND_CIPHERED:
// Apply pre-configured EEA algorythm (if enabled)
cipher_decrypt(pdu.get());
break;
default:
nas_log->error("Received DL NAS PDU with unknown sec_hdr=%02x\n", sec_hdr_type);
}
if (pcap != nullptr)
pcap->write_nas(pdu->msg, pdu->N_bytes);
rrctl::codec::enc_data_ind(msg, pdu->msg, pdu->N_bytes, lcid);
iface->write(msg);
}
uint32_t nas_ext::get_k_enb_count()
{
// FIXME: we probably need to maintain a security context
return 0; // return a dummy value for now
}
bool nas_ext::is_attached()
{
// FIXME: we probably need to maintain the state

View File

@ -102,10 +102,10 @@ std::string msg_hdr_desc(proto::msg_type type, proto::msg_disc disc, uint16_t le
namespace codec {
void enc_hdr(srslte::byte_buffer_t& buf,
proto::msg_type type,
proto::msg_disc disc,
uint16_t len)
struct proto::msg_hdr* enc_hdr(srslte::byte_buffer_t& buf,
proto::msg_type type,
proto::msg_disc disc,
uint16_t len)
{
struct proto::msg_hdr hdr = {
#if defined(__LITTLE_ENDIAN_BITFIELD)
@ -121,6 +121,8 @@ void enc_hdr(srslte::byte_buffer_t& buf,
};
buf.append_bytes((const uint8_t*) &hdr, sizeof(hdr));
return reinterpret_cast<struct proto::msg_hdr*> (&buf.msg[0]);
}
const uint8_t* dec_hdr(const srslte::byte_buffer_t& buf,