/* $NetBSD: params.c,v 1.35 2024/05/12 18:02:16 christos Exp $ */

/*-
 * Copyright (c) 2002, 2003 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Roland C. Dowdeswell.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: params.c,v 1.35 2024/05/12 18:02:16 christos Exp $");
#endif

#include <sys/types.h>
#include <sys/param.h>

#include <sys/sha2.h>
#include <sys/stat.h>

#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <util.h>
#include <uuid.h>

#ifdef HAVE_ARGON2
#include <argon2.h>
#include "argon2_utils.h"
#endif

#include "params.h"
#include "pkcs5_pbkdf2.h"
#include "utils.h"
#include "cgdconfig.h"
#include "extern.h"

static void	params_init(struct params *);

static void	print_kvpair_cstr(FILE *, int, const char *, const char *);
static void	print_kvpair_string(FILE *, int, const char *, const string_t *);
static void	print_kvpair_int(FILE *, int, const char *, size_t);
static void	print_kvpair_b64(FILE *, int, int, const char *, bits_t *);

static void	spaces(FILE *, int);

/* keygen defaults */
#define DEFAULT_SALTLEN		128
#define DEFAULT_ITERATION_TIME	2000000		/* 1 second in microseconds */

/* crypto defaults functions */
static struct crypto_defaults {
	char	alg[32];
	int	keylen;
} crypto_defaults[] = {
	{ "adiantum",		256 },
	{ "aes-cbc",		128 },
	{ "aes-xts",		256 },
	{ "3des-cbc",		192 },
	{ "blowfish-cbc",	128 }
};

static int	crypt_defaults_lookup(const char *);

struct params *
params_new(void)
{
	struct params	*p;

	p = emalloc(sizeof(*p));
	params_init(p);
	return p;
}

static void
params_init(struct params *p)
{

	p->algorithm = NULL;
	p->ivmeth = NULL;
	p->key = NULL;
	p->keylen = (size_t)-1;
	p->bsize = (size_t)-1;
	p->verify_method = VERIFY_UNKNOWN;
	p->dep_keygen = NULL;
	p->keygen = NULL;
}

void
params_free(struct params *p)
{

	if (!p)
		return;
	string_free(p->algorithm);
	string_free(p->ivmeth);
	keygen_free(p->dep_keygen);
	keygen_free(p->keygen);
}

struct params *
params_combine(struct params *p1, struct params *p2)
{
	struct params *p;

	if (p1)
		p = p1;
	else
		p = params_new();

	if (!p2)
		return p;

	if (p2->algorithm)
		string_assign(&p->algorithm, p2->algorithm);
	if (p2->ivmeth)
		string_assign(&p->ivmeth, p2->ivmeth);
	if (p2->keylen != (size_t)-1)
		p->keylen = p2->keylen;
	if (p2->bsize != (size_t)-1)
		p->bsize = p2->bsize;
	if (p2->verify_method != VERIFY_UNKNOWN)
		p->verify_method = p2->verify_method;

	p->dep_keygen = keygen_combine(p->dep_keygen, p2->dep_keygen);
	keygen_addlist(&p->keygen, p2->keygen);

	/*
	 * at this point we should have moved all allocated data
	 * in p2 into p, so we can free it.
	 */
	free(p2);
	return p;
}

int
params_filldefaults(struct params *p)
{
	size_t	i;

	if (p->verify_method == VERIFY_UNKNOWN)
		p->verify_method = VERIFY_NONE;
	if (!p->ivmeth)
		p->ivmeth = string_fromcharstar("encblkno1");
	if (p->keylen == (size_t)-1) {
		if (p->algorithm == NULL)
			return -1;
		i = crypt_defaults_lookup(string_tocharstar(p->algorithm));
		if (i != (size_t)-1) {
			p->keylen = crypto_defaults[i].keylen;
		} else {
			warnx("could not determine key length for unknown "
			    "algorithm \"%s\"",
			    string_tocharstar(p->algorithm));
			return -1;
		}
	}
	return 0;
}

