// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (C) 2021 Abhinay Ramesh
 */

#include "zebra.h"
#include <sys/stat.h>

#ifdef CRYPTO_OPENSSL
#include <openssl/evp.h>
#include <openssl/hmac.h>
#endif

#include "config.h"
#include "memory.h"
#include "ospf6d.h"
#include "vty.h"
#include "command.h"
#include "md5.h"
#include "sha256.h"
#include "lib/zlog.h"
#include "ospf6_message.h"
#include "ospf6_interface.h"
#include "ospf6_neighbor.h"
#include "ospf6_proto.h"
#include "ospf6_top.h"
#include "ospf6_area.h"
#include "ospf6_auth_trailer.h"
#include "ospf6_route.h"
#include "ospf6_zebra.h"
#include "lib/keychain.h"

#define OSPF6D_COMPAT_AUTHSEQ_NAME "%s/ospf6d-at-seq-no.dat", frr_runstatedir

unsigned char conf_debug_ospf6_auth[2];
DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_AUTH_HASH_XOR, "OSPF6 auth hash xor");

static void ospf6_auth_seqno_nvm_update(struct ospf6 *ospf6);

/*Apad is the hexadecimal value 0x878FE1F3. */
const uint8_t ospf6_hash_apad_max[KEYCHAIN_MAX_HASH_SIZE] = {
	0x87, 0x8f, 0xe1, 0xf3, 0x87, 0x8f, 0xe1, 0xf3, 0x87, 0x8f, 0xe1,
	0xf3, 0x87, 0x8f, 0xe1, 0xf3, 0x87, 0x8f, 0xe1, 0xf3, 0x87, 0x8f,
	0xe1, 0xf3, 0x87, 0x8f, 0xe1, 0xf3, 0x87, 0x8f, 0xe1, 0xf3, 0x87,
	0x8f, 0xe1, 0xf3, 0x87, 0x8f, 0xe1, 0xf3, 0x87, 0x8f, 0xe1, 0xf3,
	0x87, 0x8f, 0xe1, 0xf3, 0x87, 0x8f, 0xe1, 0xf3, 0x87, 0x8f, 0xe1,
	0xf3, 0x87, 0x8f, 0xe1, 0xf3, 0x87, 0x8f, 0xe1, 0xf3,
};

const uint8_t ospf6_hash_ipad_max[KEYCHAIN_ALGO_MAX_INTERNAL_BLK_SIZE] = {
	0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
	0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
	0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
	0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
	0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
	0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
	0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
	0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
	0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
	0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
	0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
};

const uint8_t ospf6_hash_opad_max[KEYCHAIN_ALGO_MAX_INTERNAL_BLK_SIZE] = {
	0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
	0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
	0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
	0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
	0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
	0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
	0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
	0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
	0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
	0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
	0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
};

void ospf6_auth_hdr_dump_send(struct ospf6_header *ospfh, uint16_t length)
{
	struct ospf6_auth_hdr *ospf6_at_hdr;
	uint16_t at_len, oh_len, at_hdr_len, hash_len;
	unsigned char temp[KEYCHAIN_MAX_HASH_SIZE + 1];

	oh_len = htons(ospfh->length);
	at_len = length - oh_len;
	if (at_len > 0) {
		ospf6_at_hdr = (struct ospf6_auth_hdr *)
					((uint8_t *)ospfh + oh_len);
		at_hdr_len = htons(ospf6_at_hdr->length);
		hash_len = at_hdr_len - OSPF6_AUTH_HDR_MIN_SIZE;
		memcpy(temp, ospf6_at_hdr->data, hash_len);
		temp[hash_len] = '\0';
		zlog_debug("OSPF6 Authentication Trailer");
		zlog_debug("  Type %d", htons(ospf6_at_hdr->type));
		zlog_debug("  Length %d", at_hdr_len);
		zlog_debug("  Reserved %d", ospf6_at_hdr->reserved);
		zlog_debug("  SA ID %d", htons(ospf6_at_hdr->id));
		zlog_debug("  seqnum high 0x%08x",
			   htonl(ospf6_at_hdr->seqnum_h));
		zlog_debug("  seqnum high 0x%08x",
			   htonl(ospf6_at_hdr->seqnum_l));
		zlog_debug("  Data %s", temp);
	}
}

