/********************************************************************************/
/*                                                                              */
/*                      NVRAM File Abstraction Layer                            */
/*                           Written by Ken Goldman                             */
/*                       Adapted to SWTPM by Stefan Berger                      */
/*                     IBM Thomas J. Watson Research Center                     */
/*                                                                              */
/* (c) Copyright IBM Corporation 2006, 2010, 2014, 2015.			*/
/*										*/
/* All rights reserved.								*/
/* 										*/
/* Redistribution and use in source and binary forms, with or without		*/
/* modification, are permitted provided that the following conditions are	*/
/* met:										*/
/* 										*/
/* Redistributions of source code must retain the above copyright notice,	*/
/* this list of conditions and the following disclaimer.			*/
/* 										*/
/* Redistributions in binary form must reproduce the above copyright		*/
/* notice, this list of conditions and the following disclaimer in the		*/
/* documentation and/or other materials provided with the distribution.		*/
/* 										*/
/* Neither the names of the IBM Corporation nor the names of its		*/
/* contributors may be used to endorse or promote products derived from		*/
/* this software without specific prior written permission.			*/
/* 										*/
/* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS		*/
/* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT		*/
/* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR	*/
/* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT		*/
/* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,	*/
/* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT		*/
/* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,	*/
/* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY	*/
/* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT		*/
/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE	*/
/* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.		*/
/********************************************************************************/

/* This module abstracts out all NVRAM read and write operations.

   This implementation uses standard, portable C files.

   The basic high level abstractions are:

        SWTPM_NVRAM_LoadData();
        SWTPM_NVRAM_StoreData();
        SWTPM_NVRAM_DeleteName();

   They take a 'name' that is mapped to a rooted file name.
*/

#include "config.h"

#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <fcntl.h>

#include <libtpms/tpm_error.h>
#include <libtpms/tpm_memory.h>
#include <libtpms/tpm_nvfilename.h>
#include <libtpms/tpm_library.h>

#include <openssl/sha.h>
#include <openssl/rand.h>
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
# include <openssl/core_names.h>
#else
# include <openssl/hmac.h>
#endif

#if defined(__OpenBSD__)
 # define OPENSSL_OLD_API
#else
 #if OPENSSL_VERSION_NUMBER < 0x10100000
  #define OPENSSL_OLD_API
 #endif
#endif

#include "swtpm.h"
#include "swtpm_aes.h"
#include "swtpm_debug.h"
#include "swtpm_nvstore.h"
#include "swtpm_utils.h"
#include "key.h"
#include "logging.h"
#include "tpmstate.h"
#include "tpmlib.h"
#include "tlv.h"
#include "utils.h"
#include "compiler_dependencies.h"

/* local structures */
typedef struct {
    uint8_t  version;
    uint8_t  min_version; /* min. required version */
    uint16_t hdrsize;
    uint16_t flags;
    uint32_t totlen; /* length of the header and following data */
} __attribute__((packed)) blobheader;

#define BLOB_HEADER_VERSION 2

/* flags for blobheader */
#define BLOB_FLAG_ENCRYPTED              0x01
#define BLOB_FLAG_MIGRATION_ENCRYPTED    0x02  /* encrypted with migration key */
#define BLOB_FLAG_MIGRATION_DATA         0x04  /* migration data are available */
#define BLOB_FLAG_ENCRYPTED_256BIT_KEY   0x08  /* 256 bit file key was used */
#define BLOB_FLAG_MIGRATION_256BIT_KEY   0x10  /* 256 bit migration key was used */

typedef struct {
    enum encryption_mode data_encmode;
    TPM_SYMMETRIC_KEY_DATA symkey;
} encryptionkey ;

static encryptionkey filekey = {
    .symkey = {
        .userKeyLength = 0,
    },
};

static encryptionkey migrationkey = {
    .symkey = {
        .userKeyLength = 0,
    },
};

static uint32_t g_ivec_length;
static unsigned char *g_ivec;
static struct nvram_backend_ops *g_nvram_backend_ops;

/* local prototypes */

static TPM_RESULT SWTPM_NVRAM_EncryptData(const encryptionkey *key,
                                          tlv_data *td,
                                          size_t *td_len,
                                          uint16_t tag_encrypted_data,
                                          const unsigned char *decrypt_data,
                                          uint32_t decrypt_length,
                                          uint16_t tag_ivec);

static TPM_RESULT SWTPM_NVRAM_GetDecryptedData(const encryptionkey *key,
                                               unsigned char **decrypt_data,
                                               uint32_t *decrypt_length,
                                               const unsigned char *encrypt_data,
                                               uint32_t encrypt_length,
                                               uint16_t tag_decryped_data,
                                               uint16_t tag_data,
                                               uint8_t hdrversion,
                                               uint16_t tag_ivec,
                                               uint16_t hdrflags,
                                               uint16_t flag_256bitkey);

static TPM_RESULT SWTPM_NVRAM_PrependHeader(unsigned char **data,
                                            uint32_t *length,
                                            uint16_t flags);

static TPM_RESULT SWTPM_NVRAM_CheckHeader(unsigned char *data, uint32_t length,
                                          uint32_t *dataoffset,
                                          uint16_t *hdrflags,
                                          uint8_t *hdrversion,
                                          bool quiet);

/* SWTPM_NVRAM_Init() is called once at startup.  It does any NVRAM required initialization.

   This function sets some static variables that are used by all TPM's.
*/

