/* Copyright (c) (2012,2014-2016,2018-2020) Apple Inc. All rights reserved.
 *
 * corecrypto is licensed under Apple Inc.’s Internal Use License Agreement (which
 * is contained in the License.txt file distributed with corecrypto) and only to
 * people who accept that license. IMPORTANT:  Any license rights granted to you by
 * Apple Inc. (if any) are limited to internal use within your organization only on
 * devices and computers you own or control, for the sole purpose of verifying the
 * security characteristics and correct functioning of the Apple Software.  You may
 * not, directly or indirectly, redistribute the Apple Software or any portions thereof.
 */

#include <stdbool.h>
#include "ccsymmetric.h"
#include "testmore.h"

int cc_get_ciphermode(cc_cipher_select cipher, cc_mode_select mode, cc_direction direction, cc_ciphermode_descriptor desc)
{
    desc->cipher = cipher;
    desc->mode = mode;
    desc->direction = direction;
    desc->ciphermode.data = NULL;
    int op = direction == cc_Encrypt; // save editting flip-flop logic
    switch (cipher) {
    case cc_cipherAES:
        switch (mode) {
        case cc_ModeECB:
            desc->ciphermode.ecb = (op) ? ccaes_ecb_encrypt_mode() : ccaes_ecb_decrypt_mode();
            break;
        case cc_ModeCBC:
            desc->ciphermode.cbc = (op) ? ccaes_cbc_encrypt_mode() : ccaes_cbc_decrypt_mode();
            break;
        case cc_ModeCFB:
            desc->ciphermode.cfb = (op) ? ccaes_cfb_encrypt_mode() : ccaes_cfb_decrypt_mode();
            break;
        case cc_ModeCTR:
            desc->ciphermode.ctr = ccaes_ctr_crypt_mode();
            break;
        case cc_ModeOFB:
            desc->ciphermode.ofb = ccaes_ofb_crypt_mode();
            break;
        case cc_ModeXTS:
            desc->ciphermode.xts = (op) ? ccaes_xts_encrypt_mode() : ccaes_xts_decrypt_mode();
            break;
        case cc_ModeCFB8:
            desc->ciphermode.cfb8 = (op) ? ccaes_cfb8_encrypt_mode() : ccaes_cfb8_decrypt_mode();
            break;
        case cc_ModeGCM:
            desc->ciphermode.gcm = (op) ? ccaes_gcm_encrypt_mode() : ccaes_gcm_decrypt_mode();
            break;
        case cc_ModeCCM:
            desc->ciphermode.ccm = (op) ? ccaes_ccm_encrypt_mode() : ccaes_ccm_decrypt_mode();
            break;
        }
        break;
    case cc_cipherDES:
        switch (mode) {
        case cc_ModeECB:
            desc->ciphermode.ecb = (op) ? ccdes_ecb_encrypt_mode() : ccdes_ecb_decrypt_mode();
            break;
        case cc_ModeCBC:
            desc->ciphermode.cbc = (op) ? ccdes_cbc_encrypt_mode() : ccdes_cbc_decrypt_mode();
            break;
        case cc_ModeCFB:
            desc->ciphermode.cfb = (op) ? ccdes_cfb_encrypt_mode() : ccdes_cfb_decrypt_mode();
            break;
        case cc_ModeCTR:
            desc->ciphermode.ctr = ccdes_ctr_crypt_mode();
            break;
        case cc_ModeOFB:
            desc->ciphermode.ofb = ccdes_ofb_crypt_mode();
            break;
        case cc_ModeCFB8:
            desc->ciphermode.cfb8 = (op) ? ccdes_cfb8_encrypt_mode() : ccdes_cfb8_decrypt_mode();
            break;
        }
        break;
    case cc_cipher3DES:
        switch (mode) {
        case cc_ModeECB:
            desc->ciphermode.ecb = (op) ? ccdes3_ecb_encrypt_mode() : ccdes3_ecb_decrypt_mode();
            break;
        case cc_ModeCBC:
            desc->ciphermode.cbc = (op) ? ccdes3_cbc_encrypt_mode() : ccdes3_cbc_decrypt_mode();
            break;
        case cc_ModeCFB:
            desc->ciphermode.cfb = (op) ? ccdes3_cfb_encrypt_mode() : ccdes3_cfb_decrypt_mode();
            break;
        case cc_ModeCTR:
            desc->ciphermode.ctr = ccdes3_ctr_crypt_mode();
            break;
        case cc_ModeOFB:
            desc->ciphermode.ofb = ccdes3_ofb_crypt_mode();
            break;
        case cc_ModeCFB8:
            desc->ciphermode.cfb8 = (op) ? ccdes3_cfb8_encrypt_mode() : ccdes3_cfb8_decrypt_mode();
            break;
        }
        break;
    case cc_cipherCAST:
        switch (mode) {
        case cc_ModeECB:
            desc->ciphermode.ecb = (op) ? cccast_ecb_encrypt_mode() : cccast_ecb_decrypt_mode();
            break;
        case cc_ModeCBC:
            desc->ciphermode.cbc = (op) ? cccast_cbc_encrypt_mode() : cccast_cbc_decrypt_mode();
            break;
        case cc_ModeCFB:
            desc->ciphermode.cfb = (op) ? cccast_cfb_encrypt_mode() : cccast_cfb_decrypt_mode();
            break;
        case cc_ModeCTR:
            desc->ciphermode.ctr = cccast_ctr_crypt_mode();
            break;
        case cc_ModeOFB:
            desc->ciphermode.ofb = cccast_ofb_crypt_mode();
            break;
        case cc_ModeCFB8:
            desc->ciphermode.cfb8 = (op) ? cccast_cfb8_encrypt_mode() : cccast_cfb8_decrypt_mode();
            break;
        }
        break;
    case cc_cipherRC2:
        switch (mode) {
        case cc_ModeECB:
            desc->ciphermode.ecb = (op) ? ccrc2_ecb_encrypt_mode() : ccrc2_ecb_decrypt_mode();
            break;
        case cc_ModeCBC:
            desc->ciphermode.cbc = (op) ? ccrc2_cbc_encrypt_mode() : ccrc2_cbc_decrypt_mode();
            break;
        case cc_ModeCFB:
            desc->ciphermode.cfb = (op) ? ccrc2_cfb_encrypt_mode() : ccrc2_cfb_decrypt_mode();
            break;
        case cc_ModeCTR:
            desc->ciphermode.ctr = ccrc2_ctr_crypt_mode();
            break;
        case cc_ModeOFB:
            desc->ciphermode.ofb = ccrc2_ofb_crypt_mode();
            break;
        case cc_ModeCFB8:
            desc->ciphermode.cfb8 = (op) ? ccrc2_cfb8_encrypt_mode() : ccrc2_cfb8_decrypt_mode();
            break;
        }
        break;
    case cc_cipherBlowfish:
        switch (mode) {
        case cc_ModeECB:
            desc->ciphermode.ecb = (op) ? ccblowfish_ecb_encrypt_mode() : ccblowfish_ecb_decrypt_mode();
            break;
        case cc_ModeCBC:
            desc->ciphermode.cbc = (op) ? ccblowfish_cbc_encrypt_mode() : ccblowfish_cbc_decrypt_mode();
            break;
        case cc_ModeCFB:
            desc->ciphermode.cfb = (op) ? ccblowfish_cfb_encrypt_mode() : ccblowfish_cfb_decrypt_mode();
            break;
        case cc_ModeCTR:
            desc->ciphermode.ctr = ccblowfish_ctr_crypt_mode();
            break;
        case cc_ModeOFB:
            desc->ciphermode.ofb = ccblowfish_ofb_crypt_mode();
            break;
        case cc_ModeCFB8:
            desc->ciphermode.cfb8 = (op) ? ccblowfish_cfb8_encrypt_mode() : ccblowfish_cfb8_decrypt_mode();
            break;
        }
        break;
    }
    if (desc->ciphermode.data == NULL)
        return CC_FAILURE;
    return CC_SUCCESS;
}