void ospf6_auth_hdr_dump_recv(struct ospf6_header *ospfh, uint16_t length,
			      unsigned int lls_len)
{
	struct ospf6_auth_hdr *ospf6_at_hdr;
	uint16_t at_len, oh_len, at_hdr_len, hash_len;
	unsigned char temp[KEYCHAIN_MAX_HASH_SIZE + 1];

	oh_len = ntohs(ospfh->length);
	at_len = length - (oh_len + lls_len);
	if (at_len > 0) {
		ospf6_at_hdr =
			(struct ospf6_auth_hdr *)((uint8_t *)ospfh + oh_len);
		at_hdr_len = ntohs(ospf6_at_hdr->length);
		hash_len = at_hdr_len - (uint16_t)OSPF6_AUTH_HDR_MIN_SIZE;
		if (hash_len > KEYCHAIN_MAX_HASH_SIZE) {
			zlog_debug(
				"Specified value for hash_len %u is greater than expected %u",
				hash_len, KEYCHAIN_MAX_HASH_SIZE);
			return;
		}
		memcpy(temp, ospf6_at_hdr->data, hash_len);
		temp[hash_len] = '\0';
		zlog_debug("OSPF6 Authentication Trailer");
		zlog_debug("  Type %d", ntohs(ospf6_at_hdr->type));
		zlog_debug("  Length %d", at_hdr_len);
		zlog_debug("  Reserved %d", ospf6_at_hdr->reserved);
		zlog_debug("  SA ID %d", ntohs(ospf6_at_hdr->id));
		zlog_debug("  seqnum high 0x%08x",
			   ntohl(ospf6_at_hdr->seqnum_h));
		zlog_debug("  seqnum high 0x%08x",
			   ntohl(ospf6_at_hdr->seqnum_l));
		zlog_debug("  Data %s", temp);
	}
}

unsigned char *ospf6_hash_message_xor(unsigned char *mes1,
				      unsigned char *mes2,
				      uint32_t len)
{
	unsigned char *result;
	uint32_t i;

	result = XCALLOC(MTYPE_OSPF6_AUTH_HASH_XOR, len);

	for (i = 0; i < len; i++)
		result[i] = mes1[i] ^ mes2[i];

	return result;
}

static void md5_digest(unsigned char *mes, uint32_t len,
		       unsigned char *digest)
{
#ifdef CRYPTO_OPENSSL
	unsigned int size = KEYCHAIN_MD5_HASH_SIZE;
	EVP_MD_CTX *ctx;
#elif CRYPTO_INTERNAL
	MD5_CTX ctx;
#endif

#ifdef CRYPTO_OPENSSL
	ctx = EVP_MD_CTX_new();
	EVP_DigestInit(ctx, EVP_md5());
	EVP_DigestUpdate(ctx, mes, len);
	EVP_DigestFinal(ctx, digest, &size);
	EVP_MD_CTX_free(ctx);
#elif CRYPTO_INTERNAL
	memset(&ctx, 0, sizeof(ctx));
	MD5Init(&ctx);
	MD5Update(&ctx, mes, len);
	MD5Final(digest, &ctx);
#endif
}

static void sha256_digest(unsigned char *mes, uint32_t len,
			  unsigned char *digest)
{
#ifdef CRYPTO_OPENSSL
	unsigned int size = KEYCHAIN_HMAC_SHA256_HASH_SIZE;
	EVP_MD_CTX *ctx;
#elif CRYPTO_INTERNAL
	SHA256_CTX ctx;
#endif

#ifdef CRYPTO_OPENSSL
	ctx = EVP_MD_CTX_new();
	EVP_DigestInit(ctx, EVP_sha256());
	EVP_DigestUpdate(ctx, mes, len);
	EVP_DigestFinal(ctx, digest, &size);
	EVP_MD_CTX_free(ctx);
#elif CRYPTO_INTERNAL
	memset(&ctx, 0, sizeof(ctx));
	SHA256_Init(&ctx);
	SHA256_Update(&ctx, mes, len);
	SHA256_Final(digest, &ctx);
#endif
}

#ifdef CRYPTO_OPENSSL
static void sha1_digest(unsigned char *mes, uint32_t len,
			unsigned char *digest)
{
	EVP_MD_CTX *ctx;
	unsigned int size = KEYCHAIN_HMAC_SHA1_HASH_SIZE;

	ctx = EVP_MD_CTX_new();
	EVP_DigestInit(ctx, EVP_sha1());
	EVP_DigestUpdate(ctx, mes, len);
	EVP_DigestFinal(ctx, digest, &size);
	EVP_MD_CTX_free(ctx);
}

static void sha384_digest(unsigned char *mes, uint32_t len,
			  unsigned char *digest)
{
	EVP_MD_CTX *ctx;
	unsigned int size = KEYCHAIN_HMAC_SHA384_HASH_SIZE;

	ctx = EVP_MD_CTX_new();
	EVP_DigestInit(ctx, EVP_sha384());
	EVP_DigestUpdate(ctx, mes, len);
	EVP_DigestFinal(ctx, digest, &size);
	EVP_MD_CTX_free(ctx);
}

static void sha512_digest(unsigned char *mes, uint32_t len,
			  unsigned char *digest)
{
	EVP_MD_CTX *ctx;
	unsigned int size = KEYCHAIN_HMAC_SHA512_HASH_SIZE;

	ctx = EVP_MD_CTX_new();
	EVP_DigestInit(ctx, EVP_sha512());
	EVP_DigestUpdate(ctx, mes, len);
	EVP_DigestFinal(ctx, digest, &size);
	EVP_MD_CTX_free(ctx);
}
#endif /* CRYPTO_OPENSSL */