/*
 * params_verify traverses the parameters and all of the keygen methods
 * looking for inconsistencies.  It outputs warnings on non-fatal errors
 * such as unknown encryption methods, but returns failure on fatal
 * conditions such as a PKCS5_PBKDF2 keygen without a salt.  It is intended
 * to run before key generation.
 */

int
params_verify(const struct params *p)
{
	static const char *encblkno[] = {
	    "encblkno", "encblkno1", "encblkno8"
	};
	static size_t i;
	const char *meth;

	if (!p->algorithm) {
		warnx("unspecified algorithm");
		return 0;
	}
	/*
	 * we only warn for the encryption method so that it is possible
	 * to use an older cgdconfig(8) with a new kernel that supports
	 * additional crypto algorithms.
	 */
	if (crypt_defaults_lookup(string_tocharstar(p->algorithm)) == -1)
		warnx("unknown algorithm \"%s\"(warning)",
		    string_tocharstar(p->algorithm));
	/* same rationale with IV methods. */
	if (!p->ivmeth) {
		warnx("unspecified IV method");
		return 0;
	}

	meth = string_tocharstar(p->ivmeth);
	for (i = 0; i < __arraycount(encblkno); i++)
		if (strcmp(encblkno[i], meth) == 0)
			break;

	if (i == __arraycount(encblkno))
		warnx("unknown IV method \"%s\" (warning)", meth);

	if (p->keylen == (size_t)-1) {
		warnx("unspecified key length");
		return 0;
	}

	return keygen_verify(p->keygen);
}

struct params *
params_algorithm(string_t *in)
{
	struct params *p = params_new();

	p->algorithm = in;
	return p;
}

struct params *
params_ivmeth(string_t *in)
{
	struct params *p = params_new();

	p->ivmeth = in;
	return p;
}

struct params *
params_keylen(size_t in)
{
	struct params *p = params_new();

	p->keylen = in;
	return p;
}

struct params *
params_bsize(size_t in)
{
	struct params *p = params_new();

	p->bsize = in;
	return p;
}

struct params *
params_verify_method(string_t *in)
{
	struct params *p = params_new();
	const char *vm = string_tocharstar(in);

	if (!strcmp("none", vm))
		p->verify_method = VERIFY_NONE;
	if (!strcmp("disklabel", vm))
		p->verify_method = VERIFY_DISKLABEL;
	if (!strcmp("ffs", vm))
		p->verify_method = VERIFY_FFS;
	if (!strcmp("re-enter", vm))
		p->verify_method = VERIFY_REENTER;
	if (!strcmp("mbr", vm))
		p->verify_method = VERIFY_MBR;
	if (!strcmp("gpt", vm))
		p->verify_method = VERIFY_GPT;
#ifdef HAVE_ZFS
	if (!strcmp("zfs", vm))
		p->verify_method = VERIFY_ZFS;
#endif

	string_free(in);

	if (p->verify_method == VERIFY_UNKNOWN)
		warnx("params_setverify_method: unrecognized "
		    "verify method \"%s\"", vm);
	return p;
}

struct params *
params_keygen(struct keygen *in)
{
	struct params *p = params_new();

	p->keygen = in;
	return p;
}

struct params *
params_dep_keygen(struct keygen *in)
{
	struct params *p = params_new();

	p->dep_keygen = in;
	return p;
}

struct keygen *
keygen_new(void)
{
	struct keygen *kg;

	kg = emalloc(sizeof(*kg));
	kg->kg_method = KEYGEN_UNKNOWN;
	kg->kg_iterations = (size_t)-1;
	kg->kg_memory = (size_t)-1;
	kg->kg_parallelism = (size_t)-1;
	kg->kg_version = (size_t)-1;
	kg->kg_salt = NULL;
	kg->kg_key = NULL;
	kg->kg_cmd = NULL;
	kg->kg_sharedid = NULL;
	kg->kg_sharedalg = SHARED_ALG_UNKNOWN;
	kg->kg_sharedlen = (size_t)-1;
	kg->kg_sharedinfo = NULL;
	kg->next = NULL;
	return kg;
}