TPM_RESULT SWTPM_NVRAM_Init(void)
{
    const char  *backend_uri;
    TPM_RESULT  rc = 0;
    TPM_DEBUG(" SWTPM_NVRAM_Init:\n");

    backend_uri = tpmstate_get_backend_uri();
    if (!backend_uri) {
        logprintf(STDERR_FILENO,
                  "SWTPM_NVRAM_Init: Missing backend URI.\n");
        rc = TPM_FAIL;
    } else if (strncmp(backend_uri, "dir://", 6) == 0) {
        g_nvram_backend_ops = &nvram_dir_ops;
    } else if (strncmp(backend_uri, "file://", 7) == 0) {
        g_nvram_backend_ops = &nvram_linear_ops;
    } else {
        logprintf(STDERR_FILENO,
                  "SWTPM_NVRAM_Init: Unsupported backend.\n");
        rc = TPM_FAIL;
    }

    if (rc == 0)
        rc = g_nvram_backend_ops->prepare(backend_uri);

    return rc;
}

void SWTPM_NVRAM_Shutdown(void)
{
    if (g_nvram_backend_ops)
        g_nvram_backend_ops->cleanup();
    memset(&filekey, 0, sizeof(filekey));
    memset(&migrationkey, 0, sizeof(migrationkey));
}

TPM_RESULT SWTPM_NVRAM_Lock_Storage(unsigned int retries)
{
    const char *backend_uri;

    if (!tpmstate_get_locking()) {
        /* no locking requested by user */
        return TPM_SUCCESS;
    }

    if (!g_nvram_backend_ops)
        return TPM_RETRY;

    backend_uri = tpmstate_get_backend_uri();
    if (!backend_uri) {
        logprintf(STDERR_FILENO,
                  "SWTPM_NVRAM_Lock: Missing backend URI.\n");
        return TPM_FAIL;
    }
    return g_nvram_backend_ops->lock(backend_uri, retries);
}

void SWTPM_NVRAM_Unlock(void)
{
    if (!tpmstate_get_locking()) {
        /* no locking requested by user */
        return;
    }

    g_nvram_backend_ops->unlock();
}

/* SWTPM_NVRAM_GetFilenameForName() constructs a file name from the name.
 * A temporary filename used to write to may be created. It should be rename()'d to
 * the non-temporary filename.
 */

TPM_RESULT
SWTPM_NVRAM_GetFilenameForName(char *filename,       /* output: filename */
                               size_t bufsize,
                               uint32_t tpm_number,
                               const char *name,     /* input: abstract name */
                               TPM_BOOL is_tempfile) /* input: is temporary file? */
{
    TPM_RESULT res = TPM_SUCCESS;
    int n;
    const char *suffix = "";

    TPM_DEBUG(" SWTPM_NVRAM_GetFilenameForName: For name %s\n", name);

    switch (tpmstate_get_version()) {
    case TPMLIB_TPM_VERSION_1_2:
        break;
    case TPMLIB_TPM_VERSION_2:
        suffix = "2";
        break;
    }

    if (is_tempfile) {
        n = snprintf(filename, bufsize, "TMP%s-%02lx.%s", suffix, (unsigned long)tpm_number, name);
    } else {
        n = snprintf(filename, bufsize, "tpm%s-%02lx.%s", suffix, (unsigned long)tpm_number, name);
    }
    if ((size_t)n > bufsize) {
        res = TPM_FAIL;
    }

    TPM_DEBUG("  SWTPM_NVRAM_GetFilenameForName: File name %s\n", filename);

    return res;
}


/* Load 'data' of 'length' from the 'name'.

   'data' must be freed after use.
   
   Returns
        0 on success.
        TPM_RETRY and NULL,0 on non-existent file (non-fatal, first time start up)
        TPM_FAIL on failure to load (fatal), since it should never occur
*/

TPM_RESULT
SWTPM_NVRAM_LoadData(unsigned char **data,     /* freed by caller */
                     uint32_t *length,
                     uint32_t tpm_number,
                     const char *name)
{
    TPM_RESULT    rc = 0;
    unsigned char *decrypt_data = NULL;
    uint32_t      decrypt_length;
    uint32_t      dataoffset = 0;
    uint8_t       hdrversion = 0;
    uint16_t      hdrflags;
    const char    *backend_uri = NULL;

    TPM_DEBUG(" SWTPM_NVRAM_LoadData: From file %s\n", name);
    *data = NULL;
    *length = 0;

    if (rc == 0) {
        backend_uri = tpmstate_get_backend_uri();
        rc = g_nvram_backend_ops->load(data, length, tpm_number, name,
                                       backend_uri);
    }

    /* this function needs to return the plain data -- no tlv headers */
    if (rc == 0) {
        rc = SWTPM_NVRAM_CheckHeader(*data, *length, &dataoffset,
                                      &hdrflags, &hdrversion, true);
        if (rc != 0)
            logprintf(STDERR_FILENO,
                      "SWTPM_NVRAM_LoadData: Error from SWTPM_NVRAM_CheckHeader "
                      "rc = %d\n", rc);
    }

    if (rc == 0) {
        rc = SWTPM_NVRAM_GetDecryptedData(&filekey,
                                          &decrypt_data, &decrypt_length,
                                          *data + dataoffset,
                                          *length - dataoffset,
                                          TAG_ENCRYPTED_DATA, TAG_DATA,
                                          hdrversion,
                                          TAG_IVEC_ENCRYPTED_DATA,
                                          hdrflags,
                                          BLOB_FLAG_ENCRYPTED_256BIT_KEY);
        TPM_DEBUG(" SWTPM_NVRAM_LoadData: SWTPM_NVRAM_GetDecryptedData rc = %d\n",
                  rc);
        if (rc != 0)
            logprintf(STDERR_FILENO,
                      "SWTPM_NVRAM_LoadData: Error from SWTPM_NVRAM_GetDecryptedData "
                      "rc = %d\n", rc);
    }

    free(*data);

    if (rc == 0) {
        TPM_DEBUG(" SWTPM_NVRAM_LoadData: Decrypted %u bytes of "
                  "data to %u bytes.\n",
                  *length, decrypt_length);
        *data = decrypt_data;
        *length = decrypt_length;
    } else {
        *data = NULL;
    }

    return rc;
}

/* SWTPM_NVRAM_StoreData stores 'data' of 'length' to the rooted 'filename'

   Returns
        0 on success
        TPM_FAIL for other fatal errors
*/

