/*
 * COPYRIGHT (c) International Business Machines Corp. 2001-2017
 *
 * This program is provided under the terms of the Common Public License,
 * version 1.0 (CPL-1.0). Any use, reproduction or distribution for this
 * software constitutes recipient's acceptance of CPL-1.0 terms which can be
 * found in the file LICENSE file or at
 * https://opensource.org/licenses/cpl1.0.php
 */

#include <pthread.h>
#include <string.h>             // for memcmp() et al
#include <stdlib.h>


#include "pkcs11types.h"
#include "defs.h"
#include "host_defs.h"
#include "h_extern.h"
#include "tok_spec_struct.h"
#include "trace.h"

#include <openssl/crypto.h>

// Permutation of 0..255 constructed from the digits of pi. It gives a
// "random" nonlinear byte substitution operation.
//
static const CK_BYTE S[256] = {
    41, 46, 67, 201, 162, 216, 124, 1, 61, 54, 84, 161, 236, 240, 6,
    19, 98, 167, 5, 243, 192, 199, 115, 140, 152, 147, 43, 217, 188,
    76, 130, 202, 30, 155, 87, 60, 253, 212, 224, 22, 103, 66, 111, 24,
    138, 23, 229, 18, 190, 78, 196, 214, 218, 158, 222, 73, 160, 251,
    245, 142, 187, 47, 238, 122, 169, 104, 121, 145, 21, 178, 7, 63,
    148, 194, 16, 137, 11, 34, 95, 33, 128, 127, 93, 154, 90, 144, 50,
    39, 53, 62, 204, 231, 191, 247, 151, 3, 255, 25, 48, 179, 72, 165,
    181, 209, 215, 94, 146, 42, 172, 86, 170, 198, 79, 184, 56, 210,
    150, 164, 125, 182, 118, 252, 107, 226, 156, 116, 4, 241, 69, 157,
    112, 89, 100, 113, 135, 32, 134, 91, 207, 101, 230, 45, 168, 2, 27,
    96, 37, 173, 174, 176, 185, 246, 28, 70, 97, 105, 52, 64, 126, 15,
    85, 71, 163, 35, 221, 81, 175, 58, 195, 92, 249, 206, 186, 197,
    234, 38, 44, 83, 13, 110, 133, 40, 132, 9, 211, 223, 205, 244, 65,
    129, 77, 82, 106, 220, 55, 200, 108, 193, 171, 250, 36, 225, 123,
    8, 12, 189, 177, 74, 120, 136, 149, 139, 227, 99, 232, 109, 233,
    203, 213, 254, 59, 0, 29, 57, 242, 239, 183, 14, 102, 88, 208, 228,
    166, 119, 114, 248, 235, 117, 75, 10, 49, 68, 80, 180, 143, 237,
    31, 26, 219, 153, 141, 51, 159, 17, 131, 20
};

static const CK_BYTE *padding[] = {
    (CK_BYTE *) "",
    (CK_BYTE *) "\x01",
    (CK_BYTE *) "\x02\x02",
    (CK_BYTE *) "\x03\x03\x03",
    (CK_BYTE *) "\x04\x04\x04\x04",
    (CK_BYTE *) "\x05\x05\x05\x05\x05",
    (CK_BYTE *) "\x06\x06\x06\x06\x06\x06",
    (CK_BYTE *) "\x07\x07\x07\x07\x07\x07\x07",
    (CK_BYTE *) "\x08\x08\x08\x08\x08\x08\x08\x08",
    (CK_BYTE *) "\x09\x09\x09\x09\x09\x09\x09\x09\x09",
    (CK_BYTE *) "\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a",
    (CK_BYTE *) "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b",
    (CK_BYTE *) "\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c",
    (CK_BYTE *) "\x0d\x0d\x0d\x0d\x0d\x0d\x0d\x0d\x0d\x0d\x0d\x0d\x0d",
    (CK_BYTE *) "\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e",
    (CK_BYTE *) "\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f",
    (CK_BYTE *)
        "\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10"
};