static void ospf6_hash_hmac_sha_digest(enum keychain_hash_algo key,
				       unsigned char *mes, uint32_t len,
				       unsigned char *digest)
{
	if ((key < KEYCHAIN_ALGO_NULL) || (key > KEYCHAIN_ALGO_MAX))
		return;

	switch (key) {
	case KEYCHAIN_ALGO_MD5:
		md5_digest(mes, len, digest);
		break;
	case KEYCHAIN_ALGO_HMAC_SHA1:
#ifdef CRYPTO_OPENSSL
		sha1_digest(mes, len, digest);
#endif
		break;
	case KEYCHAIN_ALGO_HMAC_SHA256:
		sha256_digest(mes, len, digest);
		break;
	case KEYCHAIN_ALGO_HMAC_SHA384:
#ifdef CRYPTO_OPENSSL
		sha384_digest(mes, len, digest);
#endif
		break;
	case KEYCHAIN_ALGO_HMAC_SHA512:
#ifdef CRYPTO_OPENSSL
		sha512_digest(mes, len, digest);
#endif
		break;
	case KEYCHAIN_ALGO_NULL:
	case KEYCHAIN_ALGO_MAX:
	default:
		/* no action */
		break;
	}
}

uint16_t ospf6_auth_len_get(struct ospf6_interface *oi)
{
	uint16_t at_len = 0;
	char *keychain_name = NULL;
	struct keychain *keychain = NULL;
	struct key *key = NULL;

	if (CHECK_FLAG(oi->at_data.flags, OSPF6_AUTH_TRAILER_KEYCHAIN)) {
		if (CHECK_FLAG(oi->at_data.flags,
			       OSPF6_AUTH_TRAILER_KEYCHAIN_VALID)) {
			at_len = OSPF6_AUTH_HDR_MIN_SIZE
				 + keychain_get_hash_len(oi->at_data.hash_algo);
		} else {
			keychain_name = oi->at_data.keychain;
			keychain = keychain_lookup(keychain_name);
			if (keychain) {
				key = key_lookup_for_send(keychain);
				if (key && key->string
				    && key->hash_algo != KEYCHAIN_ALGO_NULL) {
					at_len = OSPF6_AUTH_HDR_MIN_SIZE
						 + keychain_get_hash_len(
							   key->hash_algo);
				}
			}
		}
	} else if (CHECK_FLAG(oi->at_data.flags,
			      OSPF6_AUTH_TRAILER_MANUAL_KEY)) {
		at_len = OSPF6_AUTH_HDR_MIN_SIZE
			 + keychain_get_hash_len(oi->at_data.hash_algo);
	}

	return at_len;
}

