/* SPDX-License-Identifier: BSD-3-Clause */
/*
 * swtpm.c: Programming of a swtpm using communication via fd-passing
 *
 * Author: Stefan Berger, stefanb@linux.ibm.com
 *
 * Copyright (c) IBM Corporation, 2021
 */

#include "config.h"

#include <errno.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>

#include <glib.h>

#include <openssl/bn.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/rsa.h>
#include <openssl/sha.h>
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
# include <openssl/core_names.h>
# include <openssl/param_build.h>
#else
# include <openssl/rsa.h>
#endif

#include "swtpm.h"
#include "swtpm_utils.h"
#include "tpm_ioctl.h"
#include "sys_dependencies.h"

#define AS2BE(VAL) (((VAL) >> 8) & 0xff), ((VAL) & 0xff)
#define AS4BE(VAL) AS2BE((VAL) >> 16), AS2BE(VAL)
#define AS8BE(VAL) AS4BE((VAL) >> 32), AS4BE(VAL)

struct tpm_req_header {
    uint16_t tag;
    uint32_t size;
    uint32_t ordinal;
} __attribute__((packed));

struct tpm_resp_header {
    uint16_t tag;
    uint32_t size;
    uint32_t errcode;
} __attribute__((packed));

static int swtpm_start(struct swtpm *self)
{
    g_autofree gchar *tpmstate = g_strdup_printf("backend-uri=%s", self->state_path);
    g_autofree gchar *pidfile_arg = NULL;
    g_autofree gchar *server_fd = NULL;
    g_autofree gchar *ctrl_fd = NULL;
    g_autofree gchar *keyopts = NULL;
    g_autofree gchar *logop = NULL;
    g_autofree gchar **argv = NULL;
    struct stat statbuf;
    gboolean success;
    GError *error = NULL;
    unsigned ctr;
    int pidfile_fd;
    int ret = 1;
    char pidfile[] = "/tmp/.swtpm_setup.pidfile.XXXXXX";

    pidfile_fd = mkstemp(pidfile);
    if (pidfile_fd < 0) {
        logerr(self->logfile, "Could not create pidfile: %s\n", strerror(errno));
        goto error_no_pidfile;
    }
    // pass filename rather than fd (Cygwin)
    pidfile_arg = g_strdup_printf("file=%s", pidfile);

    argv = concat_arrays(self->swtpm_exec_l,
                         (gchar*[]){
                              "--flags", "not-need-init,startup-clear",
                              "--tpmstate", tpmstate,
                              "--pid", pidfile_arg,
#if 0
                              "--log", "file=/tmp/log,level=20",
#endif
                              NULL
                         }, FALSE);

    if (self->is_tpm2)
        argv = concat_arrays(argv, (gchar*[]){"--tpm2", NULL}, TRUE);

    if (self->keyopts != NULL) {
        keyopts = g_strdup(self->keyopts);
        argv = concat_arrays(argv, (gchar*[]){"--key", keyopts, NULL}, TRUE);
    }

    if (gl_LOGFILE != NULL) {
        logop = g_strdup_printf("file=%s", gl_LOGFILE);
        argv = concat_arrays(argv, (gchar*[]){"--log", logop, NULL}, TRUE);
    }

    if (socketpair(AF_UNIX, SOCK_STREAM, 0, self->ctrl_fds) != 0) {
        logerr(self->logfile, "Could not create socketpair: %s\n", strerror(errno));
        goto error;
    }
    ctrl_fd = g_strdup_printf("type=unixio,clientfd=%d", self->ctrl_fds[1]);

    if (socketpair(AF_UNIX, SOCK_STREAM, 0, self->data_fds) != 0) {
        logerr(self->logfile, "Could not create socketpair: %s\n", strerror(errno));
        goto error;
    }
    server_fd = g_strdup_printf("type=tcp,fd=%d", self->data_fds[1]);

    argv = concat_arrays(argv, (gchar*[]){
                             "--server", server_fd,
                             "--ctrl", ctrl_fd,
                             NULL
                         }, TRUE);

#if 0
    {
        g_autofree gchar *join = g_strjoinv(" ", argv);
        logit(self->logfile, "Starting swtpm: %s\n", join);
    }
#endif

    success = g_spawn_async(NULL, argv, NULL,
                            G_SPAWN_LEAVE_DESCRIPTORS_OPEN | G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL,
                            NULL, NULL, &self->pid, &error);
    if (!success) {
        logerr(self->logfile, "Could not start swtpm: %s\n", error->message);
        g_error_free(error);
        goto error;
    }

    /* wait until the pidfile is written to or swtpm terminates */
    for (ctr = 0; ctr < 1000; ctr++) {
        if (kill(self->pid, 0) < 0) {
            /* swtpm terminated */
            self->pid = 0;
            logerr(self->logfile, "swtpm process terminated unexpectedly.\n");
            self->cops->stop(self);
            goto error;
        }
        if (fstat(pidfile_fd, &statbuf) == 0 && statbuf.st_size > 0) {
            printf("TPM is listening on Unix socket.\n");
            ret = 0;
            break;
        }
        usleep(5000);
    }

error:
    close(pidfile_fd);
    unlink(pidfile);

error_no_pidfile:
    return ret;
}

/* Stop a running swtpm instance and close all the file descriptors connecting to it */
static void swtpm_stop(struct swtpm *self)
{
    unsigned c;
    gboolean ended = FALSE;

    if (self->pid > 0) {
        self->cops->ctrl_shutdown(self);
        for (c = 0; c < 500; c++) {
            if (kill(self->pid, 0) < 0) {
                ended = TRUE;
                break;
            }
            usleep(1000);
        }
        if (!ended)
            kill(self->pid, SIGKILL);
        waitpid(self->pid, NULL, 0);

        self->pid = 0;
    }

    if (self->ctrl_fds[0] >= 0) {
        close(self->ctrl_fds[0]);
        close(self->ctrl_fds[1]);
        self->ctrl_fds[0] = self->ctrl_fds[1] = -1;
    }

    if (self->data_fds[0] >= 0) {
        close(self->data_fds[0]);
        close(self->data_fds[1]);
        self->data_fds[0] = self->data_fds[1] = -1;
    }
}

/* Destroy a running swtpm instance */
static void swtpm_destroy(struct swtpm *self)
{
    self->cops->stop(self);
}

/* Send a command to swtpm and receive the response either via control or data channel */
static int transfer(struct swtpm *self, void *buffer, size_t buffer_len,
                    const char *cmdname, gboolean use_ctrl,
                    void *respbuffer, size_t *respbuffer_len)
{
    size_t offset;
    int sockfd;
    ssize_t n;
    unsigned char resp[4096];
    ssize_t resplen;
    uint32_t returncode;

    if (use_ctrl) {
        sockfd = self->ctrl_fds[0];
        offset = 0;
    } else {
        sockfd = self->data_fds[0];
        offset = 6;
    }

    n = write(sockfd, buffer, buffer_len);
    if (n < 0) {
        logerr(self->logfile, "Could not send %s buffer to swtpm: %s\n",
               cmdname, strerror(errno));
        return 1;
    }
    if ((size_t)n != buffer_len) {
        logerr(self->logfile, "Could not send all bytes to swtpm: %zu < %zu\n",
               (size_t)n, buffer_len);
        return 1;
    }

    resplen = read(sockfd, resp, sizeof(resp));
    if (resplen < 0) {
        logerr(self->logfile, "Could not receive response to %s from swtpm: %s\n",
               cmdname, strerror(errno));
        return 1;
    }

    if (!use_ctrl) {
        if ((size_t)resplen < sizeof(struct tpm_resp_header)) {
            logerr(self->logfile,
                   "Response for %s has only %d bytes.\n", cmdname, resplen);
            return 1;
        }
    } else if ((size_t)resplen < 4) {
        logerr(self->logfile,
               "Response for %s has only %d bytes.\n", cmdname, resplen);
        return 1;
    }

    memcpy(&returncode, &resp[offset], sizeof(returncode));
    returncode = be32toh(returncode);
    if (returncode != 0) {
        logerr(self->logfile,
               "%s failed: 0x%x\n", cmdname, returncode);
        return 1;
    }

    if (respbuffer) {
        *respbuffer_len = min((size_t)resplen, *respbuffer_len);
        memcpy(respbuffer, resp, *respbuffer_len);
    }

    return 0;
}

/* Send a CMD_SHUTDOWN over the control channel */
static int swtpm_ctrl_shutdown(struct swtpm *self)
{
    uint32_t cmd = htobe32(CMD_SHUTDOWN);

    return transfer(self, &cmd, sizeof(cmd), "CMD_SHUTDOWN", TRUE, NULL, 0);
}

/* Get the TPM specification parameters over the control channel */
static int swtpm_ctrl_get_tpm_specs_and_attrs(struct swtpm *self, gchar **result)
{
    unsigned char req[] = {AS4BE(CMD_GET_INFO),
                           AS8BE(SWTPM_INFO_TPMSPECIFICATION | SWTPM_INFO_TPMATTRIBUTES),
                           AS4BE(0), AS4BE(0)};
    unsigned char tpmresp[1024];
    size_t tpmresp_len = sizeof(tpmresp);
    int ret;
    uint32_t length;

    ret = transfer(self, req, sizeof(req), "CMD_GET_INFO", TRUE, tpmresp, &tpmresp_len);
    if (ret != 0)
        return 1;

    if (tpmresp_len < 8 + sizeof(length))
        goto err_too_short;
    memcpy(&length, &tpmresp[8], sizeof(length));
    length = htobe32(length);

    if (tpmresp_len < 12 + length)
        goto err_too_short;
    *result = g_strndup((gchar *)&tpmresp[12], length);

    return 0;

err_too_short:
    logerr(self->logfile, "Response from CMD_GET_INFO is too short!\n");

    return 1;
}

static const struct swtpm_cops swtpm_cops = {
    .start = swtpm_start,
    .stop = swtpm_stop,
    .destroy = swtpm_destroy,
    .ctrl_shutdown = swtpm_ctrl_shutdown,
    .ctrl_get_tpm_specs_and_attrs = swtpm_ctrl_get_tpm_specs_and_attrs,
};

/*
 * TPM 2 support
 */