static TPM_RESULT
SWTPM_NVRAM_StoreData_Intern(const unsigned char *data,
                             uint32_t length,
                             uint32_t tpm_number,
                             const char *name,
                             TPM_BOOL encrypt         /* encrypt if key is set */)
{
    TPM_RESULT    rc = 0;
    unsigned char *filedata = NULL;
    uint32_t      filedata_length = 0;
    tlv_data      td[3];
    size_t        td_len = 0;
    uint16_t      flags = 0;
    const char    *backend_uri = NULL;

    TPM_DEBUG(" SWTPM_NVRAM_StoreData: To name %s\n", name);

    if (rc == 0) {
        if (encrypt && SWTPM_NVRAM_Has_FileKey()) {
            td_len = 3;
            rc = SWTPM_NVRAM_EncryptData(&filekey, &td[0], &td_len,
                                         TAG_ENCRYPTED_DATA, data, length,
                                         TAG_IVEC_ENCRYPTED_DATA);
            if (rc) {
                logprintf(STDERR_FILENO,
                          "SWTPM_NVRAM_EncryptData failed: 0x%02x\n", rc);
            } else {
                TPM_DEBUG("  SWTPM_NVRAM_StoreData: Encrypted %u bytes before "
                          "write, will write %u bytes\n", length,
                          td[0].tlv.length);
            }
            flags |= BLOB_FLAG_ENCRYPTED;
            if (SWTPM_NVRAM_FileKey_Size() == SWTPM_AES256_BLOCK_SIZE)
                flags |= BLOB_FLAG_ENCRYPTED_256BIT_KEY;
        } else {
            td_len = 1;
            td[0] = TLV_DATA_CONST(TAG_DATA, length, data);
        }
    }

    if (rc == 0)
        rc = tlv_data_append(&filedata, &filedata_length, td, td_len);

    if (rc == 0)
        rc = SWTPM_NVRAM_PrependHeader(&filedata, &filedata_length, flags);

    if (rc == 0) {
        backend_uri = tpmstate_get_backend_uri();
        rc = g_nvram_backend_ops->store(filedata, filedata_length, tpm_number, name,
                                        backend_uri);
    }

    tlv_data_free(td, td_len);
    free(filedata);

    TPM_DEBUG(" SWTPM_NVRAM_StoreData: rc=%d\n", rc);

    return rc;
}

TPM_RESULT SWTPM_NVRAM_StoreData(const unsigned char *data,
                                 uint32_t length,
                                 uint32_t tpm_number,
                                 const char *name)
{
    return SWTPM_NVRAM_StoreData_Intern(data, length, tpm_number, name, TRUE);
}

/* SWTPM_NVRAM_DeleteName() deletes the 'name' from NVRAM

   Returns:
        0 on success, or if the file does not exist and mustExist is FALSE
        TPM_FAIL if the file could not be removed, since this should never occur and there is
                no recovery

   NOTE: Not portable code, but supported by Linux and Windows
*/

TPM_RESULT SWTPM_NVRAM_DeleteName(uint32_t tpm_number,
                                  const char *name,
                                  TPM_BOOL mustExist)
{
    const char *backend_uri = NULL;
    backend_uri = tpmstate_get_backend_uri();

    return g_nvram_backend_ops->delete(tpm_number, name, mustExist,
                                       backend_uri);
}


TPM_RESULT SWTPM_NVRAM_Store_Volatile(void)
{
    TPM_RESULT     rc = 0;
    const char     *name = TPM_VOLATILESTATE_NAME;
    uint32_t       tpm_number = 0;
    unsigned char  *buffer = NULL;
    uint32_t       buflen;

    TPM_DEBUG(" SWTPM_Store_Volatile: Name %s\n", name);
    if (rc == 0) {
        rc = TPMLIB_VolatileAll_Store(&buffer, &buflen);
    }
    if (rc == 0) {
        /* map name to the rooted filename */
        rc = SWTPM_NVRAM_StoreData(buffer, buflen, tpm_number, name);
    }

    free(buffer);

    return rc;
}

static TPM_RESULT
SWTPM_NVRAM_KeyParamCheck(uint32_t keylen,
                          enum encryption_mode encmode)
{
    TPM_RESULT rc = 0;

    if (keylen != SWTPM_AES128_BLOCK_SIZE &&
        keylen != SWTPM_AES256_BLOCK_SIZE) {
        rc = TPM_BAD_KEY_PROPERTY;
    }
    switch (encmode) {
    case ENCRYPTION_MODE_AES_CBC:
        break;
    case ENCRYPTION_MODE_UNKNOWN:
        rc = TPM_BAD_MODE;
    }

    return rc;
}

size_t SWTPM_NVRAM_FileKey_Size(void)
{
    return filekey.symkey.userKeyLength;
}

TPM_RESULT SWTPM_NVRAM_Set_FileKey(const unsigned char *key, uint32_t keylen,
                                   enum encryption_mode encmode)
{
    TPM_RESULT rc;

    rc = SWTPM_NVRAM_KeyParamCheck(keylen, encmode);

    if (rc == 0) {
        memcpy(filekey.symkey.userKey, key, keylen);
        filekey.symkey.userKeyLength = keylen;
        filekey.data_encmode = encmode;
    }

    return rc;
}

size_t SWTPM_NVRAM_MigrationKey_Size(void)
{
    return migrationkey.symkey.userKeyLength;
}

TPM_RESULT SWTPM_NVRAM_Set_MigrationKey(const unsigned char *key,
                                        uint32_t keylen,
                                        enum encryption_mode encmode)
{
    TPM_RESULT rc;

    rc = SWTPM_NVRAM_KeyParamCheck(keylen, encmode);

    if (rc == 0) {
        memcpy(migrationkey.symkey.userKey, key, keylen);
        migrationkey.symkey.userKeyLength = keylen;
        migrationkey.data_encmode = encmode;
    }

