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.
This commit is contained in:
Aaron van Diepen
2026-03-31 00:44:32 +02:00
parent af4b0d53e9
commit 8ec7b4009b
15 changed files with 466 additions and 37 deletions
@@ -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;
}
@@ -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 {
+134 -1
View File
@@ -9,6 +9,7 @@
#include "nr_nas_msg.h"
#include <netinet/in.h>
#include "NR_NAS_defs.h"
#include <openssl/opensslv.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
@@ -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",
+74
View File
@@ -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 <openssl/evp.h>
#include <openssl/core_names.h>
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
+23
View File
@@ -0,0 +1,23 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#ifndef CURVE_25519_OAI_H
#define CURVE_25519_OAI_H
#include <openssl/opensslv.h>
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
/* code for version 3.0 or greater */
#include <stdint.h>
#include <stddef.h>
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
+49
View File
@@ -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 <string.h>
#include "common/utils/assertions.h"
#include <openssl/evp.h>
#include <openssl/kdf.h>
#include <openssl/core_names.h>
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
+23
View File
@@ -0,0 +1,23 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#ifndef x963_KDF_OAI_H
#define x963_KDF_OAI_H
#include <openssl/opensslv.h>
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
/* code for version 3.0 or greater */
#include <stdint.h>
#include <stdlib.h>
#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
+17 -11
View File
@@ -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;
}
+5
View File
@@ -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;
/*