//
//
CK_RV md2_hash(STDLL_TokData_t *tokdata,
               SESSION *sess,
               CK_BBOOL length_only,
               DIGEST_CONTEXT *ctx,
               CK_BYTE *in_data,
               CK_ULONG in_data_len,
               CK_BYTE *out_data, CK_ULONG *out_data_len)
{
    CK_RV rc;


    if (!sess || !ctx || !out_data_len) {
        TRACE_ERROR("%s received bad argument(s)\n", __func__);
        return CKR_FUNCTION_FAILED;
    }

    if (length_only == TRUE) {
        *out_data_len = MD2_HASH_SIZE;
        return CKR_OK;
    }

    if (*out_data_len < MD2_HASH_SIZE) {
        *out_data_len = MD2_HASH_SIZE;
        TRACE_ERROR("%s\n", ock_err(ERR_BUFFER_TOO_SMALL));
        return CKR_BUFFER_TOO_SMALL;
    }

    rc = md2_hash_update(tokdata, sess, ctx, in_data, in_data_len);
    if (rc != CKR_OK) {
        TRACE_DEVEL("md2_hash_update failed.\n");
        return CKR_FUNCTION_FAILED;
    }

    return md2_hash_final(tokdata, sess, FALSE, ctx, out_data, out_data_len);
}


//
//
CK_RV md2_hash_update(STDLL_TokData_t *tokdata,
                      SESSION *sess,
                      DIGEST_CONTEXT *ctx,
                      CK_BYTE *in_data, CK_ULONG in_data_len)
{
    if (!sess || !ctx || !in_data) {
        TRACE_ERROR("%s received bad argument(s)\n", __func__);
        return CKR_FUNCTION_FAILED;
    }

    return ckm_md2_update(tokdata, (MD2_CONTEXT *) ctx->context, in_data,
                          in_data_len);
}


//
//
CK_RV md2_hash_final(STDLL_TokData_t *tokdata,
                     SESSION *sess,
                     CK_BYTE length_only,
                     DIGEST_CONTEXT *ctx,
                     CK_BYTE *out_data, CK_ULONG *out_data_len)
{
    CK_RV rc;

    if (!sess || !ctx || !out_data_len) {
        TRACE_ERROR("%s received bad argument(s)\n", __func__);
        return CKR_FUNCTION_FAILED;
    }
    if (length_only == TRUE) {
        *out_data_len = MD2_HASH_SIZE;
        return CKR_OK;
    }

    if (*out_data_len < MD2_HASH_SIZE) {
        *out_data_len = MD2_HASH_SIZE;
        TRACE_ERROR("%s\n", ock_err(ERR_BUFFER_TOO_SMALL));
        return CKR_BUFFER_TOO_SMALL;
    }

    rc = ckm_md2_final(tokdata, (MD2_CONTEXT *) ctx->context,
                       out_data, MD2_HASH_SIZE);

    if (rc == CKR_OK) {
        *out_data_len = MD2_HASH_SIZE;
        return rc;
    }

    return rc;
}