#define TPM2_ST_NO_SESSIONS  0x8001
#define TPM2_ST_SESSIONS     0x8002

#define TPM2_CC_EVICTCONTROL   0x00000120
#define TPM2_CC_NV_DEFINESPACE 0x0000012a
#define TPM2_CC_PCR_ALLOCATE   0x0000012b
#define TPM2_CC_CREATEPRIMARY  0x00000131
#define TPM2_CC_NV_WRITE       0x00000137
#define TPM2_CC_NV_WRITELOCK   0x00000138
#define TPM2_CC_SHUTDOWN       0x00000145
#define TPM2_CC_GETCAPABILITY  0x0000017a

#define TPM2_SU_CLEAR        0x0000

#define TPM2_RH_OWNER        0x40000001
#define TPM2_RS_PW           0x40000009
#define TPM2_RH_ENDORSEMENT  0x4000000b
#define TPM2_RH_PLATFORM     0x4000000c

#define TPM2_ALG_RSA      0x0001
#define TPM2_ALG_SHA1     0x0004
#define TPM2_ALG_AES      0x0006
#define TPM2_ALG_SHA256   0x000b
#define TPM2_ALG_SHA384   0x000c
#define TPM2_ALG_SHA512   0x000d
#define TPM2_ALG_SHA3_256 0x0027
#define TPM2_ALG_SHA3_384 0x0028
#define TPM2_ALG_SHA3_512 0x0029
#define TPM2_ALG_NULL     0x0010
#define TPM2_ALG_SM3      0x0012
#define TPM2_ALG_ECC      0x0023
#define TPM2_ALG_CFB      0x0043

#define TPM2_CAP_PCRS     0x00000005

#define TPM2_ECC_NIST_P384 0x0004

#define TPMA_NV_PLATFORMCREATE 0x40000000
#define TPMA_NV_AUTHREAD       0x40000
#define TPMA_NV_NO_DA          0x2000000
#define TPMA_NV_PPWRITE        0x1
#define TPMA_NV_PPREAD         0x10000
#define TPMA_NV_OWNERREAD      0x20000
#define TPMA_NV_WRITEDEFINE    0x2000

// Use standard EK Cert NVRAM, EK and SRK handles per IWG spec.
// "TCG TPM v2.0 Provisioning Guide"; Version 1.0, Rev 1.0, March 15, 2017
// Table 2
#define TPM2_NV_INDEX_RSA2048_EKCERT         0x01c00002
#define TPM2_NV_INDEX_RSA2048_EKTEMPLATE     0x01c00004
#define TPM2_NV_INDEX_RSA3072_HI_EKCERT      0x01c0001c
#define TPM2_NV_INDEX_RSA3072_HI_EKTEMPLATE  0x01c0001d
// For ECC follow "TCG EK Credential Profile For TPM Family 2.0; Level 0"
// Specification Version 2.1; Revision 13; 10 December 2018
#define TPM2_NV_INDEX_PLATFORMCERT           0x01c08000

#define TPM2_NV_INDEX_ECC_SECP384R1_HI_EKCERT     0x01c00016
#define TPM2_NV_INDEX_ECC_SECP384R1_HI_EKTEMPLATE 0x01c00017

#define TPM2_EK_RSA_HANDLE           0x81010001
#define TPM2_EK_RSA3072_HANDLE       0x8101001c
#define TPM2_EK_ECC_SECP384R1_HANDLE 0x81010016
#define TPM2_SPK_HANDLE              0x81000001

#define TPM_REQ_HEADER_INITIALIZER(TAG, SIZE, ORD) \
    { \
        .tag = htobe16(TAG), \
        .size = htobe32(SIZE), \
        .ordinal = htobe32(ORD), \
    }

struct tpm2_authblock {
    uint32_t auth;
    uint16_t foo; // FIXME
    uint8_t continueSession;
    uint16_t bar; // FIMXE
} __attribute__((packed));

#define TPM2_AUTHBLOCK_INITIALIZER(AUTH, FOO, CS, BAR) \
    { \
        .auth = htobe32(AUTH), \
        .foo = htobe16(FOO), \
        .continueSession = CS, \
        .bar = htobe16(BAR), \
    }

static const unsigned char NONCE_EMPTY[2] = {AS2BE(0)};
static const unsigned char NONCE_RSA2048[2+0x100] = {AS2BE(0x100), 0, };
static const unsigned char NONCE_RSA3072[2+0x180] = {AS2BE(0x180), 0, };
static const unsigned char NONCE_ECC_384[2+0x30] = {AS2BE(0x30), 0, };

static const struct bank_to_name {
    uint16_t hashAlg;
    const char *name;
} banks_to_names[] = {
    {TPM2_ALG_SHA1, "sha1"},
    {TPM2_ALG_SHA256, "sha256"},
    {TPM2_ALG_SHA384, "sha384"},
    {TPM2_ALG_SHA512, "sha512"},
    {TPM2_ALG_SM3, "sm3-256"},
    {TPM2_ALG_SHA3_256, "sha3-256"},
    {TPM2_ALG_SHA3_384, "sha3-384"},
    {TPM2_ALG_SHA3_512, "sha3-512"},
    {0, NULL},
};

/* function prototypes */
static int swtpm_tpm2_createprimary_rsa(struct swtpm *self, uint32_t primaryhandle, unsigned int keyflags,
                                        const unsigned char *symkeydata, size_t symkeydata_len,
                                        const unsigned char *authpolicy, size_t authpolicy_len,
                                        unsigned int rsa_keysize, gboolean havenonce, size_t off,
                                        uint32_t *curr_handle,
                                        unsigned char *ektemplate, size_t *ektemplate_len,
                                        gchar **ekparam, const gchar **key_description);

static int swtpm_tpm2_write_nvram(struct swtpm *self, uint32_t nvindex, uint32_t nvindexattrs,
                                  const unsigned char *data, size_t data_len, gboolean lock_nvram,
                                  const char *purpose);

/* Given a hash algo identifier, return the name of the hash bank */
static const char *get_name_for_bank(uint16_t hashAlg) {
    size_t i;

    for (i = 0; banks_to_names[i].name; i++) {
        if (banks_to_names[i].hashAlg == hashAlg)
            return banks_to_names[i].name;
    }
    return NULL;
}

/* Give the name of a hash bank, return its algo identifer */
static uint16_t get_hashalg_by_bankname(const char *name) {
    size_t i;

    for (i = 0; banks_to_names[i].name; i++) {
        if (strcmp(banks_to_names[i].name, name) == 0)
            return banks_to_names[i].hashAlg;
    }
    return 0;
}

/* Do an SU_CLEAR shutdown of the TPM 2 */
static int swtpm_tpm2_shutdown(struct swtpm *self)
{
    struct tpm2_shutdown_req {
        struct tpm_req_header hdr;
        uint16_t shutdownType;
    } __attribute__((packed)) req = {
        .hdr = TPM_REQ_HEADER_INITIALIZER(TPM2_ST_NO_SESSIONS, sizeof(req), TPM2_CC_SHUTDOWN),
        .shutdownType = htobe16(TPM2_SU_CLEAR)
    };

    return transfer(self, &req, sizeof(req), "TPM2_Shutdown", FALSE, NULL, NULL);
}

/* Get all available PCR banks */
static int swtpm_tpm2_get_all_pcr_banks(struct swtpm *self, gchar ***all_pcr_banks)
{
    struct tpm_req_header hdr = TPM_REQ_HEADER_INITIALIZER(TPM2_ST_NO_SESSIONS, 0, TPM2_CC_GETCAPABILITY);
    g_autofree unsigned char *req = NULL;
    ssize_t req_len;
    unsigned char tpmresp[256];
    size_t tpmresp_len = sizeof(tpmresp);
    uint16_t count, bank;
    const char *name;
    uint8_t length;
    size_t offset;
    size_t i;
    int ret;

    req_len = memconcat(&req,
                        &hdr, sizeof(hdr),
                        (unsigned char[]){AS4BE(TPM2_CAP_PCRS), AS4BE(0), AS4BE(64)}, (size_t)12,
                        NULL);
    if (req_len < 0) {
        logerr(self->logfile, "Internal error in %s: memconcat failed\n", __func__);
        return 1;
    }
    ((struct tpm_req_header *)req)->size = htobe32(req_len);

    ret = transfer(self, req, req_len, "TPM2_GetCapability", FALSE, tpmresp, &tpmresp_len);
    if (ret != 0)
        return 1;

    if (tpmresp_len < 17 + sizeof(count))
        goto err_too_short;
    memcpy(&count, &tpmresp[17], sizeof(count));
    count = be16toh(count);

    *all_pcr_banks = g_malloc0(sizeof(char *) * (count + 1));

    offset = 19;

    for (i = 0; i < count; i++) {
        gchar *n;

        if (tpmresp_len < offset + sizeof(bank))
            goto err_too_short;
        memcpy(&bank, &tpmresp[offset], sizeof(bank));
        bank = be16toh(bank);

        if (tpmresp_len < offset + 2 + sizeof(length))
            goto err_too_short;
        length = tpmresp[offset + 2];

        name = get_name_for_bank(bank);
        if (name != NULL)
            n = g_strdup(name);
        else
            n = g_strdup_printf("%02x", bank);

        (*all_pcr_banks)[i] = n;

        offset += 2 + 1 + length;
    }
    return 0;

err_too_short:
    logerr(self->logfile, "Response from TPM2_GetCapability is too short!\n");
    return 1;
}

