/*
 * tpm_funcs.c -- interface with libtpms
 *
 * (c) Copyright IBM Corporation 2015.
 *
 * Author: Stefan Berger <stefanb@us.ibm.com>
 *
 * 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.
 */

#include "config.h"

#include "sys_dependencies.h"

#include <assert.h>
#include <string.h>
#include <stdlib.h>

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

#include "tpmlib.h"
#include "logging.h"
#include "tpm_ioctl.h"
#include "swtpm_nvstore.h"
#include "locality.h"
#ifdef WITH_VTPM_PROXY
#include "vtpm_proxy.h"
#endif
#include "utils.h"
#include "compiler_dependencies.h"
#include "swtpm_utils.h"
#include "fips.h"

/*
 * convert the blobtype integer into a string that libtpms
 * understands
 */
const char *tpmlib_get_blobname(uint32_t blobtype)
{
    switch (blobtype) {
    case PTM_BLOB_TYPE_PERMANENT:
        return TPM_PERMANENT_ALL_NAME;
    case PTM_BLOB_TYPE_VOLATILE:
        return TPM_VOLATILESTATE_NAME;
    case PTM_BLOB_TYPE_SAVESTATE:
        return TPM_SAVESTATE_NAME;
    default:
        return NULL;
    }
}

TPM_RESULT tpmlib_register_callbacks(struct libtpms_callbacks *cbs)
{
    TPM_RESULT res;

    if ((res = TPMLIB_RegisterCallbacks(cbs)) != TPM_SUCCESS) {
        logprintf(STDERR_FILENO,
                  "Error: Could not register the callbacks.\n");
    }
    return res;
}

TPM_RESULT tpmlib_choose_tpm_version(TPMLIB_TPMVersion tpmversion)
{
    TPM_RESULT res;

    if ((res = TPMLIB_ChooseTPMVersion(tpmversion)) != TPM_SUCCESS) {
        const char *version = "TPM 1.2";

        if (tpmversion == TPMLIB_TPM_VERSION_2)
            version = "TPM 2";
        logprintf(STDERR_FILENO,
                  "Error: %s is not supported by libtpms.\n", version);
    }
    return res;
}

TPM_RESULT tpmlib_start(uint32_t flags, TPMLIB_TPMVersion tpmversion,
                        bool lock_nvram)
{
    TPM_RESULT res;

    if ((res = tpmlib_choose_tpm_version(tpmversion)) != TPM_SUCCESS)
        return res;

    if ((res = TPMLIB_MainInit()) != TPM_SUCCESS) {
        logprintf(STDERR_FILENO,
                  "Error: Could not initialize libtpms.\n");
        return res;
    }

    if (lock_nvram && (res = SWTPM_NVRAM_Lock_Storage(0)) != TPM_SUCCESS)
        goto error_terminate;

    if (flags & PTM_INIT_FLAG_DELETE_VOLATILE) {
        uint32_t tpm_number = 0;
        char *name = TPM_VOLATILESTATE_NAME;
        res = SWTPM_NVRAM_DeleteName(tpm_number,
                                     name,
                                     FALSE);
        if (res != TPM_SUCCESS) {
            logprintf(STDERR_FILENO,
                      "Error: Could not delete the volatile "
                      "state of the TPM.\n");
            goto error_terminate;
        }
    }

    if (fips_mode_enabled() && fips_mode_disable() < 0)
        goto error_terminate;

    return TPM_SUCCESS;

error_terminate:
    TPMLIB_Terminate();

    return res;
}

int tpmlib_get_tpm_property(enum TPMLIB_TPMProperty prop)
{
    int result = 0;
    TPM_RESULT res;

    res = TPMLIB_GetTPMProperty(prop, &result);

    assert(res == TPM_SUCCESS);

    return result;
}

uint32_t tpmlib_get_cmd_ordinal(const unsigned char *request, size_t req_len)
{
    struct tpm_req_header *hdr;

    if (req_len < sizeof(struct tpm_req_header))
        return TPM_ORDINAL_NONE;

    hdr = (struct tpm_req_header *)request;
    return be32toh(hdr->ordinal);
}

bool tpmlib_is_request_cancelable(TPMLIB_TPMVersion tpmversion,
                                  const unsigned char *request, size_t req_len)
{

    uint32_t ordinal = tpmlib_get_cmd_ordinal(request, req_len);

    if (ordinal == TPM_ORDINAL_NONE)
        return false;

    if (tpmversion == TPMLIB_TPM_VERSION_2)
        return (ordinal == TPMLIB_TPM2_CC_CreatePrimary ||
                ordinal == TPMLIB_TPM2_CC_Create);

    return (ordinal == TPMLIB_TPM_ORD_TakeOwnership ||
            ordinal == TPMLIB_TPM_ORD_CreateWrapKey);
}