    return rc;
}

# if OPENSSL_VERSION_NUMBER >= 0x30000000L

static int SWTPM_HMAC(unsigned char *md, unsigned int *md_len,
                      const void *key, int key_len,
                      const unsigned char *in, uint32_t in_length,
                      const unsigned char *ivec, uint32_t ivec_length)
{
    OSSL_PARAM params[2];
    EVP_MAC_CTX *ctx;
    EVP_MAC *hmac;
    size_t outl;
    int ret = 0;

    hmac = EVP_MAC_fetch(NULL, OSSL_MAC_NAME_HMAC, NULL);
    if (!hmac)
        return 0;

    ctx = EVP_MAC_CTX_new(hmac);
    if (!ctx)
        goto err;

    params[0] = OSSL_PARAM_construct_utf8_string(OSSL_ALG_PARAM_DIGEST,
                                                 (char *)"sha256", 0);
    params[1] = OSSL_PARAM_construct_end();

    if (!EVP_MAC_init(ctx, key, key_len, params) ||
        !EVP_MAC_update(ctx, in, in_length))
        goto err;

    if (ivec &&
        !EVP_MAC_update(ctx, ivec, ivec_length))
        goto err;

    if (!EVP_MAC_final(ctx, md, &outl, *md_len))
        goto err;
    *md_len = outl;

    ret = 1;

err:
    EVP_MAC_CTX_free(ctx);
    EVP_MAC_free(hmac);

    return ret;
}

#else

static int SWTPM_HMAC(unsigned char *md, unsigned int *md_len,
                      const void *key, int key_len,
                      const unsigned char *in, uint32_t in_length,
                      const unsigned char *ivec, uint32_t ivec_length)
{
    int ret = 0;

#if defined OPENSSL_OLD_API
    HMAC_CTX sctx, *ctx = &sctx;

    HMAC_CTX_init(ctx);
#else
    HMAC_CTX *ctx = HMAC_CTX_new();

    if (!ctx)
        return 0;
#endif


    if (!HMAC_Init_ex(ctx, key, key_len, EVP_sha256(), NULL) ||
        !HMAC_Update(ctx, in, in_length))
        goto err;

    if (ivec &&
        !HMAC_Update(ctx, ivec, ivec_length))
        goto err;

    if (!HMAC_Final(ctx, md, md_len))
        goto err;

    ret = 1;

err:
#if defined OPENSSL_OLD_API
    HMAC_CTX_cleanup(ctx);
#else
    HMAC_CTX_free(ctx);
#endif

    return ret;
}
#endif /* if OPENSSL_VERSION_NUMBER >= 0x30000000L */

/*
 * SWTPM_RollAndSetGlobalIvec: Create an IV for the AES CBC algorithm to use
 *                             Create it with a random number every time.
 *                             and leave the pointer to the data in @td.
 *
 * @td: pointer to tlv_data to get pointer to the random data
 * @tag_ivec: tag for the IV tlv header
 * @ivec_length: number of bytes needed for the ivec
 */
static TPM_RESULT SWTPM_RollAndSetGlobalIvec(tlv_data *td,
                                             uint16_t tag_ivec,
                                             uint32_t ivec_length)
{
    unsigned char data[16]; /* do not initialize */
    unsigned char hashbuf[SHA256_DIGEST_LENGTH];
    void *p;

    if (g_ivec_length < ivec_length) {
        p = realloc(g_ivec, ivec_length);
        if (!p) {
            *td = TLV_DATA_CONST(tag_ivec, 0, NULL);

            logprintf(STDOUT_FILENO,
                      "Could not allocate %u bytes.\n", ivec_length);
            return TPM_FAIL;
        }
        g_ivec = p;
        g_ivec_length = ivec_length;
    }

    if (RAND_bytes(g_ivec, g_ivec_length) != 1) {
        /* random data from stack to the rescue */
        SHA256(g_ivec, g_ivec_length, hashbuf);
        SHA256(data, sizeof(data), hashbuf);
        memcpy(g_ivec, hashbuf,
               g_ivec_length < sizeof(hashbuf)
                   ? g_ivec_length
                   : sizeof(hashbuf));
    }

    *td = TLV_DATA_CONST(tag_ivec, g_ivec_length, g_ivec);

    return 0;
}

/*
 * SWTPM_GetIvec: Get the encryption IV from the data stream. If none is
 *                found a NULL pointer is set in *ivec, otherwise a pointer
 *                to the beginning of the IV and its length are returned.
 */
static void SWTPM_GetIvec(const unsigned char *data, uint32_t length,
                          const unsigned char **ivec, uint32_t *ivec_length,
                          uint16_t tag)
{
    tlv_data td;

    if (!tlv_data_find_tag(data, length, tag, &td)) {
        *ivec = NULL;
    } else {
        *ivec = td.u.const_ptr;
        *ivec_length = td.tlv.length;
    }
}

/*
 * SWTPM_CalcHMAC
 *
 * @in: input buffer to calculate HMAC on
 * @in_length: length of input buffer
 * @td: pointer to a tlv_data structure to receive the result with the
 *      tag, length, and pointer to an allocated buffer holding the HMAC
 * @tpm_symmetric_key_token: symmetric key
 * @ivec: the IV for AES CBC
 * @ivec_length: the length of the IV
 *
 * Calculate an HMAC on the input buffer with payload and create an output
 * buffer with the HMAC
 */