/* Activate all user-chosen PCR banks and deactivate all others */
static int swtpm_tpm2_set_active_pcr_banks(struct swtpm *self, gchar **pcr_banks,
                                           gchar **all_pcr_banks, gchar ***active)
{
    struct tpm_req_header hdr = TPM_REQ_HEADER_INITIALIZER(TPM2_ST_SESSIONS, 0, TPM2_CC_PCR_ALLOCATE);
    struct tpm2_authblock authblock = TPM2_AUTHBLOCK_INITIALIZER(TPM2_RS_PW, 0, 0, 0);
    unsigned char pcrselects[6 * 10]; // supports up to 10 PCR banks
    ssize_t pcrselects_len = 0;
    size_t count = 0;
    size_t idx, j;
    uint16_t hashAlg;
    g_autofree unsigned char *req = NULL;
    ssize_t req_len, len;
    int ret;
    uint64_t activated_mask = 0;

    for (idx = 0; pcr_banks[idx] != NULL; idx++)
        ;
    *active = g_malloc0(sizeof(char *) * (idx + 1));

    for (idx = 0; pcr_banks[idx] != NULL; idx++) {
        hashAlg = 0;
        // Is user-chosen pcr_banks[idx] available?
        for (j = 0; all_pcr_banks[j] != NULL; j++) {
            if (strcmp(pcr_banks[idx], all_pcr_banks[j]) == 0) {
                hashAlg = get_hashalg_by_bankname(pcr_banks[idx]);
                break;
            }
        }
        if (hashAlg != 0 && (activated_mask & (1 << j)) == 0) {
            (*active)[count] = g_strdup(pcr_banks[idx]);
            len = concat(&pcrselects[pcrselects_len], sizeof(pcrselects) - pcrselects_len,
                         (unsigned char[]){AS2BE(hashAlg), 3, 0xff, 0xff, 0xff} , (size_t)6,
                         NULL);
            if (len < 0) {
                logerr(self->logfile, "Internal error in %s: pcrselects is too small\n", __func__);
                return 1;
            }
            pcrselects_len += len;
            count++;
            activated_mask |= (1 << j);
        }
    }

    if (count == 0) {
        logerr(self->logfile,
               "No PCR banks could be allocated. None of the selected algorithms are supported.\n");
        goto error;
    }

    // disable all the other ones not chosen by the user
    for (idx = 0; all_pcr_banks[idx] != NULL; idx++) {
        gboolean found = FALSE;

        for (j = 0; pcr_banks[j] != NULL; j++) {
            if (strcmp(pcr_banks[j], all_pcr_banks[idx]) == 0) {
                found = TRUE;
                break;
            }
        }
        if (found)
            continue;

        /* not found, so not chosen by user */
        hashAlg = get_hashalg_by_bankname(all_pcr_banks[idx]);

        len = concat(&pcrselects[pcrselects_len], sizeof(pcrselects) - pcrselects_len,
                     (unsigned char[]){AS2BE(hashAlg), 3, 0, 0, 0}, (size_t)6,
                     NULL);
        if (len < 0) {
            logerr(self->logfile, "Internal error in %s: pcrselects is too small\n", __func__);
            goto error;
        }
        pcrselects_len += len;
        count++;
    }

    req_len = memconcat(&req,
                        &hdr, sizeof(hdr),
                        (unsigned char[]){
                             AS4BE(TPM2_RH_PLATFORM), AS4BE(sizeof(authblock))
                        }, (size_t)8,
                        &authblock, sizeof(authblock),
                        (unsigned char[]){AS4BE(count)}, (size_t)4,
                        pcrselects, pcrselects_len,
                        NULL);
    if (req_len < 0) {
        logerr(self->logfile, "Internal error in %s: req is too small\n", __func__);
        goto error;
    }
    ((struct tpm_req_header *)req)->size = htobe32(req_len);

    ret = transfer(self, req, req_len, "TPM2_PCR_Allocate", FALSE, NULL, 0);
    if (ret != 0)
        goto error;

    return 0;

error:
    g_strfreev(*active);
    *active = NULL;

    return 1;
}

/* Make object at the curr_handler permanent with the perm_handle */
static int swtpm_tpm2_evictcontrol(struct swtpm *self, uint32_t curr_handle, uint32_t perm_handle)
{
    struct tpm2_evictcontrol_req {
        struct tpm_req_header hdr;
        uint32_t auth;
        uint32_t objectHandle;
        uint32_t authblockLen;
        struct tpm2_authblock authblock;
        uint32_t persistentHandle;
    } __attribute__((packed)) req = {
        .hdr = TPM_REQ_HEADER_INITIALIZER(TPM2_ST_SESSIONS, sizeof(req), TPM2_CC_EVICTCONTROL),
        .auth = htobe32(TPM2_RH_OWNER),
        .objectHandle = htobe32(curr_handle),
        .authblockLen = htobe32(sizeof(req.authblock)),
        .authblock = TPM2_AUTHBLOCK_INITIALIZER(TPM2_RS_PW, 0, 0, 0),
        .persistentHandle = htobe32(perm_handle),
    };

    return transfer(self, &req, sizeof(req), "TPM2_EvictControl", FALSE, NULL, 0);
}

/* Create an RSA EK */
static int swtpm_tpm2_createprimary_ek_rsa(struct swtpm *self, unsigned int rsa_keysize,
                                           gboolean allowsigning, gboolean decryption,
                                           uint32_t *curr_handle,
                                           unsigned char *ektemplate, size_t *ektemplate_len,
                                           gchar **ekparam, const gchar **key_description)
{
    unsigned char authpolicy[48];
    size_t authpolicy_len;
    unsigned char symkeydata[6];
    size_t symkeydata_len;
    unsigned int keyflags;
    unsigned int symkeylen;
    gboolean havenonce;
    size_t addlen, off;

    if (rsa_keysize == 2048) {
        authpolicy_len = 32;
        memcpy(authpolicy, ((unsigned char []){
            0x83, 0x71, 0x97, 0x67, 0x44, 0x84, 0xb3, 0xf8, 0x1a, 0x90, 0xcc, 0x8d,
            0x46, 0xa5, 0xd7, 0x24, 0xfd, 0x52, 0xd7, 0x6e, 0x06, 0x52, 0x0b, 0x64,
            0xf2, 0xa1, 0xda, 0x1b, 0x33, 0x14, 0x69, 0xaa
        }), authpolicy_len);
        keyflags = 0;
        symkeylen = 128;
        havenonce = TRUE;
        addlen = 0;
    } else if (rsa_keysize == 3072) {
        authpolicy_len = 48;
        memcpy(authpolicy, ((unsigned char []){
            0xB2, 0x6E, 0x7D, 0x28, 0xD1, 0x1A, 0x50, 0xBC, 0x53, 0xD8, 0x82, 0xBC,
            0xF5, 0xFD, 0x3A, 0x1A, 0x07, 0x41, 0x48, 0xBB, 0x35, 0xD3, 0xB4, 0xE4,
            0xCB, 0x1C, 0x0A, 0xD9, 0xBD, 0xE4, 0x19, 0xCA, 0xCB, 0x47, 0xBA, 0x09,
            0x69, 0x96, 0x46, 0x15, 0x0F, 0x9F, 0xC0, 0x00, 0xF3, 0xF8, 0x0E, 0x12
        }), authpolicy_len);
        keyflags = 0x40;
        symkeylen = 256;
        havenonce = FALSE;
        addlen = 16;
    } else {
        logerr(self->logfile, "Internal error in %s: unsupported RSA keysize %d.\n",
               __func__, rsa_keysize);
        return 1;
    }

    if (allowsigning && decryption) {
        // keyflags: fixedTPM, fixedParent, sensitiveDatOrigin,
        // adminWithPolicy, sign, decrypt
        keyflags |= 0x000600b2;
        // symmetric: TPM_ALG_NULL
        symkeydata_len = 2;
        memcpy(symkeydata, ((unsigned char[]) {AS2BE(TPM2_ALG_NULL)}), symkeydata_len);
        off = 72 + addlen;
    } else if (allowsigning) {
        // keyflags: fixedTPM, fixedParent, sensitiveDatOrigin,
        // adminWithPolicy, sign
        keyflags |= 0x000400b2;
        // symmetric: TPM_ALG_NULL
        symkeydata_len = 2;
        memcpy(symkeydata, ((unsigned char[]) {AS2BE(TPM2_ALG_NULL)}), symkeydata_len);
        off = 72 + addlen;
    } else {
        // keyflags: fixedTPM, fixedParent, sensitiveDatOrigin,
        // adminWithPolicy, restricted, decrypt
        keyflags |= 0x000300b2;
        // symmetric: TPM_ALG_AES, 128bit or 256bit, TPM_ALG_CFB
        symkeydata_len = 6;
        memcpy(symkeydata,
               ((unsigned char[]) {AS2BE(TPM2_ALG_AES), AS2BE(symkeylen), AS2BE(TPM2_ALG_CFB)}),
               symkeydata_len);
        off = 76 + addlen;
    }

    return swtpm_tpm2_createprimary_rsa(self, TPM2_RH_ENDORSEMENT, keyflags,
                                        symkeydata, symkeydata_len,
                                        authpolicy, authpolicy_len, rsa_keysize,
                                        havenonce, off, curr_handle,
                                        ektemplate, ektemplate_len, ekparam, key_description);
}