int cc_get_C_ciphermode(cc_cipher_select cipher, cc_mode_select mode, cc_direction direction, cc_ciphermode_descriptor desc)
{
    desc->cipher = cipher;
    desc->mode = mode;
    desc->direction = direction;
    desc->ciphermode.data = NULL;
    return CC_UNIMPLEMENTED;
}

static bool verify_and_ok_duplicate_key(const void *key, size_t keylen)
{
    if (keylen != 24) {
        return false;
    }
    if (memcmp(key, key+8, 8) == 0 || memcmp(key, key+16, 8) == 0 || memcmp(key+8, key+16, 8) == 0) {
        return true;
    }
    return false;
}

int cc_symmetric_setup(cc_ciphermode_descriptor cm, const void *key, size_t keylen, const void *iv, cc_symmetric_context_p ctx)
{
    int rc;
    switch (cm->mode) {
    case cc_ModeECB:
        rc = cm->ciphermode.ecb->init(cm->ciphermode.ecb, ctx->ctx.ecb, keylen, key);
        break;
    case cc_ModeCBC:
        rc = cm->ciphermode.cbc->init(cm->ciphermode.cbc, ctx->ctx.cbc, keylen, key);
        break;
    case cc_ModeCFB:
        rc = cm->ciphermode.cfb->init(cm->ciphermode.cfb, ctx->ctx.cfb, keylen, key, iv);
        break;
    case cc_ModeCTR:
        rc = cm->ciphermode.ctr->init(cm->ciphermode.ctr, ctx->ctx.ctr, keylen, key, iv);
        break;
    case cc_ModeOFB:
        rc = cm->ciphermode.ofb->init(cm->ciphermode.ofb, ctx->ctx.ofb, keylen, key, iv);
        break;
    case cc_ModeCFB8:
        rc = cm->ciphermode.cfb8->init(cm->ciphermode.cfb8, ctx->ctx.cfb8, keylen, key, iv);
        break;
    default:
        return CC_FAILURE;
    }
    // 3DES was modified to return an error of -1 on a 3DES key setup which repeated any of the sub-keys
    // However, we still want to test these historic test vectors. So if we get a fail on setup we verify that it is
    // for a repeated key in 3DES, and if so, we continue with the test.
    if ((rc == -1) && (cm->cipher == cc_cipher3DES)) {
        rc = verify_and_ok_duplicate_key(key, keylen) ? 0 : rc;
    }

    return (rc == 0) ? CC_SUCCESS : CC_FAILURE;
}

