/*
 * Copyright 2019 The OpenSSL Project Authors. All Rights Reserved.
 * Copyright 2019 Red Hat, Inc.
 *
 * Licensed under the Apache License 2.0 (the "License").  You may not use
 * this file except in compliance with the License.  You can obtain a copy
 * in the file LICENSE in the source distribution or at
 * https://www.openssl.org/source/license.html
 */

/*
 * This implements https://csrc.nist.gov/publications/detail/sp/800-108/final
 * section 5.1 ("counter mode") and section 5.2 ("feedback mode") in both HMAC
 * and CMAC.  That document does not name the KDFs it defines; the name is
 * derived from
 * https://csrc.nist.gov/Projects/Cryptographic-Algorithm-Validation-Program/Key-Derivation
 *
 * Note that section 5.3 ("double-pipeline mode") is not implemented, though
 * it would be possible to do so in the future.
 *
 * These versions all assume the counter is used.  It would be relatively
 * straightforward to expose a configuration handle should the need arise.
 *
 * Variable names attempt to match those of SP800-108.
 */

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

#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/cmac.h>
#include <openssl/kdf.h>

#include "internal/numbers.h"
#include "internal/cryptlib.h"
#include "crypto/evp.h"
#include "kdf_local.h"

#include "e_os.h"

#ifdef MIN
# undef MIN
#endif
#define MIN(a, b) ((a) < (b)) ? (a) : (b)

typedef struct {
    int mac_type;
    union {
        HMAC_CTX *hmac;
        CMAC_CTX *cmac;
    } m;
} MAC_CTX;

/* Our context structure. */
struct evp_kdf_impl_st {
    int mode;

    MAC_CTX *ctx_init;

    const EVP_CIPHER *cipher;
    const EVP_MD *md;

    /* Names are lowercased versions of those found in SP800-108. */
    unsigned char *ki;
    size_t ki_len;
    unsigned char *label;
    size_t label_len;
    unsigned char *context;
    size_t context_len;
    unsigned char *iv;
    size_t iv_len;
};

static MAC_CTX *EVP_MAC_CTX_new(int mac_type)
{
    MAC_CTX *ctx;

    ctx = OPENSSL_zalloc(sizeof(*ctx));
    if (ctx == NULL)
        return NULL;

    ctx->mac_type = mac_type;
    if (mac_type == EVP_KDF_KB_MAC_TYPE_HMAC) {
        if ((ctx->m.hmac = HMAC_CTX_new()) == NULL)
            goto err;
    } else {
        if ((ctx->m.cmac = CMAC_CTX_new()) == NULL)
            goto err;
    }
    return ctx;

err:
    OPENSSL_free(ctx);
    return NULL;
}

static void EVP_MAC_CTX_free(MAC_CTX *ctx)
{
    if (ctx == NULL)
        return;

    if (ctx->mac_type == EVP_KDF_KB_MAC_TYPE_HMAC)
        HMAC_CTX_free(ctx->m.hmac);
    else
        CMAC_CTX_free(ctx->m.cmac);
    OPENSSL_free(ctx);
}

static MAC_CTX *EVP_MAC_CTX_dup(MAC_CTX *sctx)
{
    MAC_CTX *ctx;

    ctx = OPENSSL_zalloc(sizeof(*sctx));
    if (ctx == NULL)
        return NULL;

    ctx->mac_type = sctx->mac_type;
    if (sctx->mac_type == EVP_KDF_KB_MAC_TYPE_HMAC) {
        if ((ctx->m.hmac = HMAC_CTX_new()) == NULL
            || HMAC_CTX_copy(ctx->m.hmac, sctx->m.hmac) <= 0)
            goto err;
    } else {
        if ((ctx->m.cmac = CMAC_CTX_new()) == NULL
            || CMAC_CTX_copy(ctx->m.cmac, sctx->m.cmac) <= 0)
            goto err;
    }
    return ctx;

err:
    EVP_MAC_CTX_free(ctx);
    return NULL;
}

static size_t EVP_MAC_size(MAC_CTX *ctx)
{
    if (ctx->mac_type == EVP_KDF_KB_MAC_TYPE_HMAC) {
        const EVP_MD *md;

        if (ctx->m.hmac == NULL)
            return 0;
        if ((md = HMAC_CTX_get_md(ctx->m.hmac)) == NULL)
            return 0;
        return (size_t)EVP_MD_size(md);
    } else {
        const EVP_CIPHER_CTX *cctx;

        if (ctx->m.cmac == NULL)
            return 0;
        if ((cctx = CMAC_CTX_get0_cipher_ctx(ctx->m.cmac)) == NULL)
            return 0;
        return EVP_CIPHER_CTX_block_size(cctx);
    }
}

