#include "k5-int.h"
#include "aead.h"

static inline krb5_data
make_data(void *data, unsigned int len)
{
    krb5_data d;

    d.magic = KV5M_DATA;
    d.data = (char *) data;
    d.length = len;
    return d;
}

/*
 * NIST SP800-108 KDF in counter mode (section 5.1).
 * Parameters:
 *   - HMAC (with hash as the hash provider) is the PRF.
 *   - A block counter of four bytes is used.
 *   - Four bytes are used to encode the output length in the PRF input.
 *
 * There are no uses requiring more than a single PRF invocation.
 */
krb5_error_code
k5_sp800_108_counter_hmac(const struct krb5_hash_provider *hash,
                          const krb5_keyblock *inkey, krb5_data *outrnd,
                          const krb5_data *label, const krb5_data *context)
{
    krb5_crypto_iov iov[5];
    krb5_error_code ret;
    krb5_data prf;
    unsigned char ibuf[4] = { 0, 0, 0, 1 }, lbuf[4];

    if (hash == NULL || outrnd->length > hash->hashsize)
        return KRB5_CRYPTO_INTERNAL;

    /* Allocate encryption data buffer. */
    ret = alloc_data(&prf, hash->hashsize);
    if (ret)
        return ret;

    /* [i]2: four-byte big-endian binary string giving the block counter (1) */
    iov[0].flags = KRB5_CRYPTO_TYPE_DATA;
    iov[0].data = make_data(ibuf, sizeof(ibuf));
    /* Label */
    iov[1].flags = KRB5_CRYPTO_TYPE_DATA;
    iov[1].data = *label;
    /* 0x00: separator byte */
    iov[2].flags = KRB5_CRYPTO_TYPE_DATA;
    iov[2].data = make_data("", 1);
    /* Context */
    iov[3].flags = KRB5_CRYPTO_TYPE_DATA;
    iov[3].data = *context;
    /* [L]2: four-byte big-endian binary string giving the output length */
    iov[4].flags = KRB5_CRYPTO_TYPE_DATA;
    iov[4].data = make_data(lbuf, sizeof(lbuf));
    store_32_be(outrnd->length * 8, lbuf);

    ret = krb5int_hmac_iov(hash, inkey, iov, 5, &prf);
    if (!ret)
        memcpy(outrnd->data, prf.data, prf.length);
    zap(prf.data, prf.length);
    free(prf.data);
    return ret;
}