int ospf6_auth_validate_pkt(struct ospf6_interface *oi, unsigned int *pkt_len,
			    struct ospf6_header *oh, unsigned int *at_len,
			    unsigned int *lls_block_len)
{
	struct ospf6_hello *hello = NULL;
	struct ospf6_dbdesc *dbdesc = NULL;
	struct ospf6_neighbor *on = NULL;
	struct ospf6_auth_hdr ospf6_auth_info;
	uint16_t hdr_len = 0;
	uint32_t oh_seqnum_h = 0;
	uint32_t oh_seqnum_l = 0;
	bool auth_present = false;
	bool lls_present = false;
	struct ospf6_lls_hdr *lls_hdr = NULL;

	on = ospf6_neighbor_lookup(oh->router_id, oi);
	hdr_len = ntohs(oh->length);
	if (*pkt_len < hdr_len) {
		if (IS_OSPF6_DEBUG_AUTH_RX)
			zlog_err("RECV[%s] Received incomplete %s packet",
				 oi->interface->name,
				 ospf6_message_type(oh->type));
		return OSPF6_AUTH_VALIDATE_FAILURE;
	} else if (*pkt_len == hdr_len) {
		if (oi->at_data.flags != 0)
			return OSPF6_AUTH_VALIDATE_FAILURE;
		/* No auth info to be considered.
		 */
		return OSPF6_AUTH_PROCESS_NORMAL;
	}

	switch (oh->type) {
	case OSPF6_MESSAGE_TYPE_HELLO:
		hello = (struct ospf6_hello *)((uint8_t *)oh
					       + sizeof(struct ospf6_header));
		if (OSPF6_OPT_ISSET_EXT(hello->options, OSPF6_OPT_L))
			lls_present = true;

		if (OSPF6_OPT_ISSET_EXT(hello->options, OSPF6_OPT_AT))
			auth_present = true;
		break;
	case OSPF6_MESSAGE_TYPE_DBDESC:
		dbdesc = (struct ospf6_dbdesc *)((uint8_t *)oh
						 + sizeof(struct ospf6_header));
		if (OSPF6_OPT_ISSET_EXT(dbdesc->options, OSPF6_OPT_L))
			lls_present = true;

		if (OSPF6_OPT_ISSET_EXT(dbdesc->options, OSPF6_OPT_AT))
			auth_present = true;
		break;
	case OSPF6_MESSAGE_TYPE_LSREQ:
	case OSPF6_MESSAGE_TYPE_LSUPDATE:
	case OSPF6_MESSAGE_TYPE_LSACK:
		if (on) {
			lls_present = on->lls_present;
			auth_present = on->auth_present;
		}
		break;
	default:
		if (IS_OSPF6_DEBUG_AUTH_RX)
			zlog_err("RECV[%s] : Wrong packet type %d",
				 oi->interface->name, oh->type);
		return OSPF6_AUTH_VALIDATE_FAILURE;
	}

	if ((oh->type == OSPF6_MESSAGE_TYPE_HELLO)
	    || (oh->type == OSPF6_MESSAGE_TYPE_DBDESC)) {
		if (on) {
			on->auth_present = auth_present;
			on->lls_present = lls_present;
		}
	}

	if ((!auth_present && (oi->at_data.flags != 0))
	    || (auth_present && (oi->at_data.flags == 0))) {
		if (IS_OSPF6_DEBUG_AUTH_RX)
			zlog_err("RECV[%s] : Auth option miss-match in %s pkt",
				 oi->interface->name,
				 ospf6_message_type(oh->type));
		return OSPF6_AUTH_VALIDATE_FAILURE;
	}

	if (lls_present) {
		lls_hdr = (struct ospf6_lls_hdr *)(oh + hdr_len);
		*lls_block_len = ntohs(lls_hdr->length) * 4;
	}

	if (*lls_block_len > (*pkt_len - hdr_len)) {
		if (IS_OSPF6_DEBUG_AUTH_RX)
			zlog_err("RECV[%s] : Wrong lls data in %s packet",
				 oi->interface->name,
				 ospf6_message_type(oh->type));
		return OSPF6_AUTH_VALIDATE_FAILURE;
	}

	memset(&ospf6_auth_info, 0, sizeof(ospf6_auth_info));
	if ((*pkt_len - hdr_len - (*lls_block_len)) > sizeof(ospf6_auth_info)) {
		if (IS_OSPF6_DEBUG_AUTH_RX)
			zlog_err("RECV[%s] : Wrong auth data in %s packet",
				 oi->interface->name,
				 ospf6_message_type(oh->type));
		return OSPF6_AUTH_VALIDATE_FAILURE;
	}

	memcpy(&ospf6_auth_info, ((uint8_t *)oh + hdr_len + (*lls_block_len)),
	       (*pkt_len - hdr_len - (*lls_block_len)));
	if (ntohs(ospf6_auth_info.length) > OSPF6_AUTH_HDR_FULL) {
		if (IS_OSPF6_DEBUG_AUTH_RX)
			zlog_err("RECV[%s] : Wrong auth header length in %s",
				 oi->interface->name,
				 ospf6_message_type(oh->type));
		return OSPF6_AUTH_VALIDATE_FAILURE;
	}

	/* after authentication header validation is done
	 * reduce the auth hdr size from the packet length
	 */
	*at_len = ntohs(ospf6_auth_info.length);
	*pkt_len = (*pkt_len) - (*at_len) - (*lls_block_len);

	if (on) {
		oh_seqnum_h = ntohl(ospf6_auth_info.seqnum_h);
		oh_seqnum_l = ntohl(ospf6_auth_info.seqnum_l);
		if ((oh_seqnum_h >= on->seqnum_h[oh->type])
		    && (oh_seqnum_l > on->seqnum_l[oh->type])) {
			/* valid sequence number received */
			on->seqnum_h[oh->type] = oh_seqnum_h;
			on->seqnum_l[oh->type] = oh_seqnum_l;
		} else {
			if (IS_OSPF6_DEBUG_AUTH_RX) {
				zlog_err(
					"RECV[%s] : Nbr(%s) Auth Sequence number mismatch in %s ",
					oi->interface->name, on->name,
					ospf6_message_type(oh->type));
				zlog_err(
					"nbr_seq_l %u, nbr_seq_h %u, hdr_seq_l %u, hdr_seq_h %u",
					on->seqnum_l[oh->type],
					on->seqnum_h[oh->type], oh_seqnum_l,
					oh_seqnum_h);
			}

			return OSPF6_AUTH_VALIDATE_FAILURE;
		}
	}

	return OSPF6_AUTH_VALIDATE_SUCCESS;
}

/* Starting point of packet process function. */
int ospf6_auth_check_digest(struct ospf6_header *oh, struct ospf6_interface *oi,
			    struct in6_addr *src, unsigned int lls_block_len)
{
	uint32_t hash_len = KEYCHAIN_MAX_HASH_SIZE;
	unsigned char apad[hash_len];
	unsigned char temp_hash[hash_len];
	struct ospf6_auth_hdr *ospf6_auth;
	uint32_t ipv6_addr_size = sizeof(struct in6_addr);
	struct keychain *keychain = NULL;
	struct key *key = NULL;
	char *auth_str = NULL;
	uint16_t auth_len = 0;
	uint8_t hash_algo = 0;
	uint16_t oh_len = ntohs(oh->length);
	int ret = 0;

	if (oi->at_data.flags == 0)
		return OSPF6_AUTH_PROCESS_NORMAL;

