/*
 * Copyright 2018 The OpenSSL Project Authors. All Rights Reserved.
 *
 * Licensed under the OpenSSL license (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
 */

#include <openssl/err.h>
#include <openssl/evp.h>
#include "internal/evp_int.h"

/* MAC PKEY context structure */

typedef struct {
    EVP_MAC_CTX *ctx;

    /*
     * We know of two MAC types:
     *
     * 1. those who take a secret in raw form, i.e. raw data as a
     *    ASN1_OCTET_STRING embedded in a EVP_PKEY.  So far, that's
     *    all of them but CMAC.
     * 2. those who take a secret with associated cipher in very generic
     *    form, i.e. a complete EVP_MAC_CTX embedded in a PKEY.  So far,
     *    only CMAC does this.
     *
     * (one might wonder why the second form isn't used for all)
     */
#define MAC_TYPE_RAW    1   /* HMAC like MAC type (all but CMAC so far) */
#define MAC_TYPE_MAC    2   /* CMAC like MAC type (only CMAC known so far) */
    int type;

    /* The following is only used for MAC_TYPE_RAW implementations */
    struct {
        const EVP_MD *md;           /* temp storage of MD */
        ASN1_OCTET_STRING ktmp;     /* temp storage for key */
    } raw_data;
} MAC_PKEY_CTX;

static int pkey_mac_init(EVP_PKEY_CTX *ctx)
{
    MAC_PKEY_CTX *hctx;
    int nid = ctx->pmeth->pkey_id;

    if ((hctx = OPENSSL_zalloc(sizeof(*hctx))) == NULL) {
        EVPerr(EVP_F_PKEY_MAC_INIT, ERR_R_MALLOC_FAILURE);
        return 0;
    }

    /* We're being smart and using the same base NIDs for PKEY and for MAC */
    hctx->ctx = EVP_MAC_CTX_new_id(nid);
    if (hctx->ctx == NULL) {
        OPENSSL_free(hctx);
        return 0;
    }

    if (nid == EVP_PKEY_CMAC) {
        hctx->type = MAC_TYPE_MAC;
    } else {
        hctx->type = MAC_TYPE_RAW;
        hctx->raw_data.ktmp.type = V_ASN1_OCTET_STRING;
    }

    EVP_PKEY_CTX_set_data(ctx, hctx);
    ctx->keygen_info_count = 0;

    return 1;
}

static void pkey_mac_cleanup(EVP_PKEY_CTX *ctx);

static int pkey_mac_copy(EVP_PKEY_CTX *dst, EVP_PKEY_CTX *src)
{
    MAC_PKEY_CTX *sctx, *dctx;

    if (!pkey_mac_init(dst))
        return 0;

    sctx = EVP_PKEY_CTX_get_data(src);
    dctx = EVP_PKEY_CTX_get_data(dst);

    if (!EVP_MAC_CTX_copy(dctx->ctx, sctx->ctx))
        goto err;

    switch (dctx->type) {
    case MAC_TYPE_RAW:
        dctx->raw_data.md = sctx->raw_data.md;
        if (ASN1_STRING_get0_data(&sctx->raw_data.ktmp) != NULL &&
            !ASN1_STRING_copy(&dctx->raw_data.ktmp, &sctx->raw_data.ktmp))
            goto err;
        break;
    case MAC_TYPE_MAC:
        /* Nothing more to do */
        break;
    default:
        /* This should be dead code */
        return 0;
    }
    return 1;
 err:
    pkey_mac_cleanup (dst);
    return 0;
}

static void pkey_mac_cleanup(EVP_PKEY_CTX *ctx)
{
    MAC_PKEY_CTX *hctx = EVP_PKEY_CTX_get_data(ctx);

    if (hctx != NULL) {
        switch (hctx->type) {
        case MAC_TYPE_RAW:
            OPENSSL_clear_free(hctx->raw_data.ktmp.data,
                               hctx->raw_data.ktmp.length);
            break;
        }
        EVP_MAC_CTX_free(hctx->ctx);
        OPENSSL_free(hctx);
        EVP_PKEY_CTX_set_data(ctx, NULL);
    }
}