void
keygen_free(struct keygen *kg)
{

	if (!kg)
		return;
	bits_free(kg->kg_salt);
	bits_free(kg->kg_key);
	string_free(kg->kg_cmd);
	string_free(kg->kg_sharedid);
	bits_free(kg->kg_sharedinfo);
	keygen_free(kg->next);
	free(kg);
}

/*
 * keygen_verify traverses the keygen structures and ensures
 * that the appropriate information is available.
 */

int
keygen_verify(const struct keygen *kg)
{

	if (!kg)
		return 1;
	switch (kg->kg_method) {
#ifdef HAVE_ARGON2
	case KEYGEN_ARGON2ID:
		if (kg->kg_iterations == (size_t)-1) {
			warnx("keygen argon2id must provide `iterations'");
			return 0;
		}
		if (kg->kg_memory == (size_t)-1) {
			warnx("keygen argon2id must provide `memory'");
			return 0;
		}
		if (kg->kg_parallelism == (size_t)-1) {
			warnx("keygen argon2id must provide `parallelism'");
			return 0;
		}
		if (kg->kg_version == (size_t)-1) {
			warnx("keygen argon2id must provide `version'");
			return 0;
		}
		if (kg->kg_cmd)
			warnx("keygen argon2id does not need a `cmd'");
		if (kg->kg_key)
			warnx("keygen argon2id does not need a `key'");
		if (!kg->kg_salt) {
			warnx("keygen argon2id must provide a salt");
			return 0;
		}
		break;
#endif
	case KEYGEN_PKCS5_PBKDF2_OLD:
		if (kg->kg_iterations == (size_t)-1) {
			warnx("keygen pkcs5_pbkdf2 must provide `iterations'");
			return 0;
		}
		if (kg->kg_key)
			warnx("keygen pkcs5_pbkdf2 does not need a `key'");
		if (!kg->kg_salt) {
			warnx("keygen pkcs5_pbkdf2 must provide a salt");
			return 0;
		}
		if (kg->kg_cmd)
			warnx("keygen pkcs5_pbkdf2 does not need a `cmd'");
		break;
	case KEYGEN_PKCS5_PBKDF2_SHA1:
		if (kg->kg_iterations == (size_t)-1) {
			warnx("keygen pkcs5_pbkdf2/sha1 must provide `iterations'");
			return 0;
		}
		if (kg->kg_key)
			warnx("keygen pkcs5_pbkdf2/sha1 does not need a `key'");
		if (!kg->kg_salt) {
			warnx("keygen pkcs5_pbkdf2/sha1 must provide a salt");
			return 0;
		}
		if (kg->kg_cmd)
			warnx("keygen pkcs5_pbkdf2/sha1 does not need a `cmd'");
		break;
	case KEYGEN_STOREDKEY:
		if (kg->kg_iterations != (size_t)-1)
			warnx("keygen storedkey does not need `iterations'");
		if (!kg->kg_key) {
			warnx("keygen storedkey must provide a key");
			return 0;
		}
		if (kg->kg_salt)
			warnx("keygen storedkey does not need `salt'");
		if (kg->kg_cmd)
			warnx("keygen storedkey does not need `cmd'");
		break;
	case KEYGEN_RANDOMKEY:
	case KEYGEN_URANDOMKEY:
		if (kg->kg_iterations != (size_t)-1)
			warnx("keygen [u]randomkey does not need `iterations'");
		if (kg->kg_key)
			warnx("keygen [u]randomkey does not need `key'");
		if (kg->kg_salt)
			warnx("keygen [u]randomkey does not need `salt'");
		if (kg->kg_cmd)
			warnx("keygen [u]randomkey does not need `cmd'");
		if (kg->kg_sharedid)
			warnx("keygen [u]randomkey makes no sense shared");
		break;
	case KEYGEN_SHELL_CMD:
		if (kg->kg_iterations != (size_t)-1)
			warnx("keygen shell_cmd does not need `iterations'");
		if (kg->kg_key)
			warnx("keygen shell_cmd does not need `key'");
		if (kg->kg_salt)
			warnx("keygen shell_cmd does not need `salt'");
		if (!kg->kg_cmd) {
			warnx("keygen shell_cmd must provide a `cmd'");
			return 0;
		}
		break;
	}
	return keygen_verify(kg->next);
}

