mirror of
https://gitlab.eurecom.fr/oai/openairinterface5g.git
synced 2026-07-13 04:30:28 +00:00
Merge remote-tracking branch 'GuidoCasati/issue-963' into integration_2026_w27
NAS: refactor Identity Request handler and enforce spec-compliant security checks (#217) Align UE Identity Request handling with TS 24.501 by checking the NAS security header and rejecting unprotected non-SUCI requests. Also, refactor Identity Response identity selection based on the requested identity types, add to a helper and handle unavailable identities in the UE context. Closes: #82 Reviewed-by: Robert Schmidt <robert.schmidt@openairinterface.org> Reviewed-By: Rakesh Mundlamuri <rakesh.mundlamuri@openairinterface.org
This commit is contained in:
25
doc/5Gnas.md
25
doc/5Gnas.md
@@ -17,13 +17,19 @@ Key 5GS NAS messages:
|
|||||||
|
|
||||||
## OAI Implementation Status
|
## OAI Implementation Status
|
||||||
|
|
||||||
The following table lists implemented NAS messages and whether there is an encoder or decoder function, and if a corresponding unit test exists.
|
The following table lists NAS messages with dedicated `encode_*` / `decode_*` codecs under
|
||||||
|
`openair3/NAS/NR_UE/5GS/` (`5GMM/MSG`, `5GSM/MSG`). Unit test entries refer to
|
||||||
|
[`nas_lib_test.c`](../openair3/NAS/NR_UE/5GS/tests/nas_lib_test.c), most are encode/decode round-trips.
|
||||||
|
|
||||||
| Type | Message | Encoding | Decoding | Unit test |
|
| Type | Message | Encoding | Decoding | Unit test |
|
||||||
|-------|-------------------------------------------|----------|----------|------------|
|
|-------|-------------------------------------------|----------|----------|------------|
|
||||||
| 5GMM | Service Request | yes | yes | yes |
|
| 5GMM | Service Request | yes | yes | yes |
|
||||||
| 5GMM | Service Accept | yes | yes | yes |
|
| 5GMM | Service Accept | yes | yes | yes |
|
||||||
| 5GMM | Service Reject | yes | yes | yes |
|
| 5GMM | Service Reject | yes | yes | yes |
|
||||||
|
| 5GMM | Authentication Failure | yes | yes | yes |
|
||||||
|
| 5GMM | Authentication Reject | yes | yes | yes |
|
||||||
|
| 5GMM | Security Mode Reject | yes | yes | yes |
|
||||||
|
| 5GMM | Identity Request | no | yes | no |
|
||||||
| 5GMM | Authentication Response | yes | no | no |
|
| 5GMM | Authentication Response | yes | no | no |
|
||||||
| 5GMM | Identity Response | yes | no | no |
|
| 5GMM | Identity Response | yes | no | no |
|
||||||
| 5GMM | Security Mode Complete | yes | no | no |
|
| 5GMM | Security Mode Complete | yes | no | no |
|
||||||
@@ -38,6 +44,23 @@ The following table lists implemented NAS messages and whether there is an encod
|
|||||||
| 5GSM | PDU Session Establishment Request | yes | no | no |
|
| 5GSM | PDU Session Establishment Request | yes | no | no |
|
||||||
| 5GSM | PDU Session Establishment Accept | no | yes | no |
|
| 5GSM | PDU Session Establishment Accept | no | yes | no |
|
||||||
|
|
||||||
|
### Runtime-handled messages
|
||||||
|
|
||||||
|
These network-originated messages are handled in [`nr_nas_msg.c`](../openair3/NAS/NR_UE/nr_nas_msg.c):
|
||||||
|
|
||||||
|
* Authentication Request
|
||||||
|
* Security Mode Command
|
||||||
|
* Downlink NAS Transport
|
||||||
|
* Deregistration Accept (UE originating)
|
||||||
|
* Registration Reject
|
||||||
|
* PDU Session Establishment Reject
|
||||||
|
|
||||||
|
## Integration testing
|
||||||
|
|
||||||
|
End-to-end attach can be tested with the [NR UE NAS simulator](../tests/nr-ue-nas-simulator/README.md),
|
||||||
|
which runs the OAI UE NAS stack and gNB NGAP against the AMF, forwarding NAS PDUs without PHY/MAC/RLC.
|
||||||
|
Use `nas_lib_test` for isolated codec round-trips.
|
||||||
|
|
||||||
### Code Structure
|
### Code Structure
|
||||||
|
|
||||||
[openair3/NAS/NR_UE/nr_nas_msg.c](../openair3/NAS/NR_UE/nr_nas_msg.c):
|
[openair3/NAS/NR_UE/nr_nas_msg.c](../openair3/NAS/NR_UE/nr_nas_msg.c):
|
||||||
|
|||||||
@@ -612,6 +612,38 @@ static int fill_imeisv(FGSMobileIdentity *mi, const uicc_t *uicc)
|
|||||||
return 19;
|
return 19;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @brief Fill requested 5GS Mobile Identity (TS 24.501, 9.11.3.4) according to identity type.
|
||||||
|
*
|
||||||
|
* @param mobile_identity Pointer to the mobile identity structure to fill
|
||||||
|
* @param nas Pointer to the UE NAS context
|
||||||
|
* @param identitytype Type of identity to fill
|
||||||
|
* @return Encoded identity size in bytes, 0 on failure
|
||||||
|
*/
|
||||||
|
static int nas_fill_5gs_mobile_identity(FGSMobileIdentity *mobile_identity, const nr_ue_nas_t *nas, uint8_t identitytype)
|
||||||
|
{
|
||||||
|
switch (identitytype) {
|
||||||
|
case FGS_MOBILE_IDENTITY_SUCI:
|
||||||
|
return fill_suci(mobile_identity, nas->uicc);
|
||||||
|
case FGS_MOBILE_IDENTITY_5G_GUTI:
|
||||||
|
if (!nas->guti) {
|
||||||
|
LOG_W(NAS, "Cannot build Identity Response with 5G-GUTI: UE has no valid GUTI\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return fill_guti(mobile_identity, nas->guti);
|
||||||
|
case FGS_MOBILE_IDENTITY_IMEISV:
|
||||||
|
return fill_imeisv(mobile_identity, nas->uicc);
|
||||||
|
case FGS_MOBILE_IDENTITY_5GS_TMSI:
|
||||||
|
if (!nas->guti) {
|
||||||
|
LOG_W(NAS, "Cannot build Identity Response with 5G-S-TMSI: UE has no valid GUTI\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return fill_fgstmsi(&mobile_identity->stmsi, nas->guti);
|
||||||
|
default:
|
||||||
|
LOG_W(NAS, "Cannot build Identity Response: requested identity type %d is unsupported\n", identitytype);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void transferRES(uint8_t ck[16], uint8_t ik[16], uint8_t *input, uint8_t rand[16], uint8_t *output, plmn_id_t *plmn_id)
|
void transferRES(uint8_t ck[16], uint8_t ik[16], uint8_t *input, uint8_t rand[16], uint8_t *output, plmn_id_t *plmn_id)
|
||||||
{
|
{
|
||||||
uint8_t S[100] = {0};
|
uint8_t S[100] = {0};
|
||||||
@@ -1116,7 +1148,8 @@ void generateServiceRequest(as_nas_info_t *initialNasMsg, nr_ue_nas_t *nas)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void generateIdentityResponse(as_nas_info_t *initialNasMsg, const uint8_t identitytype, uicc_t *uicc)
|
/** @brief Build Identity Response according to requested identity type (8.2.22 of 3GPP TS 24.501) */
|
||||||
|
static void generateIdentityResponse(const nr_ue_nas_t *nas, as_nas_info_t *initialNasMsg, uint8_t identitytype)
|
||||||
{
|
{
|
||||||
int size = sizeof(fgmm_msg_header_t);
|
int size = sizeof(fgmm_msg_header_t);
|
||||||
fgmm_nas_message_plain_t plain = {0};
|
fgmm_nas_message_plain_t plain = {0};
|
||||||
@@ -1125,11 +1158,13 @@ static void generateIdentityResponse(as_nas_info_t *initialNasMsg, const uint8_t
|
|||||||
plain.header = set_mm_header(FGS_IDENTITY_RESPONSE, PLAIN_5GS_MSG);
|
plain.header = set_mm_header(FGS_IDENTITY_RESPONSE, PLAIN_5GS_MSG);
|
||||||
size += sizeof(plain.header);
|
size += sizeof(plain.header);
|
||||||
|
|
||||||
// set identity response
|
/** Build Identity Response according to requested identity type: if identity
|
||||||
|
* is unavailable in UE context (e.g., missing 5G-GUTI), return */
|
||||||
fgmm_identity_response_msg *mm_msg = &plain.mm_msg.fgs_identity_response;
|
fgmm_identity_response_msg *mm_msg = &plain.mm_msg.fgs_identity_response;
|
||||||
if (identitytype == FGS_MOBILE_IDENTITY_SUCI) {
|
int identity_size = nas_fill_5gs_mobile_identity(&mm_msg->fgsmobileidentity, nas, identitytype);
|
||||||
size += fill_suci(&mm_msg->fgsmobileidentity, uicc);
|
if (identity_size <= 0)
|
||||||
}
|
return;
|
||||||
|
size += identity_size;
|
||||||
|
|
||||||
// encode the message
|
// encode the message
|
||||||
initialNasMsg->nas_data = malloc_or_fail(size * sizeof(*initialNasMsg->nas_data));
|
initialNasMsg->nas_data = malloc_or_fail(size * sizeof(*initialNasMsg->nas_data));
|
||||||
@@ -1161,13 +1196,15 @@ static void handle_identity_request(as_nas_info_t *initialNasMsg, nr_ue_nas_t *n
|
|||||||
"Received IDENTITY REQUEST for identity type: %s\n",
|
"Received IDENTITY REQUEST for identity type: %s\n",
|
||||||
print_info(msg.fgsmobileidentity, fgs_identity_type_text, sizeofArray(fgs_identity_type_text)));
|
print_info(msg.fgsmobileidentity, fgs_identity_type_text, sizeofArray(fgs_identity_type_text)));
|
||||||
|
|
||||||
if (mm_header.message_type == NAS_SECURITY_UNPROTECTED && msg.fgsmobileidentity != FGS_MOBILE_IDENTITY_SUCI) {
|
if (mm_header.security_header_type == PLAIN_5GS_MSG && msg.fgsmobileidentity != FGS_MOBILE_IDENTITY_SUCI) {
|
||||||
// see 3GPP TS 24.501 4.4.4.2
|
// TS 24.501, 4.4.4.2: unprotected Identity Request is processable only when the requested identity is SUCI.
|
||||||
LOG_E(NAS, "Only SUCI mobile identity is expected in a security-unprotected request\n");
|
LOG_E(NAS, "Drop unprotected Identity Request: SUCI required (TS 24.501 4.4.4.2), got %d\n", msg.fgsmobileidentity);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
generateIdentityResponse(initialNasMsg, msg.fgsmobileidentity, nas->uicc);
|
generateIdentityResponse(nas, initialNasMsg, msg.fgsmobileidentity);
|
||||||
|
if (initialNasMsg->length <= 0)
|
||||||
|
LOG_W(NAS, "Dropped Identity Response: unable to build Identity Response for identity type %d\n", msg.fgsmobileidentity);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void generateAuthenticationResp(nr_ue_nas_t *nas, as_nas_info_t *initialNasMsg, uint8_t *buf)
|
static void generateAuthenticationResp(nr_ue_nas_t *nas, as_nas_info_t *initialNasMsg, uint8_t *buf)
|
||||||
|
|||||||
@@ -77,7 +77,23 @@ You can build the tester as follows:
|
|||||||
|
|
||||||
Start the tester with the dedicated configuration file:
|
Start the tester with the dedicated configuration file:
|
||||||
|
|
||||||
openairinterface5g/build$ LD_LIBRARY_PATH=. ./tests/nr-ue-nas-simulator/nr-ue-nas-simulator-test -O ../tests/nr-ue-nas-simulator/test.conf
|
openairinterface5g/build$ ./tests/nr-ue-nas-simulator/nr-ue-nas-simulator-test -O ../tests/nr-ue-nas-simulator/test.conf
|
||||||
|
|
||||||
|
# Testing NAS procedures
|
||||||
|
|
||||||
|
## Registration with unknown 5G-GUTI
|
||||||
|
|
||||||
|
Optional `--identity-guti` or `identity-guti = 1` in the config file:
|
||||||
|
Registration Request is sent with an unknown 5G-GUTI so the AMF sends Identity
|
||||||
|
Request (SUCI) per TS 23.502 step 6, the simulator then continues the normal
|
||||||
|
attach flow. From `cmake_targets/ran_build/build`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./tests/nr-ue-nas-simulator/nr-ue-nas-simulator-test \
|
||||||
|
-O ../tests/nr-ue-nas-simulator/test.conf --identity-guti
|
||||||
|
```
|
||||||
|
|
||||||
|
Look for `Unknown-GUTI test:` then `IDENTITY REQUEST` / `IDENTITY RESPONSE` in NAS logs.
|
||||||
|
|
||||||
# Limitations
|
# Limitations
|
||||||
- The tester is limited to a fixed flow: Initial Attach -> PDU Session -> Deregistration.
|
- The tester is limited to a fixed flow: Initial Attach -> PDU Session -> Deregistration.
|
||||||
|
|||||||
@@ -11,8 +11,11 @@
|
|||||||
#include "openair2/GNB_APP/gnb_paramdef.h"
|
#include "openair2/GNB_APP/gnb_paramdef.h"
|
||||||
#include "openair3/SCTP/sctp_default_values.h"
|
#include "openair3/SCTP/sctp_default_values.h"
|
||||||
#include "openair3/NAS/NR_UE/nr_nas_msg.h"
|
#include "openair3/NAS/NR_UE/nr_nas_msg.h"
|
||||||
|
#include "openair3/UTILS/conversions.h"
|
||||||
#include "executables/nr-uesoftmodem.h"
|
#include "executables/nr-uesoftmodem.h"
|
||||||
#include "openair3/ocp-gtpu/gtp_itf.h"
|
#include "openair3/ocp-gtpu/gtp_itf.h"
|
||||||
|
#include "SIMULATION/TOOLS/sim.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
RAN_CONTEXT_t RC;
|
RAN_CONTEXT_t RC;
|
||||||
THREAD_STRUCT thread_struct;
|
THREAD_STRUCT thread_struct;
|
||||||
@@ -75,6 +78,46 @@ int nr_rlc_get_available_tx_space(const rnti_t rntiP, const logical_chan_id_t ch
|
|||||||
|
|
||||||
configmodule_interface_t *uniqCfg = NULL;
|
configmodule_interface_t *uniqCfg = NULL;
|
||||||
|
|
||||||
|
static int identity_guti = 0;
|
||||||
|
|
||||||
|
static paramdef_t nas_sim_params[] = {
|
||||||
|
BOOLPARAM("identity-guti",
|
||||||
|
"Registration with random 5G-GUTI to trigger AMF Identity Request (SUCI)\n",
|
||||||
|
PARAMFLAG_BOOL,
|
||||||
|
&identity_guti,
|
||||||
|
0),
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Seed a 5G-GUTI unknown to the AMF (TS 23.502 step 6: AMF should request SUCI) */
|
||||||
|
static void seed_unknown_guti(nr_ue_nas_t *nas, const plmn_id_t *plmn)
|
||||||
|
{
|
||||||
|
nas->guti = calloc_or_fail(1, sizeof(*nas->guti));
|
||||||
|
Guti5GSMobileIdentity_t *guti = nas->guti;
|
||||||
|
guti->typeofidentity = FGS_MOBILE_IDENTITY_5G_GUTI;
|
||||||
|
guti->mccdigit1 = MCC_HUNDREDS(plmn->mcc);
|
||||||
|
guti->mccdigit2 = MCC_MNC_DECIMAL(plmn->mcc);
|
||||||
|
guti->mccdigit3 = MCC_MNC_DIGIT(plmn->mcc);
|
||||||
|
guti->mncdigit1 = MNC_HUNDREDS(plmn->mnc, plmn->mnc_digit_length);
|
||||||
|
guti->mncdigit2 = MCC_MNC_DECIMAL(plmn->mnc);
|
||||||
|
guti->mncdigit3 = MCC_MNC_DIGIT(plmn->mnc);
|
||||||
|
/* OAI CN5G served GUAMI */
|
||||||
|
guti->amfregionid = 0x01;
|
||||||
|
guti->amfsetid = 0x001;
|
||||||
|
guti->amfpointer = 0x01;
|
||||||
|
fill_random(&guti->tmsi, sizeof(guti->tmsi));
|
||||||
|
LOG_I(NAS,
|
||||||
|
"Unknown-GUTI test: Registration Request will use 5G-GUTI MCC=%u%u%u MNC=%u%u AMFRI=%u AMFSI=%u AMFPT=%u TMSI=0x%08x\n",
|
||||||
|
guti->mccdigit1,
|
||||||
|
guti->mccdigit2,
|
||||||
|
guti->mccdigit3,
|
||||||
|
guti->mncdigit1,
|
||||||
|
guti->mncdigit2,
|
||||||
|
guti->amfregionid,
|
||||||
|
guti->amfsetid,
|
||||||
|
guti->amfpointer,
|
||||||
|
guti->tmsi);
|
||||||
|
}
|
||||||
|
|
||||||
// Emulate registration request to register UE in the AMF
|
// Emulate registration request to register UE in the AMF
|
||||||
void send_initial_ue_message(instance_t instance)
|
void send_initial_ue_message(instance_t instance)
|
||||||
{
|
{
|
||||||
@@ -82,9 +125,8 @@ void send_initial_ue_message(instance_t instance)
|
|||||||
|
|
||||||
NGAP_NAS_FIRST_REQ(msg_p).gNB_ue_ngap_id = 1; // Simulated UE ID
|
NGAP_NAS_FIRST_REQ(msg_p).gNB_ue_ngap_id = 1; // Simulated UE ID
|
||||||
|
|
||||||
NGAP_NAS_FIRST_REQ(msg_p).plmn.mcc = mcc;
|
plmn_id_t plmn = {.mcc = mcc, .mnc = mnc, .mnc_digit_length = mnc_len};
|
||||||
NGAP_NAS_FIRST_REQ(msg_p).plmn.mnc = mnc;
|
NGAP_NAS_FIRST_REQ(msg_p).plmn = plmn;
|
||||||
NGAP_NAS_FIRST_REQ(msg_p).plmn.mnc_digit_length = mnc_len;
|
|
||||||
|
|
||||||
NGAP_NAS_FIRST_REQ(msg_p).nr_cell_id = gnb_id;
|
NGAP_NAS_FIRST_REQ(msg_p).nr_cell_id = gnb_id;
|
||||||
|
|
||||||
@@ -95,16 +137,27 @@ void send_initial_ue_message(instance_t instance)
|
|||||||
// serving network name (snn)
|
// serving network name (snn)
|
||||||
// Ideally snn should be filled from SIB1, but we operate at NAS/RRC level and we dont decode SIB1
|
// Ideally snn should be filled from SIB1, but we operate at NAS/RRC level and we dont decode SIB1
|
||||||
nr_ue_nas->sn_id = calloc(1, sizeof(plmn_id_t));
|
nr_ue_nas->sn_id = calloc(1, sizeof(plmn_id_t));
|
||||||
nr_ue_nas->sn_id->mcc = mcc;
|
*nr_ue_nas->sn_id = plmn;
|
||||||
nr_ue_nas->sn_id->mnc = mnc;
|
if (identity_guti) {
|
||||||
nr_ue_nas->sn_id->mnc_digit_length = mnc_len;
|
seed_unknown_guti(nr_ue_nas, &plmn);
|
||||||
|
|
||||||
|
ngap_ue_identity_t *ue_identity = &NGAP_NAS_FIRST_REQ(msg_p).ue_identity;
|
||||||
|
const Guti5GSMobileIdentity_t *guti = nr_ue_nas->guti;
|
||||||
|
|
||||||
|
ue_identity->presenceMask = NGAP_UE_IDENTITIES_FiveG_s_tmsi | NGAP_UE_IDENTITIES_guami;
|
||||||
|
ue_identity->s_tmsi.amf_set_id = guti->amfsetid;
|
||||||
|
ue_identity->s_tmsi.amf_pointer = guti->amfpointer;
|
||||||
|
ue_identity->s_tmsi.m_tmsi = guti->tmsi;
|
||||||
|
ue_identity->guami.plmn = plmn;
|
||||||
|
ue_identity->guami.amf_region_id = guti->amfregionid;
|
||||||
|
ue_identity->guami.amf_set_id = guti->amfsetid;
|
||||||
|
ue_identity->guami.amf_pointer = guti->amfpointer;
|
||||||
|
}
|
||||||
generateRegistrationRequest(&initialNasMsg, nr_ue_nas, false);
|
generateRegistrationRequest(&initialNasMsg, nr_ue_nas, false);
|
||||||
|
|
||||||
NGAP_NAS_FIRST_REQ(msg_p).nas_pdu.buf = initialNasMsg.nas_data;
|
NGAP_NAS_FIRST_REQ(msg_p).nas_pdu.buf = initialNasMsg.nas_data;
|
||||||
NGAP_NAS_FIRST_REQ(msg_p).nas_pdu.len = initialNasMsg.length;
|
NGAP_NAS_FIRST_REQ(msg_p).nas_pdu.len = initialNasMsg.length;
|
||||||
|
|
||||||
NGAP_NAS_FIRST_REQ(msg_p).ue_identity.presenceMask = 0;
|
|
||||||
|
|
||||||
itti_send_msg_to_task(TASK_NGAP, instance, msg_p);
|
itti_send_msg_to_task(TASK_NGAP, instance, msg_p);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,6 +402,8 @@ int main(int argc, char **argv)
|
|||||||
if ((uniqCfg = load_configmodule(argc, argv, CONFIG_ENABLECMDLINEONLY)) == NULL) {
|
if ((uniqCfg = load_configmodule(argc, argv, CONFIG_ENABLECMDLINEONLY)) == NULL) {
|
||||||
exit_fun("[SOFTMODEM] Error, configuration module init failed\n");
|
exit_fun("[SOFTMODEM] Error, configuration module init failed\n");
|
||||||
}
|
}
|
||||||
|
config_get(uniqCfg, nas_sim_params, sizeofArray(nas_sim_params), NULL);
|
||||||
|
|
||||||
logInit();
|
logInit();
|
||||||
|
|
||||||
nas_init_nrue(NB_UE_INST);
|
nas_init_nrue(NB_UE_INST);
|
||||||
|
|||||||
Reference in New Issue
Block a user