/* hmac_prng.c - TinyCrypt implementation of HMAC-PRNG */

/*
    Copyright (C) 2017 by Intel Corporation, 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 name of Intel 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 OWNER 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 <tinycrypt/hmac_prng.h>
#include <tinycrypt/hmac.h>
#include <tinycrypt/constants.h>
#include <tinycrypt/utils.h>

/*
    min bytes in the seed string.
    MIN_SLEN*8 must be at least the expected security level.
*/
static const unsigned int MIN_SLEN = 32;

/*
    max bytes in the seed string;
    SP800-90A specifies a maximum of 2^35 bits (i.e., 2^32 bytes).
*/
static const unsigned int MAX_SLEN = UINT32_MAX;

/*
    max bytes in the personalization string;
    SP800-90A specifies a maximum of 2^35 bits (i.e., 2^32 bytes).
*/
static const unsigned int MAX_PLEN = UINT32_MAX;

/*
    max bytes in the additional_info string;
    SP800-90A specifies a maximum of 2^35 bits (i.e., 2^32 bytes).
*/
static const unsigned int MAX_ALEN = UINT32_MAX;

/*
    max number of generates between re-seeds;
    TinyCrypt accepts up to (2^32 - 1) which is the maximal value of
    a 32-bit unsigned int variable, while SP800-90A specifies a maximum of 2^48.
*/
static const unsigned int MAX_GENS = UINT32_MAX;

/*
    maximum bytes per generate call;
    SP800-90A specifies a maximum up to 2^19.
*/
static const unsigned int  MAX_OUT = (1 << 19);

/*
    Assumes: prng != NULL, e != NULL, len >= 0.
*/
static void update(TCHmacPrng_t prng, const uint8_t* e, unsigned int len)
{
    const uint8_t separator0 = 0x00;
    const uint8_t separator1 = 0x01;
    /* use current state, e and separator 0 to compute a new prng key: */
    (void)tc_hmac_init(&prng->h);
    (void)tc_hmac_update(&prng->h, prng->v, sizeof(prng->v));
    (void)tc_hmac_update(&prng->h, &separator0, sizeof(separator0));
    (void)tc_hmac_update(&prng->h, e, len);
    (void)tc_hmac_final(prng->key, sizeof(prng->key), &prng->h);
    /* configure the new prng key into the prng's instance of hmac */
    (void)tc_hmac_set_key(&prng->h, prng->key, sizeof(prng->key));
    /* use the new key to compute a new state variable v */
    (void)tc_hmac_init(&prng->h);
    (void)tc_hmac_update(&prng->h, prng->v, sizeof(prng->v));
    (void)tc_hmac_final(prng->v, sizeof(prng->v), &prng->h);
    /* use current state, e and separator 1 to compute a new prng key: */
    (void)tc_hmac_init(&prng->h);
    (void)tc_hmac_update(&prng->h, prng->v, sizeof(prng->v));
    (void)tc_hmac_update(&prng->h, &separator1, sizeof(separator1));
    (void)tc_hmac_update(&prng->h, e, len);
    (void)tc_hmac_final(prng->key, sizeof(prng->key), &prng->h);
    /* configure the new prng key into the prng's instance of hmac */
    (void)tc_hmac_set_key(&prng->h, prng->key, sizeof(prng->key));
    /* use the new key to compute a new state variable v */
    (void)tc_hmac_init(&prng->h);
    (void)tc_hmac_update(&prng->h, prng->v, sizeof(prng->v));
    (void)tc_hmac_final(prng->v, sizeof(prng->v), &prng->h);
}

int tc_hmac_prng_init(TCHmacPrng_t prng,
                      const uint8_t* personalization,
                      unsigned int plen)
{
    /* input sanity check: */
    if (prng == (TCHmacPrng_t) 0 ||
            personalization == (uint8_t*) 0 ||
            plen > MAX_PLEN)
    {
        return TC_CRYPTO_FAIL;
    }

    /* put the generator into a known state: */
    _set(prng->key, 0x00, sizeof(prng->key));
    _set(prng->v, 0x01, sizeof(prng->v));
    tc_hmac_set_key(&prng->h, prng->key, sizeof(prng->key));
    /* update assumes SOME key has been configured into HMAC */
    update(prng, personalization, plen);
    /* force a reseed before allowing tc_hmac_prng_generate to succeed: */
    prng->countdown = 0;
    return TC_CRYPTO_SUCCESS;
}

int tc_hmac_prng_reseed(TCHmacPrng_t prng,
                        const uint8_t* seed,
                        unsigned int seedlen,
                        const uint8_t* additional_input,
                        unsigned int additionallen)
{
    /* input sanity check: */
    if (prng == (TCHmacPrng_t) 0 ||
            seed == (const uint8_t*) 0 ||
            seedlen < MIN_SLEN ||
            seedlen > MAX_SLEN)
    {
        return TC_CRYPTO_FAIL;
    }

    if (additional_input != (const uint8_t*) 0)
    {
        /*
            Abort if additional_input is provided but has inappropriate
            length
        */
        if (additionallen == 0 ||
                additionallen > MAX_ALEN)
        {
            return TC_CRYPTO_FAIL;
        }
        else
        {
            /* call update for the seed and additional_input */
            update(prng, seed, seedlen);
            update(prng, additional_input, additionallen);
        }
    }
    else
    {
        /* call update only for the seed */
        update(prng, seed, seedlen);
    }

    /* ... and enable hmac_prng_generate */
    prng->countdown = MAX_GENS;
    return TC_CRYPTO_SUCCESS;
}

int tc_hmac_prng_generate(uint8_t* out, unsigned int outlen, TCHmacPrng_t prng)
{
    unsigned int bufferlen;

    /* input sanity check: */
    if (out == (uint8_t*) 0 ||
            prng == (TCHmacPrng_t) 0 ||
            outlen == 0 ||
            outlen > MAX_OUT)
    {
        return TC_CRYPTO_FAIL;
    }
    else if (prng->countdown == 0)
    {
        return TC_HMAC_PRNG_RESEED_REQ;
    }

    prng->countdown--;

    while (outlen != 0)
    {
        /* operate HMAC in OFB mode to create "random" outputs */
        (void)tc_hmac_init(&prng->h);
        (void)tc_hmac_update(&prng->h, prng->v, sizeof(prng->v));
        (void)tc_hmac_final(prng->v, sizeof(prng->v), &prng->h);
        bufferlen = (TC_SHA256_DIGEST_SIZE > outlen) ?
                    outlen : TC_SHA256_DIGEST_SIZE;
        (void)_copy(out, bufferlen, prng->v, bufferlen);
        out += bufferlen;
        outlen = (outlen > TC_SHA256_DIGEST_SIZE) ?
                 (outlen - TC_SHA256_DIGEST_SIZE) : 0;
    }

    /* block future PRNG compromises from revealing past state */
    update(prng, prng->v, TC_SHA256_DIGEST_SIZE);
    return TC_CRYPTO_SUCCESS;
}
