| /* |
| * |
| * Wireless daemon for Linux |
| * |
| * Copyright (C) 2016 Markus Ongyerth. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <ctype.h> |
| #include <stdio.h> |
| #include <errno.h> |
| #include <ell/ell.h> |
| |
| #include "src/missing.h" |
| #include "src/eap.h" |
| #include "src/eap-private.h" |
| #include "src/eap-mschapv2.h" |
| #include "src/mschaputil.h" |
| |
| #define MSCHAPV2_CHAL_LEN 16 |
| #define MSCHAPV2_NT_RESPONSE_LEN 24 |
| #define MSCHAPV2_AUTH_RESPONSE_LEN 20 |
| #define MSCHAPV2_MASTER_KEY_LEN 16 |
| |
| #define MSCHAPV2_OP_CHALLENGE 1 |
| #define MSCHAPV2_OP_RESPONSE 2 |
| #define MSCHAPV2_OP_SUCCESS 3 |
| #define MSCHAPV2_OP_FAILURE 4 |
| |
| struct eap_mschapv2_state { |
| uint8_t password_hash[16]; |
| char *user; |
| size_t user_len; |
| uint8_t current_id; |
| |
| uint8_t peer_challenge[MSCHAPV2_CHAL_LEN]; |
| uint8_t server_challenge[MSCHAPV2_CHAL_LEN]; |
| }; |
| |
| struct mschapv2_header { |
| uint8_t op_code; |
| uint8_t mschap_id; |
| uint16_t mschap_len; |
| } __attribute__((packed)); |
| |
| struct mschapv2_value { |
| uint8_t peer_challenge[MSCHAPV2_CHAL_LEN]; |
| uint8_t reserved[8]; |
| uint8_t nt_response[MSCHAPV2_NT_RESPONSE_LEN]; |
| uint8_t flags; |
| } __attribute__((packed)); |
| |
| struct mschapv2_response { |
| struct mschapv2_header hdr; |
| /* This will always be sizeof(value) */ |
| uint8_t val_length; |
| struct mschapv2_value value; |
| char name[0]; |
| } __attribute__((packed)); |
| |
| /** |
| * Generate the asymmetric start keys from our mschapv2 master key for MPPE |
| * This function is specified in: |
| * https://tools.ietf.org/html/draft-ietf-pppext-mschapv2-keys-02 |
| * |
| * @master_key: The master key |
| * @session_key: the destination |
| * @session_len: The length of the requested key in octets (<= 20) |
| * @server: if the key should be generated for server side |
| * @send: if the send or the receive key should be generated |
| * |
| * Returns: true on success, false if hash/encrypt couldn't be done |
| **/ |
| bool mschapv2_get_asymmetric_start_key(const uint8_t master_key[static 16], |
| uint8_t *session_key, size_t session_len, |
| bool server, bool send) |
| { |
| static const uint8_t magic2[] = { |
| 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, |
| 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20, |
| 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, |
| 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79, |
| 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, |
| 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65, |
| 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, |
| 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20, |
| 0x6b, 0x65, 0x79, 0x2e |
| }; |
| static const uint8_t magic3[] = { |
| 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, |
| 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20, |
| 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, |
| 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20, |
| 0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, |
| 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, |
| 0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, |
| 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, |
| 0x6b, 0x65, 0x79, 0x2e |
| }; |
| static const uint8_t shs_pad1[] = { |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 |
| }; |
| |
| static const uint8_t shs_pad2[] = { |
| 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, |
| 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, |
| 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, |
| 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 |
| }; |
| const uint8_t *magic; |
| struct l_checksum *check; |
| |
| if (send == server) |
| magic = magic3; |
| else |
| magic = magic2; |
| |
| check = l_checksum_new(L_CHECKSUM_SHA1); |
| if (!check) |
| return false; |
| |
| l_checksum_update(check, master_key, 16); |
| l_checksum_update(check, shs_pad1, 40); |
| l_checksum_update(check, magic, 84); |
| l_checksum_update(check, shs_pad2, 40); |
| l_checksum_get_digest(check, session_key, session_len); |
| |
| l_checksum_free(check); |
| |
| return true; |
| } |
| |
| /** |
| * Generate the master key for MPPE from mschapv2 |
| * This function is specified in: |
| * https://tools.ietf.org/html/draft-ietf-pppext-mschapv2-keys-02 |
| * |
| * @pw_hash_hash: The MD4 hash of the password hash |
| * @nt_response: The nt_response generated for mschapv2 |
| * @master_key: The destination |
| * |
| * Returns: true on success, false if hash/encrypt couldn't be done |
| **/ |
| bool mschapv2_get_master_key(const uint8_t pw_hash_hash[static 16], |
| const uint8_t nt_response[static 24], |
| uint8_t master_key[static 16]) |
| { |
| static const uint8_t magic[] = { |
| 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, |
| 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d, |
| 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 |
| }; |
| struct l_checksum *check; |
| |
| check = l_checksum_new(L_CHECKSUM_SHA1); |
| if (!check) |
| return false; |
| |
| l_checksum_update(check, pw_hash_hash, 16); |
| l_checksum_update(check, nt_response, 24); |
| l_checksum_update(check, magic, sizeof(magic)); |
| |
| l_checksum_get_digest(check, master_key, 16); |
| l_checksum_free(check); |
| |
| return true; |
| } |
| |
| static bool eap_mschapv2_reset_state(struct eap_state *eap) |
| { |
| struct eap_mschapv2_state *state = eap_get_data(eap); |
| |
| memset(state->peer_challenge, 0, sizeof(state->peer_challenge)); |
| memset(state->server_challenge, 0, sizeof(state->server_challenge)); |
| |
| return true; |
| } |
| |
| static void eap_mschapv2_state_free(struct eap_mschapv2_state *state) |
| { |
| explicit_bzero(state->password_hash, sizeof(state->password_hash)); |
| |
| explicit_bzero(state->user, state->user_len); |
| l_free(state->user); |
| state->user_len = 0; |
| |
| l_free(state); |
| } |
| |
| static void eap_mschapv2_free(struct eap_state *eap) |
| { |
| struct eap_mschapv2_state *state; |
| |
| eap_mschapv2_reset_state(eap); |
| |
| state = eap_get_data(eap); |
| eap_set_data(eap, NULL); |
| |
| eap_mschapv2_state_free(state); |
| } |
| |
| static bool eap_mschapv2_send_response(struct eap_state *eap) |
| { |
| struct eap_mschapv2_state *state = eap_get_data(eap); |
| size_t size = sizeof(struct mschapv2_response) + state->user_len; |
| uint8_t output[size + 5]; |
| struct mschapv2_response *response = |
| (struct mschapv2_response *) (output + 5); |
| bool ret; |
| |
| /* |
| * Make sure to initialize the response structure to 0 since |
| * we're not filling in some of the reserved or optional fields |
| */ |
| memset(response, 0, size); |
| |
| ret = mschapv2_generate_nt_response(state->password_hash, |
| state->peer_challenge, |
| state->server_challenge, |
| state->user, |
| response->value.nt_response); |
| |
| if (!ret) |
| return false; |
| |
| response->hdr.op_code = MSCHAPV2_OP_RESPONSE; |
| response->hdr.mschap_id = state->current_id; |
| response->hdr.mschap_len = L_BE16_TO_CPU(size); |
| response->val_length = sizeof(struct mschapv2_value); |
| |
| memcpy(response->value.peer_challenge, state->peer_challenge, |
| MSCHAPV2_CHAL_LEN); |
| memcpy(response->name, state->user, state->user_len); |
| |
| eap_method_respond(eap, output, sizeof(output)); |
| |
| return true; |
| } |
| |
| static void eap_mschapv2_handle_challenge(struct eap_state *eap, |
| const uint8_t *pkt, size_t len) |
| { |
| struct eap_mschapv2_state *state = eap_get_data(eap); |
| |
| if (pkt[0] != MSCHAPV2_CHAL_LEN) { |
| l_error("MSCHAPV2-Challenge had unexpected length: %x", |
| pkt[0]); |
| goto err; |
| } |
| |
| if (len - 1 < MSCHAPV2_CHAL_LEN) { |
| l_error("MSCHAPV2-Challenge packet was to short for challenge"); |
| goto err; |
| } |
| |
| memcpy(state->server_challenge, pkt + 1, MSCHAPV2_CHAL_LEN); |
| l_getrandom(state->peer_challenge, MSCHAPV2_CHAL_LEN); |
| |
| if (eap_mschapv2_send_response(eap)) |
| return; |
| |
| err: |
| eap_method_error(eap); |
| } |
| |
| /* |
| * We need to verify the authenticator response from the server |
| * and generate the master session key. |
| */ |
| static void eap_mschapv2_handle_success(struct eap_state *eap, |
| const uint8_t *pkt, size_t len) |
| { |
| struct eap_mschapv2_state *state = eap_get_data(eap); |
| uint8_t nt_response[24]; |
| uint8_t password_hash_hash[16]; |
| uint8_t master_key[16]; |
| uint8_t session_key[32]; |
| char authenticator_resp[42]; |
| bool ret; |
| |
| uint8_t buffer[5 + 1]; |
| |
| mschapv2_hash_nt_password_hash(state->password_hash, |
| password_hash_hash); |
| |
| ret = mschapv2_generate_nt_response(state->password_hash, |
| state->peer_challenge, |
| state->server_challenge, |
| state->user, nt_response); |
| |
| if (!ret) |
| goto done; |
| |
| ret = mschapv2_generate_authenticator_response(password_hash_hash, |
| nt_response, |
| state->peer_challenge, |
| state->server_challenge, |
| state->user, |
| authenticator_resp); |
| |
| if (!ret) |
| goto done; |
| |
| /* |
| * For iwd timing attacks are unlikely because media access will |
| * influence timing. If this code is ever taken out of iwd, memcmp |
| * should be replaced by a constant time memcmp |
| */ |
| if (len < 42 || memcmp(authenticator_resp, pkt, 42)) { |
| l_warn("Authenticator response didn't match"); |
| ret = false; |
| goto done; |
| } |
| |
| ret = mschapv2_get_master_key(password_hash_hash, nt_response, |
| master_key); |
| ret &= mschapv2_get_asymmetric_start_key(master_key, session_key, |
| 16, false, true); |
| ret &= mschapv2_get_asymmetric_start_key(master_key, session_key + 16, |
| 16, false, false); |
| |
| if (!ret) |
| goto done; |
| |
| eap_method_success(eap); |
| |
| buffer[5] = MSCHAPV2_OP_SUCCESS; |
| eap_method_respond(eap, buffer, sizeof(buffer)); |
| |
| /* The eapol set_key_material only needs msk, and that's all we got */ |
| eap_set_key_material(eap, session_key, 32, NULL, 0, NULL, 0, NULL, 0); |
| |
| done: |
| if (!ret) |
| eap_method_error(eap); |
| |
| explicit_bzero(master_key, sizeof(master_key)); |
| explicit_bzero(session_key, sizeof(session_key)); |
| explicit_bzero(password_hash_hash, sizeof(password_hash_hash)); |
| } |
| |
| static void eap_mschapv2_handle_failure(struct eap_state *eap, |
| const uint8_t *pkt, size_t len) |
| { |
| /* |
| * From what I have seen, we can't prompt the user in any useful way |
| * yet, so we can't do any proper error handling. |
| * The values we can read from this are defined in: |
| * https://tools.ietf.org/html/draft-ietf-pppext-mschap-v2-01 |
| * Section 9 |
| * |
| * At the current point, this will be a fail. |
| */ |
| l_debug(""); |
| eap_method_error(eap); |
| } |
| |
| static void eap_mschapv2_handle_request(struct eap_state *eap, |
| const uint8_t *pkt, size_t len) |
| { |
| struct eap_mschapv2_state *state = eap_get_data(eap); |
| const struct mschapv2_header *hdr = (struct mschapv2_header *) pkt; |
| size_t size = sizeof(*hdr); |
| |
| if (len < sizeof(struct mschapv2_header) + 1) { |
| l_error("EAP-MSCHAPV2 packet too short"); |
| goto err; |
| } |
| |
| state->current_id = hdr->mschap_id; |
| |
| if (L_BE16_TO_CPU(hdr->mschap_len) != len) { |
| l_error("EAP-MSCHAPV2 packet contains invalid length"); |
| goto err; |
| } |
| |
| switch (hdr->op_code) { |
| case MSCHAPV2_OP_CHALLENGE: |
| eap_mschapv2_handle_challenge(eap, pkt + size, len - size); |
| break; |
| case MSCHAPV2_OP_SUCCESS: |
| eap_mschapv2_handle_success(eap, pkt + size, len - size); |
| break; |
| case MSCHAPV2_OP_FAILURE: |
| eap_mschapv2_handle_failure(eap, pkt + size, len - size); |
| break; |
| default: |
| l_error("Got unknown OP-Code in MSCHPV2 packet: %x", |
| hdr->op_code); |
| goto err; |
| } |
| |
| return; |
| |
| err: |
| eap_method_error(eap); |
| } |
| |
| static bool set_password_from_string(struct eap_mschapv2_state *state, |
| const char *password) |
| { |
| return mschap_nt_password_hash(password, state->password_hash); |
| } |
| |
| static int eap_mschapv2_check_settings(struct l_settings *settings, |
| struct l_queue *secrets, |
| const char *prefix, |
| struct l_queue **out_missing) |
| { |
| L_AUTO_FREE_VAR(uint8_t *, password_hash) = NULL; |
| L_AUTO_FREE_VAR(char *, password) = NULL; |
| L_AUTO_FREE_VAR(char *, identity); |
| const struct eap_secret_info *secret; |
| char setting[64], setting2[64]; |
| uint8_t hash[16]; |
| int r = 0; |
| size_t hash_len; |
| |
| snprintf(setting, sizeof(setting), "%sIdentity", prefix); |
| identity = l_settings_get_string(settings, "Security", setting); |
| |
| snprintf(setting2, sizeof(setting2), "%sPassword", prefix); |
| |
| if (!identity) { |
| secret = l_queue_find(secrets, eap_secret_info_match, setting); |
| if (secret) { |
| identity = l_strdup(secret->value); |
| password = l_strdup(secret->value + |
| strlen(secret->value) + 1); |
| |
| goto validate; |
| } |
| |
| eap_append_secret(out_missing, EAP_SECRET_REMOTE_USER_PASSWORD, |
| setting, setting2, NULL, |
| EAP_CACHE_TEMPORARY); |
| return 0; |
| } |
| |
| password = l_settings_get_string(settings, "Security", setting2); |
| |
| snprintf(setting, sizeof(setting), "%sPassword-Hash", prefix); |
| password_hash = l_settings_get_bytes(settings, "Security", |
| setting, &hash_len); |
| |
| if (password && password_hash) { |
| l_error("Exactly one of (%s, %s) must be present", |
| setting, setting2); |
| r = -EEXIST; |
| goto cleanup; |
| } |
| |
| if (password_hash) { |
| if (hash_len != 16) { |
| l_error("Property %s is not a 16-byte hexstring", |
| setting); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } else if (password) |
| goto validate; |
| |
| secret = l_queue_find(secrets, eap_secret_info_match, setting2); |
| if (!secret) { |
| eap_append_secret(out_missing, EAP_SECRET_REMOTE_PASSWORD, |
| setting2, NULL, identity, |
| EAP_CACHE_TEMPORARY); |
| return 0; |
| } |
| |
| password = l_strdup(secret->value); |
| |
| validate: |
| if (!mschap_nt_password_hash(password, hash)) |
| r = -EINVAL; |
| |
| cleanup: |
| explicit_bzero(password, strlen(password)); |
| return r; |
| } |
| |
| static bool eap_mschapv2_load_settings(struct eap_state *eap, |
| struct l_settings *settings, |
| const char *prefix) |
| { |
| struct eap_mschapv2_state *state; |
| L_AUTO_FREE_VAR(char *, password) = NULL; |
| char setting[64]; |
| |
| state = l_new(struct eap_mschapv2_state, 1); |
| |
| snprintf(setting, sizeof(setting), "%sIdentity", prefix); |
| state->user = l_settings_get_string(settings, "Security", setting); |
| if (!state->user) |
| goto error; |
| |
| state->user_len = strlen(state->user); |
| |
| /* Either read the password-hash from hexdump or password and hash it */ |
| snprintf(setting, sizeof(setting), "%sPassword", prefix); |
| password = l_settings_get_string(settings, "Security", setting); |
| |
| if (password) { |
| set_password_from_string(state, password); |
| explicit_bzero(password, strlen(password)); |
| } else { |
| size_t hash_len; |
| uint8_t *hash; |
| |
| snprintf(setting, sizeof(setting), "%sPassword-Hash", prefix); |
| hash = l_settings_get_bytes(settings, "Security", setting, |
| &hash_len); |
| memcpy(state->password_hash, hash, 16); |
| explicit_bzero(hash, 16); |
| l_free(hash); |
| } |
| |
| eap_set_data(eap, state); |
| |
| return true; |
| |
| error: |
| free(state); |
| return false; |
| } |
| |
| static struct eap_method eap_mschapv2 = { |
| .request_type = EAP_TYPE_MSCHAPV2, |
| .exports_msk = true, |
| .name = "MSCHAPV2", |
| |
| .free = eap_mschapv2_free, |
| .handle_request = eap_mschapv2_handle_request, |
| .check_settings = eap_mschapv2_check_settings, |
| .load_settings = eap_mschapv2_load_settings, |
| .reset_state = eap_mschapv2_reset_state, |
| }; |
| |
| static int eap_mschapv2_init(void) |
| { |
| l_debug(""); |
| |
| if (!l_checksum_is_supported(L_CHECKSUM_MD4, false)) { |
| l_warn("EAP_MSCHAPv2 init: MD4 support not found, skipping"); |
| l_warn("Ensure that CONFIG_CRYPTO_MD4 is enabled"); |
| return -ENOTSUP; |
| } |
| |
| return eap_register_method(&eap_mschapv2); |
| } |
| |
| static void eap_mschapv2_exit(void) |
| { |
| l_debug(""); |
| eap_unregister_method(&eap_mschapv2); |
| } |
| |
| EAP_METHOD_BUILTIN(eap_mschapv2, eap_mschapv2_init, eap_mschapv2_exit) |