static int EVP_MAC_update(MAC_CTX *ctx, const unsigned char *data,
                          size_t datalen)
{
    if (ctx->mac_type == EVP_KDF_KB_MAC_TYPE_HMAC)
        return HMAC_Update(ctx->m.hmac, data, datalen);
    else
        return CMAC_Update(ctx->m.cmac, data, datalen);
}

static int EVP_MAC_final(MAC_CTX *ctx, unsigned char *out,
                         size_t *outl, size_t outsize)
{
    if (outsize != EVP_MAC_size(ctx))
        /* we do not cope with anything else */
        return 0;

    if (ctx->mac_type == EVP_KDF_KB_MAC_TYPE_HMAC) {
        unsigned int intsize = (unsigned int)outsize;
        int ret;

        ret = HMAC_Final(ctx->m.hmac, out, &intsize);
        if (outl != NULL)
            *outl = intsize;
        return ret;
    } else {
        size_t size = outsize;
        int ret;

        ret = CMAC_Final(ctx->m.cmac, out, &size);
        if (outl != NULL)
            *outl = size;
        return ret;
    }
}

static int evp_mac_init(MAC_CTX *ctx, const EVP_MD *md,
                        const EVP_CIPHER *cipher, unsigned char *key, size_t keylen)
{
    if (ctx->mac_type == EVP_KDF_KB_MAC_TYPE_HMAC) {
        if (md == NULL)
            return 0;
        return HMAC_Init_ex(ctx->m.hmac, key, (int)keylen, md, NULL);
    } else {
        if (cipher == NULL)
            return 0;
        return CMAC_Init(ctx->m.cmac, key, keylen, cipher, NULL);
    }
}

static void kbkdf_reset(EVP_KDF_IMPL *ctx);

/* Not all platforms have htobe32(). */
static uint32_t be32(uint32_t host)
{
    uint32_t big = 0;
    const union {
        long one;
        char little;
    } is_endian = { 1 };

    if (!is_endian.little)
        return host;

    big |= (host & 0xff000000) >> 24;
    big |= (host & 0x00ff0000) >> 8;
    big |= (host & 0x0000ff00) << 8;
    big |= (host & 0x000000ff) << 24;
    return big;
}

static EVP_KDF_IMPL *kbkdf_new(void)
{
    EVP_KDF_IMPL *ctx;

    ctx = OPENSSL_zalloc(sizeof(*ctx));
    if (ctx == NULL) {
        KDFerr(KDF_F_KBKDF_NEW, ERR_R_MALLOC_FAILURE);
        return NULL;
    }

    return ctx;
}

static void kbkdf_free(EVP_KDF_IMPL *ctx)
{
    kbkdf_reset(ctx);
    OPENSSL_free(ctx);
}

static void kbkdf_reset(EVP_KDF_IMPL *ctx)
{
    EVP_MAC_CTX_free(ctx->ctx_init);
    OPENSSL_clear_free(ctx->context, ctx->context_len);
    OPENSSL_clear_free(ctx->label, ctx->label_len);
    OPENSSL_clear_free(ctx->ki, ctx->ki_len);
    OPENSSL_clear_free(ctx->iv, ctx->iv_len);
    memset(ctx, 0, sizeof(*ctx));
}

/* SP800-108 section 5.1 or section 5.2 depending on mode. */
static int derive(MAC_CTX *ctx_init, int mode, unsigned char *iv,
                  size_t iv_len, unsigned char *label, size_t label_len,
                  unsigned char *context, size_t context_len,
                  unsigned char *k_i, size_t h, uint32_t l, unsigned char *ko,
                  size_t ko_len)
{
    int ret = 0;
    MAC_CTX *ctx = NULL;
    size_t written = 0, to_write, k_i_len = iv_len;
    const unsigned char zero = 0;
    uint32_t counter, i;

    /* Setup K(0) for feedback mode. */
    if (iv_len > 0)
        memcpy(k_i, iv, iv_len);

    for (counter = 1; written < ko_len; counter++) {
        i = be32(counter);

        ctx = EVP_MAC_CTX_dup(ctx_init);
        if (ctx == NULL)
            goto done;

        /* Perform feedback, if appropriate. */
        if (mode == EVP_KDF_KB_MODE_FEEDBACK && !EVP_MAC_update(ctx, k_i, k_i_len))
            goto done;

        if (!EVP_MAC_update(ctx, (unsigned char *)&i, 4)
            || !EVP_MAC_update(ctx, label, label_len)
            || !EVP_MAC_update(ctx, &zero, 1)
            || !EVP_MAC_update(ctx, context, context_len)
            || !EVP_MAC_update(ctx, (unsigned char *)&l, 4)
            || !EVP_MAC_final(ctx, k_i, NULL, h))
            goto done;

        to_write = ko_len - written;
        memcpy(ko + written, k_i, MIN(to_write, h));
        written += h;

        k_i_len = h;
        EVP_MAC_CTX_free(ctx);
        ctx = NULL;
    }

    ret = 1;
done:
    EVP_MAC_CTX_free(ctx);
    return ret;
}