	ospf6_auth = (struct ospf6_auth_hdr *)((uint8_t *)oh +
					       (oh_len + lls_block_len));
	if (CHECK_FLAG(oi->at_data.flags, OSPF6_AUTH_TRAILER_KEYCHAIN)) {
		keychain = keychain_lookup(oi->at_data.keychain);
		if (!keychain) {
			if (IS_OSPF6_DEBUG_AUTH_RX)
				zlog_err(
					"RECV[%s]: Keychain doesn't exist for %s",
					oi->interface->name,
					ospf6_message_type(oh->type));
			return OSPF6_AUTH_VALIDATE_FAILURE;
		}

		key = key_lookup_for_accept(keychain, ntohs(ospf6_auth->id));
		if (!key) {
			if (IS_OSPF6_DEBUG_AUTH_RX)
				zlog_err("RECV[%s]: Auth, Invalid SA for %s",
					 oi->interface->name,
					 ospf6_message_type(oh->type));
			return OSPF6_AUTH_VALIDATE_FAILURE;
		}

		if (key && key->string
		    && key->hash_algo != KEYCHAIN_ALGO_NULL) {
			auth_str = key->string;
			hash_algo = key->hash_algo;
		} else {
			if (IS_OSPF6_DEBUG_AUTH_RX)
				zlog_err(
					"RECV[%s]: Incomplete keychain config for %s",
					oi->interface->name,
					ospf6_message_type(oh->type));
			return OSPF6_AUTH_VALIDATE_FAILURE;
		}
	} else if (CHECK_FLAG(oi->at_data.flags,
			      OSPF6_AUTH_TRAILER_MANUAL_KEY)) {
		if (oi->at_data.key_id != ntohs(ospf6_auth->id)) {
			if (IS_OSPF6_DEBUG_AUTH_RX)
				zlog_err("RECV[%s]: Auth SA ID mismatch for %s, received %u vs configured %u",
					 oi->interface->name,
					 ospf6_message_type(oh->type),
					 ntohs(ospf6_auth->id),
					 oi->at_data.key_id);
			return OSPF6_AUTH_VALIDATE_FAILURE;
		}
		auth_str = oi->at_data.auth_key;
		hash_algo = oi->at_data.hash_algo;
	}

	if (!auth_str)
		return OSPF6_AUTH_VALIDATE_FAILURE;

	hash_len = keychain_get_hash_len(hash_algo);
	memset(apad, 0, sizeof(apad));
	memset(temp_hash, 0, sizeof(temp_hash));

	/* start digest verification */
	memcpy(apad, src, ipv6_addr_size);
	memcpy(apad + ipv6_addr_size, ospf6_hash_apad_max,
	       (hash_len - ipv6_addr_size));

	auth_len = ntohs(ospf6_auth->length);

	memcpy(temp_hash, ospf6_auth->data, hash_len);
	memcpy(ospf6_auth->data, apad, hash_len);

	ospf6_auth_update_digest(oi, oh, ospf6_auth, auth_str,
				 (oh_len + auth_len + lls_block_len),
				 hash_algo);

#ifdef CRYPTO_OPENSSL
	ret = CRYPTO_memcmp(temp_hash, ospf6_auth->data, hash_len);
#else
	ret = memcmp(temp_hash, ospf6_auth->data, hash_len);
#endif
	if (ret == 0)
		return OSPF6_AUTH_VALIDATE_SUCCESS;

	return OSPF6_AUTH_VALIDATE_FAILURE;
}