int cc_symmetric_setup_tweaked(cc_ciphermode_descriptor cm,
                               const void *key,
                               size_t keylen,
                               const void *tweak,
                               CC_UNUSED const void *iv,
                               cc_symmetric_context_p ctx)
{
    switch (cm->mode) {
    case cc_ModeXTS:
        if (cm->ciphermode.xts->init(cm->ciphermode.xts, ctx->ctx.xts, keylen, key, tweak) == 0) {
            return CC_SUCCESS;
        }
    }
    return CC_FAILURE;
}

int cc_symmetric_setup_authenticated(cc_ciphermode_descriptor cm,
                                     const void *key,
                                     size_t keylen,
                                     const void *iv,
                                     size_t iv_len,
                                     const void *adata,
                                     size_t adata_len,
                                     const void *adata2,
                                     size_t adata2_len,
                                     size_t data_len,
                                     size_t tag_len,
                                     cc_symmetric_context_p ctx)
{
    int rc;
    switch (cm->mode) {
    case cc_ModeGCM:
        rc = ccgcm_init(cm->ciphermode.gcm, ctx->ctx.gcm, keylen, key);
        if (rc == 0)
            rc = ccgcm_set_iv(cm->ciphermode.gcm, ctx->ctx.gcm, iv_len, iv);
        if (rc == 0)
            rc = ccgcm_aad(cm->ciphermode.gcm, ctx->ctx.gcm, adata_len, adata);
        break;
    case cc_ModeCCM:
        rc = ccccm_init(cm->ciphermode.ccm, ctx->ctx.ccm, keylen, key);
        if (rc == 0)
            rc =
                ccccm_set_iv(cm->ciphermode.ccm, ctx->ctx.ccm, ctx->xtra_ctx.ccm_nonce, iv_len, iv, tag_len, adata_len, data_len);
        if (rc == 0)
            rc = ccccm_cbcmac(cm->ciphermode.ccm, ctx->ctx.ccm, ctx->xtra_ctx.ccm_nonce, adata_len, adata);
        break;
    case cc_ModeSIV:
        rc = ccsiv_init(cm->ciphermode.siv, ctx->ctx.siv, keylen, key);
        if (rc == 0)
            rc = ccsiv_aad(cm->ciphermode.siv, ctx->ctx.siv, adata_len, adata);
        if (rc == 0)
            rc = ccsiv_aad(cm->ciphermode.siv, ctx->ctx.siv, adata2_len, adata2);
        if (rc == 0)
            rc = ccsiv_set_nonce(cm->ciphermode.siv, ctx->ctx.siv, iv_len, iv);
        break;
    case cc_ModeSIV_HMAC:
             rc = ccsiv_hmac_init(cm->ciphermode.siv_hmac, ctx->ctx.siv_hmac, keylen, key, tag_len);
            if (adata_len == 0){
                is(ccsiv_hmac_aad(cm->ciphermode.siv_hmac, ctx->ctx.siv_hmac, adata_len, adata), CCMODE_AD_EMPTY, "Empty associated data not reported as error.");
            }
            if (rc == 0 && adata_len != 0)
                rc = ccsiv_hmac_aad(cm->ciphermode.siv_hmac, ctx->ctx.siv_hmac, adata_len, adata);
            if (rc == 0 && adata2_len != 0)
                rc = ccsiv_hmac_aad(cm->ciphermode.siv_hmac, ctx->ctx.siv_hmac, adata2_len, adata2);
            
            if (iv_len == 0){
                is(ccsiv_hmac_set_nonce(cm->ciphermode.siv_hmac, ctx->ctx.siv_hmac, iv_len, iv), CCMODE_NONCE_EMPTY, "Empty nonce data not reported as error.");
            }
            if (rc == 0 && iv_len != 0)
                rc = ccsiv_hmac_set_nonce(cm->ciphermode.siv_hmac, ctx->ctx.siv_hmac, iv_len, iv);
            break;
        
    default:
        return CC_FAILURE;
    }
    return rc;
}

