/* SPDX-License-Identifier: GPL-2.0
 *
 * Copyright (C) 2015-2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
 */

#include "cookie.h"
#include "peer.h"
#include "device.h"
#include "messages.h"
#include "ratelimiter.h"
#include "crypto/blake2s.h"
#include "crypto/chacha20poly1305.h"

#include <linux/jiffies.h>
#include <net/ipv6.h>
#include <crypto/algapi.h>

void cookie_checker_init(struct cookie_checker *checker, struct wireguard_device *wg)
{
	init_rwsem(&checker->secret_lock);
	checker->secret_birthdate = get_jiffies_64();
	get_random_bytes(checker->secret, NOISE_HASH_LEN);
	checker->device = wg;
}

enum { COOKIE_KEY_LABEL_LEN = 8 };
static const u8 mac1_key_label[COOKIE_KEY_LABEL_LEN] = "mac1----";
static const u8 cookie_key_label[COOKIE_KEY_LABEL_LEN] = "cookie--";

static void precompute_key(u8 key[NOISE_SYMMETRIC_KEY_LEN], const u8 pubkey[NOISE_PUBLIC_KEY_LEN], const u8 label[COOKIE_KEY_LABEL_LEN])
{
	struct blake2s_state blake;

	blake2s_init(&blake, NOISE_SYMMETRIC_KEY_LEN);
	blake2s_update(&blake, label, COOKIE_KEY_LABEL_LEN);
	blake2s_update(&blake, pubkey, NOISE_PUBLIC_KEY_LEN);
	blake2s_final(&blake, key, NOISE_SYMMETRIC_KEY_LEN);
}

void cookie_checker_precompute_device_keys(struct cookie_checker *checker)
{
	down_read(&checker->device->static_identity.lock);
	if (likely(checker->device->static_identity.has_identity)) {
		precompute_key(checker->cookie_encryption_key, checker->device->static_identity.static_public, cookie_key_label);
		precompute_key(checker->message_mac1_key, checker->device->static_identity.static_public, mac1_key_label);
	} else {
		memset(checker->cookie_encryption_key, 0, NOISE_SYMMETRIC_KEY_LEN);
		memset(checker->message_mac1_key, 0, NOISE_SYMMETRIC_KEY_LEN);
	}
	up_read(&checker->device->static_identity.lock);
}

void cookie_checker_precompute_peer_keys(struct wireguard_peer *peer)
{
	precompute_key(peer->latest_cookie.cookie_decryption_key, peer->handshake.remote_static, cookie_key_label);
	precompute_key(peer->latest_cookie.message_mac1_key, peer->handshake.remote_static, mac1_key_label);
}

void cookie_init(struct cookie *cookie)
{
	memset(cookie, 0, sizeof(struct cookie));
	init_rwsem(&cookie->lock);
}

static void compute_mac1(u8 mac1[COOKIE_LEN], const void *message, size_t len, const u8 key[NOISE_SYMMETRIC_KEY_LEN])
{
	len = len - sizeof(struct message_macs) + offsetof(struct message_macs, mac1);
	blake2s(mac1, message, key, COOKIE_LEN, len, NOISE_SYMMETRIC_KEY_LEN);
}

static void compute_mac2(u8 mac2[COOKIE_LEN], const void *message, size_t len, const u8 cookie[COOKIE_LEN])
{
	len = len - sizeof(struct message_macs) + offsetof(struct message_macs, mac2);
	blake2s(mac2, message, cookie, COOKIE_LEN, len, COOKIE_LEN);
}

static void make_cookie(u8 cookie[COOKIE_LEN], struct sk_buff *skb, struct cookie_checker *checker)
{
	struct blake2s_state state;

	if (!time_is_after_jiffies64(checker->secret_birthdate + COOKIE_SECRET_MAX_AGE)) {
		down_write(&checker->secret_lock);
		checker->secret_birthdate = get_jiffies_64();
		get_random_bytes(checker->secret, NOISE_HASH_LEN);
		up_write(&checker->secret_lock);
	}

	down_read(&checker->secret_lock);

	blake2s_init_key(&state, COOKIE_LEN, checker->secret, NOISE_HASH_LEN);
	if (skb->protocol == htons(ETH_P_IP))
		blake2s_update(&state, (u8 *)&ip_hdr(skb)->saddr, sizeof(struct in_addr));
	else if (skb->protocol == htons(ETH_P_IPV6))
		blake2s_update(&state, (u8 *)&ipv6_hdr(skb)->saddr, sizeof(struct in6_addr));
	blake2s_update(&state, (u8 *)&udp_hdr(skb)->source, sizeof(__be16));
	blake2s_final(&state, cookie, COOKIE_LEN);

	up_read(&checker->secret_lock);
}