struct keygen *
keygen_generate(int method)
{
	struct keygen *kg;

	kg = keygen_new();
	if (!kg)
		return NULL;

	kg->kg_method = method;
	return kg;
}

/*
 * keygen_filldefaults walks the keygen list and fills in
 * default values.  The defaults may be either calibrated
 * or randomly generated so this function is designed to be
 * called when generating a new parameters file, not when
 * reading a parameters file.
 */

int
keygen_filldefaults(struct keygen *kg, size_t keylen)
{

	if (!kg)
		return 0;
	switch (kg->kg_method) {
	case KEYGEN_RANDOMKEY:
	case KEYGEN_URANDOMKEY:
	case KEYGEN_SHELL_CMD:
		break;
#ifdef HAVE_ARGON2
	case KEYGEN_ARGON2ID:
		kg->kg_version = ARGON2_VERSION_NUMBER;
		kg->kg_salt = bits_getrandombits(DEFAULT_SALTLEN, 1);
		argon2id_calibrate(BITS2BYTES(keylen), DEFAULT_SALTLEN,
		    &kg->kg_iterations, &kg->kg_memory, &kg->kg_parallelism);
		break;
#endif
	case KEYGEN_PKCS5_PBKDF2_OLD:
	case KEYGEN_PKCS5_PBKDF2_SHA1:
		kg->kg_salt = bits_getrandombits(DEFAULT_SALTLEN, 1);
		kg->kg_iterations = pkcs5_pbkdf2_calibrate(BITS2BYTES(keylen),
		    DEFAULT_ITERATION_TIME);
		if (kg->kg_iterations < 1) {
			warnx("could not calibrate pkcs5_pbkdf2");
			return -1;
		}
		break;
	case KEYGEN_STOREDKEY:
		/* Generate a random stored key */
		kg->kg_key = bits_getrandombits(keylen, 1);
		if (!kg->kg_key) {
			warnx("can't generate random bits for storedkey");
			return -1;
		}
		break;
	default:
		return -1;
	}

	return keygen_filldefaults(kg->next, keylen);
}

/*
 * Strip the storedkey entries in preparation for inserting a shared
 * clause with a newly generated info string to derive this key from
 * KDF.  The result is that the key generated here is independent of
 * whatever storedkeys were involved in the old one, so there is no
 * need to keep them around,
 */
void
keygen_stripstored(struct keygen **kgp)
{
	struct keygen *kg, *to_free = NULL;

	while ((kg = *kgp) != NULL) {
		if (kg->kg_method == KEYGEN_STOREDKEY) {
			*kgp = kg->next;
			kg->next = to_free;
			to_free = kg;
		} else {
			kgp = &kg->next;
		}
	}
	keygen_free(to_free);
}

int
keygen_makeshared(struct keygen *kg0)
{
	struct keygen *kg;

	for (kg = kg0; kg != NULL; kg = kg->next) {
		switch (kg->kg_method) {
		case KEYGEN_RANDOMKEY:
		case KEYGEN_URANDOMKEY:
			warnx("(u)randomkey keygen cannot be shared");
			return -1;
		case KEYGEN_SHELL_CMD:
#ifdef HAVE_ARGON2
		case KEYGEN_ARGON2ID:
#endif
		case KEYGEN_PKCS5_PBKDF2_OLD:
		case KEYGEN_PKCS5_PBKDF2_SHA1:
			break;
		case KEYGEN_STOREDKEY:
			warnx("storedkey does not make sense as shared");
			return -1;
		default:
			return -1;
		}
		if (kg->kg_sharedid != NULL) {
			warnx("keygen already shared");
			return -1;
		}
	}
	for (kg = kg0; kg != NULL; kg = kg->next) {
		struct uuid id;
		char *idstr;
		uint32_t status;

		if (uuidgen(&id, 1) == -1) {
			warn("uuidgen");
			return -1;
		}
		uuid_to_string(&id, &idstr, &status);
		if (status != uuid_s_ok) {
			warnx("uuid_to_string: %"PRIu32, status);
			return -1;
		}

		kg->kg_sharedid = string_fromcharstar(idstr);
		kg->kg_sharedalg = SHARED_ALG_HKDF_HMAC_SHA256;
		kg->kg_sharedlen = 8*SHA256_DIGEST_LENGTH;
		kg->kg_sharedinfo = bits_getrandombits(DEFAULT_SALTLEN, 0);

		free(idstr);
	}
	return 0;
}