/* Create an RSA key with the given parameters */
static int swtpm_tpm2_createprimary_rsa(struct swtpm *self, uint32_t primaryhandle, unsigned int keyflags,
                                        const unsigned char *symkeydata, size_t symkeydata_len,
                                        const unsigned char *authpolicy, size_t authpolicy_len,
                                        unsigned int rsa_keysize, gboolean havenonce, size_t off,
                                        uint32_t *curr_handle,
                                        unsigned char *ektemplate, size_t *ektemplate_len,
                                        gchar **ekparam, const gchar **key_description)
{
    const unsigned char *nonce;
    size_t nonce_len;
    uint16_t hashalg;
    struct tpm_req_header hdr = TPM_REQ_HEADER_INITIALIZER(TPM2_ST_SESSIONS, 0, TPM2_CC_CREATEPRIMARY);
    struct tpm2_authblock authblock = TPM2_AUTHBLOCK_INITIALIZER(TPM2_RS_PW, 0, 0, 0);
    g_autofree unsigned char *public = NULL;
    ssize_t public_len;
    g_autofree unsigned char *createprimary = NULL;
    ssize_t createprimary_len;
    int ret;
    unsigned char tpmresp[2048];
    size_t tpmresp_len = sizeof(tpmresp);
    uint16_t modlen;

    if (rsa_keysize == 2048) {
        nonce = NONCE_RSA2048;
        nonce_len = sizeof(NONCE_RSA2048);
        hashalg = TPM2_ALG_SHA256;
        if (key_description)
            *key_description = "rsa2048";
    } else if (rsa_keysize == 3072) {
        if (!havenonce) {
           nonce = NONCE_EMPTY;
           nonce_len = sizeof(NONCE_EMPTY);
        } else {
           nonce = NONCE_RSA3072;
           nonce_len = sizeof(NONCE_RSA3072);
        }
        hashalg = TPM2_ALG_SHA384;
        if (key_description)
            *key_description = "rsa3072";
    } else {
        logerr(self->logfile, "Internal error in %s: unsupported RSA keysize %d.\n",
               __func__, rsa_keysize);
        return 1;
    }

    public_len =
        memconcat(&public,
                  (unsigned char[]) {
                      AS2BE(TPM2_ALG_RSA), AS2BE(hashalg),
                      AS4BE(keyflags), AS2BE(authpolicy_len)
                  }, (size_t)10,
                  authpolicy, authpolicy_len,
                  symkeydata, symkeydata_len,
                  (unsigned char[]) {
                      AS2BE(TPM2_ALG_NULL), AS2BE(rsa_keysize), AS4BE(0)
                  }, (size_t)8,
                  nonce, nonce_len,
                  NULL);
    if (public_len < 0) {
        logerr(self->logfile, "Internal error in %s: memconcat failed\n", __func__);
        return 1;
    }
    if (ektemplate) {
        if (*ektemplate_len < (size_t)public_len) {
            logerr(self->logfile, "Internal error in %s: Need %zu bytes for ektemplate (rsa) but got only %zu\n",
                   __func__, public_len, *ektemplate_len);
            return 1;
        }
        memcpy(ektemplate, public, public_len);
        *ektemplate_len = public_len;
    }

    createprimary_len =
        memconcat(&createprimary,
                  &hdr, sizeof(hdr),
                  (unsigned char[]) {AS4BE(primaryhandle), AS4BE(sizeof(authblock))}, (size_t)8,
                  &authblock, sizeof(authblock),
                  (unsigned char[]) {AS2BE(4), AS4BE(0), AS2BE(public_len)}, (size_t)8,
                  public, public_len,
                  (unsigned char[]) {AS4BE(0), AS2BE(0)}, (size_t)6,
                  NULL);
    if (createprimary_len < 0) {
        logerr(self->logfile, "Internal error in %s: memconcat failed\n", __func__);
        return 1;
    }
    ((struct tpm_req_header *)createprimary)->size = htobe32(createprimary_len);

    ret = transfer(self, createprimary, createprimary_len, "TPM2_CreatePrimary(RSA)", FALSE,
                   tpmresp, &tpmresp_len);
    if (ret != 0)
        return 1;

    if (curr_handle) {
        if (tpmresp_len < 10 + sizeof(*curr_handle))
            goto err_too_short;
        memcpy(curr_handle, &tpmresp[10], sizeof(*curr_handle));
        *curr_handle = be32toh(*curr_handle);
    }

    if (tpmresp_len < off + sizeof(modlen))
         goto err_too_short;
    memcpy(&modlen, &tpmresp[off], sizeof(modlen));
    modlen = be16toh(modlen);
    if (modlen != rsa_keysize >> 3) {
        logerr(self->logfile, "Internal error in %s: Getting modulus from wrong offset %zu\n",
               __func__, off);
        return 1;
    }
    if (ekparam) {
        if (tpmresp_len < off + 2 + modlen)
            goto err_too_short;
        *ekparam = print_as_hex(&tpmresp[off + 2], modlen);
    }

    return 0;

err_too_short:
    logerr(self->logfile, "Response from TPM2_CreatePrimary(RSA) is too short!\n");
    return 1;
}

/* Create an ECC key with the given parameters */
static int swtpm_tpm2_createprimary_ecc(struct swtpm *self, uint32_t primaryhandle, unsigned int keyflags,
                                        const unsigned char *symkeydata, size_t symkeydata_len,
                                        const unsigned char *authpolicy, size_t authpolicy_len,
                                        unsigned short curveid, unsigned short hashalg,
                                        const unsigned char *nonce, size_t nonce_len,
                                        size_t off, uint32_t *curr_handle,
                                        unsigned char *ektemplate, size_t *ektemplate_len,
                                        gchar **ekparam, const gchar **key_description)
{
    struct tpm_req_header hdr = TPM_REQ_HEADER_INITIALIZER(TPM2_ST_SESSIONS, 0, TPM2_CC_CREATEPRIMARY);
    struct tpm2_authblock authblock = TPM2_AUTHBLOCK_INITIALIZER(TPM2_RS_PW, 0, 0, 0);
    g_autofree unsigned char *public = NULL;
    ssize_t public_len;
    g_autofree unsigned char *createprimary = NULL;
    ssize_t createprimary_len;
    int ret;
    unsigned char tpmresp[2048];
    size_t tpmresp_len = sizeof(tpmresp);
    size_t off2;
    uint16_t exp_ksize, ksize1, ksize2;
    const char *cid;

    public_len =
        memconcat(&public,
                  (unsigned char[]){
                      AS2BE(TPM2_ALG_ECC), AS2BE(hashalg), AS4BE(keyflags), AS2BE(authpolicy_len)
                  }, (size_t)10,
                  authpolicy, authpolicy_len,
                  symkeydata, symkeydata_len,
                  (unsigned char[]) {AS2BE(TPM2_ALG_NULL), AS2BE(curveid), AS2BE(TPM2_ALG_NULL)}, (size_t)6,
                  nonce, nonce_len,
                  nonce, nonce_len,
                  NULL);
    if (public_len < 0) {
        logerr(self->logfile, "Internal error in %s: memconcat failed\n", __func__);
        return 1;
    }
    if (ektemplate) {
        if (*ektemplate_len < (size_t)public_len) {
            logerr(self->logfile, "Internal error: Need %zu bytes for ektemplate (ecc) but got only %zu\n",
                   public_len, ektemplate_len);
            return 1;
        }
        memcpy(ektemplate, public, public_len);
        *ektemplate_len = public_len;
    }

    createprimary_len =
        memconcat(&createprimary,
                  &hdr, sizeof(hdr),
                  (unsigned char[]) {AS4BE(primaryhandle), AS4BE(sizeof(authblock))}, (size_t)8,
                  &authblock, sizeof(authblock),
                  (unsigned char[]) {AS2BE(4), AS4BE(0), AS2BE(public_len)}, (size_t)8,
                  public, public_len,
                  (unsigned char[]) {AS4BE(0), AS2BE(0)}, (size_t)6,
                  NULL);
    if (createprimary_len < 0) {
        logerr(self->logfile, "Internal error in %s: memconcat failed\n", __func__);
        return 1;
    }
    ((struct tpm_req_header *)createprimary)->size = htobe32(createprimary_len);

    ret = transfer(self, createprimary, createprimary_len, "TPM2_CreatePrimary(ECC)", FALSE,
                   tpmresp, &tpmresp_len);
    if (ret != 0)
        return 1;
    if (curr_handle) {
        if (tpmresp_len < 10 + sizeof(*curr_handle))
            goto err_too_short;
        memcpy(curr_handle, &tpmresp[10], sizeof(*curr_handle));
        *curr_handle = be32toh(*curr_handle);
    }

    if (curveid == TPM2_ECC_NIST_P384) {
        exp_ksize = 48;
        cid = "secp384r1";
        if (key_description)
            *key_description = cid;
    } else {
        logerr(self->logfile, "Unknown curveid 0x%x\n", curveid);
        return 1;
    }

    if (tpmresp_len < off + sizeof(ksize1))
        goto err_too_short;
    memcpy(&ksize1, &tpmresp[off], sizeof(ksize1));
    ksize1 = be16toh(ksize1);
    off2 = off + 2 + ksize1;

    if (tpmresp_len < off2 + sizeof(ksize2))
        goto err_too_short;
    memcpy(&ksize2, &tpmresp[off2], sizeof(ksize2));
    ksize2 = be16toh(ksize2);

    if (ksize1 != exp_ksize || ksize2 != exp_ksize) {
        logerr(self->logfile, "ECC: Getting key parameters from wrong offset\n");
        return 1;
    }

    if (ekparam) {
        unsigned char *xparam = &tpmresp[off + 2];
        unsigned char *yparam = &tpmresp[off2 + 2];
        if (tpmresp_len < off + 2 + ksize1 || tpmresp_len < off2 + 2 + ksize2)
            goto err_too_short;
        g_autofree gchar *xparam_str = print_as_hex(xparam, ksize1);
        g_autofree gchar *yparam_str = print_as_hex(yparam, ksize2);

        *ekparam = g_strdup_printf("x=%s,y=%s,id=%s", xparam_str, yparam_str, cid);
    }

    return 0;

err_too_short:
    logerr(self->logfile, "Response from TPM2_CreatePrimary(ECC) is too short!\n");
    return 1;
}

static int swtpm_tpm2_createprimary_spk_ecc_nist_p384(struct swtpm *self,
                                                      uint32_t *curr_handle)
{
    unsigned int keyflags = 0x00030472;
    const unsigned char authpolicy[0];
    size_t authpolicy_len = sizeof(authpolicy);
    const unsigned char symkeydata[] = {AS2BE(TPM2_ALG_AES), AS2BE(256), AS2BE(TPM2_ALG_CFB)};
    size_t symkeydata_len = sizeof(symkeydata);
    size_t off = 42;

    return swtpm_tpm2_createprimary_ecc(self, TPM2_RH_OWNER, keyflags, symkeydata, symkeydata_len,
                                        authpolicy, authpolicy_len, TPM2_ECC_NIST_P384, TPM2_ALG_SHA384,
                                        NONCE_ECC_384, sizeof(NONCE_ECC_384), off, curr_handle,
                                        NULL, 0, NULL, NULL);
}

static int swtpm_tpm2_createprimary_spk_rsa(struct swtpm *self, unsigned int rsa_keysize,
                                            uint32_t *curr_handle)
{
    unsigned int keyflags = 0x00030472;
    const unsigned char authpolicy[0];
    size_t authpolicy_len = sizeof(authpolicy);
    unsigned short symkeylen = 0;
    unsigned char symkeydata[6];
    size_t symkeydata_len;
    size_t off = 44;

    if (rsa_keysize == 2048)
        symkeylen = 128;
    else if (rsa_keysize == 3072)
        symkeylen = 256;

    symkeydata_len = 6;
    memcpy(symkeydata,
           ((unsigned char[]) {AS2BE(TPM2_ALG_AES), AS2BE(symkeylen), AS2BE(TPM2_ALG_CFB)}),
           symkeydata_len);

    return swtpm_tpm2_createprimary_rsa(self, TPM2_RH_OWNER, keyflags,
                                        symkeydata, symkeydata_len,
                                        authpolicy, authpolicy_len, rsa_keysize, TRUE,
                                        off, curr_handle, NULL, 0, NULL, NULL);
}