static int pkey_mac_keygen(EVP_PKEY_CTX *ctx, EVP_PKEY *pkey)
{
    MAC_PKEY_CTX *hctx = EVP_PKEY_CTX_get_data(ctx);
    int nid = ctx->pmeth->pkey_id;

    switch (hctx->type) {
    case MAC_TYPE_RAW:
        {
            ASN1_OCTET_STRING *hkey = NULL;

            if (!hctx->raw_data.ktmp.data)
                return 0;
            hkey = ASN1_OCTET_STRING_dup(&hctx->raw_data.ktmp);
            if (!hkey)
                return 0;
            EVP_PKEY_assign(pkey, nid, hkey);
        }
        break;
    case MAC_TYPE_MAC:
        {
            EVP_MAC_CTX *cmkey = EVP_MAC_CTX_new_id(nid);

            if (cmkey == NULL)
                return 0;
            if (!EVP_MAC_CTX_copy(cmkey, hctx->ctx)) {
                EVP_MAC_CTX_free(cmkey);
                return 0;
            }
            EVP_PKEY_assign(pkey, nid, cmkey);
        }
        break;
    default:
        /* This should be dead code */
        return 0;
    }

    return 1;
}

static int int_update(EVP_MD_CTX *ctx, const void *data, size_t count)
{
    MAC_PKEY_CTX *hctx = EVP_PKEY_CTX_get_data(EVP_MD_CTX_pkey_ctx(ctx));

    if (!EVP_MAC_update(hctx->ctx, data, count))
        return 0;
    return 1;
}

static int pkey_mac_signctx_init(EVP_PKEY_CTX *ctx, EVP_MD_CTX *mctx)
{
    MAC_PKEY_CTX *hctx = EVP_PKEY_CTX_get_data(ctx);
    ASN1_OCTET_STRING *key = NULL;
    int rv = 1;
    /*
     * For MACs with the EVP_PKEY_FLAG_SIGCTX_CUSTOM flag set and that
     * gets the key passed as an ASN.1 OCTET STRING, we set the key here,
     * as this may be only time it's set during a DigestSign.
     *
     * MACs that pass around the key in form of EVP_MAC_CTX are setting
     * the key through other mechanisms.  (this is only CMAC for now)
     */
    int set_key =
        hctx->type == MAC_TYPE_RAW
        && (ctx->pmeth->flags & EVP_PKEY_FLAG_SIGCTX_CUSTOM) != 0;

    if (set_key) {
        if (EVP_PKEY_id(EVP_PKEY_CTX_get0_pkey(ctx))
            != EVP_MAC_nid(EVP_MAC_CTX_mac(hctx->ctx)))
            return 0;
        key = EVP_PKEY_get0(EVP_PKEY_CTX_get0_pkey(ctx));
        if (key == NULL)
            return 0;
    }

    /* Some MACs don't support this control...  that's fine */
    EVP_MAC_ctrl(hctx->ctx, EVP_MAC_CTRL_SET_FLAGS,
                 EVP_MD_CTX_test_flags(mctx, ~EVP_MD_CTX_FLAG_NO_INIT));

    EVP_MD_CTX_set_flags(mctx, EVP_MD_CTX_FLAG_NO_INIT);
    EVP_MD_CTX_set_update_fn(mctx, int_update);

    if (set_key)
        rv = EVP_MAC_ctrl(hctx->ctx, EVP_MAC_CTRL_SET_KEY, key->data,
                          key->length);
    return rv > 0;
}

static int pkey_mac_signctx(EVP_PKEY_CTX *ctx, unsigned char *sig,
                             size_t *siglen, EVP_MD_CTX *mctx)
{
    MAC_PKEY_CTX *hctx = EVP_PKEY_CTX_get_data(ctx);

    return EVP_MAC_final(hctx->ctx, sig, siglen);
}

