/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
 * Copyright (C) 2008 by the Massachusetts Institute of Technology.
 * Copyright 1995 by Richard P. Basch.  All Rights Reserved.
 * Copyright 1995 by Lehman Brothers, Inc.  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 Richard P. Basch, Lehman Brothers and M.I.T. not be used
 * in advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.  Richard P. Basch,
 * Lehman Brothers and M.I.T. make no representations about the suitability
 * of this software for any purpose.  It is provided "as is" without
 * express or implied warranty.
 */

#include "crypto_int.h"
#include "des_int.h"
#include "f_tables.h"

void
krb5int_des3_cbc_encrypt(krb5_crypto_iov *data, unsigned long num_data,
                         const mit_des_key_schedule ks1,
                         const mit_des_key_schedule ks2,
                         const mit_des_key_schedule ks3,
                         mit_des_cblock ivec)
{
    unsigned DES_INT32 left, right;
    const unsigned DES_INT32 *kp1, *kp2, *kp3;
    const unsigned char *ip;
    struct iov_cursor cursor;
    unsigned char block[MIT_DES_BLOCK_LENGTH];

    /* Get key pointers here.  These won't need to be reinitialized. */
    kp1 = (const unsigned DES_INT32 *)ks1;
    kp2 = (const unsigned DES_INT32 *)ks2;
    kp3 = (const unsigned DES_INT32 *)ks3;

    /* Initialize left and right with the contents of the initial vector. */
    ip = (ivec != NULL) ? ivec : mit_des_zeroblock;
    left = load_32_be(ip);
    right = load_32_be(ip + 4);

    k5_iov_cursor_init(&cursor, data, num_data, MIT_DES_BLOCK_LENGTH, FALSE);
    while (k5_iov_cursor_get(&cursor, block)) {
        /* xor this block with the previous ciphertext. */
        left ^= load_32_be(block);
        right ^= load_32_be(block + 4);

        /* Encrypt what we have and store it back into block. */
        DES_DO_ENCRYPT(left, right, kp1);
        DES_DO_DECRYPT(left, right, kp2);
        DES_DO_ENCRYPT(left, right, kp3);
        store_32_be(left, block);
        store_32_be(right, block + 4);

        k5_iov_cursor_put(&cursor, block);
    }

    if (ivec != NULL) {
        store_32_be(left, ivec);
        store_32_be(right, ivec + 4);
    }
}

void
krb5int_des3_cbc_decrypt(krb5_crypto_iov *data, unsigned long num_data,
                         const mit_des_key_schedule ks1,
                         const mit_des_key_schedule ks2,
                         const mit_des_key_schedule ks3,
                         mit_des_cblock ivec)
{
    unsigned DES_INT32 left, right;
    const unsigned DES_INT32 *kp1, *kp2, *kp3;
    const unsigned char *ip;
    unsigned DES_INT32 ocipherl, ocipherr;
    unsigned DES_INT32 cipherl, cipherr;
    struct iov_cursor cursor;
    unsigned char block[MIT_DES_BLOCK_LENGTH];

    /* Get key pointers here.  These won't need to be reinitialized. */
    kp1 = (const unsigned DES_INT32 *)ks1;
    kp2 = (const unsigned DES_INT32 *)ks2;
    kp3 = (const unsigned DES_INT32 *)ks3;

    /*
     * Decrypting is harder than encrypting because of
     * the necessity of remembering a lot more things.
     * Should think about this a little more...
     */

    /* Prime the old cipher with ivec.*/
    ip = (ivec != NULL) ? ivec : mit_des_zeroblock;
    ocipherl = load_32_be(ip);
    ocipherr = load_32_be(ip + 4);

    k5_iov_cursor_init(&cursor, data, num_data, MIT_DES_BLOCK_LENGTH, FALSE);
    while (k5_iov_cursor_get(&cursor, block)) {
        /* Split this block into left and right. */
        cipherl = left = load_32_be(block);
        cipherr = right = load_32_be(block + 4);

        /* Decrypt and xor with the old cipher to get plain text. */
        DES_DO_DECRYPT(left, right, kp3);
        DES_DO_ENCRYPT(left, right, kp2);
        DES_DO_DECRYPT(left, right, kp1);
        left ^= ocipherl;
        right ^= ocipherr;

        /* Store the encrypted halves back into block. */
        store_32_be(left, block);
        store_32_be(right, block + 4);

        /* Save current cipher block halves. */
        ocipherl = cipherl;
        ocipherr = cipherr;

        k5_iov_cursor_put(&cursor, block);
    }

    if (ivec != NULL) {
        store_32_be(ocipherl, ivec);
        store_32_be(ocipherr, ivec + 4);
    }
}