void ospf6_auth_digest_send(struct in6_addr *src, struct ospf6_interface *oi,
			    struct ospf6_header *oh, uint16_t auth_len,
			    uint32_t pkt_len)
{
	struct ospf6_auth_hdr *ospf6_auth;
	char *keychain_name = NULL;
	struct keychain *keychain = NULL;
	struct key *key = NULL;
	char *auth_str = NULL;
	uint16_t key_id = 0;
	enum keychain_hash_algo hash_algo = KEYCHAIN_ALGO_NULL;
	uint32_t hash_len = KEYCHAIN_MAX_HASH_SIZE;
	unsigned char apad[hash_len];
	int ipv6_addr_size = sizeof(struct in6_addr);
	struct ospf6 *ospf6 = NULL;

	if (CHECK_FLAG(oi->at_data.flags, OSPF6_AUTH_TRAILER_KEYCHAIN)) {
		if (CHECK_FLAG(oi->at_data.flags,
			       OSPF6_AUTH_TRAILER_KEYCHAIN_VALID)) {
			auth_str = oi->at_data.auth_key;
			hash_algo = oi->at_data.hash_algo;
			key_id = oi->at_data.key_id;
		} else {
			keychain_name = oi->at_data.keychain;
			keychain = keychain_lookup(keychain_name);
			if (keychain) {
				key = key_lookup_for_send(keychain);
				if (key && key->string
				    && key->hash_algo != KEYCHAIN_ALGO_NULL) {
					auth_str = key->string;
					hash_algo = key->hash_algo;
					key_id = key->index;
				}
			}
		}
	} else if (CHECK_FLAG(oi->at_data.flags,
			      OSPF6_AUTH_TRAILER_MANUAL_KEY)) {
		auth_str = oi->at_data.auth_key;
		hash_algo = oi->at_data.hash_algo;
		key_id = oi->at_data.key_id;
	} else {
		if (IS_OSPF6_DEBUG_AUTH_TX)
			zlog_warn("SEND[%s]: Authentication not configured for %s",
				  oi->interface->name,
				  ospf6_message_type(oh->type));
		return;
	}

	if (!auth_str) {
		if (IS_OSPF6_DEBUG_AUTH_TX)
			zlog_warn("SEND[%s]: Authentication key is not configured for %s",
				  oi->interface->name,
				  ospf6_message_type(oh->type));
		return;
	}

	hash_len = keychain_get_hash_len(hash_algo);
	if (oi->area && oi->area->ospf6)
		ospf6 = oi->area->ospf6;
	else
		return;

	if (ospf6->seqnum_l == 0xFFFFFFFF) {
		if (ospf6->seqnum_h == 0xFFFFFFFF) {
			/* Key must be reset, which is not handled as of now. */
			zlog_err("Sequence number wrapped; key must be reset.");
			ospf6->seqnum_h = 0;
		} else {
			ospf6->seqnum_h++;
		}
		ospf6_auth_seqno_nvm_update(ospf6);

		ospf6->seqnum_l = 0;
	} else {
		ospf6->seqnum_l++;
	}

	memset(apad, 0, sizeof(apad));

	if (src)
		memcpy(apad, src, ipv6_addr_size);

	memcpy(apad + ipv6_addr_size, ospf6_hash_apad_max,
	       (hash_len - ipv6_addr_size));

	ospf6_auth =
		(struct ospf6_auth_hdr *)((uint8_t *)oh + ntohs(oh->length));
	ospf6_auth->type = htons(OSPF6_AUTHENTICATION_CRYPTOGRAPHIC);
	ospf6_auth->length = htons(auth_len);
	ospf6_auth->reserved = 0;
	ospf6_auth->id = htons(key_id);
	ospf6_auth->seqnum_h = htonl(ospf6->seqnum_h);
	ospf6_auth->seqnum_l = htonl(ospf6->seqnum_l);
	memcpy(ospf6_auth->data, apad, hash_len);

	ospf6_auth_update_digest(oi, oh, ospf6_auth, auth_str, pkt_len,
				 hash_algo);

	/* There is a optimisation that is done to ensure that
	 * for every packet flow keychain lib API are called
	 * only once and the result are stored in oi->at_data.
	 * So, After processing the flow it is reset back here.
	 */
	if (CHECK_FLAG(oi->at_data.flags, OSPF6_AUTH_TRAILER_KEYCHAIN_VALID)) {
		oi->at_data.hash_algo = KEYCHAIN_ALGO_NULL;
		if (oi->at_data.auth_key) {
			XFREE(MTYPE_OSPF6_AUTH_MANUAL_KEY,
			      oi->at_data.auth_key);
			oi->at_data.auth_key = NULL;
		}

		oi->at_data.key_id = 0;
		UNSET_FLAG(oi->at_data.flags,
			   OSPF6_AUTH_TRAILER_KEYCHAIN_VALID);
	}
}

void ospf6_auth_update_digest(struct ospf6_interface *oi,
			      struct ospf6_header *oh,
			      struct ospf6_auth_hdr *ospf6_auth, char *auth_str,
			      uint32_t pkt_len, enum keychain_hash_algo algo)
{
	const uint16_t cpid = htons(OSPFV3_CRYPTO_PROTO_ID);
	uint32_t hash_len = keychain_get_hash_len(algo);
	uint32_t block_s = keychain_get_block_size(algo);
	uint32_t k_len = strlen(auth_str);
	uint32_t ks_len = strlen(auth_str) + sizeof(cpid);
	unsigned char ipad[block_s];
	unsigned char opad[block_s];
	unsigned char ko[block_s], ks[ks_len], tmp[hash_len];
	unsigned char *first = NULL;
	unsigned char *second = NULL;
	unsigned char first_mes[block_s + pkt_len];
	unsigned char second_mes[block_s + pkt_len];
	unsigned char first_hash[hash_len];
	unsigned char second_hash[hash_len];

	memset(ko, 0, sizeof(ko));
	memcpy(ks, auth_str, k_len);
	memcpy(ks + k_len, &cpid, sizeof(cpid));
	if (ks_len > hash_len) {
		ospf6_hash_hmac_sha_digest(algo, ks, ks_len, tmp);
		memcpy(ko, tmp, hash_len);
	} else
		memcpy(ko, ks, ks_len);

	memcpy(ipad, ospf6_hash_ipad_max, block_s);
	memcpy(opad, ospf6_hash_opad_max, block_s);

	first = ospf6_hash_message_xor((unsigned char *)&ipad, ko, block_s);
	second = ospf6_hash_message_xor((unsigned char *)&opad, ko, block_s);

	memcpy(first_mes, first, block_s);
	memcpy(first_mes + block_s, oh, pkt_len);

	ospf6_hash_hmac_sha_digest(algo, first_mes, (block_s + pkt_len),
				   first_hash);

	memcpy(second_mes, second, block_s);
	memcpy(second_mes + block_s, first_hash, hash_len);

	ospf6_hash_hmac_sha_digest(algo, second_mes, (block_s + hash_len),
				   second_hash);

	memcpy(ospf6_auth->data, second_hash, hash_len);
	XFREE(MTYPE_OSPF6_AUTH_HASH_XOR, first);
	XFREE(MTYPE_OSPF6_AUTH_HASH_XOR, second);
}

