/* SPDX-License-Identifier: BSD-3-Clause */
/*
 * swtpm_localca.c: A tool for creating TPM 1.2 and TPM 2 certificates localy or using pkcs11
 *
 * Author: Stefan Berger, stefanb@linux.ibm.com
 *
 * Copyright (c) IBM Corporation, 2021
 */

#include "config.h"

#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <pwd.h>
#include <regex.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/types.h>

#include <glib.h>

#include "swtpm_utils.h"
#include "swtpm_localca_conf.h"
#include "swtpm_localca_utils.h"

#define SETUP_TPM2_F    1
/* for TPM 2 EK */
#define ALLOW_SIGNING_F 2
#define DECRYPTION_F    4

/* Default logging goes to stderr */
gchar *gl_LOGFILE = NULL;

#define LOCALCA_OPTIONS "swtpm-localca.options"
#define LOCALCA_CONFIG  "swtpm-localca.conf"

#if defined __APPLE__
# define CERTTOOL_NAME "gnutls-certtool"
#else
# define CERTTOOL_NAME "certtool"
#endif

/* initialize the path of the options and config files */
static int init(gchar **options_file, gchar **config_file)
{
    const gchar *configdir = g_get_user_config_dir();

    *options_file = g_build_filename(configdir, LOCALCA_OPTIONS, NULL);
    if (access(*options_file, R_OK) != 0) {
        g_free(*options_file);
        *options_file = g_build_filename(SYSCONFDIR, LOCALCA_OPTIONS, NULL);
    }

    *config_file = g_build_filename(configdir, LOCALCA_CONFIG, NULL);
    if (access(*config_file, R_OK) != 0) {
        g_free(*config_file);
        *config_file = g_build_filename(SYSCONFDIR, LOCALCA_CONFIG, NULL);
    }

    return 0;
}

/* Run the certtool command line prepared in cmd. Display error message
 * in case of failure and also display the keyfile if something goes wrong.
 */
static int run_certtool(gchar **cmd, gchar **env, const char *msg, gchar *keyfile)
{
    g_autofree gchar *standard_error = NULL;
    gint exit_status;
    GError *error = NULL;
    gboolean success;

    success = g_spawn_sync(NULL, cmd, env, G_SPAWN_STDOUT_TO_DEV_NULL, NULL, NULL,
                           NULL, &standard_error, &exit_status, &error);
    if (!success || exit_status != 0) {
        logerr(gl_LOGFILE, "%s" , msg);
        if (keyfile)
            logerr(gl_LOGFILE, " %s:", keyfile);
        if (!success) {
            logerr(gl_LOGFILE, "%s\n", error->message);
            g_error_free(error);
        } else {
            logerr(gl_LOGFILE, "%s\n", standard_error);
        }
        return 1;
    }
    return 0;
}

/* Create a root CA key and cert and a local CA key and cert. The latter will be
 * used for signing the TPM certs.
 */
