]> git.ipfire.org Git - thirdparty/hostap.git/commitdiff
EAP-SIM/AKA peer: IMSI privacy
authorJouni Malinen <quic_jouni@quicinc.com>
Sun, 1 May 2022 08:34:49 +0000 (11:34 +0300)
committerJouni Malinen <j@w1.fi>
Sun, 1 May 2022 13:25:16 +0000 (16:25 +0300)
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 <quic_jouni@quicinc.com>
src/eap_peer/eap.c
src/eap_peer/eap_aka.c
src/eap_peer/eap_config.h
src/eap_peer/eap_sim.c
wpa_supplicant/README-HS20
wpa_supplicant/config.c
wpa_supplicant/config.h
wpa_supplicant/interworking.c

index 7dcfe4fff45ea5b89a7c0d0d7a02e16f958949e1..429b20d3aa3511054073767a87c510820c03c1d5 100644 (file)
@@ -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;
 }
index 8c475f13f162a9b5f72148bebe68b4f08b2308c3..8caae1d6ae8e0c1e53d040dabadfeb248fe9fe9e 100644 (file)
@@ -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);
 }
index 49a03d841c8740b1e003ef1baed382fb348692fd..eaf514b193b161aa653117747ce473b7965877d2 100644 (file)
@@ -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
         *
index 09866277d6a85720a7aa812500e7c6c911a15a04..3b4c836d40d3157e96e6179efdc527c0df0140e4 100644 (file)
@@ -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);
index b076621db5270a9afa08e8bdd3f0914aa338a23d..a099a85b4c98cc4714162ed8457164aa31b73e40 100644 (file)
@@ -168,6 +168,12 @@ Credentials can be pre-configured for automatic network selection:
 # milenage: Milenage parameters for SIM/USIM simulator in <Ki>:<OPc>:<SQN>
 #      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
index 782bb2197db1df036fbe44366a551951071bd635..b5ffb9253047f4d14e5e23f212b4a1f8348c6235 100644 (file)
@@ -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;
index d22ef05fb8ba7f4b2fb2419c46f3751634338fc7..326953fb8a1052ebf1466b54795ebe056e736e35 100644 (file)
@@ -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
         */
index 71a5c16510d44ff2a0c751c9e7295dbb3cc5d25a..e66e402d79838ba5d76efdc5b4b9ce0eecc05607 100644 (file)
@@ -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)