static int pkey_mac_ctrl(EVP_PKEY_CTX *ctx, int type, int p1, void *p2)
{
    MAC_PKEY_CTX *hctx = EVP_PKEY_CTX_get_data(ctx);

    switch (type) {

    case EVP_PKEY_CTRL_CIPHER:
        switch (hctx->type) {
        case MAC_TYPE_RAW:
            return -2;       /* The raw types don't support ciphers */
        case MAC_TYPE_MAC:
            {
                int rv;

                if ((rv = EVP_MAC_ctrl(hctx->ctx, EVP_MAC_CTRL_SET_ENGINE,
                                       ctx->engine)) < 0
                    || (rv = EVP_MAC_ctrl(hctx->ctx, EVP_MAC_CTRL_SET_CIPHER,
                                          p2)) < 0
                    || !(rv = EVP_MAC_init(hctx->ctx)))
                    return rv;
            }
            break;
        default:
            /* This should be dead code */
            return 0;
        }
        break;

    case EVP_PKEY_CTRL_MD:
        switch (hctx->type) {
        case MAC_TYPE_RAW:
            hctx->raw_data.md = p2;
            break;
        case MAC_TYPE_MAC:
            if (ctx->pkey != NULL
                && !EVP_MAC_CTX_copy(hctx->ctx,
                                     (EVP_MAC_CTX *)ctx->pkey->pkey.ptr))
                return 0;
            if (!EVP_MAC_init(hctx->ctx))
                return 0;
            break;
        default:
            /* This should be dead code */
            return 0;
        }
        break;

    case EVP_PKEY_CTRL_SET_DIGEST_SIZE:
        return EVP_MAC_ctrl(hctx->ctx, EVP_MAC_CTRL_SET_SIZE, (size_t)p1);

    case EVP_PKEY_CTRL_SET_MAC_KEY:
        switch (hctx->type) {
        case MAC_TYPE_RAW:
            if ((!p2 && p1 > 0) || (p1 < -1))
                return 0;
            if (!ASN1_OCTET_STRING_set(&hctx->raw_data.ktmp, p2, p1))
                return 0;
            break;
        case MAC_TYPE_MAC:
            if (!EVP_MAC_ctrl(hctx->ctx, EVP_MAC_CTRL_SET_KEY, p2, p1))
                return 0;
            break;
        default:
            /* This should be dead code */
            return 0;
        }
        break;

    case EVP_PKEY_CTRL_DIGESTINIT:
        switch (hctx->type) {
        case MAC_TYPE_RAW:
            /* Ensure that we have attached the implementation */
            if (!EVP_MAC_init(hctx->ctx))
                return 0;
            {
                int rv;
                ASN1_OCTET_STRING *key =
                    (ASN1_OCTET_STRING *)ctx->pkey->pkey.ptr;

                if ((rv = EVP_MAC_ctrl(hctx->ctx, EVP_MAC_CTRL_SET_ENGINE,
                                       ctx->engine)) < 0
                    || (rv = EVP_MAC_ctrl(hctx->ctx, EVP_MAC_CTRL_SET_MD,
                                          hctx->raw_data.md)) < 0
                    || (rv = EVP_MAC_ctrl(hctx->ctx, EVP_MAC_CTRL_SET_KEY,
                                          key->data, key->length)) < 0)
                    return rv;
            }
            break;
        case MAC_TYPE_MAC:
            return -2;       /* The mac types don't support ciphers */
        default:
            /* This should be dead code */
            return 0;
        }
        break;

    default:
        return -2;

    }
    return 1;
}

static int pkey_mac_ctrl_str(EVP_PKEY_CTX *ctx,
                              const char *type, const char *value)
{
    MAC_PKEY_CTX *hctx = EVP_PKEY_CTX_get_data(ctx);

    return EVP_MAC_ctrl_str(hctx->ctx, type, value);
}

/*
 * When this is actually used, the following will be replaced with real
 * EVP_PKEY_METHODs, all exactly the same apart from the type and possibly
 * the flags.
 */

extern const EVP_PKEY_METHOD FAKE_pkey_meth;
const EVP_PKEY_METHOD FAKE_pkey_meth = {
    20870442 /* EVP_PKEY_FAKE, a beast times 31337 (you do the math) */,
    EVP_PKEY_FLAG_SIGCTX_CUSTOM,
    pkey_mac_init,
    pkey_mac_copy,
    pkey_mac_cleanup,

    0, 0,

    0,
    pkey_mac_keygen,

    0, 0,

    0, 0,

    0, 0,

    pkey_mac_signctx_init,
    pkey_mac_signctx,

    0, 0,

    0, 0,

    0, 0,

    0, 0,

    pkey_mac_ctrl,
    pkey_mac_ctrl_str
};