static TPM_RESULT
SWTPM_CalcHMAC(const unsigned char *in, uint32_t in_length,
               tlv_data *td,
               const TPM_SYMMETRIC_KEY_DATA *tpm_symmetric_key_token,
               const unsigned char *ivec, uint32_t ivec_length)
{
    TPM_RESULT rc = 0;
    unsigned char md[EVP_MAX_MD_SIZE];
    unsigned int md_len = sizeof(md);
    unsigned char *buffer = NULL;

    if (!SWTPM_HMAC(md, &md_len,
                    tpm_symmetric_key_token->userKey,
                    tpm_symmetric_key_token->userKeyLength,
                    in, in_length, ivec, ivec_length)) {
        logprintf(STDOUT_FILENO, "HMAC calculation failed.\n");
        return TPM_FAIL;
    }

    buffer = malloc(md_len);

    if (buffer) {
        *td = TLV_DATA(TAG_HMAC, md_len, buffer);
        memcpy(buffer, md, md_len);
    } else {
       logprintf(STDOUT_FILENO,
                 "Could not allocate %u bytes.\n", md_len);
       rc = TPM_FAIL;
    }

    return rc;
}

/*
 * SWTPM_CheckHMAC:
 *
 * @hmac: tlv_data with pointer to hmac bytes
 * @encrypted_data: tlv_data with pointer to encrypted data bytes
 * @tpm_symmetric_key_token: symmetric key
 * @ivec: the IV for AES CBC
 * @ivec_length: the length of the IV
 *
 * Verify the HMAC given the expected @hmac and the @tpm_symmetric_key_token
 * to calculate the HMAC over the @encrypted_data.
 */
static TPM_RESULT
SWTPM_CheckHMAC(tlv_data *hmac, tlv_data *encrypted_data,
                const TPM_SYMMETRIC_KEY_DATA *tpm_symmetric_key_token,
                const unsigned char *ivec, uint32_t ivec_length)
{
    const unsigned char *data;
    uint32_t data_length;
    unsigned int md_len;
    unsigned char md[EVP_MAX_MD_SIZE];

    md_len = EVP_MD_size(EVP_sha256());
    if (md_len > hmac->tlv.length) {
        logprintf(STDOUT_FILENO, "Insufficient bytes for CheckHMAC()\n");
        return TPM_FAIL;
    }

    data = encrypted_data->u.ptr;
    data_length = encrypted_data->tlv.length;

    if (!SWTPM_HMAC(md, &md_len,
                    tpm_symmetric_key_token->userKey,
                    tpm_symmetric_key_token->userKeyLength,
                    data, data_length, ivec, ivec_length)) {
        logprintf(STDOUT_FILENO, "HMAC() call failed.\n");
        return TPM_FAIL;
    }

    if (memcmp(hmac->u.ptr, md, md_len)) {
        logprintf(STDOUT_FILENO, "Verification of HMAC failed. "
                  "Data integrity is compromised\n");
        /* TPM_DECRYPT_ERROR indicates (to libtpms) that something
           exists but we have the wrong key. */
        return TPM_DECRYPT_ERROR;
    }

    return TPM_SUCCESS;
}

/*
 * SWTPM_CheckHash:
 *
 * @in: input buffer
 * @in_length: input buffer length
 * @out: output buffer
 * @out_length: output buffer length
 */
static TPM_RESULT
SWTPM_CheckHash(const unsigned char *in, uint32_t in_length,
                unsigned char **out, uint32_t *out_length)
{
    TPM_RESULT rc = 0;
    unsigned char *dest = NULL;
    unsigned char hashbuf[SHA256_DIGEST_LENGTH];
    const unsigned char *data = &in[sizeof(hashbuf)];
    uint32_t data_length = in_length - sizeof(hashbuf);

    /* hash the data */
    SHA256(data, data_length, hashbuf);

    if (memcmp(in, hashbuf, sizeof(hashbuf))) {
        logprintf(STDOUT_FILENO, "Verification of hash failed. "
                  "Data integrity is compromised\n");
        rc = TPM_FAIL;
    }

    if (rc == 0) {
        dest = malloc(data_length);
        if (dest) {
            *out = dest;
            *out_length = data_length;
            memcpy(dest, data, data_length);
        } else {
            logprintf(STDOUT_FILENO,
                      "Could not allocated %u bytes.\n", data_length);
            rc = TPM_FAIL;
        }
    }

    return rc;
}

static TPM_RESULT
SWTPM_NVRAM_EncryptData(const encryptionkey *key,
                        struct tlv_data *td, /* must provide 2 array members */
                        size_t *td_len,
                        uint16_t tag_encrypted_data,
                        const unsigned char *data, uint32_t length,
                        uint16_t tag_ivec)
{
    TPM_RESULT rc = 0;
    TPM_RESULT irc;
    unsigned char *tmp_data = NULL;
    uint32_t tmp_length = 0;

    *td_len = 0;

    if (key->symkey.userKeyLength > 0) {
        switch (key->data_encmode) {
        case ENCRYPTION_MODE_UNKNOWN:
            rc = TPM_BAD_MODE;
            break;
        case ENCRYPTION_MODE_AES_CBC:
            irc = SWTPM_RollAndSetGlobalIvec(&td[2], tag_ivec,
                                             key->symkey.userKeyLength);
            rc = SWTPM_SymmetricKeyData_Encrypt(&tmp_data, &tmp_length,
                                                data, length, &key->symkey,
                                                td[2].u.const_ptr,
                                                td[2].tlv.length);
            if (rc)
                 break;

            rc = SWTPM_CalcHMAC(tmp_data, tmp_length, &td[1], &key->symkey,
                                td[2].u.const_ptr, td[2].tlv.length);
            if (rc == 0) {
                td[0] = TLV_DATA(tag_encrypted_data, tmp_length, tmp_data);
                /* in case we couldn't get an IV */
                *td_len = (irc == 0) ? 3 : 2;
                tmp_data = NULL;
            }
            break;
        }
    }

    if (rc)
        tlv_data_free(td, *td_len);

    free(tmp_data);

    return rc;
}