// this routine gets called for two mechanisms actually:
//    CKM_MD2_HMAC
//    CKM_MD2_HMAC_GENERAL
//
CK_RV md2_hmac_sign(STDLL_TokData_t *tokdata,
                    SESSION *sess,
                    CK_BBOOL length_only,
                    SIGN_VERIFY_CONTEXT *ctx,
                    CK_BYTE *in_data,
                    CK_ULONG in_data_len,
                    CK_BYTE *out_data, CK_ULONG *out_data_len)
{
    OBJECT *key_obj = NULL;
    CK_ATTRIBUTE *attr = NULL;
    CK_BYTE hash[MD2_HASH_SIZE];
    DIGEST_CONTEXT digest_ctx;
    CK_MECHANISM digest_mech;
    CK_BYTE k_ipad[MD2_BLOCK_SIZE];
    CK_BYTE k_opad[MD2_BLOCK_SIZE];
    CK_ULONG key_bytes, hash_len, hmac_len;
    CK_ULONG i;
    CK_RV rc;


    if (!sess || !ctx || !out_data_len) {
        TRACE_ERROR("%s received bad argument(s)\n", __func__);
        return CKR_FUNCTION_FAILED;
    }

    if (ctx->mech.mechanism == CKM_MD2_HMAC_GENERAL) {
        hmac_len = *(CK_ULONG *) ctx->mech.pParameter;

        if (hmac_len == 0) {
            *out_data_len = 0;
            return CKR_OK;
        }
    } else {
        hmac_len = MD2_HASH_SIZE;
    }


    if (length_only == TRUE) {
        *out_data_len = hmac_len;
        return CKR_OK;
    }

    memset(&digest_ctx, 0x0, sizeof(DIGEST_CONTEXT));

    rc = object_mgr_find_in_map1(tokdata, ctx->key, &key_obj, READ_LOCK);
    if (rc != CKR_OK) {
        TRACE_ERROR("Failed to acquire key from specified handle.\n");
        if (rc == CKR_OBJECT_HANDLE_INVALID)
            return CKR_KEY_HANDLE_INVALID;
        else
            return rc;
    }
    rc = template_attribute_get_non_empty(key_obj->template, CKA_VALUE,
                                          &attr);
    if (rc != CKR_OK) {
        TRACE_ERROR("Could not find CKA_VALUE in the template\n");
        goto done;
    }

    key_bytes = attr->ulValueLen;


    // build (K XOR ipad), (K XOR opad)
    //
    if (key_bytes > MD2_BLOCK_SIZE) {
        digest_mech.mechanism = CKM_MD2;
        digest_mech.ulParameterLen = 0;
        digest_mech.pParameter = NULL;

        rc = digest_mgr_init(tokdata, sess, &digest_ctx, &digest_mech);
        if (rc != CKR_OK) {
            TRACE_DEVEL("Digest Mgr Init failed.\n");
            goto done;
        }
        hash_len = sizeof(hash);
        rc = digest_mgr_digest(tokdata, sess, FALSE, &digest_ctx,
                               attr->pValue, attr->ulValueLen, hash, &hash_len);
        if (rc != CKR_OK) {
            TRACE_DEVEL("Digest Mgr Digest failed.\n");
            digest_mgr_cleanup(&digest_ctx);
            goto done;
        }
        memset(&digest_ctx, 0x0, sizeof(DIGEST_CONTEXT));

        for (i = 0; i < hash_len; i++) {
            k_ipad[i] = hash[i] ^ 0x36;
            k_opad[i] = hash[i] ^ 0x5C;
        }

        memset(&k_ipad[i], 0x36, MD2_BLOCK_SIZE - i);
        memset(&k_opad[i], 0x5C, MD2_BLOCK_SIZE - i);
    } else {
        CK_BYTE *key = (CK_BYTE *) attr + sizeof(CK_ATTRIBUTE);

        for (i = 0; i < key_bytes; i++) {
            k_ipad[i] = key[i] ^ 0x36;
            k_opad[i] = key[i] ^ 0x5C;
        }

        memset(&k_ipad[i], 0x36, MD2_BLOCK_SIZE - key_bytes);
        memset(&k_opad[i], 0x5C, MD2_BLOCK_SIZE - key_bytes);
    }

    digest_mech.mechanism = CKM_MD2;
    digest_mech.ulParameterLen = 0;
    digest_mech.pParameter = NULL;


    // inner hash
    //
    rc = digest_mgr_init(tokdata, sess, &digest_ctx, &digest_mech);
    if (rc != CKR_OK) {
        TRACE_DEVEL("Digest Mgr Init failed.\n");
        goto done;
    }
    rc = digest_mgr_digest_update(tokdata, sess, &digest_ctx, k_ipad,
                                  MD2_BLOCK_SIZE);
    if (rc != CKR_OK) {
        TRACE_DEVEL("Digest Mgr Update failed.\n");
        goto done;
    }
    rc = digest_mgr_digest_update(tokdata, sess, &digest_ctx, in_data,
                                  in_data_len);
    if (rc != CKR_OK) {
        TRACE_DEVEL("Digest Mgr Update failed.\n");
        goto done;
    }
    hash_len = sizeof(hash);
    rc = digest_mgr_digest_final(tokdata, sess, FALSE, &digest_ctx, hash,
                                 &hash_len);
    if (rc != CKR_OK) {
        TRACE_DEVEL("Digest Mgr Final failed.\n");
        goto done;
    }
    memset(&digest_ctx, 0x0, sizeof(DIGEST_CONTEXT));


    // outer hash
    //
    rc = digest_mgr_init(tokdata, sess, &digest_ctx, &digest_mech);
    if (rc != CKR_OK) {
        TRACE_DEVEL("Digest Mgr Init failed.\n");
        goto done;
    }
    rc = digest_mgr_digest_update(tokdata, sess, &digest_ctx, k_opad,
                                  MD2_BLOCK_SIZE);
    if (rc != CKR_OK) {
        TRACE_DEVEL("Digest Mgr Update failed.\n");
        goto done;
    }
    rc = digest_mgr_digest_update(tokdata, sess, &digest_ctx, hash, hash_len);
    if (rc != CKR_OK) {
        TRACE_DEVEL("Digest Mgr Update failed.\n");
        goto done;
    }
    hash_len = sizeof(hash);
    rc = digest_mgr_digest_final(tokdata, sess, FALSE, &digest_ctx, hash,
                                 &hash_len);
    if (rc != CKR_OK) {
        TRACE_DEVEL("Digest Mgr Final failed.\n");
        goto done;
    }
    memcpy(out_data, hash, hmac_len);
    *out_data_len = hmac_len;

done:
    object_put(tokdata, key_obj, TRUE);
    key_obj = NULL;

    return rc;
}