static int kbkdf_derive(EVP_KDF_IMPL *ctx, unsigned char *key, size_t keylen)
{
    int ret = 0;
    unsigned char *k_i = NULL;
    uint32_t l = be32(keylen * 8);
    size_t h = 0;

    /* label, context, and iv are permitted to be empty.  Check everything
     * else. */
    if (ctx->ctx_init == NULL
        || evp_mac_init(ctx->ctx_init, ctx->md, ctx->cipher, ctx->ki, ctx->ki_len) <= 0) {
        if (ctx->ki_len == 0 || ctx->ki == NULL) {
            KDFerr(KDF_F_KBKDF_DERIVE, KDF_R_MISSING_KEY);
            return 0;
        }
        /* Could either be missing MAC or missing message digest or missing
         * cipher - arbitrarily, I pick this one. */
        KDFerr(KDF_F_KBKDF_DERIVE, KDF_R_MISSING_PARAMETER);
        return 0;
    }

    h = EVP_MAC_size(ctx->ctx_init);
    if (h == 0)
        goto done;
    if (ctx->iv_len != 0 && ctx->iv_len != h) {
        KDFerr(KDF_F_KBKDF_DERIVE, KDF_R_INVALID_SEED_LENGTH);
        goto done;
    }

    k_i = OPENSSL_zalloc(h);
    if (k_i == NULL)
        goto done;

    ret = derive(ctx->ctx_init, ctx->mode, ctx->iv, ctx->iv_len, ctx->label,
                 ctx->label_len, ctx->context, ctx->context_len, k_i, h, l,
                 key, keylen);
done:
    if (ret != 1)
        OPENSSL_cleanse(key, keylen);
    OPENSSL_clear_free(k_i, h);
    return ret;
}

static size_t kbkdf_size(EVP_KDF_IMPL *ctx)
{
    return UINT32_MAX/8;
}

static int kbkdf_parse_buffer_arg(unsigned char **dst, size_t *dst_len,
                                       va_list args)
{
    const unsigned char *p;
    size_t len;

    p = va_arg(args, const unsigned char *);
    len = va_arg(args, size_t);
    OPENSSL_clear_free(*dst, *dst_len);
    if (len == 0) {
        *dst = NULL;
        *dst_len = 0;
        return 1;
    }

    *dst = OPENSSL_memdup(p, len);
    if (*dst == NULL)
        return 0;

    *dst_len = len;
    return 1;
}

static int kbkdf_ctrl(EVP_KDF_IMPL *ctx, int cmd, va_list args)
{
    int t;

    switch (cmd) {
    case EVP_KDF_CTRL_SET_MD:
        ctx->md = va_arg(args, const EVP_MD *);
        if (ctx->md == NULL)
            return 0;

        return 1;

    case EVP_KDF_CTRL_SET_CIPHER:
        ctx->cipher = va_arg(args, const EVP_CIPHER *);
        if (ctx->cipher == NULL)
            return 0;

        return 1;

    case EVP_KDF_CTRL_SET_KEY:
        return kbkdf_parse_buffer_arg(&ctx->ki,
                                      &ctx->ki_len, args);

    case EVP_KDF_CTRL_SET_SALT:
        return kbkdf_parse_buffer_arg(&ctx->label,
                                           &ctx->label_len, args);

    case EVP_KDF_CTRL_SET_KB_INFO:
        return kbkdf_parse_buffer_arg(&ctx->context,
                                           &ctx->context_len, args);

    case EVP_KDF_CTRL_SET_KB_SEED:
        return kbkdf_parse_buffer_arg(&ctx->iv,
                                           &ctx->iv_len, args);

    case EVP_KDF_CTRL_SET_KB_MODE:
        t = va_arg(args, int);
        if (t != EVP_KDF_KB_MODE_COUNTER && t != EVP_KDF_KB_MODE_FEEDBACK ) {
            KDFerr(KDF_F_KBKDF_CTRL, KDF_R_VALUE_ERROR);
            return 0;
        }
        ctx->mode = t;
        return 1;

    case EVP_KDF_CTRL_SET_KB_MAC_TYPE:
        t = va_arg(args, int);
        if (t != EVP_KDF_KB_MAC_TYPE_HMAC && t != EVP_KDF_KB_MAC_TYPE_CMAC ) {
            KDFerr(KDF_F_KBKDF_CTRL, KDF_R_VALUE_ERROR);
            return 0;
        }

        if (ctx->ctx_init != NULL) {
            EVP_MAC_CTX_free(ctx->ctx_init);
        }
        ctx->ctx_init = EVP_MAC_CTX_new(t);
        if (ctx->ctx_init == NULL) {
            KDFerr(KDF_F_KBKDF_CTRL, ERR_R_MALLOC_FAILURE);
            return 0;
        }
        return 1;

    default:
        return -2;

    }
}