/* Create either an ECC or RSA storage primary key */
static int swtpm_tpm2_create_spk(struct swtpm *self, gboolean isecc, unsigned int rsa_keysize)
{
    int ret;
    uint32_t curr_handle;

    if (isecc)
        ret = swtpm_tpm2_createprimary_spk_ecc_nist_p384(self, &curr_handle);
    else
        ret = swtpm_tpm2_createprimary_spk_rsa(self, rsa_keysize, &curr_handle);

    if (ret != 0)
        return 1;

    ret = swtpm_tpm2_evictcontrol(self, curr_handle, TPM2_SPK_HANDLE);
    if (ret == 0)
        logit(self->logfile,
              "Successfully created storage primary key with handle 0x%x.\n", TPM2_SPK_HANDLE);

    return ret;
}

/* Create an ECC EK key that may be allowed to sign and/or decrypt */
static int swtpm_tpm2_createprimary_ek_ecc_nist_p384(struct swtpm *self, gboolean allowsigning,
                                                     gboolean decryption, uint32_t *curr_handle,
                                                     unsigned char *ektemplate, size_t *ektemplate_len,
                                                     gchar **ekparam, const char **key_description)
{
    unsigned char authpolicy[48]= {
        0xB2, 0x6E, 0x7D, 0x28, 0xD1, 0x1A, 0x50, 0xBC, 0x53, 0xD8, 0x82, 0xBC,
        0xF5, 0xFD, 0x3A, 0x1A, 0x07, 0x41, 0x48, 0xBB, 0x35, 0xD3, 0xB4, 0xE4,
        0xCB, 0x1C, 0x0A, 0xD9, 0xBD, 0xE4, 0x19, 0xCA, 0xCB, 0x47, 0xBA, 0x09,
        0x69, 0x96, 0x46, 0x15, 0x0F, 0x9F, 0xC0, 0x00, 0xF3, 0xF8, 0x0E, 0x12
    };
    size_t authpolicy_len = 48;
    unsigned char symkeydata[6];
    size_t symkeydata_len;
    unsigned int keyflags;
    size_t off;
    int ret;

    if (allowsigning && decryption) {
        // keyflags: fixedTPM, fixedParent, sensitiveDatOrigin,
        // userWithAuth, adminWithPolicy, sign, decrypt
        keyflags = 0x000600f2;
        // symmetric: TPM_ALG_NULL
        symkeydata_len = 2;
        memcpy(symkeydata, ((unsigned char[]){AS2BE(TPM2_ALG_NULL)}), symkeydata_len);
        off = 86;
    } else if (allowsigning) {
        // keyflags: fixedTPM, fixedParent, sensitiveDatOrigin,
        // userWithAuth, adminWithPolicy, sign
        keyflags = 0x000400f2;
        // symmetric: TPM_ALG_NULL
        symkeydata_len = 2;
        memcpy(symkeydata, ((unsigned char[]){AS2BE(TPM2_ALG_NULL)}), symkeydata_len);
        off = 86;
    } else {
        // keyflags: fixedTPM, fixedParent, sensitiveDatOrigin,
        // userWithAuth, adminWithPolicy, restricted, decrypt
        keyflags = 0x000300f2;
        // symmetric: TPM_ALG_AES, 256bit, TPM_ALG_CFB
        symkeydata_len = 6;
        memcpy(symkeydata,
               ((unsigned char[]){ AS2BE(TPM2_ALG_AES), AS2BE(256), AS2BE(TPM2_ALG_CFB)}),
               symkeydata_len);
        off = 90;
    }

    ret = swtpm_tpm2_createprimary_ecc(self, TPM2_RH_ENDORSEMENT, keyflags, symkeydata, symkeydata_len,
                                       authpolicy, authpolicy_len, TPM2_ECC_NIST_P384, TPM2_ALG_SHA384,
                                       NONCE_EMPTY, sizeof(NONCE_EMPTY), off, curr_handle,
                                       ektemplate, ektemplate_len, ekparam, key_description);
    if (ret != 0)
       logerr(self->logfile, "%s failed\n", __func__);

    return ret;
}

/* Create an ECC or RSA EK */
static int swtpm_tpm2_create_ek(struct swtpm *self, gboolean isecc, unsigned int rsa_keysize,
                                gboolean allowsigning, gboolean decryption, gboolean lock_nvram,
                                gchar **ekparam, const  gchar **key_description)
{
    uint32_t tpm2_ek_handle, nvindex, curr_handle;
    const char *keytype;
    int ret;
    unsigned char ektemplate[512];
    size_t ektemplate_len = sizeof(ektemplate);

    if (isecc) {
        tpm2_ek_handle = TPM2_EK_ECC_SECP384R1_HANDLE;
        keytype = "ECC";
        nvindex = TPM2_NV_INDEX_ECC_SECP384R1_HI_EKTEMPLATE;
    } else {
        if (rsa_keysize == 2048) {
            tpm2_ek_handle = TPM2_EK_RSA_HANDLE;
            keytype = "RSA 2048";
            nvindex = TPM2_NV_INDEX_RSA2048_EKTEMPLATE;
        } else if (rsa_keysize == 3072) {
            tpm2_ek_handle = TPM2_EK_RSA3072_HANDLE;
            keytype = "RSA 3072";
            nvindex = TPM2_NV_INDEX_RSA3072_HI_EKTEMPLATE;
        } else {
            logerr(self->logfile, "Internal error: Unsupported RSA keysize %u.\n", rsa_keysize);
            return 1;
        }
    }
    if (isecc)
        ret = swtpm_tpm2_createprimary_ek_ecc_nist_p384(self, allowsigning, decryption, &curr_handle,
                                                        ektemplate, &ektemplate_len, ekparam,
                                                        key_description);
    else
        ret = swtpm_tpm2_createprimary_ek_rsa(self, rsa_keysize, allowsigning, decryption, &curr_handle,
                                              ektemplate, &ektemplate_len, ekparam, key_description);

    if (ret == 0)
        ret = swtpm_tpm2_evictcontrol(self, curr_handle, tpm2_ek_handle);
    if (ret != 0) {
        logerr(self->logfile, "create_ek failed: 0x%x\n", ret);
        return 1;
    }

    logit(self->logfile,
          "Successfully created %s EK with handle 0x%x.\n", keytype, tpm2_ek_handle);

    if (allowsigning) {
        uint32_t nvindexattrs = TPMA_NV_PLATFORMCREATE | \
                TPMA_NV_AUTHREAD | \
                TPMA_NV_OWNERREAD | \
                TPMA_NV_PPREAD | \
                TPMA_NV_PPWRITE | \
                TPMA_NV_NO_DA | \
                TPMA_NV_WRITEDEFINE;
        ret = swtpm_tpm2_write_nvram(self, nvindex, nvindexattrs, ektemplate, ektemplate_len,
                                     lock_nvram, "EK template");
        if (ret == 0)
            logit(self->logfile,
                  "Successfully created NVRAM area 0x%x for %s EK template.\n",
                  nvindex, keytype);
    }

    return ret;
}

static int swtpm_tpm2_nvdefinespace(struct swtpm *self, uint32_t nvindex, uint32_t nvindexattrs,
                                    uint16_t data_len)
{
    struct tpm_req_header hdr = TPM_REQ_HEADER_INITIALIZER(TPM2_ST_SESSIONS, 0, TPM2_CC_NV_DEFINESPACE);
    struct tpm2_authblock authblock = TPM2_AUTHBLOCK_INITIALIZER(TPM2_RS_PW, 0, 0, 0);
    g_autofree unsigned char *nvpublic = NULL;
    ssize_t nvpublic_len;
    g_autofree unsigned char *req = NULL;
    ssize_t req_len;

    nvpublic_len = memconcat(&nvpublic,
                             (unsigned char[]){
                                 AS4BE(nvindex), AS2BE(TPM2_ALG_SHA256), AS4BE(nvindexattrs),
                                 AS2BE(0), AS2BE(data_len)}, (size_t)14,
                             NULL);
    if (nvpublic_len < 0) {
        logerr(self->logfile, "Internal error in %s: memconcat failed\n", __func__);
        return 1;
    }

    req_len = memconcat(&req,
                        &hdr, sizeof(hdr),
                        (unsigned char[]){AS4BE(TPM2_RH_PLATFORM), AS4BE(sizeof(authblock))}, (size_t)8,
                        &authblock, sizeof(authblock),
                        (unsigned char[]){AS2BE(0), AS2BE(nvpublic_len)}, (size_t)4,
                        nvpublic, nvpublic_len,
                        NULL);
    if (req_len < 0) {
        logerr(self->logfile, "Internal error in %s: memconcat failed\n", __func__);
        return 1;
    }

    ((struct tpm_req_header *)req)->size = htobe32(req_len);

    return transfer(self, req, req_len, "TPM2_NV_DefineSpace", FALSE, NULL, 0);
}

/* Write the data into the given NVIndex */
static int swtpm_tpm2_nv_write(struct swtpm *self, uint32_t nvindex,
                               const unsigned char *data, size_t data_len)
{
    struct tpm_req_header hdr = TPM_REQ_HEADER_INITIALIZER(TPM2_ST_SESSIONS, 0, TPM2_CC_NV_WRITE);
    struct tpm2_authblock authblock = TPM2_AUTHBLOCK_INITIALIZER(TPM2_RS_PW, 0, 0, 0);
    g_autofree unsigned char *req = NULL;
    ssize_t req_len;
    size_t offset = 0, txlen;
    int ret;

    while (offset < data_len) {
        txlen = min(data_len - offset, 1024);

        g_free(req);
        req_len = memconcat(&req,
                            &hdr, sizeof(hdr),
                            (unsigned char[]){
                                AS4BE(TPM2_RH_PLATFORM), AS4BE(nvindex), AS4BE(sizeof(authblock))
                            }, (size_t)12,
                            &authblock, sizeof(authblock),
                            (unsigned char[]){AS2BE(txlen)}, (size_t)2,
                            &data[offset], txlen,
                            (unsigned char[]){AS2BE(offset)}, (size_t)2,
                            NULL);
        if (req_len < 0) {
            logerr(self->logfile, "Internal error in %s: memconcat failed\n", __func__);
            return 1;
        }
        ((struct tpm_req_header *)req)->size = htobe32(req_len);

        ret = transfer(self, req, req_len, "TPM2_NV_Write", FALSE, NULL, 0);
        if (ret != 0)
            return 1;

        offset += txlen;
    }
    return 0;
}