static int create_localca_cert(const gchar *lockfile, const gchar *statedir,
                               const gchar *signkey, const gchar *signkey_password,
                               const gchar *issuercert)
{
    int lockfd;
    int ret = 1;
    struct stat statbuf;
    int template1_file_fd = -1;
    int template2_file_fd = -1;
    g_autofree gchar *template1_file = NULL;
    g_autofree gchar *template2_file = NULL;
    gchar **certtool_env = NULL;

    lockfd = lock_file(lockfile);
    if (lockfd < 0)
        return 1;

    if (stat(statedir, &statbuf) != 0) {
        if (makedir(statedir, "statedir") != 0)
            goto error;
    }

    if (access(signkey, R_OK) != 0 || access(issuercert, R_OK) != 0) {
        g_autofree gchar *directory = g_path_get_dirname(signkey);
        g_autofree gchar *cakey = g_strjoin(G_DIR_SEPARATOR_S, directory, "swtpm-localca-rootca-privkey.pem", NULL);
        g_autofree gchar *cacert = g_strjoin(G_DIR_SEPARATOR_S, directory, "swtpm-localca-rootca-cert.pem", NULL);
        const gchar *swtpm_rootca_password = g_getenv("SWTPM_ROOTCA_PASSWORD");
        g_autofree gchar *certtool = g_find_program_in_path(CERTTOOL_NAME);
        g_autofree gchar **cmd = NULL;
        g_autofree gchar *fc = NULL;
        const char *filecontent;

        if (certtool == NULL) {
            logerr(gl_LOGFILE, "Could not find %s in PATH.\n", CERTTOOL_NAME);
            goto error;
        }

        /* generate the root-CA's private key */
        cmd = concat_arrays(cmd, (gchar*[]){
                                (gchar *)certtool, "--generate-privkey", "--outfile", cakey, NULL
                            }, TRUE);
        if (swtpm_rootca_password != NULL)
            cmd = concat_arrays(cmd, (gchar*[]){
                                   "--password", (gchar *)swtpm_rootca_password, NULL
                                }, TRUE);
        if (run_certtool(cmd, certtool_env, "Could not create root-CA key", cakey))
            goto error;

        if (chmod(cakey, S_IRUSR | S_IWUSR | S_IRGRP) != 0) {
            logerr(gl_LOGFILE, "Could not chmod %s: %s\n", cakey, strerror(errno));
            goto error;
        }

        certtool_env = g_environ_setenv(NULL, "PATH", g_getenv("PATH"), TRUE);

        /* create the root-CA's cert */
        filecontent = "cn=swtpm-localca-rootca\n"
                      "ca\n"
                      "cert_signing_key\n"
                      "expiration_days = -1\n";
        template1_file_fd = write_to_tempfile(&template1_file,
                                              (const unsigned char *)filecontent, strlen(filecontent));
        if (template1_file_fd < 0)
            goto error;

        g_free(cmd);
        cmd = concat_arrays(NULL,
                            (gchar *[]) {
                                certtool,
                                "--generate-self-signed",
                                "--template", template1_file,
                                "--outfile", cacert,
                                "--load-privkey", cakey,
                                NULL
                            }, FALSE);
        if (swtpm_rootca_password != NULL)
            certtool_env = g_environ_setenv(certtool_env, "GNUTLS_PIN", swtpm_rootca_password, TRUE);

        if (run_certtool(cmd, certtool_env, "Could not create root-CA:", NULL))
            goto error;

        g_free(cmd);

        /* create the intermediate CA's key */
        cmd = concat_arrays(NULL,
                            (gchar *[]) {
                                certtool, "--generate-privkey", "--outfile", (gchar *)signkey, NULL
                            }, FALSE);
        if (signkey_password != NULL)
            cmd = concat_arrays(cmd, (gchar *[]){
                                    "--password", (gchar *)signkey_password, NULL},
                                TRUE);
        if (run_certtool(cmd, certtool_env, "Could not create local-CA key", cakey))
            goto error;

        if (chmod(signkey, S_IRUSR | S_IWUSR | S_IRGRP) != 0) {
            logerr(gl_LOGFILE, "Could not chmod %s: %s\n", signkey, strerror(errno));
            goto error;
        }

        filecontent = "cn=swtpm-localca\n"
                      "ca\n"
                      "cert_signing_key\n"
                      "expiration_days = -1\n";
        if (swtpm_rootca_password != NULL && signkey_password != NULL)
            fc = g_strdup_printf("%spassword = %s\n", filecontent, swtpm_rootca_password);
        else
            fc = g_strdup(filecontent);

        template2_file_fd = write_to_tempfile(&template2_file,
                                              (const unsigned char *)fc, strlen(fc));
        if (template2_file_fd < 0)
            goto error;

        g_free(cmd);
        cmd = concat_arrays(NULL,
                            (gchar *[]) {
                                certtool,
                                "--generate-certificate",
                                "--template", template2_file,
                                "--outfile", (gchar *)issuercert,
                                "--load-privkey", (gchar *)signkey,
                                "--load-ca-privkey", cakey,
                                "--load-ca-certificate", cacert,
                                NULL
                            }, FALSE);
        if (signkey_password != NULL)
            certtool_env = g_environ_setenv(certtool_env, "GNUTLS_PIN", signkey_password, TRUE);
        else if (swtpm_rootca_password != NULL)
            certtool_env = g_environ_setenv(certtool_env, "GNUTLS_PIN", swtpm_rootca_password, TRUE);

        if (run_certtool(cmd, certtool_env, "Could not create local-CA:", NULL))
            goto error;
    }

    ret = 0;

error:
    if (template1_file_fd >= 0)
        close(template1_file_fd);
    if (template1_file != NULL)
        unlink(template1_file);

    if (template2_file_fd >= 0)
        close(template2_file_fd);
    if (template2_file != NULL)
        unlink(template2_file);
    g_strfreev(certtool_env);

    unlock_file(lockfd);

    return ret;
}