static TPM_RESULT
SWTPM_NVRAM_DecryptData(const encryptionkey *key,
                        unsigned char **decrypt_data, uint32_t *decrypt_length,
                        const unsigned char *data, uint32_t length,
                        uint16_t tag_encrypted_data,
                        uint8_t hdrversion,
                        uint16_t tag_ivec, uint16_t hdrflags,
                        uint16_t flag_256bitkey)
{
    TPM_RESULT rc = TPM_FAIL;
    unsigned char *tmp_data = NULL;
    uint32_t tmp_length = 0;
    tlv_data td[2];
    const unsigned char *ivec = NULL;
    uint32_t ivec_length = 0;
    size_t keylen;

    if (key->symkey.userKeyLength == 0)
        return rc;

    switch (key->data_encmode) {
    case ENCRYPTION_MODE_UNKNOWN:
        rc = TPM_BAD_MODE;
        break;
    case ENCRYPTION_MODE_AES_CBC:
        switch (hdrversion) {
        case 1:
            rc = SWTPM_SymmetricKeyData_Decrypt(&tmp_data,
                                                &tmp_length,
                                                data, length,
                                                &key->symkey,
                                                NULL, 0);
            if (rc == 0) {
                rc = SWTPM_CheckHash(tmp_data, tmp_length,
                                     decrypt_data, decrypt_length);
            }
        break;
        case 2:
            keylen = (hdrflags & flag_256bitkey)
                      ? SWTPM_AES256_BLOCK_SIZE : SWTPM_AES128_BLOCK_SIZE;
            if (keylen != key->symkey.userKeyLength) {
                logprintf(STDERR_FILENO,
                          "Wrong decryption key. Need %zu bit key.\n",
                          keylen * 8);
                rc = TPM_BAD_KEY_PROPERTY;
                break;
            }

            if (!tlv_data_find_tag(data, length, TAG_HMAC, &td[0]) ||
                !tlv_data_find_tag(data, length, tag_encrypted_data,
                                   &td[1])) {
                logprintf(STDERR_FILENO,
                          "Could not find HMAC or encrypted data (tag %u) "
                          "in byte stream.\n", tag_encrypted_data);
                rc = TPM_FAIL;
                break;
            }
            /* get the IV, if there is one */
            SWTPM_GetIvec(data, length, &ivec, &ivec_length, tag_ivec);

            rc = SWTPM_CheckHMAC(&td[0], &td[1], &key->symkey,
                                 ivec, ivec_length);
            if (rc == 0) {
                rc = SWTPM_SymmetricKeyData_Decrypt(decrypt_data,
                                                    decrypt_length,
                                                    td[1].u.const_ptr,
                                                    td[1].tlv.length,
                                                    &key->symkey,
                                                    ivec, ivec_length);
            }
        break;
        }
        free(tmp_data);
    }

    return rc;
}

static TPM_RESULT
SWTPM_NVRAM_GetPlainData(unsigned char **plain, uint32_t *plain_length,
                         const unsigned char *data, uint32_t length,
                         uint16_t tag_data,
                         uint8_t hdrversion)
{
    TPM_RESULT rc = 0;
    tlv_data td[1];

    switch (hdrversion) {
    case 1:
        *plain = malloc(length);
        if (*plain) {
            memcpy(*plain, data, length);
            *plain_length = length;
        } else {
            logprintf(STDERR_FILENO,
                      "Could not allocate %u bytes.\n", length);
            rc = TPM_FAIL;
        }
    break;

    case 2:
        if (!tlv_data_find_tag(data, length, tag_data, &td[0])) {
            logprintf(STDERR_FILENO,
                      "Could not find plain data in byte stream.\n");
            rc = TPM_FAIL;
            break;
        }
        *plain = malloc(td->tlv.length);
        if (*plain) {
            memcpy(*plain, td->u.const_ptr, td->tlv.length);
            *plain_length = td->tlv.length;
        } else {
            logprintf(STDERR_FILENO,
                      "Could not allocate %u bytes.\n", td->tlv.length);
            rc = TPM_FAIL;
        }
    break;

    default:
        rc = TPM_FAIL;
    }

    return rc;
}

/*
 * SWTPM_NVRAM_GetDecryptedData: Get the decrytped data either by just returning
 *                               the data if they were not encrypted or by
 *                               actually decrypting them if there is a key.
 *                               The plain data is returned, meaning any TLV
 *                               header has been removed.
 * @key: the encryption key, may be NULL
 * @decrypt_data: pointer to a pointer for the result
 * @decrypt_length: the length of the returned data
 * @data: input data
 * @length: length of the input data
 * @tag_encrypted_data: the tag the encrypted data is stored with
 * @tag_data: the tag the plain data is stored with
 * @hdrversion: the version found in the header that determines in what
 *              format the data is stored; tag-length-value is the format
 *              in v2
 * @tag_ivec: the tag for finding the IV
 * @hdrflags: the flags from the header
 * @flag_256bitkey: the flag in the header to check whether we expect a
 *                  256 bit key; different flag for migration and state key
 */
static TPM_RESULT
SWTPM_NVRAM_GetDecryptedData(const encryptionkey *key,
                             unsigned char **decrypt_data,
                             uint32_t *decrypt_length,
                             const unsigned char *data,
                             uint32_t length,
                             uint16_t tag_encrypted_data,
                             uint16_t tag_data,
                             uint8_t hdrversion,
                             uint16_t tag_ivec,
                             uint16_t hdrflags,
                             uint16_t flag_256bitkey)
{
    if (key && key->symkey.userKeyLength > 0) {
        /* we assume the data are encrypted when there's a key given */
        return SWTPM_NVRAM_DecryptData(key, decrypt_data, decrypt_length,
                                       data, length, tag_encrypted_data,
                                       hdrversion, tag_ivec, hdrflags,
                                       flag_256bitkey);
    }
    return SWTPM_NVRAM_GetPlainData(decrypt_data, decrypt_length,
                                    data, length, tag_data, hdrversion);
}

/*
 * Prepend a header in front of the state blob
 */
