From 4262203e2de1df6635a5daed6815b5915c009633 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 18 Feb 2025 07:27:57 +0000 Subject: [PATCH 1/7] Add NAS Security Mode Reject enc/dec lib and unit test This 5GMM message is required for an invalid security mode command, e.g. when the MAC integrity fails, or the wrong Security Header is set. * Add encode/decode functions for common IEs in fgmm_lib: this library provides encoding/decoding helpers for IEs shared across multiple NAS messages. The goal is to avoid the legacy approach of creating separate files per IE * add NAS Cause enc/dec lib * Adopted byte_array_t to handle the buffer Co-authored-by: Eduard Vlad --- .../NAS/NR_UE/5GS/5GMM/MSG/CMakeLists.txt | 1 + .../5GS/5GMM/MSG/FGSNASSecurityModeReject.c | 50 +++++++++++++++++++ .../5GS/5GMM/MSG/FGSNASSecurityModeReject.h | 47 +++++++++++++++++ openair3/NAS/NR_UE/5GS/tests/nas_lib_test.c | 33 ++++++++++++ 4 files changed, 131 insertions(+) create mode 100644 openair3/NAS/NR_UE/5GS/5GMM/MSG/FGSNASSecurityModeReject.c create mode 100644 openair3/NAS/NR_UE/5GS/5GMM/MSG/FGSNASSecurityModeReject.h diff --git a/openair3/NAS/NR_UE/5GS/5GMM/MSG/CMakeLists.txt b/openair3/NAS/NR_UE/5GS/5GMM/MSG/CMakeLists.txt index bf47e478b9..0f4445c353 100644 --- a/openair3/NAS/NR_UE/5GS/5GMM/MSG/CMakeLists.txt +++ b/openair3/NAS/NR_UE/5GS/5GMM/MSG/CMakeLists.txt @@ -9,6 +9,7 @@ add_library(fgs_5gmm_lib OBJECT FGSIdentityResponse.c FGSAuthenticationResponse.c FGSNASSecurityModeComplete.c + FGSNASSecurityModeReject.c RegistrationComplete.c FGSUplinkNasTransport.c FGSDeregistrationRequestUEOriginating.c diff --git a/openair3/NAS/NR_UE/5GS/5GMM/MSG/FGSNASSecurityModeReject.c b/openair3/NAS/NR_UE/5GS/5GMM/MSG/FGSNASSecurityModeReject.c new file mode 100644 index 0000000000..3f61b14ad7 --- /dev/null +++ b/openair3/NAS/NR_UE/5GS/5GMM/MSG/FGSNASSecurityModeReject.c @@ -0,0 +1,50 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#include +#include "FGSNASSecurityModeReject.h" +#include "common/utils/ds/byte_array.h" +#include "fgmm_lib.h" + +int encode_fgs_security_mode_reject(byte_array_t *buffer, const fgs_security_mode_reject_msg *msg) +{ + if (buffer->len < 1) + return -1; // nothing to encode + + // 5GMM cause (Mandatory) + return encode_fgs_nas_cause(buffer, &msg->cause); +} + +/** @brief Decode Security Mode Reject (8.2.18 of 3GPP TS 24.501) */ +int decode_fgs_security_mode_reject(fgs_security_mode_reject_msg *msg, const byte_array_t *buffer) +{ + if (buffer->len < 1) + return -1; // nothing to decode + + // 5GMM cause (Mandatory) + return decode_fgs_nas_cause(&msg->cause, buffer); +} + +bool eq_sec_mode_reject(const fgs_security_mode_reject_msg *a, const fgs_security_mode_reject_msg *b) +{ + _NAS_EQ_CHECK_INT(a->cause, b->cause); + return true; +} diff --git a/openair3/NAS/NR_UE/5GS/5GMM/MSG/FGSNASSecurityModeReject.h b/openair3/NAS/NR_UE/5GS/5GMM/MSG/FGSNASSecurityModeReject.h new file mode 100644 index 0000000000..28c10e3700 --- /dev/null +++ b/openair3/NAS/NR_UE/5GS/5GMM/MSG/FGSNASSecurityModeReject.h @@ -0,0 +1,47 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#ifndef FGS_NAS_security_mode_reject_H_ +#define FGS_NAS_security_mode_reject_H_ + +#include +#include "fgmm_lib.h" + +/* + * Message name: security mode reject + * Description: The SECURITY MODE REJECT message is sent by the UE to the AMF to + * indicate that the corresponding security mode command has been rejected. + * See 3GPP TS 24.501 table 8.2.27.1.1 + * + * Significance: dual + * Direction: UE to AMF + */ + +typedef struct { + // 5GMM cause (Mandatory) + cause_id_t cause; +} fgs_security_mode_reject_msg; + +int encode_fgs_security_mode_reject(byte_array_t *buffer, const fgs_security_mode_reject_msg *msg); +int decode_fgs_security_mode_reject(fgs_security_mode_reject_msg *msg, const byte_array_t *buffer); +bool eq_sec_mode_reject(const fgs_security_mode_reject_msg *a, const fgs_security_mode_reject_msg *b); + +#endif /* ! defined(FGS_NAS_security_mode_reject_H_) */ diff --git a/openair3/NAS/NR_UE/5GS/tests/nas_lib_test.c b/openair3/NAS/NR_UE/5GS/tests/nas_lib_test.c index 88de4f13aa..f733bdd3a5 100644 --- a/openair3/NAS/NR_UE/5GS/tests/nas_lib_test.c +++ b/openair3/NAS/NR_UE/5GS/tests/nas_lib_test.c @@ -8,6 +8,7 @@ #include "fgmm_service_accept.h" #include "fgmm_service_reject.h" #include "fgmm_authentication_failure.h" +#include "FGSNASSecurityModeReject.h" #include "nr_nas_msg.h" void exit_function(const char *file, const char *function, const int line, const char *s, const int assert) @@ -266,6 +267,37 @@ static void test_auth_failure(void) free_fgmm_auth_failure(&dec); } +/** @brief Test NAS Security Mode Reject enc/dec */ +static void test_security_mode_reject(void) +{ + // Dummy NAS Security Mode Reject message + const fgs_security_mode_reject_msg orig = { + .cause = Illegal_UE, + }; + + // Expected encoded data + uint8_t expected_enc[] = {0x03}; + + // Buffer + uint8_t buf[8] = {0}; + byte_array_t buffer = {.buf = buf, .len = sizeof(buf)}; + + // Encode + int encoded_length = encode_fgs_security_mode_reject(&buffer, &orig); + AssertFatal(encoded_length == sizeofArray(expected_enc), "encode_fgs_security_mode_reject() failed\n"); + + // Compare the raw encoded buffer with expected encoded data + AssertFatal(memcmp(buffer.buf, expected_enc, encoded_length) == 0, "Encoding mismatch!\n"); + + // Decode + fgs_security_mode_reject_msg dec = {0}; + int decoded_length = decode_fgs_security_mode_reject(&dec, &buffer); + AssertFatal(decoded_length >= 0, "decode_fgs_security_mode_reject() failed\n"); + + // Compare original and decoded messages + AssertFatal(eq_sec_mode_reject(&orig, &dec), "test_sec_mode_reject() failed: original and decoded messages do not match\n"); +} + int main() { test_regression_registration_accept(); @@ -273,5 +305,6 @@ int main() test_service_accept(); test_service_reject(); test_auth_failure(); + test_security_mode_reject(); return 0; } From 98c458fa2c98bf2df8cc0e599921bd5876b37c67 Mon Sep 17 00:00:00 2001 From: Eduard Vlad Date: Wed, 28 May 2025 10:13:28 +0200 Subject: [PATCH 2/7] FGS NAS lib: enable encoding of Security Mode Reject Signed-off-by: Eduard Vlad Co-authored-by: Guido Casati --- openair3/NAS/NR_UE/5GS/NR_NAS_defs.h | 2 ++ openair3/NAS/NR_UE/5GS/fgs_nas_lib.c | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/openair3/NAS/NR_UE/5GS/NR_NAS_defs.h b/openair3/NAS/NR_UE/5GS/NR_NAS_defs.h index ab94925f09..2c8ecdf150 100644 --- a/openair3/NAS/NR_UE/5GS/NR_NAS_defs.h +++ b/openair3/NAS/NR_UE/5GS/NR_NAS_defs.h @@ -31,6 +31,7 @@ #include "FGSIdentityResponse.h" #include "FGSMobileIdentity.h" #include "FGSNASSecurityModeComplete.h" +#include "FGSNASSecurityModeReject.h" #include "FGSUplinkNasTransport.h" #include "RegistrationComplete.h" #include "RegistrationRequest.h" @@ -226,6 +227,7 @@ typedef struct { fgmm_auth_failure_t fgmm_auth_failure; fgs_deregistration_request_ue_originating_msg fgs_deregistration_request_ue_originating; fgs_security_mode_complete_msg fgs_security_mode_complete; + fgs_security_mode_reject_msg fgs_security_mode_reject; registration_complete_msg registration_complete; fgs_uplink_nas_transport_msg uplink_nas_transport; } mm_msg; /* 5GS Mobility Management messages */ diff --git a/openair3/NAS/NR_UE/5GS/fgs_nas_lib.c b/openair3/NAS/NR_UE/5GS/fgs_nas_lib.c index ca40131af6..3fcd82aa9b 100644 --- a/openair3/NAS/NR_UE/5GS/fgs_nas_lib.c +++ b/openair3/NAS/NR_UE/5GS/fgs_nas_lib.c @@ -81,6 +81,11 @@ int mm_msg_encode(const fgmm_nas_message_plain_t *p, uint8_t *buffer, uint32_t l case FGS_SECURITY_MODE_COMPLETE: enc_msg = encode_fgs_security_mode_complete(&p->mm_msg.fgs_security_mode_complete, buffer, len); break; + case FGS_SECURITY_MODE_REJECT: { + byte_array_t ba = {.buf = buffer, .len = len}; + enc_msg = encode_fgs_security_mode_reject(&ba, &p->mm_msg.fgs_security_mode_reject); + break; + } case FGS_UPLINK_NAS_TRANSPORT: enc_msg = encode_fgs_uplink_nas_transport(&p->mm_msg.uplink_nas_transport, buffer, len); break; From 6399d93be844195843b480833f1b914a13352632 Mon Sep 17 00:00:00 2001 From: Eduard Vlad Date: Wed, 28 May 2025 10:23:07 +0200 Subject: [PATCH 3/7] Add callback for generation of NAS Security Mode Reject message This is necessary to indicate to the Core network, that the security mode command procedure has failed. Check for the security context presence when generating the Security Mode reject message: according to 3GPP TS 24.501 5.4.2.5 "NAS security mode command not accepted by the UE" The UE shall send a SECURITY MODE REJECT containing a 5GMM cause that typically indicates one of the following cause values: (23) UE security capabilities mismatch (24) security mode rejected, unspecified The message shall be protected if a security context is available Signed-off-by: Eduard Vlad Co-authored-by: Guido Casati --- openair3/NAS/NR_UE/nr_nas_msg.c | 62 +++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/openair3/NAS/NR_UE/nr_nas_msg.c b/openair3/NAS/NR_UE/nr_nas_msg.c index d152a5d4a3..5b23c4a041 100644 --- a/openair3/NAS/NR_UE/nr_nas_msg.c +++ b/openair3/NAS/NR_UE/nr_nas_msg.c @@ -1042,6 +1042,68 @@ static void generateSecurityModeComplete(nr_ue_nas_t *nas, as_nas_info_t *initia } } +/** @brief Generates the Security Mode Reject message to be sent by the UE to the AMF + * to indicate that the corresponding security mode command has been rejected */ +static void generateSecurityModeReject(nr_ue_nas_t *nas, as_nas_info_t *initialNasMsg, cause_id_t cause) +{ + LOG_I(NAS, "Send Security Mode Reject\n"); + fgmm_msg_header_t plain_header = set_mm_header(FGS_SECURITY_MODE_REJECT, PLAIN_5GS_MSG); + int size = sizeof(plain_header); + fgs_security_mode_reject_msg msg = {.cause = cause}; + size += 1; + + /** The UE shall apply the 5G NAS security context in use + * before the initiation of the security mode control procedure, + * if any, to protect the SECURITY MODE REJECT message */ + bool has_security_context = nas->security_container && nas->security_container->integrity_context; + if (has_security_context) { + fgmm_nas_msg_security_protected_t sp = {0}; + sp.header.protocol_discriminator = FGS_MOBILITY_MANAGEMENT_MESSAGE; + sp.header.security_header_type = INTEGRITY_PROTECTED; + sp.header.sequence_number = nas->security.nas_count_ul & 0xff; + size += sizeof(sp.header); + sp.plain.header = plain_header; + sp.plain.mm_msg.fgs_security_mode_reject = msg; + // Security protected header encoding + int security_header_len = nas_protected_security_header_encode(initialNasMsg->nas_data, &sp.header, size); + initialNasMsg->length = + security_header_len + mm_msg_encode(&sp.plain, initialNasMsg->nas_data + security_header_len, size - security_header_len); + /* ciphering */ + uint8_t buf[initialNasMsg->length - 7]; + nas_stream_cipher_t stream_cipher; + stream_cipher.context = nas->security_container->ciphering_context; + AssertFatal(nas->security.nas_count_ul <= 0xffffff, "fatal: NAS COUNT UL too big (todo: fix that)\n"); + stream_cipher.count = nas->security.nas_count_ul; + stream_cipher.bearer = 1; + stream_cipher.direction = 0; + stream_cipher.message = (unsigned char *)(initialNasMsg->nas_data + 7); + /* length in bits */ + stream_cipher.blength = (initialNasMsg->length - 7) << 3; + stream_compute_encrypt(nas->security_container->ciphering_algorithm, &stream_cipher, buf); + memcpy(stream_cipher.message, buf, initialNasMsg->length - 7); + /* integrity protection */ + uint8_t mac[4]; + stream_cipher.context = nas->security_container->integrity_context; + stream_cipher.count = nas->security.nas_count_ul++; + stream_cipher.bearer = 1; + stream_cipher.direction = 0; + stream_cipher.message = (unsigned char *)(initialNasMsg->nas_data + 6); + /* length in bits */ + stream_cipher.blength = (initialNasMsg->length - 6) << 3; + stream_compute_integrity(nas->security_container->integrity_algorithm, &stream_cipher, mac); + LOG_D(NAS, "Integrity protected initial NAS message: mac = %x %x %x %x \n", mac[0], mac[1], mac[2], mac[3]); + for (int i = 0; i < 4; i++) + initialNasMsg->nas_data[2 + i] = mac[i]; + } else { + fgmm_nas_message_plain_t plain = {0}; + plain.header = plain_header; + plain.mm_msg.fgs_security_mode_reject = msg; + // encode the message + initialNasMsg->nas_data = malloc_or_fail(size); + initialNasMsg->length = mm_msg_encode(&plain, initialNasMsg->nas_data, size); + } +} + static void handle_security_mode_command(nr_ue_nas_t *nas, as_nas_info_t *initialNasMsg, uint8_t *pdu, int pdu_length) { /* retrieve integrity and ciphering algorithms */ From 896698dab5eaf95c0ab1a21a9cc30be6343b3649 Mon Sep 17 00:00:00 2001 From: Eduard Vlad Date: Fri, 31 Jan 2025 10:49:04 +0000 Subject: [PATCH 4/7] SMC: reject NULL integrity for non emergency cases Signed-off-by: Eduard Vlad --- openair3/NAS/NR_UE/nr_nas_msg.c | 6 ++++++ openair3/SECU/secu_defs.c | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/openair3/NAS/NR_UE/nr_nas_msg.c b/openair3/NAS/NR_UE/nr_nas_msg.c index 5b23c4a041..e647857336 100644 --- a/openair3/NAS/NR_UE/nr_nas_msg.c +++ b/openair3/NAS/NR_UE/nr_nas_msg.c @@ -1134,6 +1134,12 @@ static void handle_security_mode_command(nr_ue_nas_t *nas, as_nas_info_t *initia /* todo: stream_security_container_delete() is not called anywhere, deal with that */ nas->security_container = stream_security_container_init(ciphering_algorithm, integrity_algorithm, knas_enc, knas_int); + /* Handle the invalid container with a reject message */ + if(nas->security_container == NULL) { + LOG_W(NAS, "Could not create security container!\n"); + return; + } + nas_itti_kgnb_refresh_req(nas->UE_id, nas->security.kgnb); generateSecurityModeComplete(nas, initialNasMsg); } diff --git a/openair3/SECU/secu_defs.c b/openair3/SECU/secu_defs.c index 17e3ca96ec..f8edf167df 100644 --- a/openair3/SECU/secu_defs.c +++ b/openair3/SECU/secu_defs.c @@ -118,6 +118,12 @@ stream_security_container_t *stream_security_container_init(int ciphering_algori container->integrity_context = stream_integrity_init(integrity_algorithm, integrity_key); container->ciphering_context = stream_ciphering_init(ciphering_algorithm, ciphering_key); + /* Reject invalid configurations, i.e. ones that set NIA0 */ + if (container->integrity_context == NULL) { + stream_security_container_delete(container); + return NULL; + } + return container; } From 6c8a3796ae56909e4d753a2caf9b84f69a2de5e6 Mon Sep 17 00:00:00 2001 From: Eduard Vlad Date: Tue, 10 Jun 2025 14:11:03 +0200 Subject: [PATCH 5/7] nas security: Implement reusable procedures for Security Mode Command handling Retrieving the mac is necessary during the handling of the SMC, since the context is generated from the specified keys therein. Therefore, it is necessary to check the MAC right after generation and ensure the authenticity of the message. If it is not given, the message must be rejected. Signed-off-by: Eduard Vlad Co-authored-by: Guido Casati --- openair3/NAS/NR_UE/nr_nas_msg.c | 147 ++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/openair3/NAS/NR_UE/nr_nas_msg.c b/openair3/NAS/NR_UE/nr_nas_msg.c index e647857336..109037e9b1 100644 --- a/openair3/NAS/NR_UE/nr_nas_msg.c +++ b/openair3/NAS/NR_UE/nr_nas_msg.c @@ -151,6 +151,153 @@ static bool unprotected_allowed(byte_array_t buffer, fgs_nas_msg_t msg_type) } } +/** + * @brief Get the MAC of a Security Protected NAS message + * @param[in] pdu_buffer The buffer containing the NAS message + * @param[in] pdu_length The length of the NAS message + * @param[out] mac The MAC of the NAS message + * @return true if the MAC was successfully extracted, false otherwise + */ +bool nas_security_get_mac(uint8_t *pdu_buffer, int pdu_length, uint8_t *mac) +{ + /* Check for Security Protected Header */ + /* at least 8 bytes for Security Protected MM Message */ + if (pdu_length < 8) + return false; + + /* Only message type, that is not protected (c.f. + TS 24.501 9.3 Security Header Type) */ + if ((Security_header_t)pdu_buffer[1] == PLAIN_5GS_MSG) + return false; + + /* Get the MAC [EPD][SecHdr][MAC0]..[MAC3] - success */ + for (int i = 0; i < 4; i++) + mac[i] = pdu_buffer[2 + i]; + return true; +} + +/* + * @brief Get the Security Header of a NAS message + * @param[in] msg The buffer containing the NAS message + * @param[in] msg_length The length of the NAS message + * @param[out] sec_hdr The Security Header of the NAS message + * @return true if the Security Header was successfully extracted, false otherwise + */ +bool nas_security_get_sec_hdr(uint8_t *msg, int msg_length, Security_header_t *sec_hdr) +{ + /* Shortest message is [EPD][SecHdrType][Payl]*/ + if (msg_length < 3) { + LOG_E(NAS, "Invalid NAS message length %d\n", msg_length); + return false; + } + + /* Get the SecHdr and check for validity */ + uint8_t sec_hdr_type = msg[1] & 0x0f; + if (sec_hdr_type > INTEGRITY_PROTECTED_AND_CIPHERED_WITH_NEW_SECU_CTX) { + LOG_E(NAS, "Invalid Security Header Type %d\n", sec_hdr_type); + return false; + } + + /* Check ok. */ + *sec_hdr = (Security_header_t)sec_hdr_type; + return true; +} + +/* + * @brief Compute the MAC of a NAS message + * @param[in] nas The NAS context + * @param[in] pdu_buffer The buffer containing the NAS message + * @param[in] is_uplink True if the message is uplink, false Downlink + * @param[in] is3gpp_access True if the message is 3GPP access, false otherwise + * @param[in] pdu_length The length of the NAS message + * @param[out] mac The MAC of the NAS message + */ +static void nas_security_compute_mac(nr_ue_nas_t *nas, + byte_array_t buffer, + const bool is_uplink, + const bool is3gpp_access, + uint8_t *mac) +{ + uint8_t *buf = buffer.buf + SECURITY_PROTECTED_5GS_NAS_MESSAGE_HEADER_LENGTH - 1; + int len = buffer.len - SECURITY_PROTECTED_5GS_NAS_MESSAGE_HEADER_LENGTH + 1; + + /* Compute the MAC */ + nas_stream_cipher_t stream_cipher; + stream_cipher.context = nas->security_container->integrity_context; + + /* Select the right count in the context */ + if (is_uplink) + stream_cipher.count = nas->security.nas_count_ul; + else + stream_cipher.count = nas->security.nas_count_dl; + + /* 3GPP access is 1, non-3GPP access is 2 - see 3GPP TS 33.501 6.4.2.2 */ + stream_cipher.bearer = is3gpp_access ? 1 : 2; + + /* Configure Direction for MAC protection */ + stream_cipher.direction = is_uplink ? 0 : 1; + + /* Possibly encrypted message */ + stream_cipher.message = buf; + + /* length in bits */ + stream_cipher.blength = len << 3; + + stream_compute_integrity(nas->security_container->integrity_algorithm, &stream_cipher, mac); +} + +/* + * @brief: Decrypt the payload of a NAS message. The buffer is modified in place + * @param[in] nas The NAS context + * @param[in] pdu_buffer The buffer containing the full (header + payload) NAS message + * @param[in] is_uplink True if the message is uplink, false Downlink + * @param[in] is3gpp_access True if the message is 3GPP access, false otherwise + * @param[in] pdu_length The length of the NAS message + */ +static void nas_security_decrypt_payload(nr_ue_nas_t *nas, byte_array_t buffer, const bool is_uplink, const bool is3gpp_access) +{ + Security_header_t sec_hdr; + + if (!nas_security_get_sec_hdr(buffer.buf, buffer.len, &sec_hdr)) { + LOG_E(NAS, "Failed to get Security Header\n"); + return; + } + + /* Nothing to do for unencrypted msgs */ + if (sec_hdr == PLAIN_5GS_MSG || sec_hdr == INTEGRITY_PROTECTED) { + return; + } + + /* Get integrity keys, and algorithms */ + nas_stream_cipher_t stream_cipher; + stream_cipher.context = nas->security_container->ciphering_context; + + /* Use the estimated count the right count in the context */ + stream_cipher.count = nas->security.nas_count_dl; + /* 3GPP access is 1, non-3GPP access is 2 - see 3GPP TS 33.501 6.4.2.2 */ + stream_cipher.bearer = is3gpp_access ? 1 : 2; + + /* Decryption only in downlink direction */ + stream_cipher.direction = 1; + + /* [EPD][SHR][MAC0]..[MAC3][PDU...]*/ + uint8_t *plain_payload = buffer.buf + SECURITY_PROTECTED_5GS_NAS_MESSAGE_HEADER_LENGTH; + stream_cipher.message = plain_payload; + + /* length in bits */ + int plain_length = buffer.len - SECURITY_PROTECTED_5GS_NAS_MESSAGE_HEADER_LENGTH; + stream_cipher.blength = plain_length << 3; + + /* Allocate output buffer for body only */ + uint8_t *decrypted = malloc_or_fail(plain_length); + + stream_compute_encrypt(nas->security_container->ciphering_algorithm, &stream_cipher, decrypted); + + /* Override and free the decrypted payload */ + memcpy(plain_payload, decrypted, plain_length); + free(decrypted); +} + static security_state_t nas_security_rx_process(nr_ue_nas_t *nas, byte_array_t buffer, fgs_nas_msg_t msg_type) { if (nas->security_container == NULL) From a2a8e3ba6d2886e05ef9e94a22630bf349567fd5 Mon Sep 17 00:00:00 2001 From: Eduard Vlad Date: Wed, 28 May 2025 10:25:09 +0200 Subject: [PATCH 6/7] Security Mode Command: Handle incorrect authentication It is not allowed to accept an unauthenticated SMC message, as this effectively bypasses all security measures and enables a malicioius attacker to compromise the confidentiality and integrity of exchanged messages. Signed-off-by: Eduard Vlad Co-authored-by: Guido Casati --- openair3/NAS/NR_UE/nr_nas_msg.c | 83 ++++++++++++++++++++++++--------- 1 file changed, 60 insertions(+), 23 deletions(-) diff --git a/openair3/NAS/NR_UE/nr_nas_msg.c b/openair3/NAS/NR_UE/nr_nas_msg.c index 109037e9b1..f2e3b8dd07 100644 --- a/openair3/NAS/NR_UE/nr_nas_msg.c +++ b/openair3/NAS/NR_UE/nr_nas_msg.c @@ -356,35 +356,22 @@ static security_state_t nas_security_rx_process(nr_ue_nas_t *nas, byte_array_t b } /* check integrity */ - uint8_t computed_mac[NAS_INTEGRITY_SIZE]; - nas_stream_cipher_t stream_cipher; - stream_cipher.context = nas->security_container->integrity_context; - stream_cipher.count = nas->security.nas_count_dl; - stream_cipher.bearer = 1; /* todo: don't hardcode */ - stream_cipher.direction = 1; - stream_cipher.message = buffer.buf + SECURITY_PROTECTED_5GS_NAS_MESSAGE_HEADER_LENGTH - 1; - /* length in bits */ - stream_cipher.blength = (buffer.len - SECURITY_PROTECTED_5GS_NAS_MESSAGE_HEADER_LENGTH + 1) << 3; - stream_compute_integrity(nas->security_container->integrity_algorithm, &stream_cipher, computed_mac); - + /* [EPD][SHD][MAC0]...[MAC3][SEQNO][PAYL]*/ uint8_t *received_mac = buffer.buf + 2; + uint8_t computed_mac[NAS_INTEGRITY_SIZE]; + nas_security_compute_mac(nas, + buffer, + false, + true, + computed_mac); if (memcmp(received_mac, computed_mac, NAS_INTEGRITY_SIZE) != 0) return NAS_SECURITY_INTEGRITY_FAILED; /* decipher */ - uint8_t payload_len = buffer.len - SECURITY_PROTECTED_5GS_NAS_MESSAGE_HEADER_LENGTH; - uint8_t buf[payload_len]; - stream_cipher.context = nas->security_container->ciphering_context; - stream_cipher.count = nas->security.nas_count_dl; - stream_cipher.bearer = 1; /* todo: don't hardcode */ - stream_cipher.direction = 1; - stream_cipher.message = buffer.buf + SECURITY_PROTECTED_5GS_NAS_MESSAGE_HEADER_LENGTH; - /* length in bits */ - stream_cipher.blength = (payload_len) << 3; - stream_compute_encrypt(nas->security_container->ciphering_algorithm, &stream_cipher, buf); - memcpy(buffer.buf + SECURITY_PROTECTED_5GS_NAS_MESSAGE_HEADER_LENGTH, buf, payload_len); + nas_security_decrypt_payload(nas, buffer, false, true); + /* update estimated DL Counter */ nas->security.nas_count_dl++; return NAS_SECURITY_INTEGRITY_PASSED; @@ -1253,8 +1240,39 @@ static void generateSecurityModeReject(nr_ue_nas_t *nas, as_nas_info_t *initialN static void handle_security_mode_command(nr_ue_nas_t *nas, as_nas_info_t *initialNasMsg, uint8_t *pdu, int pdu_length) { + /* Handle security mode command: must be authenticated, especially if no security + context has been previously established. */ + uint8_t recv_mac[4]; + Security_header_t sec_hdr; + + /* Must have valid security header*/ + if(!nas_security_get_sec_hdr(pdu, pdu_length, &sec_hdr)) { + LOG_E(NAS, "Received Security Mode Command without integrity protection.\n"); + generateSecurityModeReject(nas, initialNasMsg, Security_mode_rejected_unspecified); + return; + } + + /* Must be integrity protected with new context (=3), see 3GPP TS 24.501 5.4.2.2 */ + if(sec_hdr != INTEGRITY_PROTECTED_WITH_NEW_SECU_CTX) { + LOG_E(NAS, "Received Security Mode Command with invalid security header type %d.\n", sec_hdr); + generateSecurityModeReject(nas, initialNasMsg, Security_mode_rejected_unspecified); + return; + } + + /* Must have a MAC - checked after deriving keys */ + if(!nas_security_get_mac(pdu, pdu_length, recv_mac)) { + LOG_E(NAS, "Received Security Mode Command with invalid MAC.\n"); + generateSecurityModeReject(nas, initialNasMsg, Security_mode_rejected_unspecified); + return; + } + /* retrieve integrity and ciphering algorithms */ - AssertFatal(pdu_length > 10, "nas: bad pdu\n"); + if (pdu_length < 10) { + LOG_E(NAS, "Invalid pdu_length=%d : send Security Mode Reject\n", pdu_length); + // 3GPP TS 24.501 5.4.2.5 NAS security mode command not accepted by the UE + generateSecurityModeReject(nas, initialNasMsg, Security_mode_rejected_unspecified); + } + int ciphering_algorithm = (pdu[10] >> 4) & 0x0f; int integrity_algorithm = pdu[10] & 0x0f; @@ -1284,6 +1302,25 @@ static void handle_security_mode_command(nr_ue_nas_t *nas, as_nas_info_t *initia /* Handle the invalid container with a reject message */ if(nas->security_container == NULL) { LOG_W(NAS, "Could not create security container!\n"); + generateSecurityModeReject(nas, initialNasMsg, Security_mode_rejected_unspecified); + return; + } + + /* Check MAC and delete context if it does not match */ + uint8_t computed_mac[NAS_INTEGRITY_SIZE]; + byte_array_t ba = {.buf = pdu, .len = pdu_length}; + nas_security_compute_mac(nas, ba, false, true, computed_mac); + + /* Teardown security container if mismatch. */ + if(memcmp(computed_mac, recv_mac, NAS_INTEGRITY_SIZE) != 0) { + LOG_W(NAS, "MAC does not match\n"); + LOG_W(NAS, "Expected: %x %x %x %x\n", computed_mac[0], computed_mac[1], computed_mac[2], computed_mac[3]); + LOG_W(NAS, "Received: %x %x %x %x\n", recv_mac[0], recv_mac[1], recv_mac[2], recv_mac[3]); + stream_security_container_delete(nas->security_container); + nas->security_container = NULL; + + /* Signal rejection */ + generateSecurityModeReject(nas, initialNasMsg, Security_mode_rejected_unspecified); return; } From 5e45ecb1239ac5241cefbff6030087255f3ade94 Mon Sep 17 00:00:00 2001 From: Eduard Vlad Date: Thu, 22 May 2025 14:31:34 +0200 Subject: [PATCH 7/7] Security Mode Command: Perform Algorithm Rejection in NAS Handler This change performs the algorithm rejection in the corresponding NAS handler for 5G, since the protocol definitions differ between LTE and 5G, regarding NIA0/EIA0 user plane traffic. Therefore, for 5G NIA0 is rejected, since it is not allowed under normal circumstances, while LTE user plane traffic can indeed have EIA0 selected. Signed-off-by: Eduard Vlad --- openair3/NAS/NR_UE/nr_nas_msg.c | 9 +++++++-- openair3/SECU/secu_defs.c | 6 ------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/openair3/NAS/NR_UE/nr_nas_msg.c b/openair3/NAS/NR_UE/nr_nas_msg.c index f2e3b8dd07..1a88b03205 100644 --- a/openair3/NAS/NR_UE/nr_nas_msg.c +++ b/openair3/NAS/NR_UE/nr_nas_msg.c @@ -1296,8 +1296,13 @@ static void handle_security_mode_command(nr_ue_nas_t *nas, as_nas_info_t *initia } printf("\n"); - /* todo: stream_security_container_delete() is not called anywhere, deal with that */ - nas->security_container = stream_security_container_init(ciphering_algorithm, integrity_algorithm, knas_enc, knas_int); + if (integrity_algorithm != EIA0_ALG_ID) { + nas->security_container = stream_security_container_init(ciphering_algorithm, integrity_algorithm, knas_enc, knas_int); + } else { + LOG_E(NAS, "Rejecting Invalid NULL integrity %d for 5G!\n", + integrity_algorithm); + nas->security_container = NULL; + } /* Handle the invalid container with a reject message */ if(nas->security_container == NULL) { diff --git a/openair3/SECU/secu_defs.c b/openair3/SECU/secu_defs.c index f8edf167df..17e3ca96ec 100644 --- a/openair3/SECU/secu_defs.c +++ b/openair3/SECU/secu_defs.c @@ -118,12 +118,6 @@ stream_security_container_t *stream_security_container_init(int ciphering_algori container->integrity_context = stream_integrity_init(integrity_algorithm, integrity_key); container->ciphering_context = stream_ciphering_init(ciphering_algorithm, ciphering_key); - /* Reject invalid configurations, i.e. ones that set NIA0 */ - if (container->integrity_context == NULL) { - stream_security_container_delete(container); - return NULL; - } - return container; }