static void tpmlib_write_error_response(unsigned char **rbuffer,
                                        uint32_t *rlength,
                                        uint32_t *rTotal,
                                        TPM_RESULT errcode,
                                        TPMLIB_TPMVersion tpmversion)
{
    struct tpm_resp_header errresp = {
        .tag = (tpmversion == TPMLIB_TPM_VERSION_2)
               ? htobe16(0x8001)
               : htobe16(0xc4),
        .size = htobe32(sizeof(errresp)),
        .errcode = htobe32(errcode),
    };

    if (*rbuffer == NULL ||
        *rTotal < sizeof(errresp)) {
        free(*rbuffer);

        *rbuffer = malloc(sizeof(errresp));
        if (*rbuffer)
            *rTotal = sizeof(errresp);
        else
            *rTotal = 0;
    }
    if (*rbuffer) {
        *rlength = sizeof(errresp);
        memcpy(*rbuffer, &errresp, sizeof(errresp));
    }
}

void tpmlib_write_fatal_error_response(unsigned char **rbuffer,
                                       uint32_t *rlength,
                                       uint32_t *rTotal,
                                       TPMLIB_TPMVersion tpmversion)
{
    TPM_RESULT errcode = (tpmversion == TPMLIB_TPM_VERSION_2)
                         ? TPM_RC_FAILURE
                         : TPM_FAIL;

    tpmlib_write_error_response(rbuffer, rlength, rTotal, errcode,
                                tpmversion);
}

void tpmlib_write_locality_error_response(unsigned char **rbuffer,
                                          uint32_t *rlength,
                                          uint32_t *rTotal,
                                          TPMLIB_TPMVersion tpmversion)
{
    TPM_RESULT errcode = (tpmversion == TPMLIB_TPM_VERSION_2)
                         ? TPM_RC_LOCALITY
                         : TPM_BAD_LOCALITY;

    tpmlib_write_error_response(rbuffer, rlength, rTotal, errcode,
                                tpmversion);
}

void tpmlib_write_success_response(unsigned char **rbuffer,
                                   uint32_t *rlength,
                                   uint32_t *rTotal,
                                   TPMLIB_TPMVersion tpmversion)
{
    tpmlib_write_error_response(rbuffer, rlength, rTotal, 0,
                                tpmversion);
}

#ifdef WITH_VTPM_PROXY
static void tpmlib_write_shortmsg_error_response(unsigned char **rbuffer,
                                                 uint32_t *rlength,
                                                 uint32_t *rTotal,
                                                 TPMLIB_TPMVersion tpmversion)
{
    TPM_RESULT errcode = (tpmversion == TPMLIB_TPM_VERSION_2)
                         ? TPM_RC_INSUFFICIENT
                         : TPM_BAD_PARAM_SIZE;

    tpmlib_write_error_response(rbuffer, rlength, rTotal, errcode,
                                tpmversion);
}

static TPM_RESULT tpmlib_process_setlocality(unsigned char **rbuffer,
                                             uint32_t *rlength,
                                             uint32_t *rTotal,
                                             unsigned char *command,
                                             uint32_t command_length,
                                             TPMLIB_TPMVersion tpmversion,
                                             uint32_t locality_flags,
                                             TPM_MODIFIER_INDICATOR *locality)
{
    TPM_MODIFIER_INDICATOR new_locality;

    if (command_length >= sizeof(struct tpm_req_header) + sizeof(char)) {
        if (!(locality_flags & LOCALITY_FLAG_ALLOW_SETLOCALITY)) {
            /* SETLOCALITY command is not allowed */
            tpmlib_write_fatal_error_response(rbuffer,
                                              rlength, rTotal,
                                              tpmversion);
        } else {
            new_locality = command[sizeof(struct tpm_req_header)];
            if (new_locality >=5 ||
                (new_locality == 4 &&
                 locality_flags & LOCALITY_FLAG_REJECT_LOCALITY_4)) {
                tpmlib_write_locality_error_response(rbuffer,
                                                     rlength, rTotal,
                                                    tpmversion);
            } else {
                tpmlib_write_success_response(rbuffer,
                                              rlength, rTotal,
                                              tpmversion);
                *locality = new_locality;
            }
        }
    } else {
        tpmlib_write_shortmsg_error_response(rbuffer,
                                             rlength, rTotal,
                                             tpmversion);
    }
    return TPM_SUCCESS;
}