int cc_symmetric_crypt(cc_symmetric_context_p ctx, const void *iv, const void *in, void *out, size_t len)
{
    int rc;
    switch (ctx->mode_desc->mode) {
    case cc_ModeECB:
        rc = ctx->mode_desc->ciphermode.ecb->ecb(ctx->ctx.ecb, len / ctx->mode_desc->ciphermode.ecb->block_size, in, out);
        break;
    case cc_ModeCBC: {
        cccbc_iv_decl(ctx->mode_desc->ciphermode.cbc->block_size, cbciv);
        if ((rc = cccbc_set_iv(ctx->mode_desc->ciphermode.cbc, cbciv, iv))) {
            return rc;
        }
        if ((rc = ctx->mode_desc->ciphermode.cbc->cbc(
                 ctx->ctx.cbc, cbciv, len / ctx->mode_desc->ciphermode.cbc->block_size, in, out))) {
            return rc;
        }
    } break;
    case cc_ModeCFB:
        rc = ctx->mode_desc->ciphermode.cfb->cfb(ctx->ctx.cfb, len / ctx->mode_desc->ciphermode.cfb->block_size, in, out);
        break;
    case cc_ModeCTR:
        rc = ctx->mode_desc->ciphermode.ctr->ctr(ctx->ctx.ctr, len / ctx->mode_desc->ciphermode.ctr->block_size, in, out);
        break;
    case cc_ModeOFB:
        rc = ctx->mode_desc->ciphermode.ofb->ofb(ctx->ctx.ofb, len / ctx->mode_desc->ciphermode.ofb->block_size, in, out);
        break;
    case cc_ModeCFB8:
        rc = ctx->mode_desc->ciphermode.cfb8->cfb8(ctx->ctx.cfb8, len / ctx->mode_desc->ciphermode.cfb8->block_size, in, out);
        break;
    case cc_ModeXTS: {
        ccxts_tweak_decl(ctx->mode_desc->ciphermode.xts->tweak_size, xts_iv);
        if ((rc = ccxts_set_tweak(ctx->mode_desc->ciphermode.xts, ctx->ctx.xts, xts_iv, iv))) {
            return rc;
        }
        if (ctx->mode_desc->ciphermode.xts->xts(
                ctx->ctx.xts, xts_iv, len / ctx->mode_desc->ciphermode.xts->block_size, in, out) == NULL) {
            return -1;
        }
    } break;
    case cc_ModeGCM:
        rc =
            ccgcm_update(ctx->mode_desc->ciphermode.gcm, ctx->ctx.gcm, len / ctx->mode_desc->ciphermode.gcm->block_size, in, out);
        break;
    case cc_ModeCCM:
        rc = ccccm_update(ctx->mode_desc->ciphermode.ccm,
                          ctx->ctx.ccm,
                          ctx->xtra_ctx.ccm_nonce,
                          len / ctx->mode_desc->ciphermode.ccm->block_size,
                          in,
                          out);
        break;
    case cc_ModeSIV:
        rc = ccsiv_crypt(ctx->mode_desc->ciphermode.siv, ctx->ctx.siv, len / ctx->mode_desc->ciphermode.siv->block_size, in, out);
        break;
    case cc_ModeSIV_HMAC:
        rc = ccsiv_hmac_crypt(ctx->mode_desc->ciphermode.siv_hmac, ctx->ctx.siv_hmac, len, in, out);
        break;
    default:
        return CC_FAILURE;
    }
    return rc;
}

