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
|
||||
|
||||
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 |
|
||||
|-------|-------------------------------------------|----------|----------|------------|
|
||||
| 5GMM | Service Request | yes | yes | yes |
|
||||
| 5GMM | Service Accept | 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 | Identity Response | 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 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
|
||||
|
||||
[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;
|
||||
}
|
||||
|
||||
/** @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)
|
||||
{
|
||||
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);
|
||||
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);
|
||||
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;
|
||||
if (identitytype == FGS_MOBILE_IDENTITY_SUCI) {
|
||||
size += fill_suci(&mm_msg->fgsmobileidentity, uicc);
|
||||
}
|
||||
int identity_size = nas_fill_5gs_mobile_identity(&mm_msg->fgsmobileidentity, nas, identitytype);
|
||||
if (identity_size <= 0)
|
||||
return;
|
||||
size += identity_size;
|
||||
|
||||
// encode the message
|
||||
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",
|
||||
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) {
|
||||
// see 3GPP TS 24.501 4.4.4.2
|
||||
LOG_E(NAS, "Only SUCI mobile identity is expected in a security-unprotected request\n");
|
||||
if (mm_header.security_header_type == PLAIN_5GS_MSG && msg.fgsmobileidentity != FGS_MOBILE_IDENTITY_SUCI) {
|
||||
// TS 24.501, 4.4.4.2: unprotected Identity Request is processable only when the requested identity is SUCI.
|
||||
LOG_E(NAS, "Drop unprotected Identity Request: SUCI required (TS 24.501 4.4.4.2), got %d\n", msg.fgsmobileidentity);
|
||||
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)
|
||||
|
||||
@@ -77,7 +77,23 @@ You can build the tester as follows:
|
||||
|
||||
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
|
||||
- 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 "openair3/SCTP/sctp_default_values.h"
|
||||
#include "openair3/NAS/NR_UE/nr_nas_msg.h"
|
||||
#include "openair3/UTILS/conversions.h"
|
||||
#include "executables/nr-uesoftmodem.h"
|
||||
#include "openair3/ocp-gtpu/gtp_itf.h"
|
||||
#include "SIMULATION/TOOLS/sim.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
RAN_CONTEXT_t RC;
|
||||
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;
|
||||
|
||||
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
|
||||
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).plmn.mcc = mcc;
|
||||
NGAP_NAS_FIRST_REQ(msg_p).plmn.mnc = mnc;
|
||||
NGAP_NAS_FIRST_REQ(msg_p).plmn.mnc_digit_length = mnc_len;
|
||||
plmn_id_t plmn = {.mcc = mcc, .mnc = mnc, .mnc_digit_length = mnc_len};
|
||||
NGAP_NAS_FIRST_REQ(msg_p).plmn = plmn;
|
||||
|
||||
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)
|
||||
// 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->mcc = mcc;
|
||||
nr_ue_nas->sn_id->mnc = mnc;
|
||||
nr_ue_nas->sn_id->mnc_digit_length = mnc_len;
|
||||
*nr_ue_nas->sn_id = plmn;
|
||||
if (identity_guti) {
|
||||
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);
|
||||
|
||||
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).ue_identity.presenceMask = 0;
|
||||
|
||||
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) {
|
||||
exit_fun("[SOFTMODEM] Error, configuration module init failed\n");
|
||||
}
|
||||
config_get(uniqCfg, nas_sim_params, sizeofArray(nas_sim_params), NULL);
|
||||
|
||||
logInit();
|
||||
|
||||
nas_init_nrue(NB_UE_INST);
|
||||
|
||||
Reference in New Issue
Block a user