static int kbkdf_ctrl_str(EVP_KDF_IMPL *ctx, const char *type,
                               const char *value)
{
    if (value == NULL) {
        KDFerr(KDF_F_KDF_SSHKDF_CTRL_STR, KDF_R_VALUE_MISSING);
        return 0;
    }

    if (strcmp(type, "digest") == 0)
        return kdf_md2ctrl(ctx, kbkdf_ctrl, EVP_KDF_CTRL_SET_MD, value);
    /* alias, for historical reasons */
    if (strcmp(type, "md") == 0)
        return kdf_md2ctrl(ctx, kbkdf_ctrl, EVP_KDF_CTRL_SET_MD, value);

    if (strcmp(type, "cipher") == 0)
        return kdf_cipher2ctrl(ctx, kbkdf_ctrl, EVP_KDF_CTRL_SET_CIPHER, value);

    if (strcmp(type, "key") == 0)
        return kdf_str2ctrl(ctx, kbkdf_ctrl,
                            EVP_KDF_CTRL_SET_KEY, value);

    if (strcmp(type, "hexkey") == 0)
        return kdf_hex2ctrl(ctx, kbkdf_ctrl,
                            EVP_KDF_CTRL_SET_KEY, value);

    if (strcmp(type, "salt") == 0)
        return kdf_str2ctrl(ctx, kbkdf_ctrl,
                            EVP_KDF_CTRL_SET_SALT, value);

    if (strcmp(type, "hexsalt") == 0)
        return kdf_hex2ctrl(ctx, kbkdf_ctrl,
                            EVP_KDF_CTRL_SET_SALT, value);

    if (strcmp(type, "info") == 0)
        return kdf_str2ctrl(ctx, kbkdf_ctrl,
                            EVP_KDF_CTRL_SET_KB_INFO, value);

    if (strcmp(type, "hexinfo") == 0)
        return kdf_hex2ctrl(ctx, kbkdf_ctrl,
                            EVP_KDF_CTRL_SET_KB_INFO, value);

    if (strcmp(type, "seed") == 0)
        return kdf_str2ctrl(ctx, kbkdf_ctrl,
                            EVP_KDF_CTRL_SET_KB_SEED, value);

    if (strcmp(type, "hexseed") == 0)
        return kdf_hex2ctrl(ctx, kbkdf_ctrl,
                            EVP_KDF_CTRL_SET_KB_SEED, value);

    if (strcmp(type, "mode") == 0) {
        int mode;

        if (strcasecmp(value, "counter") == 0) {
            mode = EVP_KDF_KB_MODE_COUNTER;
        } else if (strcasecmp(value, "feedback") == 0) {
            mode = EVP_KDF_KB_MODE_FEEDBACK;
        } else {
            KDFerr(KDF_F_KBKDF_CTRL_STR, KDF_R_VALUE_ERROR);
            return 0;
        }

        return call_ctrl(kbkdf_ctrl, ctx, EVP_KDF_CTRL_SET_KB_MODE,
                         mode);
    }

    if (strcmp(type, "mac_type") == 0) {
        int mac_type;

        if (strcasecmp(value, "hmac") == 0) {
            mac_type = EVP_KDF_KB_MAC_TYPE_HMAC;
        } else if (strcasecmp(value, "cmac") == 0) {
            mac_type = EVP_KDF_KB_MAC_TYPE_CMAC;
        } else {
            KDFerr(KDF_F_KBKDF_CTRL_STR, KDF_R_VALUE_ERROR);
            return 0;
        }

        return call_ctrl(kbkdf_ctrl, ctx, EVP_KDF_CTRL_SET_KB_MAC_TYPE,
                         mac_type);
    }

    KDFerr(KDF_F_KBKDF_CTRL_STR, KDF_R_UNKNOWN_PARAMETER_TYPE);
    return -2;
}

const EVP_KDF_METHOD kb_kdf_meth = {
    EVP_KDF_KB,
    kbkdf_new,
    kbkdf_free,
    kbkdf_reset,
    kbkdf_ctrl,
    kbkdf_ctrl_str,
    kbkdf_size,
    kbkdf_derive,
};