static int swtpm_tpm2_nv_writelock(struct swtpm *self, uint32_t nvindex)
{
    struct tpm_req_header hdr = TPM_REQ_HEADER_INITIALIZER(TPM2_ST_SESSIONS, 0, TPM2_CC_NV_WRITELOCK);
    struct tpm2_authblock authblock = TPM2_AUTHBLOCK_INITIALIZER(TPM2_RS_PW, 0, 0, 0);
    g_autofree unsigned char *req;
    ssize_t req_len;

    req_len = memconcat(&req,
                        &hdr, sizeof(hdr),
                        (unsigned char[]){
                           AS4BE(TPM2_RH_PLATFORM), AS4BE(nvindex), AS4BE(sizeof(authblock))
                        }, (size_t)12,
                        &authblock, sizeof(authblock),
                        NULL);
    if (req_len < 0) {
        logerr(self->logfile, "Internal error in %s: memconcat failed\n", __func__);
        return 1;
    }

    ((struct tpm_req_header *)req)->size = htobe32(req_len);

    return transfer(self, req, req_len, "TPM2_NV_WriteLock", FALSE, NULL, 0);
}

static int swtpm_tpm2_write_nvram(struct swtpm *self, uint32_t nvindex, uint32_t nvindexattrs,
                                  const unsigned char *data, size_t data_len, gboolean lock_nvram,
                                  const char *purpose)
{
    int ret = swtpm_tpm2_nvdefinespace(self, nvindex, nvindexattrs, data_len);
    if (ret != 0) {
        logerr(self->logfile, "Could not create NVRAM area 0x%x for %s.\n", nvindex, purpose);
        return 1;
    }

    ret = swtpm_tpm2_nv_write(self, nvindex, data, data_len);
    if (ret != 0) {
        logerr(self->logfile,
               "Could not write %s into NVRAM area 0x%x.\n", purpose, nvindex);
        return 1;
    }

    if (lock_nvram) {
        ret = swtpm_tpm2_nv_writelock(self, nvindex);
        if (ret != 0) {
            logerr(self->logfile, "Could not lock EK template NVRAM area 0x%x.\n", nvindex);
            return 1;
        }
    }

    return 0;
}

/* Write the platform certificate into an NVRAM area */
static int swtpm_tpm2_write_ek_cert_nvram(struct swtpm *self, gboolean isecc,
                                           unsigned int rsa_keysize, gboolean lock_nvram,
                                           const unsigned char *data, size_t data_len)
{
    uint32_t nvindex = 0;
    g_autofree gchar *keytype = NULL;
    uint32_t nvindexattrs = TPMA_NV_PLATFORMCREATE |
            TPMA_NV_AUTHREAD |
            TPMA_NV_OWNERREAD |
            TPMA_NV_PPREAD |
            TPMA_NV_PPWRITE |
            TPMA_NV_NO_DA |
            TPMA_NV_WRITEDEFINE;
    int ret;

    if (!isecc) {
        if (rsa_keysize == 2048)
            nvindex = TPM2_NV_INDEX_RSA2048_EKCERT;
        else if (rsa_keysize == 3072)
            nvindex = TPM2_NV_INDEX_RSA3072_HI_EKCERT;
        keytype = g_strdup_printf("RSA %d", rsa_keysize);
    } else {
        nvindex = TPM2_NV_INDEX_ECC_SECP384R1_HI_EKCERT;
        keytype = g_strdup("ECC");
    }

    ret = swtpm_tpm2_write_nvram(self, nvindex, nvindexattrs, data, data_len, lock_nvram,
                                 "EK Certificate");
    if (ret == 0)
        logit(self->logfile,
              "Successfully created NVRAM area 0x%x for %s EK certificate.\n",
              nvindex, keytype);
    else
        logerr(self->logfile,
               "Could not create NVRAM area 0x%x for %s EK certificate.\n",
               nvindex, keytype);
    return ret;
}

static int swtpm_tpm2_write_platform_cert_nvram(struct swtpm *self, gboolean lock_nvram,
                                                const unsigned char *data, size_t data_len)
{
    uint32_t nvindex = TPM2_NV_INDEX_PLATFORMCERT;
    uint32_t nvindexattrs = TPMA_NV_PLATFORMCREATE |
            TPMA_NV_AUTHREAD |
            TPMA_NV_OWNERREAD |
            TPMA_NV_PPREAD |
            TPMA_NV_PPWRITE |
            TPMA_NV_NO_DA |
            TPMA_NV_WRITEDEFINE;
    int ret;

    ret = swtpm_tpm2_write_nvram(self, nvindex, nvindexattrs, data, data_len, lock_nvram,
                                 "Platform Certificate");
    if (ret == 0)
        logit(self->logfile,
              "Successfully created NVRAM area 0x%x for platform certificate.\n", nvindex);
    else
        logerr(self->logfile,
               "Could not create NVRAM area 0x%x for platform certificate.\n", nvindex);

    return ret;
}

static const struct swtpm2_ops swtpm_tpm2_ops = {
    .shutdown = swtpm_tpm2_shutdown,
    .create_spk = swtpm_tpm2_create_spk,
    .create_ek = swtpm_tpm2_create_ek,
    .get_all_pcr_banks = swtpm_tpm2_get_all_pcr_banks,
    .set_active_pcr_banks = swtpm_tpm2_set_active_pcr_banks,
    .write_ek_cert_nvram = swtpm_tpm2_write_ek_cert_nvram,
    .write_platform_cert_nvram = swtpm_tpm2_write_platform_cert_nvram,
};

/*
 * TPM 1.2 support
 */
#define TPM_TAG_RQU_COMMAND       0x00c1
#define TPM_TAG_RQU_AUTH1_COMMAND 0x00c2

#define TPM_ORD_OIAP                     0x0000000A
#define TPM_ORD_TAKE_OWNERSHIP           0x0000000D
#define TPM_ORD_PHYSICAL_ENABLE          0x0000006F
#define TPM_ORD_PHYSICAL_SET_DEACTIVATED 0x00000072
#define TPM_ORD_NV_DEFINE_SPACE          0x000000CC
#define TPM_ORD_NV_WRITE_VALUE           0x000000CD
#define TSC_ORD_PHYSICAL_PRESENCE        0x4000000A

#define TPM_ST_CLEAR 0x0001

#define TPM_PHYSICAL_PRESENCE_CMD_ENABLE  0x0020
#define TPM_PHYSICAL_PRESENCE_PRESENT     0x0008

#define TPM_ALG_RSA 0x00000001

#define TPM_KEY_STORAGE 0x0011

#define TPM_AUTH_ALWAYS 0x01

#define TPM_PID_OWNER  0x0005

#define TPM_ES_RSAESOAEP_SHA1_MGF1 0x0003
#define TPM_SS_NONE 0x0001

#define TPM_TAG_PCR_INFO_LONG   0x0006
#define TPM_TAG_NV_ATTRIBUTES   0x0017
#define TPM_TAG_NV_DATA_PUBLIC  0x0018
#define TPM_TAG_KEY12           0x0028

#define TPM_LOC_ZERO   0x01
#define TPM_LOC_ALL    0x1f

#define TPM_NV_INDEX_D_BIT        0x10000000
#define TPM_NV_INDEX_EKCERT       0xF000
#define TPM_NV_INDEX_PLATFORMCERT 0xF002

#define TPM_NV_INDEX_LOCK 0xFFFFFFFF

#define TPM_NV_PER_OWNERREAD   0x00020000
#define TPM_NV_PER_OWNERWRITE  0x00000002

#define TPM_ET_OWNER 0x02
#define TPM_ET_NV    0x0b

#define TPM_KH_EK    0x40000006


static int swtpm_tpm12_tsc_physicalpresence(struct swtpm *self, uint16_t physicalpresence)
{
    struct tpm12_tsc_physicalpresence {
        struct tpm_req_header hdr;
        uint16_t pp;
    } req = {
        .hdr = TPM_REQ_HEADER_INITIALIZER(TPM_TAG_RQU_COMMAND, sizeof(req), TSC_ORD_PHYSICAL_PRESENCE),
        .pp = htobe16(physicalpresence),
    };

    return transfer(self, &req, sizeof(req), "TSC_PhysicalPresence", FALSE, NULL, NULL);
}

static int swtpm_tpm12_physical_enable(struct swtpm *self)
{
    struct tpm_req_header req = TPM_REQ_HEADER_INITIALIZER(TPM_TAG_RQU_COMMAND, sizeof(req), TPM_ORD_PHYSICAL_ENABLE);

    return transfer(self, &req, sizeof(req), "TPM_PhysicalEnable", FALSE, NULL, NULL);
}

static int swtpm_tpm12_physical_set_deactivated(struct swtpm *self, uint8_t state)
{
    struct tpm12_tsc_physical_set_deactivated {
        struct tpm_req_header hdr;
        uint8_t state;
    } req = {
        .hdr = TPM_REQ_HEADER_INITIALIZER(TPM_TAG_RQU_COMMAND, sizeof(req), TPM_ORD_PHYSICAL_SET_DEACTIVATED),
        .state = state,
    };

    return transfer(self, &req, sizeof(req), "TSC_PhysicalSetDeactivated", FALSE, NULL, NULL);
}

