#include <linux/export.h>
#include <linux/slab.h>
#include <linux/key.h>
#include <linux/err.h>
#include <keys/asymmetric-type.h>
#include "x509_parser.h"

static void keyring_clear(struct key *keyring)
{
	struct key *key, *tmp;

	if (!keyring->keyring)
		return;

	list_for_each_entry_safe(key, tmp, &keyring->list, list) {
		WARN_ON(refcount_read(&key->refcount) > 1);
		key_put(key);
	}
}

void key_put(struct key *key)
{
	if (refcount_dec_and_test(&key->refcount)) {
		keyring_clear(key);
		list_del(&key->list);
		kfree(key->description);
		public_key_free(key->public_key);
		public_key_signature_free(key->sig);
		kfree(key->kids.id[0]);
		kfree(key->kids.id[1]);
		kfree(key);
	}
}
EXPORT_SYMBOL_GPL(key_put);

static struct key *key_alloc(void)
{
	struct key *key = kzalloc(sizeof(*key), GFP_KERNEL);

	if (!key)
		return NULL;
	refcount_set(&key->refcount, 1);
	INIT_LIST_HEAD(&key->list);

	return key;
}

struct key *bp_keyring_alloc(void)
{
	struct key *key = key_alloc();

	if (!key)
		return NULL;

	key->keyring = true;

	return key;
}
EXPORT_SYMBOL_GPL(bp_keyring_alloc);

key_ref_t bp_key_create_or_update(key_ref_t keyring,
				  const char *description,
				  const void *payload,
				  size_t plen)
{
	struct key *key = key_alloc();
	struct x509_certificate *cert;
	const char *q;
	size_t srlen, sulen;
	char *desc = NULL, *p;
	int err;

	if (!key)
		return NULL;

	cert = x509_cert_parse(payload, plen);
	if (IS_ERR(cert)) {
		err = PTR_ERR(cert);
		goto free;
	}

	if (cert->unsupported_sig) {
		public_key_signature_free(cert->sig);
		cert->sig = NULL;
	}

	sulen = strlen(cert->subject);
	if (cert->raw_skid) {
		srlen = cert->raw_skid_size;
		q = cert->raw_skid;
	} else {
		srlen = cert->raw_serial_size;
		q = cert->raw_serial;
	}

	err = -ENOMEM;
	desc = kmalloc(sulen + 2 + srlen * 2 + 1, GFP_KERNEL);
	if (!desc)
		goto free;
	p = memcpy(desc, cert->subject, sulen);
	p += sulen;
	*p++ = ':';
	*p++ = ' ';
	p = bin2hex(p, q, srlen);
	*p = 0;
	key->description = desc;

	key->kids.id[0] = cert->id;
	key->kids.id[1] = cert->skid;
	key->public_key = cert->pub;
	key->sig = cert->sig;

	cert->id = NULL;
	cert->skid = NULL;
	cert->pub = NULL;
	cert->sig = NULL;
	x509_free_certificate(cert);

	refcount_inc(&key->refcount);
	list_add_tail(&key->list, &key_ref_to_ptr(keyring)->list);

	return make_key_ref(key, 0);
free:
	kfree(key);
	return ERR_PTR(err);
}
EXPORT_SYMBOL_GPL(bp_key_create_or_update);

struct key *find_asymmetric_key(struct key *keyring,
				const struct asymmetric_key_id *id_0,
				const struct asymmetric_key_id *id_1,
				bool partial)
{
	struct key *key;

	if (WARN_ON(partial))
		return ERR_PTR(-ENOENT);
	if (WARN_ON(!keyring))
		return ERR_PTR(-EINVAL);

	list_for_each_entry(key, &keyring->list, list) {
		const struct asymmetric_key_ids *kids = &key->kids;

		if (id_0 && (!kids->id[0] ||
			     !asymmetric_key_id_same(id_0, kids->id[0])))
			continue;
		if (id_1 && (!kids->id[1] ||
			     !asymmetric_key_id_same(id_0, kids->id[1])))
			continue;

		refcount_inc(&key->refcount);
		return key;
	}

	return ERR_PTR(-ENOKEY);
}

struct asymmetric_key_id *
asymmetric_key_generate_id(const void *val_1, size_t len_1,
			   const void *val_2, size_t len_2)
{
	struct asymmetric_key_id *kid;

	kid = kmalloc(sizeof(struct asymmetric_key_id) + len_1 + len_2,
		      GFP_KERNEL);
	if (!kid)
		return ERR_PTR(-ENOMEM);
	kid->len = len_1 + len_2;
	memcpy(kid->data, val_1, len_1);
	memcpy(kid->data + len_1, val_2, len_2);
	return kid;
}