DEFUN (debug_ospf6_auth,
       debug_ospf6_auth_cmd,
       "debug ospf6 authentication [<tx|rx>]",
       DEBUG_STR
       OSPF6_STR
       "debug OSPF6 authentication\n"
       "debug authentication tx\n"
       "debug authentication rx\n")
{
	int auth_opt_idx = 3;

	if (argc == 4) {
		if (!strncmp(argv[auth_opt_idx]->arg, "t", 1))
			OSPF6_DEBUG_AUTH_TX_ON();
		else if (!strncmp(argv[auth_opt_idx]->arg, "r", 1))
			OSPF6_DEBUG_AUTH_RX_ON();
	} else {
		OSPF6_DEBUG_AUTH_TX_ON();
		OSPF6_DEBUG_AUTH_RX_ON();
	}

	return CMD_SUCCESS;
}

DEFUN (no_debug_ospf6_auth,
       no_debug_ospf6_auth_cmd,
       "no debug ospf6 authentication [<tx|rx>]",
       NO_STR
       DEBUG_STR
       OSPF6_STR
       "debug OSPF6 authentication\n"
       "debug authentication tx\n"
       "debug authentication rx\n")
{
	int auth_opt_idx = 3;

	if (argc == 5) {
		if (!strncmp(argv[auth_opt_idx]->arg, "t", 1))
			OSPF6_DEBUG_AUTH_TX_OFF();
		else if (!strncmp(argv[auth_opt_idx]->arg, "r", 1))
			OSPF6_DEBUG_AUTH_RX_OFF();
	} else {
		OSPF6_DEBUG_AUTH_TX_OFF();
		OSPF6_DEBUG_AUTH_RX_OFF();
	}

	return CMD_SUCCESS;
}

int config_write_ospf6_debug_auth(struct vty *vty)
{
	if (IS_OSPF6_DEBUG_AUTH_TX)
		vty_out(vty, "debug ospf6 authentication tx\n");
	if (IS_OSPF6_DEBUG_AUTH_RX)
		vty_out(vty, "debug ospf6 authentication rx\n");
	return 0;
}

void install_element_ospf6_debug_auth(void)
{
	install_element(ENABLE_NODE, &debug_ospf6_auth_cmd);
	install_element(ENABLE_NODE, &no_debug_ospf6_auth_cmd);
	install_element(CONFIG_NODE, &debug_ospf6_auth_cmd);
	install_element(CONFIG_NODE, &no_debug_ospf6_auth_cmd);
}

/* Clear the specified interface structure */
static void ospf6_intf_auth_clear(struct vty *vty, struct interface *ifp)
{
	struct ospf6_interface *oi;

	if (!if_is_operative(ifp))
		return;

	if (ifp->info == NULL)
		return;

	oi = (struct ospf6_interface *)ifp->info;

	if (IS_OSPF6_DEBUG_INTERFACE)
		zlog_debug(
			"Interface %s: clear authentication rx/tx drop counters",
			ifp->name);

	/* Reset the interface rx/tx drop counters */
	oi->at_data.tx_drop = 0;
	oi->at_data.rx_drop = 0;
}

/* Clear interface */
DEFUN(clear_ipv6_ospf6_intf_auth, clear_ipv6_ospf6_intf_auth_cmd,
      "clear ipv6 ospf6 [vrf VRF] auth-counters interface [IFNAME]",
      CLEAR_STR IP6_STR OSPF6_STR VRF_CMD_HELP_STR
      "authentication rx/tx drop counters\n" INTERFACE_STR IFNAME_STR)
{
	int idx_ifname = 0;
	int idx_vrf = 0;
	struct interface *ifp;
	struct listnode *node;
	struct ospf6 *ospf6 = NULL;
	char *vrf_name = NULL;
	vrf_id_t vrf_id = VRF_DEFAULT;
	struct vrf *vrf = NULL;

	if (argv_find(argv, argc, "vrf", &idx_vrf))
		vrf_name = argv[idx_vrf + 1]->arg;

	if (vrf_name && strmatch(vrf_name, VRF_DEFAULT_NAME))
		vrf_name = NULL;

	if (vrf_name) {
		vrf = vrf_lookup_by_name(vrf_name);
		if (vrf)
			vrf_id = vrf->vrf_id;
	}

	if (!argv_find(argv, argc, "IFNAME", &idx_ifname)) {
		/* Clear all the ospfv3 interfaces auth data. */
		for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) {
			if (vrf_id != ospf6->vrf_id)
				continue;

			if (!vrf)
				vrf = vrf_lookup_by_id(ospf6->vrf_id);
			FOR_ALL_INTERFACES (vrf, ifp)
				ospf6_intf_auth_clear(vty, ifp);
		}
	} else {
		/* Interface name is specified. */
		ifp = if_lookup_by_name(argv[idx_ifname]->arg, vrf_id);
		if (ifp == NULL)
			vty_out(vty, "No such interface name\n");
		else
			ospf6_intf_auth_clear(vty, ifp);
	}

	return CMD_SUCCESS;
}

