/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/* lib/crypto/krb/cmac.c */
/*
 * Copyright 2010 by the Massachusetts Institute of Technology.
 * All Rights Reserved.
 *
 * Export of this software from the United States of America may
 *   require a specific license from the United States Government.
 *   It is the responsibility of any person or organization contemplating
 *   export to obtain such a license before exporting.
 *
 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
 * distribute this software and its documentation for any purpose and
 * without fee is hereby granted, provided that the above copyright
 * notice appear in all copies and that both that copyright notice and
 * this permission notice appear in supporting documentation, and that
 * the name of M.I.T. not be used in advertising or publicity pertaining
 * to distribution of the software without specific, written prior
 * permission.  Furthermore if you modify this software you must label
 * your software as modified software and not distribute it in such a
 * fashion that it might be confused with the original M.I.T. software.
 * M.I.T. makes no representations about the suitability of
 * this software for any purpose.  It is provided "as is" without express
 * or implied warranty.
 */
/*
 * Portions Copyright (C) The Internet Society (2006).
 *
 * This document is subject to the rights, licenses and restrictions
 * contained in BCP 78, and except as set forth therein, the authors
 * retain all their rights.
 *
 * This document and the information contained herein are provided on an
 * "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS
 * OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY AND THE INTERNET
 * ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE
 * INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED
 * WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
 */

#include "crypto_int.h"

#define BLOCK_SIZE 16

static unsigned char const_Rb[BLOCK_SIZE] = {
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87
};

static void
xor_128(unsigned char *a, unsigned char *b, unsigned char *out)
{
    int z;

    for (z = 0; z < BLOCK_SIZE / 4; z++) {
        unsigned char *aptr = &a[z * 4];
        unsigned char *bptr = &b[z * 4];
        unsigned char *outptr = &out[z * 4];

        store_32_n(load_32_n(aptr) ^ load_32_n(bptr), outptr);
    }
}

static void
leftshift_onebit(unsigned char *input, unsigned char *output)
{
    int i;
    unsigned char overflow = 0;

    for (i = BLOCK_SIZE - 1; i >= 0; i--) {
        output[i] = input[i] << 1;
        output[i] |= overflow;
        overflow = (input[i] & 0x80) ? 1 : 0;
    }
}

/* Generate subkeys K1 and K2 as described in RFC 4493 figure 2.2. */
static krb5_error_code
generate_subkey(const struct krb5_enc_provider *enc,
                krb5_key key,
                unsigned char *K1,
                unsigned char *K2)
{
    unsigned char L[BLOCK_SIZE];
    unsigned char tmp[BLOCK_SIZE];
    krb5_data d;
    krb5_error_code ret;

    /* L := encrypt(K, const_Zero) */
    memset(L, 0, sizeof(L));
    d = make_data(L, BLOCK_SIZE);
    ret = encrypt_block(enc, key, &d);
    if (ret != 0)
        return ret;

    /* K1 := (MSB(L) == 0) ? L << 1 : (L << 1) XOR const_Rb */
    if ((L[0] & 0x80) == 0) {
        leftshift_onebit(L, K1);
    } else {
        leftshift_onebit(L, tmp);
        xor_128(tmp, const_Rb, K1);
    }

    /* K2 := (MSB(K1) == 0) ? K1 << 1 : (K1 << 1) XOR const_Rb */
    if ((K1[0] & 0x80) == 0) {
        leftshift_onebit(K1, K2);
    } else {
        leftshift_onebit(K1, tmp);
        xor_128(tmp, const_Rb, K2);
    }

    return 0;
}

/* Pad out lastb with a 1 bit followed by 0 bits, placing the result in pad. */
static void
padding(unsigned char *lastb, unsigned char *pad, int length)
{
    int j;

    /* original last block */
    for (j = 0; j < BLOCK_SIZE; j++) {
        if (j < length) {
            pad[j] = lastb[j];
        } else if (j == length) {
            pad[j] = 0x80;
        } else {
            pad[j] = 0x00;
        }
    }
}

/*
 * Implementation of CMAC algorithm. When used with AES, this function
 * is compatible with RFC 4493 figure 2.3.
 */
krb5_error_code
krb5int_cmac_checksum(const struct krb5_enc_provider *enc, krb5_key key,
                      const krb5_crypto_iov *data, size_t num_data,
                      krb5_data *output)
{
    unsigned char Y[BLOCK_SIZE], M_last[BLOCK_SIZE], padded[BLOCK_SIZE];
    unsigned char K1[BLOCK_SIZE], K2[BLOCK_SIZE];
    unsigned char input[BLOCK_SIZE];
    unsigned int n, i, flag;
    krb5_error_code ret;
    struct iov_cursor cursor;
    size_t length;
    krb5_crypto_iov iov[1];
    krb5_data d;

    assert(enc->cbc_mac != NULL);

    if (enc->block_size != BLOCK_SIZE)
        return KRB5_BAD_MSIZE;

    length = iov_total_length(data, num_data, TRUE);

    /* Step 1. */
    ret = generate_subkey(enc, key, K1, K2);
    if (ret != 0)
        return ret;

    /* Step 2. */
    n = (length + BLOCK_SIZE - 1) / BLOCK_SIZE;

    /* Step 3. */
    if (n == 0) {
        n = 1;
        flag = 0;
    } else {
        flag = ((length % BLOCK_SIZE) == 0);
    }

    iov[0].flags = KRB5_CRYPTO_TYPE_DATA;
    iov[0].data = make_data(input, BLOCK_SIZE);

    /* Step 5 (we'll do step 4 in a bit). */
    memset(Y, 0, BLOCK_SIZE);
    d = make_data(Y, BLOCK_SIZE);

    /* Step 6 (all but last block). */
    k5_iov_cursor_init(&cursor, data, num_data, BLOCK_SIZE, TRUE);
    for (i = 0; i < n - 1; i++) {
        k5_iov_cursor_get(&cursor, input);

        ret = enc->cbc_mac(key, iov, 1, &d, &d);
        if (ret != 0)
            return ret;
    }

    /* Step 4. */
    k5_iov_cursor_get(&cursor, input);
    if (flag) {
        /* last block is complete block */
        xor_128(input, K1, M_last);
    } else {
        padding(input, padded, length % BLOCK_SIZE);
        xor_128(padded, K2, M_last);
    }

    /* Step 6 (last block). */
    iov[0].data = make_data(M_last, BLOCK_SIZE);
    ret = enc->cbc_mac(key, iov, 1, &d, &d);
    if (ret != 0)
        return ret;

    assert(output->length >= d.length);

    output->length = d.length;
    memcpy(output->data, d.data, d.length);

    return 0;
}
