/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifdef FREEBL_NO_DEPEND
#include "stubs.h"
#endif

#include "rijndael.h"
#include "blapi.h"
#include "cmac.h"
#include "secerr.h"
#include "nspr.h"

struct CMACContextStr {
    /* Information about the block cipher to use internally. The cipher should
     * be placed in ECB mode so that we can use it to directly encrypt blocks.
     *
     *
     * To add a new cipher, add an entry to CMACCipher, update CMAC_Init,
     * cmac_Encrypt, and CMAC_Destroy methods to handle the new cipher, and
     * add a new Context pointer to the cipher union with the correct type. */
    CMACCipher cipherType;
    union {
        AESContext *aes;
    } cipher;
    int blockSize;

    /* Internal keys which are conditionally used by the algorithm. Derived
     * from encrypting the NULL block. We leave the storing of (and the
     * cleanup of) the CMAC key to the underlying block cipher. */
    unsigned char k1[MAX_BLOCK_SIZE];
    unsigned char k2[MAX_BLOCK_SIZE];

    /* When Update is called with data which isn't a multiple of the block
     * size, we need a place to put it. HMAC handles this by passing it to
     * the underlying hash function right away; we can't do that as the
     * contract on the cipher object is different. */
    unsigned int partialIndex;
    unsigned char partialBlock[MAX_BLOCK_SIZE];

    /* Last encrypted block. This gets xor-ed with partialBlock prior to
     * encrypting it. NIST defines this to be the empty string to begin. */
    unsigned char lastBlock[MAX_BLOCK_SIZE];
};

static void
cmac_ShiftLeftOne(unsigned char *out, const unsigned char *in, int length)
{
    int i = 0;
    for (; i < length - 1; i++) {
        out[i] = in[i] << 1;
        out[i] |= in[i + 1] >> 7;
    }
    out[i] = in[i] << 1;
}

static SECStatus
cmac_Encrypt(CMACContext *ctx, unsigned char *output,
             const unsigned char *input,
             unsigned int inputLen)
{
    if (ctx->cipherType == CMAC_AES) {
        unsigned int tmpOutputLen;
        SECStatus rv = AES_Encrypt(ctx->cipher.aes, output, &tmpOutputLen,
                                   ctx->blockSize, input, inputLen);

        /* Assumption: AES_Encrypt (when in ECB mode) always returns an
         * output of length equal to blockSize (what was pass as the value
         * of the maxOutputLen parameter). */
        PORT_Assert(tmpOutputLen == ctx->blockSize);
        return rv;
    }

    return SECFailure;
}

/* NIST SP.800-38B, 6.1 Subkey Generation */
static SECStatus
cmac_GenerateSubkeys(CMACContext *ctx)
{
    unsigned char null_block[MAX_BLOCK_SIZE] = { 0 };
    unsigned char L[MAX_BLOCK_SIZE];
    unsigned char v;
    unsigned char i;

    /* Step 1: L = AES(key, null_block) */
    if (cmac_Encrypt(ctx, L, null_block, ctx->blockSize) != SECSuccess) {
        return SECFailure;
    }

    /* In the following, some effort has been made to be constant time. Rather
     * than conditioning on the value of the MSB (of L or K1), we use the loop
     * to build a mask for the conditional constant. */

    /* Step 2: If MSB(L) = 0, K1 = L << 1. Else, K1 = (L << 1) ^ R_b. */
    cmac_ShiftLeftOne(ctx->k1, L, ctx->blockSize);
    v = L[0] >> 7;
    for (i = 1; i <= 7; i <<= 1) {
        v |= (v << i);
    }
    ctx->k1[ctx->blockSize - 1] ^= (0x87 & v);

    /* Step 3: If MSB(K1) = 0, K2 = K1 << 1. Else, K2 = (K1 <, 1) ^ R_b. */
    cmac_ShiftLeftOne(ctx->k2, ctx->k1, ctx->blockSize);
    v = ctx->k1[0] >> 7;
    for (i = 1; i <= 7; i <<= 1) {
        v |= (v << i);
    }
    ctx->k2[ctx->blockSize - 1] ^= (0x87 & v);

    /* Any intermediate value in the computation of the subkey shall be
     * secret. */
    PORT_Memset(null_block, 0, MAX_BLOCK_SIZE);
    PORT_Memset(L, 0, MAX_BLOCK_SIZE);

    /* Step 4: Return the values. */
    return SECSuccess;
}

/* NIST SP.800-38B, 6.2 MAC Generation step 6 */
static SECStatus
cmac_UpdateState(CMACContext *ctx)
{
    if (ctx == NULL || ctx->partialIndex != ctx->blockSize) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    /* Step 6: C_i = CIPHER(key, C_{i-1} ^ M_i)  for 1 <= i <= n, and
     *         C_0 is defined as the empty string. */

    for (unsigned int index = 0; index < ctx->blockSize; index++) {
        ctx->partialBlock[index] ^= ctx->lastBlock[index];
    }

    return cmac_Encrypt(ctx, ctx->lastBlock, ctx->partialBlock, ctx->blockSize);
}