static TPM_RESULT
SWTPM_NVRAM_PrependHeader(unsigned char **data, uint32_t *length,
                          uint16_t flags)
{
    unsigned char *out = NULL;
    uint32_t out_len = sizeof(blobheader) + *length;
    blobheader bh = {
        .version = BLOB_HEADER_VERSION,
        .min_version = 1,
        .hdrsize = htons(sizeof(bh)),
        .flags = htons(flags),
        .totlen = htonl(out_len),
    };
    TPM_RESULT res;

    out = malloc(out_len);
    if (!out) {
        logprintf(STDERR_FILENO,
                  "Could not allocate %u bytes.\n", out_len);
        res = TPM_FAIL;
        goto error;
    }

    memcpy(out, &bh, sizeof(bh));
    memcpy(&out[sizeof(bh)], *data, *length);

    free(*data);

    *data = out;
    *length = out_len;

    return TPM_SUCCESS;

 error:
    free(*data);
    *data = NULL;
    *length = 0;

    return res;
}


static TPM_RESULT
SWTPM_NVRAM_CheckHeader(unsigned char *data, uint32_t length,
                        uint32_t *dataoffset, uint16_t *hdrflags,
                        uint8_t *hdrversion, bool quiet)
{
    blobheader *bh = (blobheader *)data;
    uint16_t hdrsize;

    if (length < sizeof(bh)) {
        if (!quiet)
            logprintf(STDERR_FILENO,
                      "not enough bytes for header: %u\n", length);
        return TPM_BAD_PARAMETER;
    }

    if (ntohl(bh->totlen) != length) {
        if (!quiet)
            logprintf(STDERR_FILENO,
                      "broken header: bh->totlen %u != %u\n",
                      htonl(bh->totlen), length);
        return TPM_BAD_PARAMETER;
    }

    if (bh->min_version > BLOB_HEADER_VERSION) {
        if (!quiet)
            logprintf(STDERR_FILENO,
                      "Minimum required version for the blob is %d, we "
                      "only support version %d\n", bh->min_version,
                      BLOB_HEADER_VERSION);
        return TPM_BAD_VERSION;
    }

    hdrsize = ntohs(bh->hdrsize);
    if (hdrsize != sizeof(blobheader)) {
        logprintf(STDERR_FILENO,
                  "bad header size: %u != %zu\n",
                  hdrsize, sizeof(blobheader));
        return TPM_BAD_DATASIZE;
    }

    *hdrversion = bh->version;
    *dataoffset = hdrsize;
    *hdrflags = ntohs(bh->flags);

    return TPM_SUCCESS;
}

/*
 * Get the state blob with the current name; read it from the filesystem.
 * Decrypt it if the caller asks for it and if a key is set. Return
 * whether it's still encrypyted.
 */
TPM_RESULT SWTPM_NVRAM_GetStateBlob(unsigned char **data,
                                    uint32_t *length,
                                    uint32_t tpm_number,
                                    const char *name,
                                    TPM_BOOL decrypt,
                                    TPM_BOOL *is_encrypted)
{
    TPM_RESULT res;
    uint16_t flags = 0;
    tlv_data td[3];
    size_t td_len;
    unsigned char *plain = NULL, *buffer = NULL;
    uint32_t plain_len, buffer_len = 0;

    *data = NULL;
    *length = 0;

    res = SWTPM_NVRAM_LoadData(&plain, &plain_len, tpm_number, name);
    if (res)
        return res;

    /* @plain contains unencrypted data without tlv headers */

    /* if the user doesn't want decryption and there's a file key, we need to
       encrypt the data */
    if (!decrypt && SWTPM_NVRAM_Has_FileKey()) {
        td_len = 3;
        res = SWTPM_NVRAM_EncryptData(&filekey, &td[0], &td_len,
                                      TAG_ENCRYPTED_DATA, plain, plain_len,
                                      TAG_IVEC_ENCRYPTED_DATA);
        if (res)
            goto err_exit;

        *is_encrypted = TRUE;
        if (SWTPM_NVRAM_FileKey_Size() == SWTPM_AES256_BLOCK_SIZE)
            flags |= BLOB_FLAG_ENCRYPTED_256BIT_KEY;
    } else {
        *is_encrypted = FALSE;
        td[0] = TLV_DATA(TAG_DATA, plain_len, plain);
        plain = NULL;
        td_len = 1;
    }

    res = tlv_data_append(&buffer, &buffer_len, td, td_len);
    if (res)
        goto err_exit;

    tlv_data_free(td, td_len);

    /* @buffer contains tlv data */

    if (SWTPM_NVRAM_Has_MigrationKey()) {
        /* we have to encrypt it now with the migration key */
        flags |= BLOB_FLAG_MIGRATION_ENCRYPTED;
        if (SWTPM_NVRAM_MigrationKey_Size() == SWTPM_AES256_BLOCK_SIZE)
             flags |= BLOB_FLAG_MIGRATION_256BIT_KEY;

        td_len = 3;
        res = SWTPM_NVRAM_EncryptData(&migrationkey, &td[0], &td_len,
                                      TAG_ENCRYPTED_MIGRATION_DATA,
                                      buffer, buffer_len,
                                      TAG_IVEC_ENCRYPTED_MIGRATION_DATA);
        if (res)
            goto err_exit;
    } else {
        td[0] = TLV_DATA(TAG_MIGRATION_DATA, buffer_len, buffer);
        buffer = NULL;
        td_len = 1;
    }
    flags |= BLOB_FLAG_MIGRATION_DATA;

    res = tlv_data_append(data, length, td, td_len);
    if (res)
        goto err_exit;

    /* put the header in clear text */
    if (*is_encrypted)
        flags |= BLOB_FLAG_ENCRYPTED;

    res = SWTPM_NVRAM_PrependHeader(data, length, flags);

err_exit:
    tlv_data_free(td, td_len);
    free(buffer);
    free(plain);

    return res;
}

