/*
 *
 *  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)
