/**
 * \file
 * \brief Utilities to create and verify a JSON Web Token (JWT)
 *
 * \copyright (c) 2015-2020 Microchip Technology Inc. and its subsidiaries.
 *
 * \page License
 *
 * Subject to your compliance with these terms, you may use Microchip software
 * and any derivatives exclusively with Microchip products. It is your
 * responsibility to comply with third party license terms applicable to your
 * use of third party software (including open source software) that may
 * accompany Microchip software.
 *
 * THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES, WHETHER
 * EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE, INCLUDING ANY IMPLIED
 * WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, AND FITNESS FOR A
 * PARTICULAR PURPOSE. IN NO EVENT WILL MICROCHIP BE LIABLE FOR ANY INDIRECT,
 * SPECIAL, PUNITIVE, INCIDENTAL OR CONSEQUENTIAL LOSS, DAMAGE, COST OR EXPENSE
 * OF ANY KIND WHATSOEVER RELATED TO THE SOFTWARE, HOWEVER CAUSED, EVEN IF
 * MICROCHIP HAS BEEN ADVISED OF THE POSSIBILITY OR THE DAMAGES ARE
 * FORESEEABLE. TO THE FULLEST EXTENT ALLOWED BY LAW, MICROCHIP'S TOTAL
 * LIABILITY ON ALL CLAIMS IN ANY WAY RELATED TO THIS SOFTWARE WILL NOT EXCEED
 * THE AMOUNT OF FEES, IF ANY, THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR
 * THIS SOFTWARE.
 */

#include "cryptoauthlib.h"
#include "basic/atca_helpers.h"
#include "crypto/atca_crypto_sw_sha2.h"
#include "jwt/atca_jwt.h"
#include <stdio.h>

/** \brief The only supported JWT format for this library */
static const char g_jwt_header[] = "{\"alg\":\"ES256\",\"typ\":\"JWT\"}";

/**
 * \brief Check the provided context to see what character needs to be added in
 * order to append a claim
 */
void atca_jwt_check_payload_start(
    atca_jwt_t* jwt     /**< [in] JWT Context to use */
    )
{
    /* Rationality checks: a) must be valid, b) buf must be valid, c) must not be at the start, d) must have room */
    if (jwt && jwt->buf && jwt->cur && (jwt->cur < jwt->buflen - 1))
    {
        /* Check the previous */
        char c = jwt->buf[jwt->cur - 1];
        if ('.' == c)
        {
            jwt->buf[jwt->cur++] = '{';
        }
        else if ('{' != c)
        {
            jwt->buf[jwt->cur++] = ',';
        }
    }
}

/**
 * \brief Initialize a JWT structure
 */
ATCA_STATUS atca_jwt_init(
    atca_jwt_t* jwt,    /**< [in] JWT Context to initialize */
    char*       buf,    /**< [inout] Pointer to a buffer to store the token */
    uint16_t    buflen  /**< [in] Length of the buffer */
    )
{
    ATCA_STATUS ret = ATCA_BAD_PARAM;
    size_t tSize;

    if (jwt && buf && buflen)
    {
        jwt->buf = buf;
        jwt->buflen = buflen;
        jwt->cur = 0;

        /* Encode the header into the buffer */
        tSize = jwt->buflen;
        ret = atcab_base64encode_((const uint8_t*)g_jwt_header, strlen(g_jwt_header), jwt->buf,
                                  &tSize, atcab_b64rules_urlsafe);
        if (ATCA_SUCCESS == ret)
        {
            jwt->cur += (uint16_t)tSize;

            /* Check length */
            if (jwt->cur < jwt->buflen - 1)
            {
                /* Add the separator */
                jwt->buf[jwt->cur++] = '.';
            }
            else
            {
                ret = ATCA_INVALID_SIZE;
            }
        }
    }
    return ret;
}

/**
 * \brief Close the claims of a token, encode them, then sign the result
 */