/*
 * Set the state blob with the given name; the caller tells us if
 * the blob is encrypted; if it is encrypted, it will be written
 * into the file as-is, otherwise it will be encrypted if a key is set.
 */
TPM_RESULT SWTPM_NVRAM_SetStateBlob(unsigned char *data,
                                    uint32_t length,
                                    TPM_BOOL is_encrypted,
                                    uint32_t tpm_number SWTPM_ATTR_UNUSED,
                                    uint32_t blobtype)
{
    TPM_RESULT res;
    uint32_t dataoffset;
    unsigned char *plain = NULL, *mig_decrypt = NULL;
    uint32_t plain_len = 0, mig_decrypt_len = 0;
    uint16_t hdrflags;
    enum TPMLIB_StateType st = tpmlib_blobtype_to_statetype(blobtype);
    const char *blobname = tpmlib_get_blobname(blobtype);
    uint8_t hdrversion;

    if (st == 0) {
        logprintf(STDERR_FILENO,
                  "Unknown blob type %u\n", blobtype);
        return TPM_BAD_PARAMETER;
    }

    if (length == 0)
        return TPMLIB_SetState(st, NULL, 0);

    res = SWTPM_NVRAM_CheckHeader(data, length, &dataoffset, &hdrflags,
                                  &hdrversion, false);
    if (res != TPM_SUCCESS)
        return res;

    if (length - dataoffset == 0)
        return TPMLIB_SetState(st, NULL, 0);

    /*
     * We allow setting of blobs that were not encrypted before;
     * we just will not decrypt them even if the migration key is
     * set. This allows to 'upgrade' to encryption. 'Downgrading'
     * will not be possible once a migration key was used.
     */
    if ((hdrflags & BLOB_FLAG_MIGRATION_ENCRYPTED)) {
        /*
         * we first need to decrypt the data with the migration key
         */
        if (!SWTPM_NVRAM_Has_MigrationKey()) {
            logprintf(STDERR_FILENO,
                      "Missing migration key to decrypt %s\n", blobname);
            return TPM_KEYNOTFOUND;
        }

        res = SWTPM_NVRAM_DecryptData(&migrationkey,
                                      &mig_decrypt, &mig_decrypt_len,
                                      &data[dataoffset], length - dataoffset,
                                      TAG_ENCRYPTED_MIGRATION_DATA,
                                      hdrversion,
                                      TAG_IVEC_ENCRYPTED_MIGRATION_DATA,
                                      hdrflags, BLOB_FLAG_MIGRATION_256BIT_KEY);
        if (res) {
            logprintf(STDERR_FILENO,
                      "Decrypting the %s blob with the migration key failed; "
                      "res = %d\n", blobname, res);
            return res;
        }
    } else {
        res = SWTPM_NVRAM_GetPlainData(&mig_decrypt, &mig_decrypt_len,
                                       &data[dataoffset], length - dataoffset,
                                       TAG_MIGRATION_DATA,
                                       hdrversion);
        if (res)
            return res;
    }

    /*
     * Migration key has decrytped the data; if they are still encrypted
     * with the state encryption key, we need to decrypt them using that
     * key now.
     */
    if (is_encrypted || (hdrflags & BLOB_FLAG_ENCRYPTED)) {
        if (!SWTPM_NVRAM_Has_FileKey()) {
            logprintf(STDERR_FILENO,
                      "Missing state key to decrypt %s\n", blobname);
            res = TPM_KEYNOTFOUND;
            goto cleanup;
        }

        res = SWTPM_NVRAM_DecryptData(&filekey, &plain, &plain_len,
                                      mig_decrypt, mig_decrypt_len,
                                      TAG_ENCRYPTED_DATA,
                                      hdrversion, TAG_IVEC_ENCRYPTED_DATA,
                                      hdrflags, BLOB_FLAG_ENCRYPTED_256BIT_KEY);
        if (res) {
            logprintf(STDERR_FILENO,
                      "Decrypting the %s blob with the state key "
                      "failed; res = %d\n", blobname, res);
            goto cleanup;
        }
    } else {
        res = SWTPM_NVRAM_GetPlainData(&plain, &plain_len,
                                       mig_decrypt, mig_decrypt_len,
                                       TAG_DATA,
                                       hdrversion);
        if (res)
            goto cleanup;
    }

    /* SetState will make a copy of the buffer */
    res = TPMLIB_SetState(st, plain, plain_len);

    free(plain);

cleanup:
    free(mig_decrypt);

    return res;
}

/* Example JSON output:
 *  { "type": "swtpm",
 *    "states": [ "permall", "volatilestate", "savestate" ]
 *  }
 */
int SWTPM_NVRAM_PrintJson(void)
{
    TPM_RESULT rc = 0;
    const char *backend_uri;
    const char *states[] = {
        TPM_PERMANENT_ALL_NAME,
        TPM_VOLATILESTATE_NAME,
        TPM_SAVESTATE_NAME,
    };
    char state_str[200] = "";
    size_t i, n, o, blobsize;
    int ret = -1;

    rc = SWTPM_NVRAM_Init();
    if (rc == 0) {
        o = 0;
        backend_uri = tpmstate_get_backend_uri();

        for (i = 0; i < ARRAY_LEN(states); i++) {
            rc = g_nvram_backend_ops->check_state(backend_uri, states[i],
                                                  &blobsize);
            if (rc == TPM_SUCCESS) {
                n = snprintf(&state_str[o], sizeof(state_str) - o,
                             "%s {\"name\": \"%s\", \"size\": %zu}",
                             (o > 0) ? "," : "",
                             states[i], blobsize);
                if (n >= sizeof(state_str) - o)
                    goto exit;
                o += n;
            } else if (rc != TPM_RETRY) {
                /* Error other than ENOENT */
                goto exit;
            }
        }
        printf("{ \"type\": \"swtpm\", \"states\": [%s%s] }",
               state_str,  (o > 0) ? " ": "");
        ret = 0;
    }

exit:
    return ret;
}