//
//
CK_RV md2_hmac_verify(STDLL_TokData_t *tokdata,
                      SESSION *sess,
                      SIGN_VERIFY_CONTEXT *ctx,
                      CK_BYTE *in_data,
                      CK_ULONG in_data_len,
                      CK_BYTE *signature, CK_ULONG sig_len)
{
    CK_BYTE hmac[MD2_HASH_SIZE];
    SIGN_VERIFY_CONTEXT hmac_ctx;
    CK_ULONG hmac_len, len;
    CK_RV rc;

    if (!sess || !ctx || !in_data || !signature) {
        TRACE_ERROR("%s received bad argument(s)\n", __func__);
        return CKR_FUNCTION_FAILED;
    }
    if (ctx->mech.mechanism == CKM_MD2_HMAC_GENERAL)
        hmac_len = *(CK_ULONG *) ctx->mech.pParameter;
    else
        hmac_len = MD2_HASH_SIZE;

    memset(&hmac_ctx, 0, sizeof(SIGN_VERIFY_CONTEXT));

    rc = sign_mgr_init(tokdata, sess, &hmac_ctx, &ctx->mech, FALSE, ctx->key);
    if (rc != CKR_OK) {
        TRACE_DEVEL("Sign Mgr Init failed.\n");
        return rc;
    }
    len = sizeof(hmac);
    rc = sign_mgr_sign(tokdata, sess, FALSE, &hmac_ctx,
                       in_data, in_data_len, hmac, &len);
    if (rc != CKR_OK) {
        TRACE_DEVEL("Sign Mgr Sign failed.\n");
        return rc;
    }
    if ((len != hmac_len) || (len != sig_len)) {
        TRACE_ERROR("%s\n", ock_err(ERR_SIGNATURE_LEN_RANGE));
        return CKR_SIGNATURE_LEN_RANGE;
    }
    if (CRYPTO_memcmp(hmac, signature, hmac_len) != 0) {
        TRACE_ERROR("%s\n", ock_err(ERR_SIGNATURE_INVALID));
        return CKR_SIGNATURE_INVALID;
    }

    return CKR_OK;
}


