From 42871a5d25ea3d252c52eaff001f0373e9bae210 Mon Sep 17 00:00:00 2001 From: Jouni Malinen Date: Sun, 1 May 2022 11:34:49 +0300 Subject: [PATCH] EAP-SIM/AKA peer: IMSI privacy Add support for IMSI privacy in the EAP-SIM/AKA peer implementation. If the new wpa_supplicant network configuration parameter imsi_privacy_key is used to specify an RSA public key in a form of a PEM encoded X.509v3 certificate, that key will be used to encrypt the permanent identity (IMSI) in the transmitted EAP messages. Signed-off-by: Jouni Malinen --- src/eap_peer/eap.c | 26 +++++++++++ src/eap_peer/eap_aka.c | 83 ++++++++++++++++++++++++++++++++++ src/eap_peer/eap_config.h | 10 +++++ src/eap_peer/eap_sim.c | 84 +++++++++++++++++++++++++++++++++++ wpa_supplicant/README-HS20 | 6 +++ wpa_supplicant/config.c | 12 +++++ wpa_supplicant/config.h | 10 +++++ wpa_supplicant/interworking.c | 6 +++ 8 files changed, 237 insertions(+) diff --git a/src/eap_peer/eap.c b/src/eap_peer/eap.c index 7dcfe4fff..429b20d3a 100644 --- a/src/eap_peer/eap.c +++ b/src/eap_peer/eap.c @@ -1673,6 +1673,7 @@ struct wpabuf * eap_sm_buildIdentity(struct eap_sm *sm, int id, int encrypted) struct wpabuf *resp; const u8 *identity; size_t identity_len; + struct wpabuf *privacy_identity = NULL; if (config == NULL) { wpa_printf(MSG_WARNING, "EAP: buildIdentity: configuration " @@ -1695,6 +1696,30 @@ struct wpabuf * eap_sm_buildIdentity(struct eap_sm *sm, int id, int encrypted) identity_len = config->machine_identity_len; wpa_hexdump_ascii(MSG_DEBUG, "EAP: using machine identity", identity, identity_len); + } else if (config->imsi_privacy_key && config->identity && + config->identity_len > 0) { + const u8 *pos = config->identity; + const u8 *end = config->identity + config->identity_len; + + privacy_identity = wpabuf_alloc(9 + config->identity_len); + if (!privacy_identity) + return NULL; + + /* Include method prefix */ + if (*pos == '0' || *pos == '1' || *pos == '6') + wpabuf_put_u8(privacy_identity, *pos); + wpabuf_put_str(privacy_identity, "anonymous"); + + /* Include realm */ + while (pos < end && *pos != '@') + pos++; + wpabuf_put_data(privacy_identity, pos, end - pos); + + identity = wpabuf_head(privacy_identity); + identity_len = wpabuf_len(privacy_identity); + wpa_hexdump_ascii(MSG_DEBUG, + "EAP: using IMSI privacy anonymous identity", + identity, identity_len); } else { identity = config->identity; identity_len = config->identity_len; @@ -1731,6 +1756,7 @@ struct wpabuf * eap_sm_buildIdentity(struct eap_sm *sm, int id, int encrypted) return NULL; wpabuf_put_data(resp, identity, identity_len); + wpabuf_free(privacy_identity); return resp; } diff --git a/src/eap_peer/eap_aka.c b/src/eap_peer/eap_aka.c index 8c475f13f..8caae1d6a 100644 --- a/src/eap_peer/eap_aka.c +++ b/src/eap_peer/eap_aka.c @@ -9,6 +9,7 @@ #include "includes.h" #include "common.h" +#include "utils/base64.h" #include "pcsc_funcs.h" #include "crypto/crypto.h" #include "crypto/sha1.h" @@ -58,6 +59,7 @@ struct eap_aka_data { u16 last_kdf_attrs[EAP_AKA_PRIME_KDF_MAX]; size_t last_kdf_count; int error_code; + struct crypto_rsa_key *imsi_privacy_key; }; @@ -101,6 +103,25 @@ static void * eap_aka_init(struct eap_sm *sm) data->eap_method = EAP_TYPE_AKA; + if (config && config->imsi_privacy_key) { +#ifdef CRYPTO_RSA_OAEP_SHA256 + data->imsi_privacy_key = crypto_rsa_key_read( + config->imsi_privacy_key, false); + if (!data->imsi_privacy_key) { + wpa_printf(MSG_ERROR, + "EAP-AKA: Failed to read/parse IMSI privacy key %s", + config->imsi_privacy_key); + os_free(data); + return NULL; + } +#else /* CRYPTO_RSA_OAEP_SHA256 */ + wpa_printf(MSG_ERROR, + "EAP-AKA: No support for imsi_privacy_key in the build"); + os_free(data); + return NULL; +#endif /* CRYPTO_RSA_OAEP_SHA256 */ + } + /* Zero is a valid error code, so we need to initialize */ data->error_code = NO_EAP_METHOD_ERROR; @@ -160,6 +181,9 @@ static void eap_aka_deinit(struct eap_sm *sm, void *priv) wpabuf_free(data->id_msgs); os_free(data->network_name); eap_aka_clear_keys(data, 0); +#ifdef CRYPTO_RSA_OAEP_SHA256 + crypto_rsa_key_free(data->imsi_privacy_key); +#endif /* CRYPTO_RSA_OAEP_SHA256 */ os_free(data); } } @@ -617,6 +641,47 @@ static struct wpabuf * eap_aka_synchronization_failure( } +#ifdef CRYPTO_RSA_OAEP_SHA256 +static struct wpabuf * +eap_aka_encrypt_identity(struct crypto_rsa_key *imsi_privacy_key, + const u8 *identity, size_t identity_len) +{ + struct wpabuf *imsi_buf, *enc; + char *b64; + size_t b64_len; + + wpa_hexdump_ascii(MSG_DEBUG, "EAP-AKA: Encrypt permanent identity", + identity, identity_len); + + imsi_buf = wpabuf_alloc_copy(identity, identity_len); + if (!imsi_buf) + return NULL; + enc = crypto_rsa_oaep_sha256_encrypt(imsi_privacy_key, imsi_buf); + wpabuf_free(imsi_buf); + if (!enc) + return NULL; + + b64 = base64_encode_no_lf(wpabuf_head(enc), wpabuf_len(enc), &b64_len); + wpabuf_free(enc); + if (!b64) + return NULL; + + enc = wpabuf_alloc(1 + b64_len); + if (!enc) { + os_free(b64); + return NULL; + } + wpabuf_put_u8(enc, '\0'); + wpabuf_put_data(enc, b64, b64_len); + os_free(b64); + wpa_hexdump_ascii(MSG_DEBUG, "EAP-AKA: Encrypted permanent identity", + wpabuf_head(enc), wpabuf_len(enc)); + + return enc; +} +#endif /* CRYPTO_RSA_OAEP_SHA256 */ + + static struct wpabuf * eap_aka_response_identity(struct eap_sm *sm, struct eap_aka_data *data, u8 id, @@ -625,6 +690,7 @@ static struct wpabuf * eap_aka_response_identity(struct eap_sm *sm, const u8 *identity = NULL; size_t identity_len = 0; struct eap_sim_msg *msg; + struct wpabuf *enc_identity = NULL; data->reauth = 0; if (id_req == ANY_ID && data->reauth_id) { @@ -649,6 +715,22 @@ static struct wpabuf * eap_aka_response_identity(struct eap_sm *sm, ids &= ~CLEAR_PSEUDONYM; eap_aka_clear_identities(sm, data, ids); } +#ifdef CRYPTO_RSA_OAEP_SHA256 + if (identity && data->imsi_privacy_key) { + enc_identity = eap_aka_encrypt_identity( + data->imsi_privacy_key, + identity, identity_len); + if (!enc_identity) { + wpa_printf(MSG_INFO, + "EAP-AKA: Failed to encrypt permanent identity"); + return eap_aka_client_error( + data, id, + EAP_AKA_UNABLE_TO_PROCESS_PACKET); + } + identity = wpabuf_head(enc_identity); + identity_len = wpabuf_len(enc_identity); + } +#endif /* CRYPTO_RSA_OAEP_SHA256 */ } if (id_req != NO_ID_REQ) eap_aka_clear_identities(sm, data, CLEAR_EAP_ID); @@ -663,6 +745,7 @@ static struct wpabuf * eap_aka_response_identity(struct eap_sm *sm, eap_sim_msg_add(msg, EAP_SIM_AT_IDENTITY, identity_len, identity, identity_len); } + wpabuf_free(enc_identity); return eap_sim_msg_finish(msg, data->eap_method, NULL, NULL, 0); } diff --git a/src/eap_peer/eap_config.h b/src/eap_peer/eap_config.h index 49a03d841..eaf514b19 100644 --- a/src/eap_peer/eap_config.h +++ b/src/eap_peer/eap_config.h @@ -317,6 +317,16 @@ struct eap_peer_config { u8 *imsi_identity; size_t imsi_identity_len; + /** + * imsi_privacy_key - IMSI privacy key (PEM encoded X.509v3 certificate) + * + * This field is used with EAP-SIM/AKA/AKA' to encrypt the permanent + * identity (IMSI) to improve privacy. The X.509v3 certificate needs to + * include a 2048-bit RSA public key and this is from the operator who + * authenticates the SIM/USIM. + */ + char *imsi_privacy_key; + /** * machine_identity - EAP Identity for machine credential * diff --git a/src/eap_peer/eap_sim.c b/src/eap_peer/eap_sim.c index 09866277d..3b4c836d4 100644 --- a/src/eap_peer/eap_sim.c +++ b/src/eap_peer/eap_sim.c @@ -9,7 +9,9 @@ #include "includes.h" #include "common.h" +#include "utils/base64.h" #include "pcsc_funcs.h" +#include "crypto/crypto.h" #include "crypto/milenage.h" #include "crypto/random.h" #include "eap_peer/eap_i.h" @@ -49,6 +51,7 @@ struct eap_sim_data { int result_ind, use_result_ind; int use_pseudonym; int error_code; + struct crypto_rsa_key *imsi_privacy_key; }; @@ -98,6 +101,25 @@ static void * eap_sim_init(struct eap_sm *sm) return NULL; } + if (config && config->imsi_privacy_key) { +#ifdef CRYPTO_RSA_OAEP_SHA256 + data->imsi_privacy_key = crypto_rsa_key_read( + config->imsi_privacy_key, false); + if (!data->imsi_privacy_key) { + wpa_printf(MSG_ERROR, + "EAP-SIM: Failed to read/parse IMSI privacy key %s", + config->imsi_privacy_key); + os_free(data); + return NULL; + } +#else /* CRYPTO_RSA_OAEP_SHA256 */ + wpa_printf(MSG_ERROR, + "EAP-SIM: No support for imsi_privacy_key in the build"); + os_free(data); + return NULL; +#endif /* CRYPTO_RSA_OAEP_SHA256 */ + } + /* Zero is a valid error code, so we need to initialize */ data->error_code = NO_EAP_METHOD_ERROR; @@ -162,6 +184,9 @@ static void eap_sim_deinit(struct eap_sm *sm, void *priv) os_free(data->reauth_id); os_free(data->last_eap_identity); eap_sim_clear_keys(data, 0); +#ifdef CRYPTO_RSA_OAEP_SHA256 + crypto_rsa_key_free(data->imsi_privacy_key); +#endif /* CRYPTO_RSA_OAEP_SHA256 */ os_free(data); } } @@ -481,6 +506,47 @@ static struct wpabuf * eap_sim_client_error(struct eap_sim_data *data, u8 id, } +#ifdef CRYPTO_RSA_OAEP_SHA256 +static struct wpabuf * +eap_sim_encrypt_identity(struct crypto_rsa_key *imsi_privacy_key, + const u8 *identity, size_t identity_len) +{ + struct wpabuf *imsi_buf, *enc; + char *b64; + size_t b64_len; + + wpa_hexdump_ascii(MSG_DEBUG, "EAP-SIM: Encrypt permanent identity", + identity, identity_len); + + imsi_buf = wpabuf_alloc_copy(identity, identity_len); + if (!imsi_buf) + return NULL; + enc = crypto_rsa_oaep_sha256_encrypt(imsi_privacy_key, imsi_buf); + wpabuf_free(imsi_buf); + if (!enc) + return NULL; + + b64 = base64_encode_no_lf(wpabuf_head(enc), wpabuf_len(enc), &b64_len); + wpabuf_free(enc); + if (!b64) + return NULL; + + enc = wpabuf_alloc(1 + b64_len); + if (!enc) { + os_free(b64); + return NULL; + } + wpabuf_put_u8(enc, '\0'); + wpabuf_put_data(enc, b64, b64_len); + os_free(b64); + wpa_hexdump_ascii(MSG_DEBUG, "EAP-SIM: Encrypted permanent identity", + wpabuf_head(enc), wpabuf_len(enc)); + + return enc; +} +#endif /* CRYPTO_RSA_OAEP_SHA256 */ + + static struct wpabuf * eap_sim_response_start(struct eap_sm *sm, struct eap_sim_data *data, u8 id, enum eap_sim_id_req id_req) @@ -489,6 +555,7 @@ static struct wpabuf * eap_sim_response_start(struct eap_sm *sm, size_t identity_len = 0; struct eap_sim_msg *msg; struct wpabuf *resp; + struct wpabuf *enc_identity = NULL; data->reauth = 0; if (id_req == ANY_ID && data->reauth_id) { @@ -513,6 +580,22 @@ static struct wpabuf * eap_sim_response_start(struct eap_sm *sm, ids &= ~CLEAR_PSEUDONYM; eap_sim_clear_identities(sm, data, ids); } +#ifdef CRYPTO_RSA_OAEP_SHA256 + if (identity && data->imsi_privacy_key) { + enc_identity = eap_sim_encrypt_identity( + data->imsi_privacy_key, + identity, identity_len); + if (!enc_identity) { + wpa_printf(MSG_INFO, + "EAP-SIM: Failed to encrypt permanent identity"); + return eap_sim_client_error( + data, id, + EAP_SIM_UNABLE_TO_PROCESS_PACKET); + } + identity = wpabuf_head(enc_identity); + identity_len = wpabuf_len(enc_identity); + } +#endif /* CRYPTO_RSA_OAEP_SHA256 */ } if (id_req != NO_ID_REQ) eap_sim_clear_identities(sm, data, CLEAR_EAP_ID); @@ -526,6 +609,7 @@ static struct wpabuf * eap_sim_response_start(struct eap_sm *sm, eap_sim_msg_add(msg, EAP_SIM_AT_IDENTITY, identity_len, identity, identity_len); } + wpabuf_free(enc_identity); if (!data->reauth) { wpa_hexdump(MSG_DEBUG, " AT_NONCE_MT", data->nonce_mt, EAP_SIM_NONCE_MT_LEN); diff --git a/wpa_supplicant/README-HS20 b/wpa_supplicant/README-HS20 index b076621db..a099a85b4 100644 --- a/wpa_supplicant/README-HS20 +++ b/wpa_supplicant/README-HS20 @@ -168,6 +168,12 @@ Credentials can be pre-configured for automatic network selection: # milenage: Milenage parameters for SIM/USIM simulator in :: # format # +# imsi_privacy_key: IMSI privacy key (PEM encoded X.509v3 certificate) +# This field is used with EAP-SIM/AKA/AKA' to encrypt the permanent +# identity (IMSI) to improve privacy. The X.509v3 certificate needs to +# include a 2048-bit RSA public key and this is from the operator who +# authenticates the SIM/USIM. +# # domain_suffix_match: Constraint for server domain name # If set, this FQDN is used as a suffix match requirement for the AAA # server certificate in SubjectAltName dNSName element(s). If a diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c index 782bb2197..b5ffb9253 100644 --- a/wpa_supplicant/config.c +++ b/wpa_supplicant/config.c @@ -2503,6 +2503,7 @@ static const struct parse_data ssid_fields[] = { { INTe(machine_ocsp, machine_cert.ocsp) }, { INT(eapol_flags) }, { INTe(sim_num, sim_num) }, + { STRe(imsi_privacy_key, imsi_privacy_key) }, { STRe(openssl_ciphers, openssl_ciphers) }, { INTe(erp, erp) }, #endif /* IEEE8021X_EAPOL */ @@ -2770,6 +2771,7 @@ static void eap_peer_config_free(struct eap_peer_config *eap) bin_clear_free(eap->identity, eap->identity_len); os_free(eap->anonymous_identity); os_free(eap->imsi_identity); + os_free(eap->imsi_privacy_key); os_free(eap->machine_identity); bin_clear_free(eap->password, eap->password_len); bin_clear_free(eap->machine_password, eap->machine_password_len); @@ -2873,6 +2875,7 @@ void wpa_config_free_cred(struct wpa_cred *cred) os_free(cred->req_conn_capab_port[i]); os_free(cred->req_conn_capab_port); os_free(cred->req_conn_capab_proto); + os_free(cred->imsi_privacy_key); os_free(cred); } @@ -3905,6 +3908,12 @@ int wpa_config_set_cred(struct wpa_cred *cred, const char *var, return 0; } + if (os_strcmp(var, "imsi_privacy_key") == 0) { + os_free(cred->imsi_privacy_key); + cred->imsi_privacy_key = val; + return 0; + } + if (line) { wpa_printf(MSG_ERROR, "Line %d: unknown cred field '%s'.", line, var); @@ -4055,6 +4064,9 @@ char * wpa_config_get_cred_no_key(struct wpa_cred *cred, const char *var) if (os_strcmp(var, "imsi") == 0) return alloc_strdup(cred->imsi); + if (os_strcmp(var, "imsi_privacy_key") == 0) + return alloc_strdup(cred->imsi_privacy_key); + if (os_strcmp(var, "milenage") == 0) { if (!(cred->milenage)) return NULL; diff --git a/wpa_supplicant/config.h b/wpa_supplicant/config.h index d22ef05fb..326953fb8 100644 --- a/wpa_supplicant/config.h +++ b/wpa_supplicant/config.h @@ -180,6 +180,16 @@ struct wpa_cred { */ char *milenage; + /** + * imsi_privacy_key - IMSI privacy key (PEM encoded X.509v3 certificate) + * + * This field is used with EAP-SIM/AKA/AKA' to encrypt the permanent + * identity (IMSI) to improve privacy. The X.509v3 certificate needs to + * include a 2048-bit RSA public key and this is from the operator who + * authenticates the SIM/USIM. + */ + char *imsi_privacy_key; + /** * engine - Use an engine for private key operations */ diff --git a/wpa_supplicant/interworking.c b/wpa_supplicant/interworking.c index 71a5c1651..e66e402d7 100644 --- a/wpa_supplicant/interworking.c +++ b/wpa_supplicant/interworking.c @@ -1065,6 +1065,12 @@ static int interworking_connect_3gpp(struct wpa_supplicant *wpa_s, goto fail; } + if (cred->imsi_privacy_key && cred->imsi_privacy_key[0]) { + if (wpa_config_set_quoted(ssid, "imsi_privacy_key", + cred->imsi_privacy_key) < 0) + goto fail; + } + wpa_s->next_ssid = ssid; wpa_config_update_prio_list(wpa_s->conf); if (!only_add)