int
keygen_tweakshared(struct keygen *kg0)
{
	struct keygen *kg;

	for (kg = kg0; kg != NULL; kg = kg->next) {
		switch (kg->kg_method) {
		case KEYGEN_RANDOMKEY:
		case KEYGEN_URANDOMKEY:
			warnx("(u)randomkey keygen cannot be shared");
			return -1;
		case KEYGEN_SHELL_CMD:
#ifdef HAVE_ARGON2
		case KEYGEN_ARGON2ID:
#endif
		case KEYGEN_PKCS5_PBKDF2_OLD:
		case KEYGEN_PKCS5_PBKDF2_SHA1:
			break;
		case KEYGEN_STOREDKEY:
			warnx("storedkey does not make sense as shared");
			return -1;
		default:
			return -1;
		}
		if (kg->kg_sharedid == NULL) {
			warnx("keygen not shared");
			return -1;
		}
	}
	for (kg = kg0; kg != NULL; kg = kg->next) {
		if (kg->kg_method == KEYGEN_STOREDKEY)
			continue;
		bits_free(kg->kg_sharedinfo);
		kg->kg_sharedinfo = bits_getrandombits(DEFAULT_SALTLEN, 0);
	}
	return 0;
}

struct keygen *
keygen_combine(struct keygen *kg1, struct keygen *kg2)
{
	if (!kg1 && !kg2)
		return NULL;

	if (!kg1)
		kg1 = keygen_new();

	if (!kg2)
		return kg1;

	if (kg2->kg_method != KEYGEN_UNKNOWN)
		kg1->kg_method = kg2->kg_method;

	if (kg2->kg_iterations != (size_t)-1 && kg2->kg_iterations > 0)
		kg1->kg_iterations = kg2->kg_iterations;

	if (kg2->kg_memory != (size_t)-1 && kg2->kg_memory > 0)
		kg1->kg_memory = kg2->kg_memory;

	if (kg2->kg_parallelism != (size_t)-1 && kg2->kg_parallelism > 0)
		kg1->kg_parallelism = kg2->kg_parallelism;

	if (kg2->kg_version != (size_t)-1 && kg2->kg_version > 0)
		kg1->kg_version = kg2->kg_version;

	if (kg2->kg_salt)
		bits_assign(&kg1->kg_salt, kg2->kg_salt);

	if (kg2->kg_key)
		bits_assign(&kg1->kg_key, kg2->kg_key);

	if (kg2->kg_cmd)
		string_assign(&kg1->kg_cmd, kg2->kg_cmd);

	if (kg2->kg_sharedid)
		string_assign(&kg1->kg_sharedid, kg2->kg_sharedid);
	if (kg2->kg_sharedalg != SHARED_ALG_UNKNOWN) {
		kg1->kg_sharedalg = kg2->kg_sharedalg;
		kg1->kg_sharedlen = kg2->kg_sharedlen;
	}
	if (kg2->kg_sharedinfo)
		bits_assign(&kg1->kg_sharedinfo, kg2->kg_sharedinfo);

	return kg1;
}