enum cookie_mac_state cookie_validate_packet(struct cookie_checker *checker, struct sk_buff *skb, bool check_cookie)
{
	u8 computed_mac[COOKIE_LEN];
	u8 cookie[COOKIE_LEN];
	enum cookie_mac_state ret;
	struct message_macs *macs = (struct message_macs *)(skb->data + skb->len - sizeof(struct message_macs));

	ret = INVALID_MAC;
	compute_mac1(computed_mac, skb->data, skb->len, checker->message_mac1_key);
	if (crypto_memneq(computed_mac, macs->mac1, COOKIE_LEN))
		goto out;

	ret = VALID_MAC_BUT_NO_COOKIE;

	if (!check_cookie)
		goto out;

	make_cookie(cookie, skb, checker);

	compute_mac2(computed_mac, skb->data, skb->len, cookie);
	if (crypto_memneq(computed_mac, macs->mac2, COOKIE_LEN))
		goto out;

	ret = VALID_MAC_WITH_COOKIE_BUT_RATELIMITED;
	if (!ratelimiter_allow(skb, dev_net(checker->device->dev)))
		goto out;

	ret = VALID_MAC_WITH_COOKIE;

out:
	return ret;
}

void cookie_add_mac_to_packet(void *message, size_t len, struct wireguard_peer *peer)
{
	struct message_macs *macs = (struct message_macs *)((u8 *)message + len - sizeof(struct message_macs));

	down_write(&peer->latest_cookie.lock);
	compute_mac1(macs->mac1, message, len, peer->latest_cookie.message_mac1_key);
	memcpy(peer->latest_cookie.last_mac1_sent, macs->mac1, COOKIE_LEN);
	peer->latest_cookie.have_sent_mac1 = true;
	up_write(&peer->latest_cookie.lock);

	down_read(&peer->latest_cookie.lock);
	if (peer->latest_cookie.is_valid && time_is_after_jiffies64(peer->latest_cookie.birthdate + COOKIE_SECRET_MAX_AGE - COOKIE_SECRET_LATENCY))
		compute_mac2(macs->mac2, message, len, peer->latest_cookie.cookie);
	else
		memset(macs->mac2, 0, COOKIE_LEN);
	up_read(&peer->latest_cookie.lock);
}

void cookie_message_create(struct message_handshake_cookie *dst, struct sk_buff *skb, __le32 index, struct cookie_checker *checker)
{
	struct message_macs *macs = (struct message_macs *)((u8 *)skb->data + skb->len - sizeof(struct message_macs));
	u8 cookie[COOKIE_LEN];

	dst->header.type = cpu_to_le32(MESSAGE_HANDSHAKE_COOKIE);
	dst->receiver_index = index;
	get_random_bytes_wait(dst->nonce, COOKIE_NONCE_LEN);

	make_cookie(cookie, skb, checker);
	xchacha20poly1305_encrypt(dst->encrypted_cookie, cookie, COOKIE_LEN, macs->mac1, COOKIE_LEN, dst->nonce, checker->cookie_encryption_key);
}

void cookie_message_consume(struct message_handshake_cookie *src, struct wireguard_device *wg)
{
	u8 cookie[COOKIE_LEN];
	struct index_hashtable_entry *entry;
	bool ret;

	entry = index_hashtable_lookup(&wg->index_hashtable, INDEX_HASHTABLE_HANDSHAKE | INDEX_HASHTABLE_KEYPAIR, src->receiver_index);
	if (unlikely(!entry))
		return;

	down_read(&entry->peer->latest_cookie.lock);
	if (unlikely(!entry->peer->latest_cookie.have_sent_mac1)) {
		up_read(&entry->peer->latest_cookie.lock);
		goto out;
	}
	ret = xchacha20poly1305_decrypt(cookie, src->encrypted_cookie, sizeof(src->encrypted_cookie), entry->peer->latest_cookie.last_mac1_sent, COOKIE_LEN, src->nonce, entry->peer->latest_cookie.cookie_decryption_key);
	up_read(&entry->peer->latest_cookie.lock);

	if (ret) {
		down_write(&entry->peer->latest_cookie.lock);
		memcpy(entry->peer->latest_cookie.cookie, cookie, COOKIE_LEN);
		entry->peer->latest_cookie.birthdate = get_jiffies_64();
		entry->peer->latest_cookie.is_valid = true;
		entry->peer->latest_cookie.have_sent_mac1 = false;
		up_write(&entry->peer->latest_cookie.lock);
	} else
		net_dbg_ratelimited("%s: Could not decrypt invalid cookie response\n", wg->dev->name);

out:
	peer_put(entry->peer);
}