/* Extract the ECC parameters from a string like x=12,y=34,id=secp384r1.
 * This function returns 1  on error, 2 if the ECC parameters could be extracted
 * and 0 if no parameters could be extracted (likely a modulus).
 */
static gboolean extract_ecc_params(const gchar *ekparams, gchar **ecc_x, gchar **ecc_y, gchar **ecc_curveid)
{
    regmatch_t pmatch[5];
    regex_t preg;
    int ret;

    if (regcomp(&preg, "x=([0-9A-Fa-f]+),y=([0-9A-Fa-f]+)(,id=([^,]+))?",
                REG_EXTENDED) != 0) {
        logerr(gl_LOGFILE, "Internal error: Could not compile regex\n");
        return 1;
    }

    ret = 0;
    if (regexec(&preg, ekparams, 5, pmatch, 0) == 0) {
        *ecc_x = g_strndup(&ekparams[pmatch[1].rm_so],
                           pmatch[1].rm_eo - pmatch[1].rm_so);
        *ecc_y = g_strndup(&ekparams[pmatch[2].rm_so],
                           pmatch[2].rm_eo - pmatch[2].rm_so);
        if (pmatch[4].rm_so > 0 && pmatch[4].rm_eo > 0)
            *ecc_curveid = g_strndup(&ekparams[pmatch[4].rm_so],
                                     pmatch[4].rm_eo - pmatch[4].rm_so);
        ret = 2;
    }

    regfree(&preg);

    return ret;
}

/* Get the next serial number from the certserial file; if it contains
 * a non-numeric content start over with serial number '1'.
 */
static int get_next_serial(const gchar *certserial, const gchar *lockfile,
                           gchar **serial_str)
{
    g_autofree gchar *buffer = NULL;
    size_t buffer_len;
    unsigned long long serial, serial_n;
    char *endptr = NULL;
    int lockfd;
    int ret = 1;

    lockfd = lock_file(lockfile);
    if (lockfd < 0)
        return 1;

    if (access(certserial, R_OK) != 0)
        write_file(certserial, (unsigned char *)"1", 1);
    if (read_file(certserial, &buffer, &buffer_len) != 0)
        goto error;

    if (buffer_len > 0) {
        serial = strtoull(buffer, &endptr, 10);
        if (*endptr == '\0') {
            serial_n = serial + 1;
        } else {
            serial_n = 1;
        }
    } else {
        serial_n = 1;
    }
    *serial_str = g_strdup_printf("%llu", serial_n);
    write_file(certserial, (unsigned char *)*serial_str, strlen(*serial_str));
    ret = 0;

error:
    unlock_file(lockfd);

    return ret;
}