//
// CKM routines
//


// MD2 block update operation. Continues an MD2 message-digest
//   operation, processing another message block, and updating the
//   context.
//
CK_RV ckm_md2_update(STDLL_TokData_t *tokdata,
                     MD2_CONTEXT *context, CK_BYTE *input, CK_ULONG inputLen)
{
    CK_ULONG i, index, partLen;

    // Update number of bytes mod 16
    //
    index = context->count;
    context->count = (index + inputLen) & 0xf;

    partLen = 16 - index;

    // Process any complete 16-byte blocks
    //
    if (inputLen >= partLen) {
        memcpy((CK_BYTE *) & context->buffer[index], (CK_BYTE *) input,
               partLen);
        ckm_md2_transform(tokdata, context->state, context->checksum,
                          context->buffer);

        for (i = partLen; i + 15 < inputLen; i += 16)
            ckm_md2_transform(tokdata, context->state, context->checksum,
                              &input[i]);

        index = 0;
    } else {
        i = 0;
    }

    // Buffer remaining input
    //
    memcpy((CK_BYTE *) & context->buffer[index], (CK_BYTE *) & input[i],
           inputLen - i);

    return CKR_OK;
}


// MD2 finalization. Ends an MD2 message-digest operation, writing the
//   message digest and zeroizing the context.
//
CK_RV ckm_md2_final(STDLL_TokData_t *tokdata,
                    MD2_CONTEXT *context,
                    CK_BYTE *out_data, CK_ULONG out_data_len)
{
    CK_ULONG index, padLen;

    if (!context || !out_data || (out_data_len < MD2_HASH_SIZE)) {
        TRACE_ERROR("%s received bad argument(s)\n", __func__);
        return CKR_FUNCTION_FAILED;
    }
    // Pad input to 16-byte multiple (1 - 16 pad bytes)
    //
    index = context->count;
    padLen = 16 - index;
    ckm_md2_update(tokdata, context, (CK_BYTE *)padding[padLen], padLen);

    // Add checksum
    //
    ckm_md2_update(tokdata, context, context->checksum, 16);

    // Store state in digest
    //
    memcpy((CK_BYTE *) out_data, (CK_BYTE *) context->state, 16);

    return CKR_OK;
}


// MD2 basic transformation. Transforms state and updates checksum
//   based on block.
//
void ckm_md2_transform(STDLL_TokData_t *tokdata,
                       CK_BYTE *state, CK_BYTE *checksum, CK_BYTE *block)
{
    CK_ULONG i, j, t;
    CK_BYTE x[48];

    UNUSED(tokdata);

    // Form encryption block from state, block, state ^ block.
    //
    memcpy((CK_BYTE *) x, (CK_BYTE *) state, 16);
    memcpy((CK_BYTE *) x + 16, (CK_BYTE *) block, 16);

    for (i = 0; i < 16; i++)
        x[i + 32] = state[i] ^ block[i];

    // Encrypt block (18 rounds).
    //
    t = 0;
    for (i = 0; i < 18; i++) {
        for (j = 0; j < 48; j++)
            t = x[j] ^= S[t];
        t = (t + i) & 0xff;
    }

    // Save new state
    //
    memcpy((CK_BYTE *) state, (CK_BYTE *) x, 16);

    // Update checksum.
    //
    t = checksum[15];
    for (i = 0; i < 16; i++)
        t = checksum[i] ^= S[block[i] ^ t];

}