void cc_symmetric_final(CC_UNUSED cc_symmetric_context_p ctx)
{
}

int cc_symmetric_authenticated_finalize(cc_symmetric_context_p ctx, char *tag, size_t tag_len)
{
    int rc;
    switch (ctx->mode_desc->mode) {
    case cc_ModeGCM:
        rc = ctx->mode_desc->ciphermode.gcm->finalize(ctx->ctx.gcm, tag_len, tag);
        break;
    case cc_ModeCCM:
        rc = ctx->mode_desc->ciphermode.ccm->finalize(ctx->ctx.ccm, ctx->xtra_ctx.ccm_nonce, tag);
        break;
    default:
        return CC_FAILURE;
    }
    return rc;
}

int cc_symmetric_oneshot(cc_ciphermode_descriptor cm,
                         const void *key,
                         size_t keylen,
                         const void *iv,
                         const void *in,
                         void *out,
                         size_t len)
{
    int rc;
    switch (cm->mode) {
    case cc_ModeECB:
        rc = ccecb_one_shot(cm->ciphermode.ecb, keylen, key, len / cm->ciphermode.ecb->block_size, in, out);
        break;
    case cc_ModeCBC:
        rc = cccbc_one_shot(cm->ciphermode.cbc, keylen, key, iv, len / cm->ciphermode.cbc->block_size, in, out);
        break;
    case cc_ModeCFB:
        rc = cccfb_one_shot(cm->ciphermode.cfb, keylen, key, iv, len / cm->ciphermode.cfb->block_size, in, out);
        break;
    case cc_ModeCTR:
        rc = ccctr_one_shot(cm->ciphermode.ctr, keylen, key, iv, len / cm->ciphermode.ctr->block_size, in, out);
        break;
    case cc_ModeOFB:
        rc = ccofb_one_shot(cm->ciphermode.ofb, keylen, key, iv, len / cm->ciphermode.ofb->block_size, in, out);
        break;
    case cc_ModeCFB8:
        rc = cccfb8_one_shot(cm->ciphermode.cfb8, keylen, key, iv, len / cm->ciphermode.cfb8->block_size, in, out);
        break;
    default:
        return CC_FAILURE;
    }
    return rc;
}

int cc_symmetric_reset(cc_symmetric_context_p ctx)
{
    int rc;
    switch (ctx->mode_desc->mode) {
    case cc_ModeGCM:
        rc = ccgcm_reset(ctx->mode_desc->ciphermode.gcm, ctx->ctx.gcm);
        break;
    case cc_ModeCCM:
        rc = ccccm_reset(ctx->mode_desc->ciphermode.ccm, ctx->ctx.ccm, ctx->xtra_ctx.ccm_nonce);
        break;
    case cc_ModeSIV:
        rc = ccsiv_reset(ctx->mode_desc->ciphermode.siv, ctx->ctx.siv);
        break;
    default:
        rc = CC_FAILURE;
    }
    return rc;
}

size_t cc_symmetric_bloc_size(cc_ciphermode_descriptor cm)
{
    size_t block_size = 0;

    switch (cm->mode) {
    case cc_ModeECB:
        block_size = cm->ciphermode.ecb->block_size;
        break;
    case cc_ModeCBC:
        block_size = cm->ciphermode.cbc->block_size;
        break;
    case cc_ModeCFB:
        block_size = cm->ciphermode.cfb->block_size;
        break;
    case cc_ModeCTR:
        block_size = cm->ciphermode.ctr->block_size;
        break;
    case cc_ModeOFB:
        block_size = cm->ciphermode.ofb->block_size;
        break;
    case cc_ModeCFB8:
        block_size = cm->ciphermode.cfb8->block_size;
        break;
    default:
        return 0;
    }
    return block_size;
}