/* Initialize the TPM1.2 */
static int swtpm_tpm12_run_swtpm_bios(struct swtpm *self)
{
    if (swtpm_tpm12_tsc_physicalpresence(self, TPM_PHYSICAL_PRESENCE_CMD_ENABLE) ||
        swtpm_tpm12_tsc_physicalpresence(self, TPM_PHYSICAL_PRESENCE_PRESENT) ||
        swtpm_tpm12_physical_enable(self) ||
        swtpm_tpm12_physical_set_deactivated(self, 0))
        return 1;

    return 0;
}

static int swptm_tpm12_create_endorsement_keypair(struct swtpm *self,
                                                  gchar **pubek, size_t *pubek_len)
{
    unsigned char req[] = {
        0x00, 0xc1, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x78, 0x38, 0xf0, 0x30, 0x81, 0x07, 0x2b,
        0x0c, 0xa9, 0x10, 0x98, 0x08, 0xc0, 0x4B, 0x05, 0x11, 0xc9, 0x50, 0x23, 0x52, 0xc4, 0x00, 0x00,
        0x00, 0x01, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
        0x00, 0x02, 0x00, 0x00, 0x00, 0x00
    };
    unsigned char tpmresp[512];
    size_t tpmresp_len = sizeof(tpmresp);
    uint32_t length;
    int ret;

    ret = transfer(self, &req, sizeof(req), "TPM_CreateEndorsementKeyPair", FALSE, &tpmresp, &tpmresp_len);
    if (ret != 0)
        return 1;

    if (tpmresp_len < 34 + sizeof(length))
        goto err_too_short;
    memcpy(&length, &tpmresp[34], sizeof(length));
    length = be32toh(length);
    if (length != 256) {
        logerr(self->logfile, "Offset to EK Public key is wrong.\n");
        return 1;
    }

    *pubek_len = 256;
    if (tpmresp_len < 38 + *pubek_len)
        goto err_too_short;
    *pubek = g_malloc(256);
    memcpy(*pubek, &tpmresp[38], *pubek_len);

    return 0;

err_too_short:
    logerr(self->logfile, "Response from TPM_CreateEndorsementKeyPair is too short!\n");
    return 1;
}

/* Create an OIAP session */
static int swtpm_tpm12_oiap(struct swtpm *self, uint32_t *authhandle, unsigned char nonce_even[SHA_DIGEST_LENGTH])
{
    struct tpm_req_header req = TPM_REQ_HEADER_INITIALIZER(TPM_TAG_RQU_COMMAND, sizeof(req), TPM_ORD_OIAP);
    unsigned char tpmresp[64];
    size_t tpmresp_len = sizeof(tpmresp);
    int ret;

    ret = transfer(self, &req, sizeof(req), "TPM_OIAP", FALSE, &tpmresp, &tpmresp_len);
    if (ret != 0)
        return ret;

    if (tpmresp_len < 10 + sizeof(*authhandle) || tpmresp_len < 14 + SHA_DIGEST_LENGTH)
        goto err_too_short;
    memcpy(authhandle, &tpmresp[10], sizeof(*authhandle));
    *authhandle = be32toh(*authhandle);
    memcpy(nonce_even, &tpmresp[14], SHA_DIGEST_LENGTH);

    return 0;

err_too_short:
    logerr(self->logfile, "Response from TPM_OIAP is too short!\n");
    return 1;
}

static int swtpm_tpm12_take_ownership(struct swtpm *self, const unsigned char ownerpass_digest[SHA_DIGEST_LENGTH],
                                      const unsigned char srkpass_digest[SHA_DIGEST_LENGTH],
                                      const unsigned char *pubek, size_t pubek_len)
{
    struct tpm_req_header hdr = TPM_REQ_HEADER_INITIALIZER(TPM_TAG_RQU_AUTH1_COMMAND, 0, TPM_ORD_TAKE_OWNERSHIP);
    EVP_PKEY *pkey = NULL;
    EVP_PKEY_CTX *ctx = NULL;
    BIGNUM *exp = BN_new();
    BIGNUM *mod = NULL;
#if OPENSSL_VERSION_NUMBER < 0x30000000L
    RSA *rsakey = RSA_new();
#endif
    int ret = 1;
    const EVP_MD *sha1 = EVP_sha1();
    g_autofree unsigned char *enc_owner_auth = g_malloc(pubek_len);
    size_t enc_owner_auth_len = pubek_len;
    g_autofree unsigned char *enc_srk_auth = g_malloc(pubek_len);
    size_t enc_srk_auth_len = pubek_len;
    uint32_t auth_handle;
    unsigned char nonce_even[SHA_DIGEST_LENGTH];
    unsigned char nonce_odd[SHA_DIGEST_LENGTH] = {1, 2, 3, 4, 5, 6, };
    g_autofree unsigned char *tpm_rsa_key_parms = NULL;
    ssize_t tpm_rsa_key_parms_len;
    g_autofree unsigned char *tpm_key_parms = NULL;
    ssize_t tpm_key_parms_len;
    g_autofree unsigned char *tpm_key12 = NULL;
    ssize_t tpm_key12_len;
    g_autofree unsigned char *in_auth_setup_params = NULL;
    ssize_t in_auth_setup_params_len;
    g_autofree unsigned char *macinput = NULL;
    ssize_t macinput_len;
    unsigned char in_param_digest[SHA_DIGEST_LENGTH];
    unsigned char owner_auth[SHA_DIGEST_LENGTH];
    unsigned int owner_auth_len = sizeof(owner_auth);
    uint8_t continue_auth_session = 0;
    unsigned char req[1024];
    ssize_t req_len, len;
    struct tpm_req_header *trh;

    mod = BN_bin2bn((const unsigned char *)pubek, pubek_len, NULL);
    if (exp == NULL || mod == NULL ||
        BN_hex2bn(&exp, "10001") == 0) {
        logerr(self->logfile, "Could not create public RSA key!\n");
        goto error_free_bn;
    }

#if OPENSSL_VERSION_NUMBER >= 0x30000000L
    ctx = EVP_PKEY_CTX_new_from_name(NULL, "rsa", NULL);
    if (ctx != NULL) {
        OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new();
        OSSL_PARAM *params;

        if (bld == NULL ||
            OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_E, exp) != 1 ||
            OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_N, mod) != 1 ||
            (params = OSSL_PARAM_BLD_to_param(bld)) == NULL) {
            OSSL_PARAM_BLD_free(bld);
            goto error_free_bn;
        }
        OSSL_PARAM_BLD_free(bld);

        if (EVP_PKEY_fromdata_init(ctx) != 1 ||
            EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) != 1) {
            logerr(self->logfile, "Could not set pkey parameters!\n");
            OSSL_PARAM_free(params);
            goto error_free_bn;
        }
        OSSL_PARAM_free(params);

        EVP_PKEY_CTX_free(ctx);
    } else {
        logerr(self->logfile, "Could not create key creation context!\n");
        goto error_free_bn;
    }
    ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL);
    if (ctx == NULL)
        goto error_free_bn;
#else
    pkey = EVP_PKEY_new();
    if (pkey == NULL) {
        logerr(self->logfile, "Could not allocate pkey!\n");
        goto error_free_bn;
    }

# if OPENSSL_VERSION_NUMBER < 0x10100000
    rsakey->n = mod;
    rsakey->e = exp;
# else
    if (RSA_set0_key(rsakey, mod, exp, NULL) != 1) {
        logerr(self->logfile, "Could not create public RSA key!\n");
        goto error_free_bn;
    }
# endif
    if (EVP_PKEY_assign_RSA(pkey, rsakey) != 1) {
        logerr(self->logfile, "Could not create public RSA key!\n");
        goto error_free_pkey_and_rsa;
    }

    ctx = EVP_PKEY_CTX_new(pkey, NULL);
    if (ctx == NULL)
        goto error_free_pkey;
#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L */

    if (EVP_PKEY_encrypt_init(ctx) < 1 ||
        EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) < 1 ||
        EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, sha1) < 1 ||
        EVP_PKEY_CTX_set_rsa_oaep_md(ctx, sha1) < 1 ||
        EVP_PKEY_CTX_set0_rsa_oaep_label(ctx, strdup("TCPA"), 4) < 1 ||
        EVP_PKEY_encrypt(ctx, enc_owner_auth, &enc_owner_auth_len,
                         ownerpass_digest, SHA_DIGEST_LENGTH) < 1||
        EVP_PKEY_encrypt(ctx, enc_srk_auth, &enc_srk_auth_len,
                         srkpass_digest, SHA_DIGEST_LENGTH) < 1) {
        logerr(self->logfile, "Internal error in %s: encryption failed\n", __func__);
        goto error;
    }
    ret = swtpm_tpm12_oiap(self, &auth_handle, nonce_even);
    if (ret != 0)
        goto error;

    tpm_rsa_key_parms_len = memconcat(&tpm_rsa_key_parms,
                                      (unsigned char[]){
                                          AS4BE(2048), AS4BE(2), AS4BE(0)
                                      }, (size_t)12,
                                      NULL);
    if (tpm_rsa_key_parms_len < 0) {
        logerr(self->logfile, "Internal error in %s: out of memory\n");
        goto error;
    }

    tpm_key_parms_len = memconcat(&tpm_key_parms,
                                  (unsigned char[]){
                                      AS4BE(TPM_ALG_RSA),
                                      AS2BE(TPM_ES_RSAESOAEP_SHA1_MGF1),
                                      AS2BE(TPM_SS_NONE),
                                      AS4BE(tpm_rsa_key_parms_len)}, (size_t)12,
                                  tpm_rsa_key_parms, tpm_rsa_key_parms_len,
                                  NULL);
    if (tpm_key_parms_len < 0) {
        logerr(self->logfile, "Internal error in %s: out of memory\n");
        goto error;
    }

    tpm_key12_len = memconcat(&tpm_key12,
                              (unsigned char[]){
                                  AS2BE(TPM_TAG_KEY12), AS2BE(0),
                                  AS2BE(TPM_KEY_STORAGE), AS4BE(0), TPM_AUTH_ALWAYS
                              }, (size_t)11,
                              tpm_key_parms, tpm_key_parms_len,
                              (unsigned char[]){AS4BE(0), AS4BE(0), AS4BE(0)}, (size_t)12,
                              NULL);
    if (tpm_key12_len < 0) {
        logerr(self->logfile, "Internal error in %s: out of memory\n");
        goto error;
    }

    req_len = concat(req, sizeof(req),
                     &hdr, sizeof(hdr),
                     (unsigned char[]){AS2BE(TPM_PID_OWNER), AS4BE(enc_owner_auth_len)}, (size_t)6,
                     enc_owner_auth, enc_owner_auth_len,
                     (unsigned char[]){AS4BE(enc_srk_auth_len)}, (size_t)4,
                     enc_srk_auth, enc_srk_auth_len,
                     tpm_key12, tpm_key12_len,
                     NULL);
    if (req_len < 0) {
        logerr(self->logfile, "Internal error in %s: req is too small\n");
        goto error;
    }
    SHA1(&req[6], req_len - 6, in_param_digest);

    in_auth_setup_params_len = memconcat(&in_auth_setup_params,
                                         nonce_even, sizeof(nonce_even),
                                         nonce_odd, sizeof(nonce_odd),
                                         &continue_auth_session, (size_t)1,
                                         NULL);
    if (in_auth_setup_params_len < 0) {
        logerr(self->logfile, "Internal error in %s: out of memory\n");
        goto error;
    }

    macinput_len = memconcat(&macinput,
                             in_param_digest, sizeof(in_param_digest),
                             in_auth_setup_params, in_auth_setup_params_len,
                             NULL);
    if (macinput_len < 0) {
        logerr(self->logfile, "Internal error in %s: out of memory\n");
        goto error;
    }

    HMAC(sha1, ownerpass_digest, SHA_DIGEST_LENGTH, macinput, macinput_len,
         owner_auth, &owner_auth_len);

    len = concat(&req[req_len], sizeof(req) - req_len,
                 (unsigned char[]){AS4BE(auth_handle)}, (size_t)4,
                 nonce_odd, sizeof(nonce_odd),
                 &continue_auth_session, (size_t)1,
                 owner_auth, owner_auth_len,
                 NULL);
    if (len < 0) {
        logerr(self->logfile, "Internal error in %s: req is too small\n");
        goto error;
    }
    req_len += len;

    trh = (struct tpm_req_header *)req; /* old gcc type-punned pointer */
    trh->size = htobe32(req_len);

    ret = transfer(self, req, req_len, "TPM_TakeOwnership", FALSE, NULL, 0);

