]> git.ipfire.org Git - thirdparty/hostap.git/blobdiff - src/eap_peer/eap_peap.c
EAP-PEAP peer: Support vendor EAP method in Phase 2
[thirdparty/hostap.git] / src / eap_peer / eap_peap.c
index 7740cf99fff65b1de92deaf8099bff1d0132e64c..5ca61459d274b198808519b9909eae2fc381af5c 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * EAP peer method: EAP-PEAP (draft-josefsson-pppext-eap-tls-eap-10.txt)
- * Copyright (c) 2004-2008, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2004-2019, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -59,6 +59,7 @@ struct eap_peap_data {
        size_t id_len;
 
        struct wpabuf *pending_phase2_req;
+       struct wpabuf *pending_resp;
        enum { NO_BINDING, OPTIONAL_BINDING, REQUIRE_BINDING } crypto_binding;
        int crypto_binding_used;
        u8 binding_nonce[32];
@@ -69,8 +70,8 @@ struct eap_peap_data {
 };
 
 
-static int eap_peap_parse_phase1(struct eap_peap_data *data,
-                                const char *phase1)
+static void eap_peap_parse_phase1(struct eap_peap_data *data,
+                                 const char *phase1)
 {
        const char *pos;
 
@@ -125,8 +126,6 @@ static int eap_peap_parse_phase1(struct eap_peap_data *data,
                wpa_printf(MSG_DEBUG, "EAP-PEAP: SoH version 2 enabled");
        }
 #endif /* EAP_TNC */
-
-       return 0;
 }
 
 
@@ -144,11 +143,8 @@ static void * eap_peap_init(struct eap_sm *sm)
        data->peap_outer_success = 2;
        data->crypto_binding = OPTIONAL_BINDING;
 
-       if (config && config->phase1 &&
-           eap_peap_parse_phase1(data, config->phase1) < 0) {
-               eap_peap_deinit(sm, data);
-               return NULL;
-       }
+       if (config && config->phase1)
+               eap_peap_parse_phase1(data, config->phase1);
 
        if (eap_peer_select_phase2_methods(config, "auth=",
                                           &data->phase2_types,
@@ -170,6 +166,15 @@ static void * eap_peap_init(struct eap_sm *sm)
 }
 
 
+static void eap_peap_free_key(struct eap_peap_data *data)
+{
+       if (data->key_data) {
+               bin_clear_free(data->key_data, EAP_TLS_KEY_LEN + EAP_EMSK_LEN);
+               data->key_data = NULL;
+       }
+}
+
+
 static void eap_peap_deinit(struct eap_sm *sm, void *priv)
 {
        struct eap_peap_data *data = priv;
@@ -179,10 +184,11 @@ static void eap_peap_deinit(struct eap_sm *sm, void *priv)
                data->phase2_method->deinit(sm, data->phase2_priv);
        os_free(data->phase2_types);
        eap_peer_tls_ssl_deinit(sm, &data->ssl);
-       os_free(data->key_data);
+       eap_peap_free_key(data);
        os_free(data->session_id);
-       wpabuf_free(data->pending_phase2_req);
-       os_free(data);
+       wpabuf_clear_free(data->pending_phase2_req);
+       wpabuf_clear_free(data->pending_resp);
+       bin_clear_free(data, sizeof(*data));
 }
 
 
@@ -247,6 +253,7 @@ static int eap_peap_derive_cmk(struct eap_sm *sm, struct eap_peap_data *data)
 {
        u8 *tk;
        u8 isk[32], imck[60];
+       int resumed, res;
 
        /*
         * Tunnel key (TK) is the first 60 octets of the key generated by
@@ -257,8 +264,12 @@ static int eap_peap_derive_cmk(struct eap_sm *sm, struct eap_peap_data *data)
                return -1;
        wpa_hexdump_key(MSG_DEBUG, "EAP-PEAP: TK", tk, 60);
 
-       if (data->reauth &&
-           tls_connection_resumed(sm->ssl_ctx, data->ssl.conn)) {
+       resumed = tls_connection_resumed(sm->ssl_ctx, data->ssl.conn);
+       wpa_printf(MSG_DEBUG,
+                  "EAP-PEAP: CMK derivation - reauth=%d resumed=%d phase2_eap_started=%d phase2_success=%d",
+                  data->reauth, resumed, data->phase2_eap_started,
+                  data->phase2_success);
+       if (data->reauth && !data->phase2_eap_started && resumed) {
                /* Fast-connect: IPMK|CMK = TK */
                os_memcpy(data->ipmk, tk, 40);
                wpa_hexdump_key(MSG_DEBUG, "EAP-PEAP: IPMK from TK",
@@ -281,9 +292,11 @@ static int eap_peap_derive_cmk(struct eap_sm *sm, struct eap_peap_data *data)
         * in the end of the label just before ISK; is that just a typo?)
         */
        wpa_hexdump_key(MSG_DEBUG, "EAP-PEAP: TempKey", tk, 40);
-       if (peap_prfplus(data->peap_version, tk, 40,
-                        "Inner Methods Compound Keys",
-                        isk, sizeof(isk), imck, sizeof(imck)) < 0)
+       res = peap_prfplus(data->peap_version, tk, 40,
+                          "Inner Methods Compound Keys",
+                          isk, sizeof(isk), imck, sizeof(imck));
+       forced_memzero(isk, sizeof(isk));
+       if (res < 0)
                return -1;
        wpa_hexdump_key(MSG_DEBUG, "EAP-PEAP: IMCK (IPMKj)",
                        imck, sizeof(imck));
@@ -292,6 +305,7 @@ static int eap_peap_derive_cmk(struct eap_sm *sm, struct eap_peap_data *data)
        wpa_hexdump_key(MSG_DEBUG, "EAP-PEAP: IPMK (S-IPMKj)", data->ipmk, 40);
        os_memcpy(data->cmk, imck + 40, 20);
        wpa_hexdump_key(MSG_DEBUG, "EAP-PEAP: CMK (CMKj)", data->cmk, 20);
+       forced_memzero(imck, sizeof(imck));
 
        return 0;
 }
@@ -328,7 +342,8 @@ static int eap_tlv_add_cryptobinding(struct eap_sm *sm,
                    addr[0], len[0]);
        wpa_hexdump(MSG_MSGDUMP, "EAP-PEAP: Compound_MAC data 2",
                    addr[1], len[1]);
-       hmac_sha1_vector(data->cmk, 20, 2, addr, len, mac);
+       if (hmac_sha1_vector(data->cmk, 20, 2, addr, len, mac) < 0)
+               return -1;
        wpa_hexdump(MSG_MSGDUMP, "EAP-PEAP: Compound_MAC", mac, SHA1_MAC_LEN);
        data->crypto_binding_used = 1;
 
@@ -370,7 +385,7 @@ static struct wpabuf * eap_tlv_build_result(struct eap_sm *sm,
        wpabuf_put_be16(msg, status); /* Status */
 
        if (crypto_tlv_used && eap_tlv_add_cryptobinding(sm, data, msg)) {
-               wpabuf_free(msg);
+               wpabuf_clear_free(msg);
                return NULL;
        }
 
@@ -588,6 +603,8 @@ static int eap_peap_phase2_request(struct eap_sm *sm,
        u8 *pos;
        struct eap_method_ret iret;
        struct eap_peer_config *config = eap_get_config(sm);
+       int vendor;
+       enum eap_type method;
 
        if (len <= sizeof(struct eap_hdr)) {
                wpa_printf(MSG_INFO, "EAP-PEAP: too short "
@@ -639,10 +656,11 @@ static int eap_peap_phase2_request(struct eap_sm *sm,
                                        if (*resp == NULL) {
                                                ret->methodState = METHOD_DONE;
                                                ret->decision = DECISION_FAIL;
+                                               wpabuf_clear_free(buf);
                                                return -1;
                                        }
                                        wpabuf_put_buf(*resp, buf);
-                                       wpabuf_free(buf);
+                                       wpabuf_clear_free(buf);
                                        break;
                                }
                        }
@@ -650,13 +668,26 @@ static int eap_peap_phase2_request(struct eap_sm *sm,
 #endif /* EAP_TNC */
                /* fall through */
        default:
+               vendor = EAP_VENDOR_IETF;
+               method = *pos;
+
+               if (method == EAP_TYPE_EXPANDED) {
+                       if (len < sizeof(struct eap_hdr) + 8) {
+                               wpa_printf(MSG_INFO,
+                                          "EAP-PEAP: Too short Phase 2 request (expanded header) (len=%lu)",
+                                          (unsigned long) len);
+                               return -1;
+                       }
+                       vendor = WPA_GET_BE24(pos + 1);
+                       method = WPA_GET_BE32(pos + 4);
+               }
+
                if (data->phase2_type.vendor == EAP_VENDOR_IETF &&
                    data->phase2_type.method == EAP_TYPE_NONE) {
                        size_t i;
                        for (i = 0; i < data->num_phase2_types; i++) {
-                               if (data->phase2_types[i].vendor !=
-                                   EAP_VENDOR_IETF ||
-                                   data->phase2_types[i].method != *pos)
+                               if (data->phase2_types[i].vendor != vendor ||
+                                   data->phase2_types[i].method != method)
                                        continue;
 
                                data->phase2_type.vendor =
@@ -670,8 +701,9 @@ static int eap_peap_phase2_request(struct eap_sm *sm,
                                break;
                        }
                }
-               if (*pos != data->phase2_type.method ||
-                   *pos == EAP_TYPE_NONE) {
+               if (vendor != data->phase2_type.vendor ||
+                   method != data->phase2_type.method ||
+                   (vendor == EAP_VENDOR_IETF && method == EAP_TYPE_NONE)) {
                        if (eap_peer_tls_phase2_nak(data->phase2_types,
                                                    data->num_phase2_types,
                                                    hdr, resp))
@@ -713,8 +745,9 @@ static int eap_peap_phase2_request(struct eap_sm *sm,
 
        if (*resp == NULL &&
            (config->pending_req_identity || config->pending_req_password ||
-            config->pending_req_otp || config->pending_req_new_password)) {
-               wpabuf_free(data->pending_phase2_req);
+            config->pending_req_otp || config->pending_req_new_password ||
+            config->pending_req_sim)) {
+               wpabuf_clear_free(data->pending_phase2_req);
                data->pending_phase2_req = wpabuf_alloc_copy(hdr, len);
        }
 
@@ -793,7 +826,7 @@ continue_req:
                struct wpabuf *nmsg = wpabuf_alloc(sizeof(struct eap_hdr) +
                                                   wpabuf_len(in_decrypted));
                if (nmsg == NULL) {
-                       wpabuf_free(in_decrypted);
+                       wpabuf_clear_free(in_decrypted);
                        return 0;
                }
                nhdr = wpabuf_put(nmsg, sizeof(*nhdr));
@@ -803,7 +836,7 @@ continue_req:
                nhdr->length = host_to_be16(sizeof(struct eap_hdr) +
                                            wpabuf_len(in_decrypted));
 
-               wpabuf_free(in_decrypted);
+               wpabuf_clear_free(in_decrypted);
                in_decrypted = nmsg;
        }
 
@@ -812,7 +845,7 @@ continue_req:
                wpa_printf(MSG_INFO, "EAP-PEAP: Too short Phase 2 "
                           "EAP frame (len=%lu)",
                           (unsigned long) wpabuf_len(in_decrypted));
-               wpabuf_free(in_decrypted);
+               wpabuf_clear_free(in_decrypted);
                return 0;
        }
        len = be_to_host16(hdr->length);
@@ -821,7 +854,7 @@ continue_req:
                           "Phase 2 EAP frame (len=%lu hdr->length=%lu)",
                           (unsigned long) wpabuf_len(in_decrypted),
                           (unsigned long) len);
-               wpabuf_free(in_decrypted);
+               wpabuf_clear_free(in_decrypted);
                return 0;
        }
        if (len < wpabuf_len(in_decrypted)) {
@@ -838,7 +871,7 @@ continue_req:
        case EAP_CODE_REQUEST:
                if (eap_peap_phase2_request(sm, data, ret, in_decrypted,
                                            &resp)) {
-                       wpabuf_free(in_decrypted);
+                       wpabuf_clear_free(in_decrypted);
                        wpa_printf(MSG_INFO, "EAP-PEAP: Phase2 Request "
                                   "processing failed");
                        return 0;
@@ -858,7 +891,7 @@ continue_req:
                                           "completed successfully");
                                ret->methodState = METHOD_DONE;
                                ret->decision = DECISION_FAIL;
-                               wpabuf_free(in_decrypted);
+                               wpabuf_clear_free(in_decrypted);
                                return 0;
                        }
                        wpa_printf(MSG_DEBUG, "EAP-PEAP: Version 1 - "
@@ -868,7 +901,7 @@ continue_req:
                        ret->methodState = METHOD_DONE;
                        data->phase2_success = 1;
                        if (data->peap_outer_success == 2) {
-                               wpabuf_free(in_decrypted);
+                               wpabuf_clear_free(in_decrypted);
                                wpa_printf(MSG_DEBUG, "EAP-PEAP: Use TLS ACK "
                                           "to finish authentication");
                                return 1;
@@ -914,7 +947,7 @@ continue_req:
                break;
        }
 
-       wpabuf_free(in_decrypted);
+       wpabuf_clear_free(in_decrypted);
 
        if (resp) {
                int skip_change2 = 0;
@@ -941,7 +974,7 @@ continue_req:
                        wpa_printf(MSG_INFO, "EAP-PEAP: Failed to encrypt "
                                   "a Phase 2 frame");
                }
-               wpabuf_free(resp);
+               wpabuf_clear_free(resp);
        }
 
        return 0;
@@ -959,6 +992,7 @@ static struct wpabuf * eap_peap_process(struct eap_sm *sm, void *priv,
        struct wpabuf *resp;
        const u8 *pos;
        struct eap_peap_data *data = priv;
+       struct wpabuf msg;
 
        pos = eap_peer_tls_process_init(sm, &data->ssl, EAP_TYPE_PEAP, ret,
                                        reqData, &left, &flags);
@@ -989,23 +1023,68 @@ static struct wpabuf * eap_peap_process(struct eap_sm *sm, void *priv,
                           * should always be, anyway */
        }
 
+       wpabuf_set(&msg, pos, left);
+
        resp = NULL;
        if (tls_connection_established(sm->ssl_ctx, data->ssl.conn) &&
            !data->resuming) {
-               struct wpabuf msg;
-               wpabuf_set(&msg, pos, left);
                res = eap_peap_decrypt(sm, data, ret, req, &msg, &resp);
        } else {
+               if (sm->waiting_ext_cert_check && data->pending_resp) {
+                       struct eap_peer_config *config = eap_get_config(sm);
+
+                       if (config->pending_ext_cert_check ==
+                           EXT_CERT_CHECK_GOOD) {
+                               wpa_printf(MSG_DEBUG,
+                                          "EAP-PEAP: External certificate check succeeded - continue handshake");
+                               resp = data->pending_resp;
+                               data->pending_resp = NULL;
+                               sm->waiting_ext_cert_check = 0;
+                               return resp;
+                       }
+
+                       if (config->pending_ext_cert_check ==
+                           EXT_CERT_CHECK_BAD) {
+                               wpa_printf(MSG_DEBUG,
+                                          "EAP-PEAP: External certificate check failed - force authentication failure");
+                               ret->methodState = METHOD_DONE;
+                               ret->decision = DECISION_FAIL;
+                               sm->waiting_ext_cert_check = 0;
+                               return NULL;
+                       }
+
+                       wpa_printf(MSG_DEBUG,
+                                  "EAP-PEAP: Continuing to wait external server certificate validation");
+                       return NULL;
+               }
+
                res = eap_peer_tls_process_helper(sm, &data->ssl,
                                                  EAP_TYPE_PEAP,
-                                                 data->peap_version, id, pos,
-                                                 left, &resp);
+                                                 data->peap_version, id, &msg,
+                                                 &resp);
+
+               if (res < 0) {
+                       wpa_printf(MSG_DEBUG,
+                                  "EAP-PEAP: TLS processing failed");
+                       ret->methodState = METHOD_DONE;
+                       ret->decision = DECISION_FAIL;
+                       return resp;
+               }
+
+
+               if (sm->waiting_ext_cert_check) {
+                       wpa_printf(MSG_DEBUG,
+                                  "EAP-PEAP: Waiting external server certificate validation");
+                       wpabuf_clear_free(data->pending_resp);
+                       data->pending_resp = resp;
+                       return NULL;
+               }
 
                if (tls_connection_established(sm->ssl_ctx, data->ssl.conn)) {
                        char *label;
                        wpa_printf(MSG_DEBUG,
                                   "EAP-PEAP: TLS done, proceed to Phase 2");
-                       os_free(data->key_data);
+                       eap_peap_free_key(data);
                        /* draft-josefsson-ppext-eap-tls-eap-05.txt
                         * specifies that PEAPv1 would use "client PEAP
                         * encryption" as the label. However, most existing
@@ -1021,12 +1100,19 @@ static struct wpabuf * eap_peap_process(struct eap_sm *sm, void *priv,
                                   "key derivation", label);
                        data->key_data =
                                eap_peer_tls_derive_key(sm, &data->ssl, label,
-                                                       EAP_TLS_KEY_LEN);
+                                                       NULL, 0,
+                                                       EAP_TLS_KEY_LEN +
+                                                       EAP_EMSK_LEN);
                        if (data->key_data) {
-                               wpa_hexdump_key(MSG_DEBUG, 
+                               wpa_hexdump_key(MSG_DEBUG,
                                                "EAP-PEAP: Derived key",
                                                data->key_data,
                                                EAP_TLS_KEY_LEN);
+                               wpa_hexdump_key(MSG_DEBUG,
+                                               "EAP-PEAP: Derived EMSK",
+                                               data->key_data +
+                                               EAP_TLS_KEY_LEN,
+                                               EAP_EMSK_LEN);
                        } else {
                                wpa_printf(MSG_DEBUG, "EAP-PEAP: Failed to "
                                           "derive key");
@@ -1068,14 +1154,12 @@ static struct wpabuf * eap_peap_process(struct eap_sm *sm, void *priv,
                }
 
                if (res == 2) {
-                       struct wpabuf msg;
                        /*
                         * Application data included in the handshake message.
                         */
-                       wpabuf_free(data->pending_phase2_req);
+                       wpabuf_clear_free(data->pending_phase2_req);
                        data->pending_phase2_req = resp;
                        resp = NULL;
-                       wpabuf_set(&msg, pos, left);
                        res = eap_peap_decrypt(sm, data, ret, req, &msg,
                                               &resp);
                }
@@ -1086,7 +1170,7 @@ static struct wpabuf * eap_peap_process(struct eap_sm *sm, void *priv,
        }
 
        if (res == 1) {
-               wpabuf_free(resp);
+               wpabuf_clear_free(resp);
                return eap_peer_tls_build_ack(id, EAP_TYPE_PEAP,
                                              data->peap_version);
        }
@@ -1106,8 +1190,14 @@ static Boolean eap_peap_has_reauth_data(struct eap_sm *sm, void *priv)
 static void eap_peap_deinit_for_reauth(struct eap_sm *sm, void *priv)
 {
        struct eap_peap_data *data = priv;
-       wpabuf_free(data->pending_phase2_req);
+
+       if (data->phase2_priv && data->phase2_method &&
+           data->phase2_method->deinit_for_reauth)
+               data->phase2_method->deinit_for_reauth(sm, data->phase2_priv);
+       wpabuf_clear_free(data->pending_phase2_req);
        data->pending_phase2_req = NULL;
+       wpabuf_clear_free(data->pending_resp);
+       data->pending_resp = NULL;
        data->crypto_binding_used = 0;
 }
 
@@ -1115,8 +1205,7 @@ static void eap_peap_deinit_for_reauth(struct eap_sm *sm, void *priv)
 static void * eap_peap_init_for_reauth(struct eap_sm *sm, void *priv)
 {
        struct eap_peap_data *data = priv;
-       os_free(data->key_data);
-       data->key_data = NULL;
+       eap_peap_free_key(data);
        os_free(data->session_id);
        data->session_id = NULL;
        if (eap_peer_tls_reauth_init(sm, &data->ssl)) {
@@ -1148,7 +1237,7 @@ static int eap_peap_get_status(struct eap_sm *sm, void *priv, char *buf,
                                  "EAP-PEAPv%d Phase2 method=%s\n",
                                  data->peap_version,
                                  data->phase2_method->name);
-               if (ret < 0 || (size_t) ret >= buflen - len)
+               if (os_snprintf_error(buflen - len, ret))
                        return len;
                len += ret;
        }
@@ -1194,6 +1283,7 @@ static u8 * eap_peap_getKey(struct eap_sm *sm, void *priv, size_t *len)
                os_memcpy(key, csk, EAP_TLS_KEY_LEN);
                wpa_hexdump(MSG_DEBUG, "EAP-PEAP: Derived key",
                            key, EAP_TLS_KEY_LEN);
+               forced_memzero(csk, sizeof(csk));
        } else
                os_memcpy(key, data->key_data, EAP_TLS_KEY_LEN);
 
@@ -1201,6 +1291,29 @@ static u8 * eap_peap_getKey(struct eap_sm *sm, void *priv, size_t *len)
 }
 
 
+static u8 * eap_peap_get_emsk(struct eap_sm *sm, void *priv, size_t *len)
+{
+       struct eap_peap_data *data = priv;
+       u8 *key;
+
+       if (!data->key_data || !data->phase2_success)
+               return NULL;
+
+       if (data->crypto_binding_used) {
+               /* [MS-PEAP] does not define EMSK derivation */
+               return NULL;
+       }
+
+       key = os_memdup(data->key_data + EAP_TLS_KEY_LEN, EAP_EMSK_LEN);
+       if (!key)
+               return NULL;
+
+       *len = EAP_EMSK_LEN;
+
+       return key;
+}
+
+
 static u8 * eap_peap_get_session_id(struct eap_sm *sm, void *priv, size_t *len)
 {
        struct eap_peap_data *data = priv;
@@ -1209,12 +1322,11 @@ static u8 * eap_peap_get_session_id(struct eap_sm *sm, void *priv, size_t *len)
        if (data->session_id == NULL || !data->phase2_success)
                return NULL;
 
-       id = os_malloc(data->id_len);
+       id = os_memdup(data->session_id, data->id_len);
        if (id == NULL)
                return NULL;
 
        *len = data->id_len;
-       os_memcpy(id, data->session_id, data->id_len);
 
        return id;
 }
@@ -1223,7 +1335,6 @@ static u8 * eap_peap_get_session_id(struct eap_sm *sm, void *priv, size_t *len)
 int eap_peer_peap_register(void)
 {
        struct eap_method *eap;
-       int ret;
 
        eap = eap_peer_method_alloc(EAP_PEER_METHOD_INTERFACE_VERSION,
                                    EAP_VENDOR_IETF, EAP_TYPE_PEAP, "PEAP");
@@ -1235,14 +1346,12 @@ int eap_peer_peap_register(void)
        eap->process = eap_peap_process;
        eap->isKeyAvailable = eap_peap_isKeyAvailable;
        eap->getKey = eap_peap_getKey;
+       eap->get_emsk = eap_peap_get_emsk;
        eap->get_status = eap_peap_get_status;
        eap->has_reauth_data = eap_peap_has_reauth_data;
        eap->deinit_for_reauth = eap_peap_deinit_for_reauth;
        eap->init_for_reauth = eap_peap_init_for_reauth;
        eap->getSessionId = eap_peap_get_session_id;
 
-       ret = eap_peer_method_register(eap);
-       if (ret)
-               eap_peer_method_free(eap);
-       return ret;
+       return eap_peer_method_register(eap);
 }