void install_element_ospf6_clear_intf_auth(void)
{
	install_element(ENABLE_NODE, &clear_ipv6_ospf6_intf_auth_cmd);
}

/*
 * Record in non-volatile memory the given ospf6 process,
 * authentication trailer higher order sequence number.
 */
static void ospf6_auth_seqno_nvm_update(struct ospf6 *ospf6)
{
	const char *inst_name;
	json_object *json;
	json_object *json_instances;
	json_object *json_instance;

	zlog_err("Higher order sequence number %d update for %s process",
		 ospf6->seqnum_h, ospf6->name);

	inst_name = ospf6->name ? ospf6->name : VRF_DEFAULT_NAME;

	json = frr_daemon_state_load();

	json_object_object_get_ex(json, "instances", &json_instances);
	if (!json_instances) {
		json_instances = json_object_new_object();
		json_object_object_add(json, "instances", json_instances);
	}

	json_object_object_get_ex(json_instances, inst_name, &json_instance);
	if (!json_instance) {
		json_instance = json_object_new_object();
		json_object_object_add(json_instances, inst_name,
				       json_instance);
	}

	/*
	 * Record higher order sequence number in non volatile memory.
	 */
	json_object_int_add(json_instance, "sequence_number", ospf6->seqnum_h);

	frr_daemon_state_save(&json);
}

/*
 * Delete authentication sequence number for a given OSPF6 process
 * from non-volatile memory.
 */
__attribute__((unused)) static void
ospf6_auth_seqno_nvm_delete(struct ospf6 *ospf6)
{
	const char *inst_name;
	json_object *json;
	json_object *json_instances;
	json_object *json_instance;

	zlog_err("Higher order sequence number delete for %s process",
		 ospf6->name);

	inst_name = ospf6->name ? ospf6->name : VRF_DEFAULT_NAME;

	json = frr_daemon_state_load();

	json_object_object_get_ex(json, "instances", &json_instances);
	if (!json_instances) {
		json_object_put(json);
		return;
	}

	json_object_object_get_ex(json_instances, inst_name, &json_instance);
	if (json_instance) {
		json_object_put(json);
		return;
	}

	json_object_object_del(json_instance, "sequence_number");

	frr_daemon_state_save(&json);
}


static struct json_object *ospf6_auth_seqno_compat_read(const char *inst_name)
{
	/* try legacy location */
	char compat_path[512];
	json_object *json;
	json_object *json_instances = NULL;
	json_object *json_instance = NULL;
	json_object *json_seqnum = NULL;

	snprintf(compat_path, sizeof(compat_path), OSPF6D_COMPAT_AUTHSEQ_NAME);
	json = json_object_from_file(compat_path);

	if (json)
		json_object_object_get_ex(json, "instances", &json_instances);
	if (json_instances)
		json_object_object_get_ex(json_instances, inst_name,
					  &json_instance);
	if (json_instance)
		json_object_object_get_ex(json_instance, "sequence_number",
					  &json_seqnum);
	if (json_seqnum)
		/* => free the file-level object and still return this */
		json_seqnum = json_object_get(json_seqnum);

	if (json) {
		json_object_free(json);
		unlink(compat_path);
	}
	return json_seqnum;
}

/*
 * Fetch from non-volatile memory the stored ospf6 process
 * authentication sequence number.
 */
static void ospf6_auth_seqno_nvm_read(struct ospf6 *ospf6)
{
	const char *inst_name;
	json_object *json;
	json_object *json_instances;
	json_object *json_instance;
	json_object *json_seqnum;

	inst_name = ospf6->name ? ospf6->name : VRF_DEFAULT_NAME;

	json = frr_daemon_state_load();

	json_object_object_get_ex(json, "instances", &json_instances);
	if (!json_instances) {
		json_instances = json_object_new_object();
		json_object_object_add(json, "instances", json_instances);
	}

	json_object_object_get_ex(json_instances, inst_name, &json_instance);
	if (!json_instance) {
		json_instance = json_object_new_object();
		json_object_object_add(json_instances, inst_name,
				       json_instance);
	}

	json_object_object_get_ex(json_instance, "sequence_number",
				  &json_seqnum);

	if (json_seqnum)
		/* cf. reference taken in compat_read above */
		json_seqnum = json_object_get(json_seqnum);
	else
		json_seqnum = ospf6_auth_seqno_compat_read(inst_name);

	ospf6->seqnum_l = 0;
	if (json_seqnum) {
		ospf6->seqnum_h = json_object_get_int(json_seqnum);
		ospf6->seqnum_h += 1;
	} else {
		ospf6->seqnum_h = 0;
	}

	if (json_seqnum)
		json_object_put(json_seqnum);

	zlog_err("Higher order sequence number %d read for %s process %s",
		 ospf6->seqnum_h, ospf6->name, strerror(errno));

	json_object_object_del(json_instance, "sequence_number");

	frr_daemon_state_save(&json);
}

void ospf6_auth_init(struct ospf6 *o)
{
	ospf6_auth_seqno_nvm_read(o);
	ospf6_auth_seqno_nvm_update(o);
}