error:
    EVP_PKEY_free(pkey);
    EVP_PKEY_CTX_free(ctx);
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
    BN_free(exp);
    BN_free(mod);
#endif
    return ret;

error_free_bn:
    BN_free(exp);
    BN_free(mod);

#if OPENSSL_VERSION_NUMBER < 0x30000000L
error_free_pkey_and_rsa:
    RSA_free(rsakey);
error_free_pkey:
#else
    EVP_PKEY_CTX_free(ctx);
#endif
    EVP_PKEY_free(pkey);

    return 1;
}

static int swtpm_tpm12_nv_define_space(struct swtpm *self, uint32_t nvindex,
                                       uint32_t nvindexattrs, size_t size)
{
    struct tpm_req_header hdr = TPM_REQ_HEADER_INITIALIZER(TPM_TAG_RQU_COMMAND, 0, TPM_ORD_NV_DEFINE_SPACE);
    g_autofree unsigned char *pcr_info_short = NULL;
    ssize_t pcr_info_short_len;
    g_autofree unsigned char *nv_data_public = NULL;
    ssize_t nv_data_public_len;
    g_autofree unsigned char *req = NULL;
    ssize_t req_len;
    unsigned char zeroes[SHA_DIGEST_LENGTH] = {0, };

    pcr_info_short_len = memconcat(&pcr_info_short,
                                   (unsigned char[]){AS2BE(3), 0, 0, 0, TPM_LOC_ALL}, (size_t)6,
                                   zeroes, sizeof(zeroes),
                                   NULL);
    if (pcr_info_short_len < 0) {
        logerr(self->logfile, "Internal error in %s: out of memory\n");
        return 1;
    }

    nv_data_public_len = memconcat(&nv_data_public,
                                   (unsigned char[]){
                                       AS2BE(TPM_TAG_NV_DATA_PUBLIC), AS4BE(nvindex)
                                   }, (size_t)6,
                                   pcr_info_short, pcr_info_short_len,
                                   pcr_info_short, pcr_info_short_len,
                                   (unsigned char[]){
                                       AS2BE(TPM_TAG_NV_ATTRIBUTES), AS4BE(nvindexattrs),
                                       0, 0, 0, AS4BE(size)
                                   }, (size_t)13,
                                   NULL);
    if (nv_data_public_len < 0) {
        logerr(self->logfile, "Internal error in %s: out of memory\n");
        return 1;
    }

    req_len = memconcat(&req,
                        &hdr, sizeof(hdr),
                        nv_data_public, nv_data_public_len,
                        zeroes, sizeof(zeroes),
                        NULL);
    if (req_len < 0) {
        logerr(self->logfile, "Internal error in %s: out of memory\n");
        return 1;
    }

    ((struct tpm_req_header *)req)->size = htobe32(req_len);

    return transfer(self, req, req_len, "TPM_NV_DefineSpace", FALSE, NULL, 0);
}

static int swtpm_tpm12_nv_write_value(struct swtpm *self, uint32_t nvindex,
                                      const unsigned char *data, size_t data_len)
{
    struct tpm_req_header hdr = TPM_REQ_HEADER_INITIALIZER(TPM_TAG_RQU_COMMAND, 0, TPM_ORD_NV_WRITE_VALUE);
    g_autofree unsigned char *req = NULL;
    ssize_t req_len;

    req_len = memconcat(&req,
                        &hdr, sizeof(hdr),
                        (unsigned char[]){AS4BE(nvindex), AS4BE(0), AS4BE(data_len)}, (size_t)12,
                        data, data_len,
                        NULL);
    if (req_len < 0) {
        logerr(self->logfile, "Internal error in %s: out of memory\n");
        return 1;
    }

    ((struct tpm_req_header *)req)->size = htobe32(req_len);

    return transfer(self, req, req_len, "TPM_NV_DefineSpace", FALSE, NULL, 0);
}

/* Write the EK Certificate into NVRAM */
static int swtpm_tpm12_write_ek_cert_nvram(struct swtpm *self,
                                           const unsigned char *data, size_t data_len)
{
    uint32_t nvindex = TPM_NV_INDEX_EKCERT | TPM_NV_INDEX_D_BIT;
    int ret = swtpm_tpm12_nv_define_space(self, nvindex,
                                          TPM_NV_PER_OWNERREAD | TPM_NV_PER_OWNERWRITE, data_len);
    if (ret != 0)
        return 1;

    ret = swtpm_tpm12_nv_write_value(self, nvindex, data, data_len);
    if (ret != 0)
        return 1;

    return 0;
}

/* Write the Platform Certificate into NVRAM */
static int swtpm_tpm12_write_platform_cert_nvram(struct swtpm *self,
                                                 const unsigned char *data, size_t data_len)
{
    uint32_t nvindex = TPM_NV_INDEX_PLATFORMCERT | TPM_NV_INDEX_D_BIT;
    int ret = swtpm_tpm12_nv_define_space(self, nvindex,
                                          TPM_NV_PER_OWNERREAD | TPM_NV_PER_OWNERWRITE, data_len);
    if (ret != 0)
        return 1;

    ret = swtpm_tpm12_nv_write_value(self, nvindex, data, data_len);
    if (ret != 0)
        return 1;

    return 0;
}

static int swtpm_tpm12_nv_lock(struct swtpm *self)
{
    return swtpm_tpm12_nv_define_space(self, TPM_NV_INDEX_LOCK, 0, 0);
}

static const struct swtpm12_ops swtpm_tpm12_ops = {
    .run_swtpm_bios = swtpm_tpm12_run_swtpm_bios,
    .create_endorsement_key_pair = swptm_tpm12_create_endorsement_keypair,
    .take_ownership = swtpm_tpm12_take_ownership,
    .write_ek_cert_nvram = swtpm_tpm12_write_ek_cert_nvram,
    .write_platform_cert_nvram = swtpm_tpm12_write_platform_cert_nvram,
    .nv_lock = swtpm_tpm12_nv_lock,
};

static void swtpm_init(struct swtpm *swtpm,
                       gchar **swtpm_exec_l, const gchar *state_path,
                       const gchar *keyopts, const gchar *logfile,
                       int *fds_to_pass, size_t n_fds_to_pass,
                       gboolean is_tpm2)
{
    swtpm->cops = &swtpm_cops;
    swtpm->swtpm_exec_l = swtpm_exec_l;
    swtpm->state_path = state_path;
    swtpm->keyopts = keyopts;
    swtpm->logfile = logfile;
    swtpm->fds_to_pass = fds_to_pass;
    swtpm->n_fds_to_pass = n_fds_to_pass;
    swtpm->is_tpm2 = is_tpm2;

    swtpm->pid = -1;
    swtpm->ctrl_fds[0] = swtpm->ctrl_fds[1] = -1;
    swtpm->data_fds[0] = swtpm->data_fds[1] = -1;
}

struct swtpm12 *swtpm12_new(gchar **swtpm_exec_l, const gchar *state_path,
                            const gchar *keyopts, const gchar *logfile,
                            int *fds_to_pass, size_t n_fds_to_pass)
{
    struct swtpm12 *swtpm12 = g_malloc0(sizeof(struct swtpm12));

    swtpm_init(&swtpm12->swtpm, swtpm_exec_l, state_path, keyopts, logfile,
               fds_to_pass, n_fds_to_pass, FALSE);
    swtpm12->ops = &swtpm_tpm12_ops;

    return swtpm12;
}

struct swtpm2 *swtpm2_new(gchar **swtpm_exec_l, const gchar *state_path,
                         const gchar *keyopts, const gchar *logfile,
                         int *fds_to_pass, size_t n_fds_to_pass)
{
    struct swtpm2 *swtpm2 = g_malloc0(sizeof(struct swtpm2));

    swtpm_init(&swtpm2->swtpm, swtpm_exec_l, state_path, keyopts, logfile,
               fds_to_pass, n_fds_to_pass, TRUE);
    swtpm2->ops = &swtpm_tpm2_ops;

    return swtpm2;
}

void swtpm_free(struct swtpm *swtpm) {
    if (!swtpm)
        return;
    g_free(swtpm);
}