TPM_RESULT tpmlib_process(unsigned char **rbuffer,
                          uint32_t *rlength,
                          uint32_t *rTotal,
                          unsigned char *command,
                          uint32_t command_length,
                          uint32_t locality_flags,
                          TPM_MODIFIER_INDICATOR *locality,
                          TPMLIB_TPMVersion tpmversion)
{
    /* process those commands we need to handle, e.g. SetLocality */
    struct tpm_req_header *req = (struct tpm_req_header *)command;
    uint32_t ordinal;

    if (command_length < sizeof(*req)) {
        tpmlib_write_shortmsg_error_response(rbuffer,
                                             rlength, rTotal,
                                             tpmversion);
        return TPM_SUCCESS;
    }

    ordinal = be32toh(req->ordinal);

    switch (tpmversion) {
    case TPMLIB_TPM_VERSION_1_2:
        switch (ordinal) {
        case TPM_CC_SET_LOCALITY:
            return tpmlib_process_setlocality(rbuffer, rlength, rTotal,
                                              command, command_length,
                                              tpmversion, locality_flags,
                                              locality);
        }
        break;

    case TPMLIB_TPM_VERSION_2:
        switch (ordinal) {
        case TPM2_CC_SET_LOCALITY:
            return tpmlib_process_setlocality(rbuffer, rlength, rTotal,
                                              command, command_length,
                                              tpmversion, locality_flags,
                                              locality);
        }
        break;
    }
    return TPM_SUCCESS;
}

#else

TPM_RESULT tpmlib_process(unsigned char **rbuffer SWTPM_ATTR_UNUSED,
                          uint32_t *rlength SWTPM_ATTR_UNUSED,
                          uint32_t *rTotal SWTPM_ATTR_UNUSED,
                          unsigned char *command SWTPM_ATTR_UNUSED,
                          uint32_t command_length SWTPM_ATTR_UNUSED,
                          uint32_t locality_flags SWTPM_ATTR_UNUSED,
                          TPM_MODIFIER_INDICATOR *locality SWTPM_ATTR_UNUSED,
                          TPMLIB_TPMVersion tpmversion SWTPM_ATTR_UNUSED)
{
    return TPM_SUCCESS;
}

#endif /* WITH_VTPM_PROXY */

enum TPMLIB_StateType tpmlib_blobtype_to_statetype(uint32_t blobtype)
{
    switch (blobtype) {
    case PTM_BLOB_TYPE_PERMANENT:
        return TPMLIB_STATE_PERMANENT;
    case PTM_BLOB_TYPE_VOLATILE:
        return TPMLIB_STATE_VOLATILE;
    case PTM_BLOB_TYPE_SAVESTATE:
        return TPMLIB_STATE_SAVE_STATE;
    }
    return 0;
}

/*
 * tpmlib_handle_tcg_tpm2_cmd_header
 *
 * Determine whether the given byte stream is a raw TPM 2 command or
 * whether it has a tcg_tpm2_cmd_header prefixed and if so return
 * the offset after the header where the actual command is. In all
 * other cases return 0.
 */
off_t tpmlib_handle_tcg_tpm2_cmd_header(const unsigned char *command,
                                        uint32_t command_length,
                                        TPM_MODIFIER_INDICATOR *locality)
{
    struct tpm_req_header *hdr = (struct tpm_req_header *)command;
    struct tpm2_send_command_prefix *tcgprefix;
    off_t ret = 0;

    /* return 0 for short packets or plain TPM 2 command */
    if (command_length < sizeof(*hdr) ||
        be16toh(hdr->tag) == TPM2_ST_NO_SESSION ||
        be16toh(hdr->tag) == TPM2_ST_SESSIONS ||
        command_length < sizeof(*tcgprefix))
        return 0;

    tcgprefix = (struct tpm2_send_command_prefix *)command;
    if (be32toh(tcgprefix->cmd) == TPM2_SEND_COMMAND) {
        ret = sizeof(*tcgprefix);
        *locality = tcgprefix->locality;
    }

    return ret;
}

/*
 * Create a Startup command with the given startupType for the
 * given TPM version.
 */