SECStatus
CMAC_Init(CMACContext *ctx, CMACCipher type,
          const unsigned char *key, unsigned int key_len)
{
    if (ctx == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return SECFailure;
    }

    /* We only currently support AES-CMAC. */
    if (type != CMAC_AES) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    PORT_Memset(ctx, 0, sizeof(*ctx));

    ctx->blockSize = AES_BLOCK_SIZE;
    ctx->cipherType = CMAC_AES;
    ctx->cipher.aes = AES_CreateContext(key, NULL, NSS_AES, 1, key_len,
                                        ctx->blockSize);
    if (ctx->cipher.aes == NULL) {
        return SECFailure;
    }

    return CMAC_Begin(ctx);
}

CMACContext *
CMAC_Create(CMACCipher type, const unsigned char *key,
            unsigned int key_len)
{
    CMACContext *result = PORT_New(CMACContext);

    if (CMAC_Init(result, type, key, key_len) != SECSuccess) {
        CMAC_Destroy(result, PR_TRUE);
        return NULL;
    }

    return result;
}

SECStatus
CMAC_Begin(CMACContext *ctx)
{
    if (ctx == NULL) {
        return SECFailure;
    }

    /* Ensure that our blockSize is less than the maximum. When this fails,
     * a cipher with a larger block size was added and MAX_BLOCK_SIZE needs
     * to be updated accordingly. */
    PORT_Assert(ctx->blockSize <= MAX_BLOCK_SIZE);

    if (cmac_GenerateSubkeys(ctx) != SECSuccess) {
        return SECFailure;
    }

    /* Set the index to write partial blocks at to zero. This saves us from
     * having to clear ctx->partialBlock. */
    ctx->partialIndex = 0;

    /* Step 5: Let C_0 = 0^b. */
    PORT_Memset(ctx->lastBlock, 0, ctx->blockSize);

    return SECSuccess;
}

/* NIST SP.800-38B, 6.2 MAC Generation */
SECStatus
CMAC_Update(CMACContext *ctx, const unsigned char *data,
            unsigned int data_len)
{
    int data_index = 0;
    if (ctx == NULL) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    if (data == NULL || data_len == 0) {
        return SECSuccess;
    }

    /* Copy as many bytes from data into ctx->partialBlock as we can, up to
     * the maximum of the remaining data and the remaining space in
     * ctx->partialBlock.
     *
     * Note that we swap the order (encrypt *then* copy) because the last
     * block is different from the rest. If we end on an even multiple of
     * the block size, we have to be able to XOR it with K1. But we won't know
     * that it is the last until CMAC_Finish is called (and by then, CMAC_Update
     * has already returned). */
    while (data_index < data_len) {
        if (ctx->partialIndex == ctx->blockSize) {
            if (cmac_UpdateState(ctx) != SECSuccess) {
                return SECFailure;
            }

            ctx->partialIndex = 0;
        }

        unsigned int copy_len = data_len - data_index;
        if (copy_len > (ctx->blockSize - ctx->partialIndex)) {
            copy_len = ctx->blockSize - ctx->partialIndex;
        }

        PORT_Memcpy(ctx->partialBlock + ctx->partialIndex, data + data_index, copy_len);
        data_index += copy_len;
        ctx->partialIndex += copy_len;
    }

    return SECSuccess;
}

/* NIST SP.800-38B, 6.2 MAC Generation */
SECStatus
CMAC_Finish(CMACContext *ctx, unsigned char *result,
            unsigned int *result_len,
            unsigned int max_result_len)
{
    if (ctx == NULL || result == NULL || max_result_len == 0) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    if (max_result_len > ctx->blockSize) {
        /* This is a weird situation. The PKCS #11 soft tokencode passes
         * sizeof(result) here, which is hard-coded as SFTK_MAX_MAC_LENGTH.
         * This later gets truncated to min(SFTK_MAX_MAC_LENGTH, requested). */
        max_result_len = ctx->blockSize;
    }

    /* Step 4: If M_n* is a complete block, M_n = K1 ^ M_n*. Else,
     * M_n = K2 ^ (M_n* || 10^j). */
    if (ctx->partialIndex == ctx->blockSize) {
        /* XOR in K1. */
        for (unsigned int index = 0; index < ctx->blockSize; index++) {
            ctx->partialBlock[index] ^= ctx->k1[index];
        }
    } else {
        /* Use 10* padding on the partial block. */
        ctx->partialBlock[ctx->partialIndex++] = 0x80;
        PORT_Memset(ctx->partialBlock + ctx->partialIndex, 0,
                    ctx->blockSize - ctx->partialIndex);
        ctx->partialIndex = ctx->blockSize;

        /* XOR in K2. */
        for (unsigned int index = 0; index < ctx->blockSize; index++) {
            ctx->partialBlock[index] ^= ctx->k2[index];
        }
    }

    /* Encrypt the block. */
    if (cmac_UpdateState(ctx) != SECSuccess) {
        return SECFailure;
    }

    /* Step 7 & 8: T = MSB_tlen(C_n); return T. */
    PORT_Memcpy(result, ctx->lastBlock, max_result_len);
    if (result_len != NULL) {
        *result_len = max_result_len;
    }
    return SECSuccess;
}

void
CMAC_Destroy(CMACContext *ctx, PRBool free_it)
{
    if (ctx == NULL) {
        return;
    }

    if (ctx->cipherType == CMAC_AES && ctx->cipher.aes != NULL) {
        AES_DestroyContext(ctx->cipher.aes, PR_TRUE);
    }

    /* Destroy everything in the context. This includes sensitive data in
     * K1, K2, and lastBlock. */
    PORT_Memset(ctx, 0, sizeof(*ctx));

    if (free_it == PR_TRUE) {
        PORT_Free(ctx);
    }
}