ATCA_STATUS atca_jwt_finalize(
    atca_jwt_t* jwt,    /**< [in] JWT Context to use */
    uint16_t    key_id  /**< [in] Key Id (Slot number) used to sign */
    )
{
    ATCA_STATUS status;
    uint16_t i;
    size_t rem;
    size_t tSize;

    if (!jwt || !jwt->buf || !jwt->buflen || !jwt->cur)
    {
        return ATCA_BAD_PARAM;
    }

    /* Verify the payload is closed */
    if ('}' != jwt->buf[jwt->cur - 1])
    {
        jwt->buf[jwt->cur++] = '}';
    }

    /* Find the start of the "claims" portion of the token - header should
       already be encoded */
    for (i = 0; i < jwt->cur; i++)
    {
        if ('.' == jwt->buf[i])
        {
            i++;
            break;
        }
    }

    /* Make sure there is enough remaining buffer given base64 4/3 expansion */
    rem = (jwt->cur - i + ATCA_SIG_SIZE) * 4;
    rem /= 3;

    /* Increase Count to accomodate: 1 for the '.', 1 for the null terminator,
        and 1 for padding */
    rem += 3;

    if (rem > (size_t)(jwt->buflen - jwt->cur))
    {
        return ATCA_INVALID_SIZE;
    }

    /* Calculate the payload length */
    rem = jwt->cur - i;
    /* Move the payload to make room for the encoding */
    memmove(jwt->buf + jwt->buflen - jwt->cur, &jwt->buf[i], rem);

    /* Encode the payload into the buffer */
    tSize = jwt->buflen;
    status = atcab_base64encode_((uint8_t*)(jwt->buf + jwt->buflen - jwt->cur), rem,
                                 &jwt->buf[i], &tSize, atcab_b64rules_urlsafe);
    if (ATCA_SUCCESS != status)
    {
        return status;
    }

    jwt->cur = (uint16_t)(i + tSize);

    /* Make sure there room to add the signature
        ECDSA(P256) -> 64 bytes -> base64 -> 86.3 (87) -> 88 including null */
    if (jwt->cur >= jwt->buflen - 88)
    {
        /* Something broke */
        return ATCA_INVALID_SIZE;
    }

    /* Create digest of the message store and store in the buffer */
    status = atcac_sw_sha2_256((const uint8_t*)jwt->buf, jwt->cur, (uint8_t*)(jwt->buf + jwt->buflen - 32));
    if (ATCA_SUCCESS != status)
    {
        return status;
    }

    /* Create ECSDA signature of the digest and store it back in the buffer */
    status = atcab_sign(key_id, (const uint8_t*)(jwt->buf + jwt->buflen - ATCA_SHA_DIGEST_SIZE),
                        (uint8_t*)(jwt->buf + jwt->buflen - 64));
    if (ATCA_SUCCESS != status)
    {
        return status;
    }

    /* Add the separator */
    jwt->buf[jwt->cur++] = '.';

    /* Encode the signature and store it in the buffer */
    tSize = jwt->buflen - jwt->cur;
    atcab_base64encode_((const uint8_t*)(jwt->buf + jwt->buflen - ATCA_SIG_SIZE), ATCA_SIG_SIZE,
                        &jwt->buf[jwt->cur], &tSize, atcab_b64rules_urlsafe);
    jwt->cur += (uint16_t)tSize;

    if (jwt->cur >= jwt->buflen)
    {
        /* Something broke */
        return ATCA_INVALID_SIZE;
    }

    /* Make sure resulting buffer is null terminated */
    jwt->buf[jwt->cur] = 0;

    return status;
}

/**
 * \brief Add a string claim to a token
 * \note This function does not escape strings so the user has to ensure they
 *       are valid for use in a JSON string first
 */
ATCA_STATUS atca_jwt_add_claim_string(
    atca_jwt_t* jwt,    /**< [in] JWT Context to use */
    const char* claim,  /**< [in] Name of the claim to be inserted */
    const char* value   /**< [in] Null terminated string to be insterted */
    )
{
    int32_t written;
    int32_t remaining;

    if (jwt && jwt->buf && jwt->buflen && claim && value)
    {
        atca_jwt_check_payload_start(jwt);

        remaining = jwt->buflen - jwt->cur;
        written = snprintf(&jwt->buf[jwt->cur], remaining, "\"%s\":\"%s\"", claim, value);
        if (0 < written && written < remaining)
        {
            jwt->cur += written;
            return ATCA_SUCCESS;
        }
        else
        {
            return ATCA_GEN_FAIL;
        }
    }
    else
    {
        return ATCA_BAD_PARAM;
    }
}

/**
 * \brief Add a numeric claim to a token
 * \note This function does not escape strings so the user has to ensure the
 *       claim is valid first
 */
ATCA_STATUS atca_jwt_add_claim_numeric(
    atca_jwt_t* jwt,    /**< [in] JWT Context to use */
    const char* claim,  /**< [in] Name of the claim to be inserted */
    int32_t     value   /**< [in] integer value to be inserted */
    )
{
    int32_t written;
    int32_t remaining;

    if (jwt && jwt->buf && jwt->buflen && claim)
    {
        atca_jwt_check_payload_start(jwt);

        remaining = jwt->buflen - jwt->cur;
        written = snprintf(&jwt->buf[jwt->cur], remaining, "\"%s\":%ld", claim, (long)value);
        if (0 < written && written < remaining)
        {
            jwt->cur += written;
            return 0;
        }
        else
        {
            return ATCA_GEN_FAIL;
        }
    }
    else
    {
        return ATCA_BAD_PARAM;
    }
}

/**
 * \brief Verifies the signature of a jwt using the provided public key
 */
ATCA_STATUS atca_jwt_verify(
    const char*    buf,      /**< [in] Buffer holding an encoded jwt */
    uint16_t       buflen,   /**< [in] Length of the buffer/jwt */
    const uint8_t* pubkey    /**< [in] Public key (raw byte format) */
    )
{
    ATCA_STATUS status = ATCA_GEN_FAIL;
    uint8_t digest[ATCA_KEY_SIZE];
    uint8_t signature[ATCA_SIG_SIZE];
    size_t sig_len = sizeof(signature);
    const char* pStr = buf;

    bool verified = false;

    if (!buf || !buflen || !pubkey)
    {
        return ATCA_BAD_PARAM;
    }

    do
    {
        /* Payload */
        pStr = strchr(pStr, '.') + 1;
        if (!pStr)
        {
            break;
        }

        /* Signature */
        pStr = strchr(pStr, '.') + 1;
        if (!pStr)
        {
            break;
        }

        /* Extract the signature */
        if (ATCA_SUCCESS != (status = atcab_base64decode_(pStr, strlen(pStr),
                                                          signature, &sig_len, atcab_b64rules_urlsafe)))
        {
            break;
        }

        /* Digest the token */
        if (ATCA_SUCCESS != (status = atcac_sw_sha2_256((const uint8_t*)buf, pStr - buf - 1, digest)))
        {
            break;
        }

        /* Do a signature verification using the device */
        if (ATCA_SUCCESS != (status = atcab_verify_extern(digest, signature,
                                                          pubkey, &verified)))
        {
            break;
        }

        if (!verified)
        {
            status = ATCA_CHECKMAC_VERIFY_FAILED;
        }
    }
    while (0);

    return status;
}