uint32_t tpmlib_create_startup_cmd(uint16_t startupType,
                                   TPMLIB_TPMVersion tpmversion,
                                   unsigned char *buffer,
                                   uint32_t buffersize)
{
    struct tpm_startup ts;
    uint32_t tocopy = min(sizeof(ts), buffersize);

    ts.hdr.size = htobe32(sizeof(ts));

    switch (tpmversion) {
    case TPMLIB_TPM_VERSION_1_2:
        ts.hdr.tag = htobe16(TPM_TAG_RQU_COMMAND);
        ts.hdr.ordinal = htobe32(TPMLIB_TPM_ORD_Startup);
        ts.startupType = htobe16(startupType);
        break;
    case TPMLIB_TPM_VERSION_2:
        ts.hdr.tag = htobe16(TPM2_ST_NO_SESSION);
        ts.hdr.ordinal = htobe32(TPMLIB_TPM2_CC_Startup);
        switch (startupType) {
        case TPM_ST_CLEAR:
            ts.startupType = htobe16(TPM2_SU_CLEAR);
            break;
        case TPM_ST_STATE:
            ts.startupType = htobe16(TPM2_SU_STATE);
            break;
        case TPM_ST_DEACTIVATED:
            tocopy = 0;
            logprintf(STDERR_FILENO,
                      "TPM 2 does not support startup deactivated.\n");
            break;
        default:
            tocopy = 0;
            logprintf(STDERR_FILENO,
                      "%s: internal error; unsupported startup type for TPM 2\n", __func__);
            break;
        }
        break;
    default:
        tocopy = 0;
        logprintf(STDERR_FILENO,
                  "%s: internal error; invalid TPM version\n", __func__);
        break;
    }

    if (tocopy)
        memcpy(buffer, &ts, tocopy);
    return tocopy;
}

/*
 * tpmlib_maybe_send_tpm2_shutdown: Send a TPM2_Shutdown() if necesssary
 *
 * @tpmversion: version of TPM
 * @lastCommand: last command that was sent to the TPM 2 before
 *               this will be updated if TPM2_Shutdown was sent
 *
 * Send a TPM2_Shutdown(SU_STATE) to a TPM 2 if the last-processed command was
 * not TPM2_Shutdown. If the command fails, send TPM2_Shutdown(SU_CLEAR).
 */
void tpmlib_maybe_send_tpm2_shutdown(TPMLIB_TPMVersion tpmversion,
                                     uint32_t *lastCommand)
{
    TPM_RESULT res;
    unsigned char *rbuffer = NULL;
    uint32_t rlength = 0;
    uint32_t rTotal = 0;
    struct tpm2_shutdown {
        struct tpm_req_header hdr;
        uint16_t shutdownType;
    } tpm2_shutdown = {
        .hdr = {
            .tag = htobe16(TPM2_ST_NO_SESSION),
            .size = htobe32(sizeof(tpm2_shutdown)),
            .ordinal = htobe32(TPMLIB_TPM2_CC_Shutdown),
        },
    };
    uint16_t shutdownTypes[2] = {
        TPM2_SU_STATE, TPM2_SU_CLEAR,
    };
    struct tpm_resp_header *rsp;
    uint32_t errcode;
    size_t i;

    /* Only send TPM2_Shutdown for a TPM 2 and only if TPM2_Shutdown()
     * was not already sent. Send a TPM2_Shutdown(SU_STATE) first since
     * this is preserves additional state that will not matter if the
     * VM starts with TPM2_Startup(SU_CLEAR). Only if this command fails
     * send TPM2_Shutdown(SU_CLEAR).
     */
    if (tpmversion != TPMLIB_TPM_VERSION_2 ||
        *lastCommand == TPMLIB_TPM2_CC_Shutdown)
        return;

    for (i = 0; i < ARRAY_LEN(shutdownTypes); i++) {
#if 0
        logprintf(STDERR_FILENO,
                  "Need to send TPM2_Shutdown(%s); previous command: 0x%08x\n",
                  shutdownTypes[i] == TPM2_SU_STATE ? "SU_STATE" : "SU_CLEAR",
                  *lastCommand);
#endif
        tpm2_shutdown.shutdownType = htobe16(shutdownTypes[i]);

        res = TPMLIB_Process(&rbuffer, &rlength, &rTotal,
                             (unsigned char *)&tpm2_shutdown,
                             sizeof(tpm2_shutdown));

        if (res || rlength < sizeof(struct tpm_resp_header))
            continue;
        rsp = (struct tpm_resp_header *)rbuffer;

        errcode = be32toh(rsp->errcode);
        if (errcode == TPM_SUCCESS) {
            *lastCommand = TPMLIB_TPM2_CC_Shutdown;
            break;
        } else if (errcode == TPM_RC_INITIALIZE) {
            /* TPM not initialized by TPM2_Startup - won't work */
            break;
        }
    }
    free(rbuffer);
}