struct keygen *
keygen_method(string_t *in)
{
	struct keygen *kg = keygen_new();
	const char *kgm = string_tocharstar(in);

#ifdef HAVE_ARGON2
	if (!strcmp("argon2id", kgm))
		kg->kg_method = KEYGEN_ARGON2ID;
#endif
	if (!strcmp("pkcs5_pbkdf2", kgm))
		kg->kg_method = KEYGEN_PKCS5_PBKDF2_OLD;
	if (!strcmp("pkcs5_pbkdf2/sha1", kgm))
		kg->kg_method = KEYGEN_PKCS5_PBKDF2_SHA1;
	if (!strcmp("randomkey", kgm))
		kg->kg_method = KEYGEN_RANDOMKEY;
	if (!strcmp("storedkey", kgm))
		kg->kg_method = KEYGEN_STOREDKEY;
	if (!strcmp("urandomkey", kgm))
		kg->kg_method = KEYGEN_URANDOMKEY;
	if (!strcmp("shell_cmd", kgm))
		kg->kg_method = KEYGEN_SHELL_CMD;

	string_free(in);

	if (kg->kg_method == KEYGEN_UNKNOWN)
		warnx("unrecognized key generation method \"%s\"", kgm);
	return kg;
}

struct keygen *
keygen_set_method(struct keygen *kg, string_t *in)
{

	return keygen_combine(kg, keygen_method(in));
}

struct keygen *
keygen_salt(bits_t *in)
{
	struct keygen *kg = keygen_new();

	kg->kg_salt = in;
	return kg;
}

struct keygen *
keygen_iterations(size_t in)
{
	struct keygen *kg = keygen_new();

	kg->kg_iterations = in;
	return kg;
}

struct keygen *
keygen_memory(size_t in)
{
	struct keygen *kg = keygen_new();

	kg->kg_memory = in;
	return kg;
}

struct keygen *
keygen_parallelism(size_t in)
{
	struct keygen *kg = keygen_new();

	kg->kg_parallelism = in;
	return kg;
}

struct keygen *
keygen_version(size_t in)
{
	struct keygen *kg = keygen_new();

	kg->kg_version = in;
	return kg;
}

void
keygen_addlist(struct keygen **l, struct keygen *e)
{
	struct keygen *t;

	if (*l) {
		t = *l;
		for (;t->next; t = t->next)
			;
		t->next = e;
	} else {
		*l = e;
	}
}

struct keygen *
keygen_key(bits_t *in)
{
	struct keygen *kg = keygen_new();

	kg->kg_key = in;
	return kg;
}

struct keygen *
keygen_cmd(string_t *in)
{
	struct keygen *kg = keygen_new();

	kg->kg_cmd = in;
	return kg;
}

struct keygen *
keygen_shared(string_t *id, string_t *alg, bits_t *info)
{
	struct keygen *kg = keygen_new();
	const char *algname = string_tocharstar(alg);

	if (!strcmp("hkdf-hmac-sha256", algname)) {
		kg->kg_sharedalg = SHARED_ALG_HKDF_HMAC_SHA256;
		kg->kg_sharedlen = 8*SHA256_DIGEST_LENGTH;
	}

	if (kg->kg_sharedalg == SHARED_ALG_UNKNOWN) {
		warnx("unrecognized shared key derivation algorithm \"%s\"",
		    algname);
	}

	kg->kg_sharedid = id;
	kg->kg_sharedinfo = info;
	return kg;
}

struct params *
params_fget(FILE *f)
{
	struct params *p;

	p = cgdparsefile(f);

	if (!p)
		return NULL;

	/*
	 * We deal with the deprecated keygen structure by prepending it
	 * to the list of keygens, so that the rest of the code does not
	 * have to deal with this backwards compat issue.  The deprecated
	 * ``xor_key'' field may be stored in p->dep_keygen->kg_key.  If
	 * it exists, we construct a storedkey keygen struct as well.  Also,
	 * default the iteration count to 128 as the old code did.
	 */

	if (p->dep_keygen) {
		if (p->dep_keygen->kg_iterations == (size_t)-1)
			p->dep_keygen->kg_iterations = 128;
		p->dep_keygen->next = p->keygen;
		if (p->dep_keygen->kg_key) {
			p->keygen = keygen_generate(KEYGEN_STOREDKEY);
			p->keygen->kg_key = p->dep_keygen->kg_key;
			p->dep_keygen->kg_key = NULL;
			p->keygen->next = p->dep_keygen;
		} else {
			p->keygen = p->dep_keygen;
		}
		p->dep_keygen = NULL;
	}
	return p;
}