/* Create a TPM 1.2 or TPM 2 EK or platform cert */
static int create_cert(unsigned long flags, const gchar *typ, const gchar *directory,
                       gchar *ekparams, const gchar *vmid, gchar **tpm_spec_params,
                       gchar **tpm_attr_params, const gchar *signkey,
                       const gchar *signkey_password, const gchar *issuercert,
                       const gchar *parentkey_password, gchar **swtpm_cert_env,
                       const gchar *certserial, const gchar *lockfile,
                       const gchar *optsfile)
{
    gchar ** optsfile_lines = NULL;
    g_autofree gchar **options = NULL;
    g_autofree gchar **keyparams = NULL;
    g_autofree gchar **cmd = NULL;
    g_autofree gchar *subject = NULL;
    g_autofree gchar *ecc_x = NULL;
    g_autofree gchar *ecc_y = NULL;
    g_autofree gchar *ecc_curveid = NULL;
    g_autofree gchar *certfile = NULL;
    g_autofree gchar *serial_str = NULL;
    gchar **to_free = NULL;
    gchar **split;
    const char *certtype;
    int signkey_pwd_fd = -1;
    int parentkey_pwd_fd  = -1;
    g_autofree gchar *signkey_pwd_file = NULL;
    g_autofree gchar *signkey_pwd_file_param = NULL;
    g_autofree gchar *parentkey_pwd_file = NULL;
    g_autofree gchar *parentkey_pwd_file_param = NULL;
    gboolean success;
    g_autofree gchar *standard_output = NULL;
    g_autofree gchar *standard_error = NULL;
    g_autofree gchar *swtpm_cert_path = NULL;
    GError *error = NULL;
    gint exit_status;
    int ret = 1;
    size_t i, j;

    swtpm_cert_path = g_find_program_in_path("swtpm_cert");
    if (swtpm_cert_path == NULL) {
        logerr(gl_LOGFILE, "Could not find swtpm_cert in PATH.\n");
        return 1;
    }

    if (get_next_serial(certserial, lockfile, &serial_str) != 0)
        return 1;

    /* try to read the optsfile - failure to read is fine */
    read_file_lines(optsfile, &optsfile_lines);

    /* split each line from the optsfile and add the stripped parameters to options */
    for (i = 0; optsfile_lines != NULL && optsfile_lines[i] != NULL; i++) {
        gchar *chomped = g_strchomp(optsfile_lines[i]);
        if (strlen(chomped) == 0)
            continue;

        split = g_strsplit(chomped, " ", -1);
        for (j = 0; split[j] != NULL; j++) {
            chomped = g_strchomp(split[j]);
            if (strlen(chomped) > 0) {
                gchar *to_add = g_strdup(chomped);
                options = concat_arrays(options, (gchar *[]){to_add, NULL}, TRUE);
                /* need to collect this also to free later on */
                to_free = concat_arrays(to_free, (gchar *[]){to_add, NULL}, TRUE);
            }
        }
        g_strfreev(split);
    }

    if (vmid != NULL)
        subject = g_strdup_printf("CN=%s", vmid);
    else
        subject = g_strdup("CN=unknown");

    if (flags & SETUP_TPM2_F)
        options = concat_arrays(options, (gchar *[]){"--tpm2", NULL}, TRUE);
    else
        options = concat_arrays(options, (gchar *[]){"--add-header", NULL}, TRUE);

    if (strcmp(typ, "ek") == 0) {
        if (flags & ALLOW_SIGNING_F)
            options = concat_arrays(options, (gchar *[]){"--allow-signing", NULL}, TRUE);
        if (flags & DECRYPTION_F)
            options = concat_arrays(options, (gchar *[]){"--decryption", NULL}, TRUE);
    }

    switch (extract_ecc_params(ekparams, &ecc_x, &ecc_y, &ecc_curveid)) {
    case 1:
        goto error;
    case 2:
        keyparams = concat_arrays((gchar *[]){
                                      "--ecc-x", ecc_x,
                                      "--ecc-y", ecc_y,
                                      NULL
                                  },
                                  NULL, FALSE);
        if (ecc_curveid != NULL)
           keyparams = concat_arrays(keyparams,
                                     (gchar *[]){
                                         "--ecc-curveid", ecc_curveid,
                                         NULL
                                     }, TRUE);
        break;
    case 0:
        keyparams = concat_arrays((gchar *[]){
                                      "--modulus", ekparams,
                                      NULL},
                                   NULL, FALSE);
        break;
    }

    cmd = concat_arrays((gchar *[]){
                            swtpm_cert_path, "--subject", subject, NULL
                        }, options, FALSE);

    if (signkey_password != NULL) {
        signkey_pwd_fd = write_to_tempfile(&signkey_pwd_file,
                                           (unsigned char *)signkey_password, strlen(signkey_password));
        if (signkey_pwd_fd < 0)
            goto error;

        signkey_pwd_file_param = g_strdup_printf("file:%s", signkey_pwd_file);
        cmd = concat_arrays(cmd, (gchar*[]){"--signkey-pwd", signkey_pwd_file_param, NULL}, TRUE);
    }
    if (parentkey_password != NULL) {
        parentkey_pwd_fd = write_to_tempfile(&parentkey_pwd_file,
                                             (unsigned char *)parentkey_password, strlen(parentkey_password));
        if (parentkey_pwd_fd < 0)
            goto error;

        parentkey_pwd_file_param = g_strdup_printf("file:%s", parentkey_pwd_file);
        cmd = concat_arrays(cmd, (gchar*[]){"--parentkey-pwd", parentkey_pwd_file_param, NULL}, TRUE);
    }

    if (strcmp(typ, "ek") == 0)
        cmd = concat_arrays(cmd, tpm_spec_params, TRUE);

    cmd = concat_arrays(cmd, tpm_attr_params, TRUE);

    if (strcmp(typ, "platform") == 0) {
        certfile = g_strjoin(G_DIR_SEPARATOR_S, directory, "platform.cert", NULL);
        cmd = concat_arrays(cmd,
                            (gchar *[]){
                                "--type", "platform",
                                "--out-cert", certfile,
                                NULL},
                            TRUE);
    } else {
        certfile = g_strjoin(G_DIR_SEPARATOR_S, directory, "ek.cert", NULL);
        cmd = concat_arrays(cmd,
                            (gchar *[]){
                                "--out-cert", certfile,
                                NULL
                            }, TRUE);
    }

    cmd = concat_arrays(cmd, keyparams, TRUE);
    cmd = concat_arrays(cmd, (gchar *[]){
                            "--signkey", (gchar *)signkey,
                            "--issuercert", (gchar *)issuercert,
                            "--days", "-1",
                            "--serial", (gchar *)serial_str,
                            NULL
                        }, TRUE);

    if (strcmp(typ, "ek") == 0)
        certtype = "EK";
    else
        certtype = "platform";
#if 0
    {
        g_autofree gchar *join = g_strjoinv(" ", cmd);
        fprintf(stderr, "Starting: %s\n", join);
    }
#endif
    success = g_spawn_sync(NULL, cmd, swtpm_cert_env, G_SPAWN_DEFAULT, NULL, NULL,
                           &standard_output, &standard_error, &exit_status, &error);
    if (!success) {
        logerr(gl_LOGFILE, "Could not run swtpm_cert: %s\n", error);
        g_error_free(error);
        goto error;
    }
    if (exit_status != 0) {
        logerr(gl_LOGFILE, "Could not create %s certificate locally\n", certtype);
        logerr(gl_LOGFILE, "%s\n", standard_error);
        goto error;
    }

    logit(gl_LOGFILE, "Successfully created %s certificate locally.\n", certtype);
    ret = 0;

error:
    g_strfreev(optsfile_lines);
    g_strfreev(to_free);

    if (signkey_pwd_fd >= 0)
       close(signkey_pwd_fd);
    if (signkey_pwd_file)
       unlink(signkey_pwd_file);

    if (parentkey_pwd_fd >= 0)
       close(parentkey_pwd_fd);
    if (parentkey_pwd_file)
       unlink(parentkey_pwd_file);

    return ret;
}

