root/sbin/cgdconfig/params.c
/* $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;
}