struct params *
params_cget(const char *fn)
{
	struct params	*p;
	FILE		*f;
	char		filename[MAXPATHLEN];

	if ((f = fopen(fn, "r")) == NULL && fn[0] != '/') {
		snprintf(filename, sizeof(filename), "%s/%s",
		    CGDCONFIG_DIR, fn);
		fn = filename;
		f = fopen(fn, "r");
	}

	if (f == NULL) {
		warn("failed to open params file \"%s\"", fn);
		return NULL;
	}
	p = params_fget(f);
	(void)fclose(f);
	return p;
}

#define WRAP_COL	50
#define TAB_COL		8

static void
spaces(FILE *f, int len)
{

	while (len-- > 0)
		(void)fputc(' ', f);
}

static void
print_kvpair_cstr(FILE *f, int ts, const char *key, const char *val)
{

	spaces(f, ts);
	(void)fprintf(f, "%s %s;\n", key, val);
}

static void
print_kvpair_string(FILE *f, int ts, const char *key, const string_t *val)
{

	print_kvpair_cstr(f, ts, key, string_tocharstar(val));
}

static void
print_kvpair_int(FILE *f, int ts, const char *key, size_t val)
{
	char	*tmp;

	if (!key || val == (size_t)-1)
		return;

	if (asprintf(&tmp, "%zu", val) == -1)
		err(1, NULL);
	print_kvpair_cstr(f, ts, key, tmp);
	free(tmp);
}

/*
 * prints out a base64 encoded k-v pair to f.  It encodes the length
 * of the bitstream as a 32bit unsigned integer in network byte order
 * up front.
 */

static void
print_kvpair_b64(FILE *f, int curpos, int ts, const char *key, bits_t *val)
{
	string_t	*str;
	int		 i;
	int		 len;
	int		 pos;
	const char	*out;

	if (!key || !val)
		return;

	str = bits_encode(val);
	out = string_tocharstar(str);
	len = strlen(out);

	spaces(f, ts);
	(void)fprintf(f, "%s ", key);
	curpos += ts + strlen(key) + 1;
	ts = curpos;

	for (i=0, pos=curpos; i < len; i++, pos++) {
		if (pos > WRAP_COL) {
			(void)fprintf(f, " \\\n");
			spaces(f, ts);
			pos = ts;
		}
		(void)fputc(out[i], f);
	}
	(void)fprintf(f, ";\n");
	string_free(str);
}

static void
print_shared(FILE *f, int ts, struct keygen *kg)
{
	static const char *const sharedalgs[] = {
		[SHARED_ALG_UNKNOWN] = "unknown",
		[SHARED_ALG_HKDF_HMAC_SHA256] = "hkdf-hmac-sha256",
	};

	if (kg->kg_sharedid == NULL ||
	    kg->kg_sharedalg < 0 ||
	    (size_t)kg->kg_sharedalg >= __arraycount(sharedalgs))
		return;
	fprintf(f, "%*sshared \"%s\" \\\n", ts, "",
	    string_tocharstar(kg->kg_sharedid));
	ts += 4;
	fprintf(f, "%*salgorithm %s \\\n", ts, "",
	    sharedalgs[kg->kg_sharedalg]);
	print_kvpair_b64(f, 0, ts, "subkey", kg->kg_sharedinfo);
}