static void usage(const char *prgname)
{
   printf(
        "Usage: %s [options]\n"
        "\n"
        "The following options are supported:\n"
        "\n"
        "--type type           The type of certificate to create: 'ek' or 'platform'\n"
        "--ek key-param        The modulus of an RSA key or x=...,y=,... for an EC key\n"
        "--dir directory       The directory to write the resulting certificate into\n"
        "--vmid vmid           The ID of the virtual machine\n"
        "--optsfile file       A file containing options to pass to swtpm_cert\n"
        "--configfile file     A file containing configuration parameters for directory,\n"
        "                      signing key and password and certificate to use\n"
        "--logfile file        A file to write a log into\n"
        "--tpm-spec-family s   The implemented spec family, e.g., '2.0'\n"
        "--tpm-spec-revision i The spec revision of the TPM as integer; e.g., 146\n"
        "--tpm-spec-level i    The spec level of the TPM; must be an integer; e.g. 0\n"
        "--tpm-manufacturer s  The manufacturer of the TPM; e.g., id:00001014\n"
        "--tpm-model s         The model of the TPM; e.g., 'swtpm'\n"
        "--tpm-version i       The (firmware) version of the TPM; e.g., id:20160511\n"
        "--tpm2                Generate a certificate for a TPM 2\n"
        "--allow-signing       The TPM 2's EK can be used for signing\n"
        "--decryption          The TPM 2's EK can be used for decryption\n"
        "--help, -h            Display this help screen and exit\n"
        "\n"
        "\n"
        "The following environment variables are supported:\n"
        "\n"
        "SWTPM_ROOTCA_PASSWORD  The root CA's private key password\n"
        "\n", prgname);
}

