From 8ec7b4009ba2aebc2abae7b14afdc5fcea94f6c7 Mon Sep 17 00:00:00 2001 From: Aaron van Diepen Date: Mon, 23 Mar 2026 15:38:22 +0100 Subject: [PATCH] Implement SUCI Profile Scheme A for 5G UE Extend UICC configuration parsing to provide: routing_indicator, protection_scheme, home_network_public_key, and home_network_public_key_id. Use the protection_scheme value to decide what SUCI Profile Scheme to apply during SUCI generation. Add support for Profile Scheme A which provides ECIES-based encryption using Curve25519 and X9.63 KDF as outlined in TS 33.501 Section C.3.4.1 Profile A. When a configuration file specifies an unsupported Profile Scheme, the NAS layer triggers a fatal error. This occurs either because Profile Scheme B is unimplemented or the build uses OpenSSL < 3.0, which lacks Curve25519 and X9.63 KDF support, ensuring users are informed of the incompatibility. --- CMakeLists.txt | 4 +- cmake_targets/at_commands/CMakeLists.txt | 2 + common/utils/utils.c | 45 ++++++ common/utils/utils.h | 2 + doc/5Gnas.md | 62 ++++++-- doc/Doxyfile | 4 + .../NR_UE/5GS/5GMM/IES/FGSMobileIdentity.c | 45 ++++-- .../NR_UE/5GS/5GMM/IES/FGSMobileIdentity.h | 2 +- openair3/NAS/NR_UE/nr_nas_msg.c | 135 +++++++++++++++++- openair3/SECU/curve_25519.c | 74 ++++++++++ openair3/SECU/curve_25519.h | 23 +++ openair3/SECU/x963_kdf.c | 49 +++++++ openair3/SECU/x963_kdf.h | 23 +++ openair3/UICC/usim_interface.c | 28 ++-- openair3/UICC/usim_interface.h | 5 + 15 files changed, 466 insertions(+), 37 deletions(-) create mode 100644 openair3/SECU/curve_25519.c create mode 100644 openair3/SECU/curve_25519.h create mode 100644 openair3/SECU/x963_kdf.c create mode 100644 openair3/SECU/x963_kdf.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 976fdd728e..6e8758cea3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -674,6 +674,8 @@ target_link_libraries(UTIL PUBLIC instrumentation) set(SECURITY_SRC ${OPENAIR3_DIR}/SECU/secu_defs.c + ${OPENAIR3_DIR}/SECU/curve_25519.c + ${OPENAIR3_DIR}/SECU/x963_kdf.c ${OPENAIR3_DIR}/SECU/kdf.c ${OPENAIR3_DIR}/SECU/aes_128_ctr.c ${OPENAIR3_DIR}/SECU/aes_128_cbc_cmac.c @@ -1911,7 +1913,7 @@ target_link_libraries(nr-uesoftmodem PRIVATE target_link_libraries(nr-uesoftmodem PRIVATE pthread m CONFIG_LIB rt nr_ue_phy_meas) target_link_libraries(nr-uesoftmodem PRIVATE ${T_LIB}) -target_link_libraries(nr-uesoftmodem PRIVATE nr_nas lib_uicc usim_lib) +target_link_libraries(nr-uesoftmodem PRIVATE nr_nas lib_uicc usim_lib SECURITY) target_link_libraries(nr-uesoftmodem PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs) # force the generation of ASN.1 so that we don't need to wait during the build diff --git a/cmake_targets/at_commands/CMakeLists.txt b/cmake_targets/at_commands/CMakeLists.txt index 30482cdf81..2f70e1e440 100755 --- a/cmake_targets/at_commands/CMakeLists.txt +++ b/cmake_targets/at_commands/CMakeLists.txt @@ -80,6 +80,8 @@ add_boolean_option(NAS_BUILT_IN_UE False "UE NAS layer present in this # SECU LIB ################################################################################ set(secu_cn_SRC + ${OPENAIR3_DIR}/SECU/curve_25519.c + ${OPENAIR3_DIR}/SECU/x963_kdf.c ${OPENAIR3_DIR}/SECU/aes_128_ctr.c ${OPENAIR3_DIR}/SECU/aes_128_cbc_cmac.c ${OPENAIR3_DIR}/SECU/sha_256_hmac.c diff --git a/common/utils/utils.c b/common/utils/utils.c index c2e388b5fd..1b4d742ef7 100644 --- a/common/utils/utils.c +++ b/common/utils/utils.c @@ -94,6 +94,51 @@ char *itoa(int i) { return strdup(buffer); } +/**************************************************************************** + ** ** + ** Name: digit_string_to_bcd_value() ** + ** ** + ** Description: Converts a decimal ASCII coded string into its BCD encoded** + ** value. ** + ** ** + ** Inputs: bcd_value: Output buffer ** + ** digit_string: Input digit string ** + ** size: Size of bcd_value in bytes ** + ** ** + ** Outputs: bcd_value: Converted BCD value ** + ** Return: 0 on success, -1 on error ** + ** ** + ***************************************************************************/ +int digit_string_to_bcd_value(uint8_t *bcd_value, const char *digit_string, int size) +{ + int i; + uint8_t len = strlen(digit_string); + + /* Accept even (size*2) or odd (size*2 - 1) digit counts */ + if (len < size * 2 - 1 || len > size * 2) { + fprintf(stderr, "the string '%s' should be of length %d or %d\n", digit_string, size * 2 - 1, size * 2); + return -1; + } + + for (i = 0; i < size; i++) { + uint8_t low = digit_string[2 * i] - '0'; + uint8_t high = (2 * i + 1 < len) ? digit_string[2 * i + 1] - '0' : 0xF; + + if (low > 9 || (high > 9 && high != 0xF)) + goto error; + + bcd_value[i] = (high << 4) | low; + } + + return 0; + +error: + fprintf(stderr, "the string '%s' is not a valid digit string\n", digit_string); + for (i = 0; i < size; i++) + bcd_value[i] = 0; + return -1; +} + /** * @brief Convert a version string x.y.z into numbers. * diff --git a/common/utils/utils.h b/common/utils/utils.h index ada33e7ad9..796bfcbc1f 100644 --- a/common/utils/utils.h +++ b/common/utils/utils.h @@ -123,6 +123,8 @@ static inline void *malloc_or_fail(size_t size) int hex_char_to_hex_value (char c); // Converts an hexadecimal ASCII coded string into its value.** int hex_string_to_hex_value (uint8_t *hex_value, const char *hex_string, int size); +// Converts a decimal ASCII coded string into its BCD encoded value. +int digit_string_to_bcd_value(uint8_t *bcd_value, const char *digit_string, int size); /* Map task id to printable name. */ typedef struct { diff --git a/doc/5Gnas.md b/doc/5Gnas.md index 27fdaf99f5..27e1b48e55 100644 --- a/doc/5Gnas.md +++ b/doc/5Gnas.md @@ -78,18 +78,22 @@ The simulation reads values from a named section in the config file. **Config options in the `uicc` section**: -| Parameter | Description | Default value | -|---------------|-----------------------------------|--------------------------------------------| -| `imsi` | User IMSI | `2089900007487` | -| `nmc_size` | Number of digits in NMC | `2` | -| `key` | Subscription key (Ki) | `fec86ba6eb707ed08905757b1bb44b8f` | -| `opc` | OPc value | `c42449363bbad02b66d16bc975d77cc1` | -| `amf` | AMF value | `8000` | -| `sqn` | Sequence number | `000000` | -| `dnn` | Default DNN (APN) | `oai` | -| `nssai_sst` | NSSAI slice/service type | `1` | -| `nssai_sd` | NSSAI slice differentiator | `0xffffff` | -| `imeisv` | IMEISV string | `6754567890123413` | +| Parameter | Description | Default value | +|------------------------------|-----------------------------------|--------------------------------------------------------------------| +| `imsi` | User IMSI | `2089900007487` | +| `nmc_size` | Number of digits in NMC | `2` | +| `key` | Subscription key (Ki) | `fec86ba6eb707ed08905757b1bb44b8f` | +| `opc` | OPc value | `c42449363bbad02b66d16bc975d77cc1` | +| `amf` | AMF value | `8000` | +| `sqn` | Sequence number | `000000` | +| `dnn` | Default DNN (APN) | `oai` | +| `nssai_sst` | NSSAI slice/service type | `1` | +| `nssai_sd` | NSSAI slice differentiator | `0xffffff` | +| `imeisv` | IMEISV string | `6754567890123413` | +| `routing_indicator` | Routing Indicator | `0000` | +| `protection_scheme` | SUCI Profile Scheme | `0` | +| `home_network_public_key_id` | Home Network Public Key ID | `1` | +| `home_network_public_key` | Home Network Public Key | `5a8d38864820197c3394b92613b20b91633cbd897119273bf8e4a6f4eec0a650` | These are parsed and stored in the `uicc_t` structure. @@ -116,3 +120,37 @@ The **IMEISV**, is encoded using the `fill_imeisv()` helper. This function extra See TS 24.501 §4.4 for reference. +#### SUCI (Subscription Concealed Identifier) + +The **SUCI**, is generated using the `fill_suci()` helper. This function extracts the MCC, MNC, and MSIN from the configured `imsi` with the use of `nmc_size` in the UICC context and populates the mobile identity structure. + +Contains: +* MCC and MNC (public network identity) +* Routing Indicator +* Protection Scheme ID +* Home Network Public Key ID +* Concealed or clear MSIN depending on the protection scheme + +If the UE is unable to generate SUCI due to configuration or crypto limitations, the UE will fail to generate a `Registration Request`. + +###### 0. Null Scheme (TS 33.501 §C.2) + +MSIN in cleartext. + +###### 1. Profile A (TS 33.501 §C.3.4.1) + +MSIN is concealed using elliptic curve cryptography. +Based on: +* Curve25519 for key agreement +* X9.63 KDF for key derivation + +(requires OpenSSL ≥ 3.0) + +###### 2. Profile B (TS 33.501 §C.3.4.2) + +MSIN is concealed using elliptic curve cryptography. +Based on: +* P-256 for key agreement +* X9.63 KDF for key derivation + +(currently not supported) diff --git a/doc/Doxyfile b/doc/Doxyfile index f89d2fd26f..1beb3b0a37 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -1066,6 +1066,8 @@ INPUT = \ @CMAKE_CURRENT_SOURCE_DIR@/../openair3/NGAP/ngap_gNB_overload.c \ @CMAKE_CURRENT_SOURCE_DIR@/../openair3/NGAP/ngap_gNB_default_values.h \ @CMAKE_CURRENT_SOURCE_DIR@/../openair3/SECU/secu_defs.c \ +@CMAKE_CURRENT_SOURCE_DIR@/../openair3/SECU/curve_25519.h \ +@CMAKE_CURRENT_SOURCE_DIR@/../openair3/SECU/x963_kdf.h \ @CMAKE_CURRENT_SOURCE_DIR@/../openair3/SECU/sha_256_hmac.h \ @CMAKE_CURRENT_SOURCE_DIR@/../openair3/SECU/nas_stream_eea0.h \ @CMAKE_CURRENT_SOURCE_DIR@/../openair3/SECU/aes_128_ecb.h \ @@ -1088,6 +1090,8 @@ INPUT = \ @CMAKE_CURRENT_SOURCE_DIR@/../openair3/SECU/aes_128_ctr.c \ @CMAKE_CURRENT_SOURCE_DIR@/../openair3/SECU/nas_stream_eia2.h \ @CMAKE_CURRENT_SOURCE_DIR@/../openair3/SECU/snow3g.c \ +@CMAKE_CURRENT_SOURCE_DIR@/../openair3/SECU/curve_25519.c \ +@CMAKE_CURRENT_SOURCE_DIR@/../openair3/SECU/x963_kdf.c \ @CMAKE_CURRENT_SOURCE_DIR@/../openair3/SECU/sha_256_hmac.c \ @CMAKE_CURRENT_SOURCE_DIR@/../openair3/SECU/aes_128_cbc_cmac.h \ @CMAKE_CURRENT_SOURCE_DIR@/../openair3/SECU/nas_stream_eia1.c \ diff --git a/openair3/NAS/NR_UE/5GS/5GMM/IES/FGSMobileIdentity.c b/openair3/NAS/NR_UE/5GS/5GMM/IES/FGSMobileIdentity.c index 6dec4a0fd5..a342ccd5a4 100644 --- a/openair3/NAS/NR_UE/5GS/5GMM/IES/FGSMobileIdentity.c +++ b/openair3/NAS/NR_UE/5GS/5GMM/IES/FGSMobileIdentity.c @@ -17,7 +17,7 @@ static int decode_guti_5gs_mobile_identity(Guti5GSMobileIdentity_t *guti, const uint8_t *buffer); static int encode_guti_5gs_mobile_identity(const Guti5GSMobileIdentity_t *guti, uint8_t *buffer); -static int encode_suci_5gs_mobile_identity(const Suci5GSMobileIdentity_t *suci, uint8_t *buffer); +static int encode_suci_5gs_mobile_identity(const Suci5GSMobileIdentity_t *suci, uint8_t *buffer, uint32_t len); static int encode_imeisv_5gs_mobile_identity(const Imeisv5GSMobileIdentity_t *imeisv, uint8_t *buffer); int encode_stmsi_5gs_mobile_identity(uint8_t *buffer, const Stmsi5GSMobileIdentity_t *stmsi, uint32_t len) @@ -121,7 +121,7 @@ int encode_5gs_mobile_identity(const FGSMobileIdentity *fgsmobileidentity, uint8 } if (fgsmobileidentity->suci.typeofidentity == FGS_MOBILE_IDENTITY_SUCI) { - encoded_rc = encode_suci_5gs_mobile_identity(&fgsmobileidentity->suci, buffer + encoded); + encoded_rc = encode_suci_5gs_mobile_identity(&fgsmobileidentity->suci, buffer + encoded, len); } if (fgsmobileidentity->imeisv.typeofidentity == FGS_MOBILE_IDENTITY_IMEISV) { @@ -218,9 +218,13 @@ static int encode_guti_5gs_mobile_identity(const Guti5GSMobileIdentity_t *guti, return encoded; } -static int encode_suci_5gs_mobile_identity(const Suci5GSMobileIdentity_t *suci, uint8_t *buffer) +static int encode_suci_5gs_mobile_identity(const Suci5GSMobileIdentity_t *suci, uint8_t *buffer, uint32_t len) { uint32_t encoded = 0; + + if (len < 8) + return -1; + *(buffer + encoded) = (suci->supiformat << 4) | (suci->typeofidentity); encoded++; *(buffer + encoded) = ((suci->mccdigit2 & 0xf) << 4) | (suci->mccdigit1 & 0xf); @@ -242,15 +246,34 @@ static int encode_suci_5gs_mobile_identity(const Suci5GSMobileIdentity_t *suci, *(buffer + encoded) = suci->homenetworkpki; encoded++; - const char *ptr = suci->schemeoutput; - while (ptr < suci->schemeoutput + strlen(suci->schemeoutput)) { - buffer[encoded] = ((*(ptr + 1) - '0') << 4) | (*(ptr) - '0'); - encoded++; - ptr += 2; - } + DevAssert(suci->schemeoutput); + size_t rawlen = strlen(suci->schemeoutput); + size_t outlen = (rawlen + 1) / 2; - if (strlen(suci->schemeoutput) % 2 == 1) - buffer[encoded++] = ((*(ptr - 1) - '0')) | 0xF0; + if (len - encoded < outlen) + return -1; + + if (suci->protectionschemeId == 0) { + AssertFatal(suci->homenetworkpki == 0, + "Invalid SUCI: homenetworkpki=%u with null protection scheme (must be 0)\n", + suci->homenetworkpki); + int rc = digit_string_to_bcd_value(buffer + encoded, suci->schemeoutput, outlen); + AssertFatal(rc == 0, + "BCD encoding failed (rc=%d, input=\"%s\", len=%zu, out_len=%zu)\n", + rc, + suci->schemeoutput, + rawlen, + outlen); + } else { + int rc = hex_string_to_hex_value(buffer + encoded, suci->schemeoutput, outlen); + AssertFatal(rc == 0, + "Encoding SUCI schemeoutput failed (rc=%d, input=\"%s\", len=%zu, out_len=%zu)\n", + rc, + suci->schemeoutput, + rawlen, + outlen); + } + encoded += outlen; return encoded; } diff --git a/openair3/NAS/NR_UE/5GS/5GMM/IES/FGSMobileIdentity.h b/openair3/NAS/NR_UE/5GS/5GMM/IES/FGSMobileIdentity.h index c9d29370f9..8cf8b1bc63 100644 --- a/openair3/NAS/NR_UE/5GS/5GMM/IES/FGSMobileIdentity.h +++ b/openair3/NAS/NR_UE/5GS/5GMM/IES/FGSMobileIdentity.h @@ -41,7 +41,7 @@ typedef struct { uint8_t spare6: 1; uint8_t protectionschemeId: 4; uint8_t homenetworkpki; - char schemeoutput[32]; + char schemeoutput[128]; } Suci5GSMobileIdentity_t; typedef struct { diff --git a/openair3/NAS/NR_UE/nr_nas_msg.c b/openair3/NAS/NR_UE/nr_nas_msg.c index 7f94b2a747..c6d227294d 100644 --- a/openair3/NAS/NR_UE/nr_nas_msg.c +++ b/openair3/NAS/NR_UE/nr_nas_msg.c @@ -9,6 +9,7 @@ #include "nr_nas_msg.h" #include #include "NR_NAS_defs.h" +#include #include #include #include @@ -53,6 +54,18 @@ #include "key_nas_deriver.h" #include "nr-uesoftmodem.h" +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#include "curve_25519.h" +#include "aes_128_ctr.h" +#include "x963_kdf.h" +#include "sha_256_hmac.h" +static const char hex[] = "0123456789abcdef"; +#elif OPENSSL_VERSION_NUMBER >= 0x10100000L +#define MY_OPENSSL_VERSION_STR OpenSSL_version(OPENSSL_VERSION) +#else +#define MY_OPENSSL_VERSION_STR SSLeay_version(SSLEAY_VERSION) +#endif + static nr_ue_nas_t nr_ue_nas[MAX_NUM_NR_UE_INST] = {0}; nr_ue_nas_t *get_nr_ue_nas_info(uint8_t ue_inst) @@ -388,6 +401,83 @@ static security_state_t nas_security_rx_process(nr_ue_nas_t *nas, byte_array_t b return NAS_SECURITY_INTEGRITY_PASSED; } +static void suci_profile_scheme_a_generate_output(const uint8_t home_network_public_key[32], + const char *msin, + const size_t msin_len, + char *schemeoutput) +{ + /* 1> Eph. key pair generation */ + uint8_t eph_priv[32] = {0}; + uint8_t eph_pub[32] = {0}; + x25519_generate_keypair(eph_priv, eph_pub); + + /* 2> Key agreement */ + uint8_t eph_shared_key[32] = {0}; + x25519_shared_secret(eph_priv, home_network_public_key, eph_shared_key); + + explicit_bzero(eph_priv, 32); + + /* 3> Key derivation */ + uint8_t kdf_output[64] = {0}; + byte_array_t kdf_secret = {.buf = eph_shared_key, .len = 32}; + byte_array_t kdf_info = {.buf = eph_pub, .len = 32}; + x963_kdf(kdf_secret, kdf_info, 64, kdf_output); + + explicit_bzero(eph_shared_key, 32); + + aes_128_t aes_ctx; + aes_ctx.type = AES_INITIALIZATION_VECTOR_16; + memcpy(aes_ctx.key, kdf_output, 16); + memcpy(aes_ctx.iv16.iv, kdf_output + 16, 16); + + uint8_t eph_mac_key[32] = {0}; + memcpy(eph_mac_key, kdf_output + 32, 32); + + explicit_bzero(kdf_output, 64); + + /* 4> Symmetric encryption */ + size_t msin_bcd_len = (msin_len + 1) / 2; + uint8_t msin_bcd[msin_bcd_len]; + memset(msin_bcd, 0, msin_bcd_len); + + int rc = digit_string_to_bcd_value(msin_bcd, msin, msin_bcd_len); + AssertFatal(rc == 0, "Encoding MSIN failed (rc=%d, input=\"%s\", len=%zu, out_len=%zu)", rc, msin, msin_len, msin_bcd_len); + + byte_array_t payload = {.buf = msin_bcd, .len = msin_bcd_len}; + uint8_t ciphertext[msin_bcd_len]; + aes_128_ctr(&aes_ctx, payload, msin_bcd_len, ciphertext); + + explicit_bzero(aes_ctx.key, 16); + explicit_bzero(aes_ctx.iv16.iv, 16); + explicit_bzero(msin_bcd, msin_bcd_len); + + /* 5> MAC function */ + uint8_t mac_full[32] = {0}; + byte_array_t mac_input = {.buf = ciphertext, .len = msin_bcd_len}; + sha_256_hmac(eph_mac_key, mac_input, 32, mac_full); + + explicit_bzero(eph_mac_key, 32); + + /* Build SUCI scheme output --- */ + /* eph_pub (32 bytes -> 64 hex chars) */ + for (int i = 0; i < 32; i++) { + *schemeoutput++ = hex[eph_pub[i] >> 4]; + *schemeoutput++ = hex[eph_pub[i] & 0x0F]; + } + + /* ciphertext (~45 bytes -> ~90 hex chars) */ + for (int i = 0; i < msin_bcd_len; i++) { + *schemeoutput++ = hex[ciphertext[i] >> 4]; + *schemeoutput++ = hex[ciphertext[i] & 0x0F]; + } + + /* MAC (8 bytes -> 16 hex chars) */ + for (int i = 0; i < 8; i++) { + *schemeoutput++ = hex[mac_full[i] >> 4]; + *schemeoutput++ = hex[mac_full[i] & 0x0F]; + } +} + static int fill_suci(FGSMobileIdentity *mi, const uicc_t *uicc) { mi->suci.typeofidentity = FGS_MOBILE_IDENTITY_SUCI; @@ -397,7 +487,50 @@ static int fill_suci(FGSMobileIdentity *mi, const uicc_t *uicc) mi->suci.mccdigit1 = uicc->imsiStr[0] - '0'; mi->suci.mccdigit2 = uicc->imsiStr[1] - '0'; mi->suci.mccdigit3 = uicc->imsiStr[2] - '0'; - memcpy(mi->suci.schemeoutput, uicc->imsiStr + 3 + uicc->nmc_size, strlen(uicc->imsiStr) - (3 + uicc->nmc_size)); + + mi->suci.routingindicatordigit1 = uicc->routing_indicatorStr[0] - '0'; + mi->suci.routingindicatordigit2 = uicc->routing_indicatorStr[1] - '0'; + mi->suci.routingindicatordigit3 = uicc->routing_indicatorStr[2] - '0'; + mi->suci.routingindicatordigit4 = uicc->routing_indicatorStr[3] - '0'; + + char *msin = uicc->imsiStr + 3 + uicc->nmc_size; + uint8_t msin_len = strlen(msin); + + mi->suci.protectionschemeId = uicc->protection_scheme; + switch (uicc->protection_scheme) { + case 0: /* Null scheme (TS 33.501 C.2) */ + { + mi->suci.homenetworkpki = 0; + memcpy(mi->suci.schemeoutput, msin, msin_len); + break; + } + case 1: /* Profile A (TS 33.501 C.3.4.1) */ + { +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + mi->suci.homenetworkpki = uicc->home_network_public_key_id; + suci_profile_scheme_a_generate_output(uicc->home_network_public_key, msin, msin_len, mi->suci.schemeoutput); +#else + AssertFatal(false, + "Protection Scheme not supported when using a version below OpenSSL 3.0 %d (OpenSSL version: %s)\n", + uicc->protection_scheme, + MY_OPENSSL_VERSION_STR); +#endif + break; + } + case 2: /* Profile B (TS 33.501 C.3.4.2) */ + { + mi->suci.homenetworkpki = uicc->home_network_public_key_id; + + AssertFatal(false, "Unsupported Protection Scheme in UICC %d\n", uicc->protection_scheme); + + break; + } + default: // Unknown schemes + { + AssertFatal(false, "Unknown Protection Scheme in UICC %d\n", uicc->protection_scheme); + } + } + LOG_D(NAS, "SUCI in registration request: SUPI type: %d Type of Identity: %u MCC: %u%u%u, MNC: %u%u%u, \ Routing Indicator %d%d%d%d Protection Scheme ID: %u, Home Network PKI: %u, Scheme Output: %s\n", diff --git a/openair3/SECU/curve_25519.c b/openair3/SECU/curve_25519.c new file mode 100644 index 0000000000..64553259bd --- /dev/null +++ b/openair3/SECU/curve_25519.c @@ -0,0 +1,74 @@ +/* + * SPDX-License-Identifier: LicenseRef-CSSL-1.0 + */ + +#include "curve_25519.h" + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + +/* code for version 3.0 or greater */ + +#include "common/utils/assertions.h" + +#include +#include + +void x25519_generate_keypair(uint8_t priv[32], uint8_t pub[32]) +{ + DevAssert(priv != NULL); + DevAssert(pub != NULL); + + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_X25519, NULL); + DevAssert(pctx != NULL); + + int rc = EVP_PKEY_keygen_init(pctx); + DevAssert(rc == 1); + + EVP_PKEY *pkey = NULL; + rc = EVP_PKEY_keygen(pctx, &pkey); + DevAssert(rc == 1); + + EVP_PKEY_CTX_free(pctx); + + size_t len = 32; + + rc = EVP_PKEY_get_raw_private_key(pkey, priv, &len); + DevAssert(rc == 1 && len == 32); + + rc = EVP_PKEY_get_raw_public_key(pkey, pub, &len); + DevAssert(rc == 1 && len == 32); + + EVP_PKEY_free(pkey); +} + +void x25519_shared_secret(const uint8_t priv[32], const uint8_t peer_pub[32], uint8_t secret[32]) +{ + DevAssert(priv != NULL); + DevAssert(peer_pub != NULL); + DevAssert(secret != NULL); + + EVP_PKEY *pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, NULL, priv, 32); + DevAssert(pkey != NULL); + + EVP_PKEY *peerkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_X25519, NULL, peer_pub, 32); + DevAssert(peerkey != NULL); + + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL); + DevAssert(ctx != NULL); + + int rc = EVP_PKEY_derive_init(ctx); + DevAssert(rc == 1); + + rc = EVP_PKEY_derive_set_peer(ctx, peerkey); + DevAssert(rc == 1); + + size_t len = 32; + rc = EVP_PKEY_derive(ctx, secret, &len); + DevAssert(rc == 1 && len == 32); + + EVP_PKEY_free(pkey); + EVP_PKEY_free(peerkey); + EVP_PKEY_CTX_free(ctx); +} + +#endif \ No newline at end of file diff --git a/openair3/SECU/curve_25519.h b/openair3/SECU/curve_25519.h new file mode 100644 index 0000000000..e49f2be8c7 --- /dev/null +++ b/openair3/SECU/curve_25519.h @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: LicenseRef-CSSL-1.0 + */ + +#ifndef CURVE_25519_OAI_H +#define CURVE_25519_OAI_H + +#include + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + +/* code for version 3.0 or greater */ + +#include +#include + +void x25519_generate_keypair(uint8_t priv[32], uint8_t pub[32]); + +void x25519_shared_secret(const uint8_t priv[32], const uint8_t peer_pub[32], uint8_t secret[32]); + +#endif + +#endif \ No newline at end of file diff --git a/openair3/SECU/x963_kdf.c b/openair3/SECU/x963_kdf.c new file mode 100644 index 0000000000..332ca38b2a --- /dev/null +++ b/openair3/SECU/x963_kdf.c @@ -0,0 +1,49 @@ +/* + * SPDX-License-Identifier: LicenseRef-CSSL-1.0 + */ + +#include "x963_kdf.h" + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + +/* code for version 3.0 or greater */ + +#include +#include "common/utils/assertions.h" +#include +#include +#include + +void x963_kdf(const byte_array_t sharedsecret, const byte_array_t sharedinfo, size_t len, uint8_t out[len]) +{ + DevAssert(sharedsecret.buf != NULL); + DevAssert(sharedsecret.len > 0); + DevAssert(out != NULL); + DevAssert(len > 0); + + EVP_KDF *kdf = EVP_KDF_fetch(NULL, "X963KDF", NULL); + DevAssert(kdf != NULL); + + EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf); + DevAssert(kctx != NULL); + + EVP_KDF_free(kdf); + + OSSL_PARAM params[4], *p = params; + + *p++ = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST, SN_sha256, strlen(SN_sha256)); + *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY, sharedsecret.buf, sharedsecret.len); + + if (sharedinfo.buf != NULL && sharedinfo.len > 0) { + *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_INFO, sharedinfo.buf, sharedinfo.len); + } + + *p = OSSL_PARAM_construct_end(); + + int rc = EVP_KDF_derive(kctx, out, len, params); + DevAssert(rc == 1); + + EVP_KDF_CTX_free(kctx); +} + +#endif \ No newline at end of file diff --git a/openair3/SECU/x963_kdf.h b/openair3/SECU/x963_kdf.h new file mode 100644 index 0000000000..e831617629 --- /dev/null +++ b/openair3/SECU/x963_kdf.h @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: LicenseRef-CSSL-1.0 + */ + +#ifndef x963_KDF_OAI_H +#define x963_KDF_OAI_H + +#include + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + +/* code for version 3.0 or greater */ + +#include +#include + +#include "byte_array.h" + +void x963_kdf(const byte_array_t sharedsecret, const byte_array_t sharedinfo, size_t len, uint8_t out[len]); + +#endif + +#endif diff --git a/openair3/UICC/usim_interface.c b/openair3/UICC/usim_interface.c index 40f69bfdc6..9ec066bad2 100644 --- a/openair3/UICC/usim_interface.c +++ b/openair3/UICC/usim_interface.c @@ -14,21 +14,26 @@ extern uint16_t NB_UE_INST; " key: cyphering key\n"\ " opc: cyphering OPc\n"\ " imiesv: string with IMEISV value\n" +#define DEFAULT_HOME_NETWORK_PUBLIC_KEY "5a8d38864820197c3394b92613b20b91633cbd897119273bf8e4a6f4eec0a650" /*-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------*/ /* configuration parameters for the rfsimulator device */ /* optname helpstr paramflags XXXptr defXXXval type numelt */ /*-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------*/ -#define UICC_PARAMS_DESC { \ - {"imsi", "USIM IMSI\n", 0, .strptr=&uicc->imsiStr, .defstrval="2089900007487", TYPE_STRING, 0 }, \ - {"nmc_size" "number of digits in NMC", 0, .iptr=&uicc->nmc_size, .defintval=2, TYPE_INT, 0 }, \ - {"key", "USIM Ki\n", 0, .strptr=&uicc->keyStr, .defstrval="fec86ba6eb707ed08905757b1bb44b8f", TYPE_STRING, 0 }, \ - {"opc", "USIM OPc\n", 0, .strptr=&uicc->opcStr, .defstrval="c42449363bbad02b66d16bc975d77cc1", TYPE_STRING, 0 }, \ - {"amf", "USIM amf\n", 0, .strptr=&uicc->amfStr, .defstrval="8000", TYPE_STRING, 0 }, \ - {"sqn", "USIM sqn\n", 0, .strptr=&uicc->sqnStr, .defstrval="000000", TYPE_STRING, 0 }, \ - {"dnn", "UE dnn (apn)\n", 0, .strptr=&uicc->dnnStr, .defstrval="oai", TYPE_STRING, 0 }, \ - {"nssai_sst", "UE nssai\n", 0, .iptr=&uicc->nssai_sst, .defintval=1, TYPE_INT, 0 }, \ - {"nssai_sd", "UE nssai\n", 0, .iptr=&uicc->nssai_sd, .defintval=0xffffff, TYPE_INT, 0 }, \ - {"imeisv", "IMEISV\n", 0, .strptr=&uicc->imeisvStr, .defstrval="6754567890123413", TYPE_STRING, 0 }, \ +#define UICC_PARAMS_DESC { \ + {"imsi", "USIM IMSI\n", 0, .strptr=&uicc->imsiStr, .defstrval="2089900007487", TYPE_STRING, 0}, \ + {"nmc_size", "number of digits in NMC", 0, .iptr=&uicc->nmc_size, .defintval=2, TYPE_INT, 0}, \ + {"key", "USIM Ki\n", 0, .strptr=&uicc->keyStr, .defstrval="fec86ba6eb707ed08905757b1bb44b8f", TYPE_STRING, 0}, \ + {"opc", "USIM OPc\n", 0, .strptr=&uicc->opcStr, .defstrval="c42449363bbad02b66d16bc975d77cc1", TYPE_STRING, 0}, \ + {"amf", "USIM amf\n", 0, .strptr=&uicc->amfStr, .defstrval="8000", TYPE_STRING, 0}, \ + {"sqn", "USIM sqn\n", 0, .strptr=&uicc->sqnStr, .defstrval="000000", TYPE_STRING, 0}, \ + {"dnn", "UE dnn (apn)\n", 0, .strptr=&uicc->dnnStr, .defstrval="oai", TYPE_STRING, 0}, \ + {"nssai_sst", "UE nssai\n", 0, .iptr=&uicc->nssai_sst, .defintval=1, TYPE_INT, 0}, \ + {"nssai_sd", "UE nssai\n", 0, .iptr=&uicc->nssai_sd, .defintval=0xffffff, TYPE_INT, 0}, \ + {"imeisv", "IMEISV\n", 0, .strptr=&uicc->imeisvStr, .defstrval="6754567890123413", TYPE_STRING, 0}, \ + {"routing_indicator", "Routing Indicator\n", 0, .strptr = &uicc->routing_indicatorStr, .defstrval="0000", TYPE_STRING, 0}, \ + {"protection_scheme", "SUCI Profile Scheme\n", 0, .iptr = &uicc->protection_scheme, .defintval=0, TYPE_INT, 0}, \ + {"home_network_public_key_id", "Home Network Public Key ID\n", 0, .iptr=&uicc->home_network_public_key_id, .defintval=1, TYPE_INT, 0}, \ + {"home_network_public_key", "Home Network Public Key\n", 0, .strptr=&uicc->home_network_public_keyStr, .defstrval=DEFAULT_HOME_NETWORK_PUBLIC_KEY, TYPE_STRING, 0}, \ }; static uicc_t** uiccArray=NULL; @@ -62,6 +67,7 @@ uicc_t *init_uicc(char *sectionName) { to_hex(uicc->opcStr,uicc->opc, sizeof(uicc->opc) ); to_hex(uicc->sqnStr,uicc->sqn, sizeof(uicc->sqn) ); to_hex(uicc->amfStr,uicc->amf, sizeof(uicc->amf) ); + to_hex(uicc->home_network_public_keyStr, uicc->home_network_public_key, sizeof(uicc->home_network_public_key)); return uicc; } diff --git a/openair3/UICC/usim_interface.h b/openair3/UICC/usim_interface.h index 371915b3f6..09ba9f2dfb 100644 --- a/openair3/UICC/usim_interface.h +++ b/openair3/UICC/usim_interface.h @@ -37,6 +37,10 @@ typedef struct { int nssai_sd; pdu_session_config_t pdu_sessions[256]; int n_pdu_sessions; + char *routing_indicatorStr; + int protection_scheme; + char *home_network_public_keyStr; + int home_network_public_key_id; uint8_t key[16]; uint8_t opc[16]; uint8_t amf[2]; @@ -49,6 +53,7 @@ typedef struct { uint8_t ck[16]; uint8_t ik[16]; uint8_t milenage_res[8]; + uint8_t home_network_public_key[32]; } uicc_t; /*