int
keygen_fput(struct keygen *kg, int ts, FILE *f)
{
	int	curpos = 0;

	if (!kg)
		return 0;
	(void)fprintf(f, "keygen ");
	curpos += strlen("keygen ");
	switch (kg->kg_method) {
	case KEYGEN_STOREDKEY:
		(void)fprintf(f, "storedkey ");
		curpos += strlen("storedkey ");
		print_kvpair_b64(f, curpos, 0, "key", kg->kg_key);
		break;
	case KEYGEN_RANDOMKEY:
		(void)fprintf(f, "randomkey;\n");
		break;
	case KEYGEN_URANDOMKEY:
		(void)fprintf(f, "urandomkey;\n");
		break;
#ifdef HAVE_ARGON2
	case KEYGEN_ARGON2ID:
		(void)fprintf(f, "argon2id {\n");
		print_kvpair_int(f, ts, "iterations", kg->kg_iterations);
		print_kvpair_int(f, ts, "memory", kg->kg_memory);
		print_kvpair_int(f, ts, "parallelism", kg->kg_parallelism);
		print_kvpair_int(f, ts, "version", kg->kg_version);
		print_kvpair_b64(f, 0, ts, "salt", kg->kg_salt);
		print_shared(f, ts, kg);
		(void)fprintf(f, "};\n");
		break;
#endif
	case KEYGEN_PKCS5_PBKDF2_OLD:
		(void)fprintf(f, "pkcs5_pbkdf2 {\n");
		print_kvpair_int(f, ts, "iterations", kg->kg_iterations);
		print_kvpair_b64(f, 0, ts, "salt", kg->kg_salt);
		print_shared(f, ts, kg);
		(void)fprintf(f, "};\n");
		break;
	case KEYGEN_PKCS5_PBKDF2_SHA1:
		(void)fprintf(f, "pkcs5_pbkdf2/sha1 {\n");
		print_kvpair_int(f, ts, "iterations", kg->kg_iterations);
		print_kvpair_b64(f, 0, ts, "salt", kg->kg_salt);
		print_shared(f, ts, kg);
		(void)fprintf(f, "};\n");
		break;
	default:
		warnx("keygen_fput: %d not a valid method", kg->kg_method);
		break;
	}
	return keygen_fput(kg->next, ts, f);
}

int
params_fput(struct params *p, FILE *f)
{
	int	ts = 0;		/* tabstop of 0 spaces */

	print_kvpair_string(f, ts, "algorithm", p->algorithm);
	print_kvpair_string(f, ts, "iv-method", p->ivmeth);
	print_kvpair_int(f, ts, "keylength", p->keylen);
	print_kvpair_int(f, ts, "blocksize", p->bsize);
	switch (p->verify_method) {
	case VERIFY_NONE:
		print_kvpair_cstr(f, ts, "verify_method", "none");
		break;
	case VERIFY_DISKLABEL:
		print_kvpair_cstr(f, ts, "verify_method", "disklabel");
		break;
	case VERIFY_FFS:
		print_kvpair_cstr(f, ts, "verify_method", "ffs");
		break;
	case VERIFY_REENTER:
		print_kvpair_cstr(f, ts, "verify_method", "re-enter");
		break;
	case VERIFY_MBR:
		print_kvpair_cstr(f, ts, "verify_method", "mbr");
		break;
	case VERIFY_GPT:
		print_kvpair_cstr(f, ts, "verify_method", "gpt");
		break;
#ifdef HAVE_ZFS
	case VERIFY_ZFS:
		print_kvpair_cstr(f, ts, "verify_method", "zfs");
		break;
#endif
	default:
		warnx("unsupported verify_method (%d)", p->verify_method);
		return -1;
	}
	return keygen_fput(p->keygen, TAB_COL, f);
}

int
params_cput(struct params *p, const char *fn)
{
	FILE	*f;

	if (fn && *fn) {
		if ((f = fopen(fn, "w")) == NULL) {
			warn("could not open outfile \"%s\"", fn);
			return -1;
		}
	} else {
		f = stdout;
	}
	return params_fput(p, f);
}

static int
crypt_defaults_lookup(const char *alg)
{
	unsigned	i;

	for (i=0; i < (sizeof(crypto_defaults) / sizeof(crypto_defaults[0])); i++)
		if (!strcmp(alg, crypto_defaults[i].alg))
			return i;

	return -1;
}