int main(int argc, char *argv[])
{
    int opt, option_index = 0;
    static const struct option long_options[] = {
        {"type", required_argument, NULL, 't'},
        {"ek", required_argument, NULL, 'e'},
        {"dir", required_argument, NULL, 'd'},
        {"vmid", required_argument, NULL, 'v'},
        {"optsfile", required_argument, NULL, 'o'},
        {"configfile", required_argument, NULL, 'c'},
        {"logfile", required_argument, NULL, 'l'},
        {"tpm-spec-family", required_argument, NULL, 'f'},
        {"tpm-spec-revision", required_argument, NULL, 'r'},
        {"tpm-spec-level", required_argument, NULL, '1'},
        {"tpm-manufacturer", required_argument, NULL, 'a'},
        {"tpm-model", required_argument, NULL, 'm'},
        {"tpm-version", required_argument, NULL, 's'},
        {"tpm2", no_argument, NULL, '2'},
        {"allow-signing", no_argument, NULL, 'i'},
        {"decryption", no_argument, NULL, 'y'},
        {"help", no_argument, NULL, 'h'},
        {NULL, 0, NULL, 0},
    };
    g_autofree gchar *default_options_file = NULL;
    g_autofree gchar *default_config_file = NULL;
    g_autofree gchar *optsfile = NULL;
    g_autofree gchar *configfile = NULL;
    unsigned long flags = 0;
    g_autofree gchar *typ =g_strdup("");
    g_autofree gchar *ekparams = g_strdup("");
    g_autofree gchar *directory = g_strdup("."); /* default to current directory */
    g_autofree gchar *vmid = NULL;
    g_autofree gchar *lockfile = NULL;
    g_autofree gchar *statedir = NULL;
    g_autofree gchar *signkey = NULL;
    g_autofree gchar *signkey_password = NULL;
    g_autofree gchar *parentkey_password = NULL;
    g_autofree gchar *issuercert = NULL;
    g_autofree gchar *certserial = NULL;
    gchar **tpm_spec_params = NULL;
    gchar **tpm_attr_params = NULL;
    gchar **config_file_lines = NULL;
    gchar **swtpm_cert_env = NULL;
    const struct passwd *curr_user;
    struct stat statbuf;
    int ret = 1;

    if (init(&default_options_file, &default_config_file) < 0)
        goto error;
    optsfile = g_strdup(default_options_file);
    configfile = g_strdup(default_config_file);

    while ((opt = getopt_long(argc, argv, "h?",
                              long_options, &option_index)) != -1) {
        switch (opt) {
        case 't': /* --type */
            g_free(typ);
            typ = g_strdup(optarg);
            break;
        case 'e': /* --ek */
            g_free(ekparams);
            ekparams = g_strdup(optarg);
            break;
        case 'd': /* --dir */
            g_free(directory);
            directory = g_strdup(optarg);
            break;
        case 'v': /* --vmid */
            g_free(vmid);
            vmid = g_strdup(optarg);
            vmid_replacechars(vmid);
            break;
        case 'o': /* --optsfile */
            g_free(optsfile);
            optsfile = g_strdup(optarg);
            break;
        case 'c': /* --configfile */
            g_free(configfile);
            configfile = g_strdup(optarg);
            break;
        case 'l': /* --logfile */
            g_free(gl_LOGFILE);
            gl_LOGFILE = g_strdup(optarg);
            break;
        case 'f': /* --tpm-spec-family */
        case 'r': /* --tpm-spec-revision */
        case '1': /* --tpm-spec-level */
            tpm_spec_params = concat_arrays(tpm_spec_params,
                          (gchar *[]) {
                              g_strdup_printf("--%s", long_options[option_index].name), g_strdup(optarg), NULL
                          }, TRUE);
            break;
        case 'a': /* --tpm-manufacturer */
        case 'm': /* --tpm-model */
        case 's': /* --tpm-version */
            tpm_attr_params = concat_arrays(tpm_attr_params,
                          (gchar *[]) {
                              g_strdup_printf("--%s", long_options[option_index].name), g_strdup(optarg), NULL
                          }, TRUE);
            break;
        case '2': /* --tpm2 */
            flags |= SETUP_TPM2_F;
            break;
        case 'i': /* --allow-signing */
            flags |= ALLOW_SIGNING_F;
            break;
        case 'y': /* --decryption */
            flags |= DECRYPTION_F;
            break;
        case '?':
        case 'h': /* --help */
            usage(argv[0]);
            if (opt == 'h')
                ret = 0;
            goto out;
        default:
            fprintf(stderr, "Unknown option code %d\n", opt);
            usage(argv[0]);
            goto error;
        }
    }

    curr_user = getpwuid(getuid());

    if (gl_LOGFILE != NULL) {
        FILE *tmpfile;

        if (stat(gl_LOGFILE, &statbuf) == 0 &&
            (statbuf.st_mode & S_IFMT) == S_IFLNK) {
            fprintf(stderr, "Logfile must not be a symlink.\n");
            goto error;
        }
        tmpfile = fopen(gl_LOGFILE, "a"); // do not truncate
        if (tmpfile == NULL) {
            fprintf(stderr, "Cannot write to logfile %s.\n", gl_LOGFILE);
            goto error;
        }
        fclose(tmpfile);
    }

    if (access(optsfile, R_OK) != 0) {
        logerr(gl_LOGFILE, "Need read rights on options file %s for user %s.\n",
               optsfile, curr_user ? curr_user->pw_name : "<unknown>");
        goto error;
    }

    if (access(configfile, R_OK) != 0) {
        logerr(gl_LOGFILE, "Need read rights on config file %s for user %s.\n",
               configfile, curr_user ? curr_user->pw_name : "<unknown>");
        goto error;
    }

    if (read_file_lines(configfile, &config_file_lines) != 0)
        goto error;

    statedir = get_config_value(config_file_lines, "statedir", NULL);
    if (statedir == NULL) {
        logerr(gl_LOGFILE, "Missing 'statedir' config value in config file %s.\n", configfile);
        goto error;
    }
    if (makedir(statedir, "statedir") != 0)
        goto error;
    if (access(statedir, W_OK | R_OK) != 0) {
        logerr(gl_LOGFILE, "Need read/write rights on statedir %s for user %s.\n",
               statedir, curr_user ? curr_user->pw_name : "<unknown>");
        goto error;
    }

    lockfile = g_strjoin(G_DIR_SEPARATOR_S, statedir, ".lock.swtpm-localca", NULL);
    if (stat(lockfile, &statbuf) == 0 &&
        access(lockfile, W_OK | R_OK) != 0) {
        logerr(gl_LOGFILE, "Need read/write rights on %s for user %s.\n",
               lockfile, curr_user ? curr_user->pw_name : "<unknown>");
        goto error;
    }

    signkey = get_config_value(config_file_lines, "signingkey", NULL);
    if (signkey == NULL) {
        logerr(gl_LOGFILE, "Missing 'signingkey' config value in config file %s.\n",
               configfile);
        goto error;
    }

    if (!g_str_has_prefix(signkey, "tpmkey:file=") &&
        !g_str_has_prefix(signkey, "tpmkey:uuid=") &&
        !g_str_has_prefix(signkey, "pkcs11:")) {
        g_autofree gchar *d = g_path_get_dirname(signkey);
        if (makedir(d, "signkey") != 0)
            goto error;
    }

    signkey_password = get_config_value(config_file_lines, "signingkey_password", NULL);
    parentkey_password = get_config_value(config_file_lines, "parentkey_password", NULL);

    issuercert = get_config_value(config_file_lines, "issuercert", NULL);
    if (issuercert == NULL) {
        logerr(gl_LOGFILE, "Missing 'issuercert' config value in config file %s.\n", configfile);
        goto error;
    }
    {
       g_autofree gchar *d = g_path_get_dirname(issuercert);
       if (makedir(d, "issuercert") != 0)
           goto error;
    }

    swtpm_cert_env = g_get_environ();

    // TPM keys are GNUTLS URIs...
    if (g_str_has_prefix(signkey, "tpmkey:file=") || g_str_has_prefix(signkey, "tpmkey:uuid=")) {
        g_autofree gchar *tss_tcsd_hostname = NULL;
        g_autofree gchar *tss_tcsd_port = NULL;

        tss_tcsd_hostname = get_config_value(config_file_lines,
                                             "TSS_TCSD_HOSTNAME", "localhost");
        tss_tcsd_port = get_config_value(config_file_lines,
                                         "TSS_TCSD_PORT", "30003");
        swtpm_cert_env = g_environ_setenv(swtpm_cert_env,
                                          "TSS_TCSD_HOSTNAME", tss_tcsd_hostname, TRUE);
        swtpm_cert_env = g_environ_setenv(swtpm_cert_env,
                                          "TSS_TCSD_PORT", tss_tcsd_port, TRUE);

        logit(gl_LOGFILE, "CA uses a GnuTLS TPM key; using TSS_TCSD_HOSTNAME=%s " \
                          "TSS_TCSD_PORT=%s\n", tss_tcsd_hostname, tss_tcsd_port);
    } else if (g_str_has_prefix(signkey, "pkcs11:")) {
        gchar *tmp = str_replace(signkey, "\\;", ";"); /* historical reasons ... */
        g_free(signkey);
        signkey = tmp;

        if (signkey_password != NULL) {
            swtpm_cert_env = g_environ_setenv(swtpm_cert_env,
                                              "SWTPM_PKCS11_PIN", g_strdup(signkey_password), TRUE);
            logit(gl_LOGFILE, "CA uses a PKCS#11 key; using SWTPM_PKCS11_PIN\n");
        } else {
            g_autofree gchar *swtpm_pkcs11_pin = NULL;

            swtpm_pkcs11_pin = get_config_value(config_file_lines,
                                                "SWTPM_PKCS11_PIN", "swtpm-tpmca");
            swtpm_cert_env = g_environ_setenv(swtpm_cert_env,
                                              "SWTPM_PKCS11_PIN", swtpm_pkcs11_pin, TRUE);
            logit(gl_LOGFILE, "CA uses a PKCS#11 key; using SWTPM_PKCS11_PIN\n");
        }
        ret = get_config_envvars(config_file_lines, &swtpm_cert_env);
        if (ret != 0)
            goto error;
    } else {
        int create_certs = 0;

        /* create certificate if either the signing key or issuer cert are missing */
        if (access(signkey, R_OK) != 0) {
            if (stat(signkey, &statbuf) == 0) {
                logerr(gl_LOGFILE, "Need read rights on signing key %s for user %s.\n",
                       signkey, curr_user ? curr_user->pw_name : "<unknown>");
                goto error;
            }
            create_certs = 1;
        }

        if (access(issuercert, R_OK) != 0) {
            if (stat(issuercert, &statbuf) == 0) {
                logerr(gl_LOGFILE, "Need read rights on issuer certificate %s for user %s.\n",
                       issuercert, curr_user ? curr_user->pw_name : "<unknown>");
                goto error;
            }
            create_certs = 1;
        }

        if (create_certs) {
            logit(gl_LOGFILE, "Creating root CA and a local CA's signing key and issuer cert.\n");
            if (create_localca_cert(lockfile, statedir, signkey, signkey_password,
                                    issuercert) != 0) {
                logerr(gl_LOGFILE, "Error creating local CA's signing key and cert.\n");
                goto error;
            }

            if (access(signkey, R_OK) != 0) {
                logerr(gl_LOGFILE, "Need read rights on signing key %s for user %s.\n",
                       signkey, curr_user ? curr_user->pw_name : "<unknown>");
                goto error;
            }
        }
    }

    if (access(issuercert, R_OK) != 0) {
        logerr(gl_LOGFILE, "Need read rights on issuer certificate %s for user %s.\n",
               issuercert, curr_user ? curr_user->pw_name : "<unknown>");
        goto error;
    }

    {
        g_autofree gchar *d = NULL;
        g_autofree gchar *p = g_strjoin(G_DIR_SEPARATOR_S, statedir, "certserial", NULL);

        certserial = get_config_value(config_file_lines, "certserial", p);
        d = g_path_get_dirname(certserial);
        if (makedir(d, "certserial") != 0)
            goto error;
    }

    ret = create_cert(flags, typ, directory, ekparams, vmid, tpm_spec_params, tpm_attr_params,
                      signkey, signkey_password, issuercert, parentkey_password, swtpm_cert_env,
                      certserial, lockfile, optsfile);

out:
error:
    g_strfreev(config_file_lines);
    g_strfreev(swtpm_cert_env);
    g_strfreev(tpm_attr_params);
    g_strfreev(tpm_spec_params);

    return ret;
}
