/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/* lib/crypto/builtin/des/f_cksum.c */
/*
 * Copyright (C) 1990 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.
 */

/* DES implementation donated by Dennis Ferguson */

/*
 * des_cbc_cksum.c - compute an 8 byte checksum using DES in CBC mode
 */
#include "des_int.h"
#include "f_tables.h"

/*
 * This routine performs DES cipher-block-chaining checksum operation,
 * a.k.a.  Message Authentication Code.  It ALWAYS encrypts from input
 * to a single 64 bit output MAC checksum.
 *
 * The key schedule is passed as an arg, as well as the cleartext or
 * ciphertext. The cleartext and ciphertext should be in host order.
 *
 * NOTE-- the output is ALWAYS 8 bytes long.  If not enough space was
 * provided, your program will get trashed.
 *
 * The input is null padded, at the end (highest addr), to an integral
 * multiple of eight bytes.
 */

unsigned long
mit_des_cbc_cksum(const krb5_octet *in, krb5_octet *out,
                  unsigned long length, const mit_des_key_schedule schedule,
                  const krb5_octet *ivec)
{
    unsigned DES_INT32 left, right;
    const unsigned DES_INT32 *kp;
    const unsigned char *ip;
    unsigned char *op;
    register DES_INT32 len;

    /*
     * Initialize left and right with the contents of the initial
     * vector.
     */
    ip = ivec;
    GET_HALF_BLOCK(left, ip);
    GET_HALF_BLOCK(right, ip);

    /*
     * Suitably initialized, now work the length down 8 bytes
     * at a time.
     */
    ip = in;
    len = length;
    while (len > 0) {
        /*
         * Get more input, xor it in.  If the length is
         * greater than or equal to 8 this is straight
         * forward.  Otherwise we have to fart around.
         */
        if (len >= 8) {
            unsigned DES_INT32 temp;
            GET_HALF_BLOCK(temp, ip);
            left  ^= temp;
            GET_HALF_BLOCK(temp, ip);
            right ^= temp;
            len -= 8;
        } else {
            /*
             * Oh, shoot.  We need to pad the
             * end with zeroes.  Work backwards
             * to do this.
             */
            ip += (int) len;
            switch(len) {
            case 7:
                right ^= (*(--ip) & FF_UINT32) <<  8;
            case 6:
                right ^= (*(--ip) & FF_UINT32) << 16;
            case 5:
                right ^= (*(--ip) & FF_UINT32) << 24;
            case 4:
                left  ^=  *(--ip) & FF_UINT32;
            case 3:
                left  ^= (*(--ip) & FF_UINT32) <<  8;
            case 2:
                left  ^= (*(--ip) & FF_UINT32) << 16;
            case 1:
                left  ^= (*(--ip) & FF_UINT32) << 24;
                break;
            }
            len = 0;
        }

        /*
         * Encrypt what we have
         */
        kp = (const unsigned DES_INT32 *)schedule;
        DES_DO_ENCRYPT(left, right, kp);
    }

    /*
     * Done.  Left and right have the checksum.  Put it into
     * the output.
     */
    op = out;
    PUT_HALF_BLOCK(left, op);
    PUT_HALF_BLOCK(right, op);

    /*
     * Return right.  I'll bet the MIT code returns this
     * inconsistantly (with the low order byte of the checksum
     * not always in the low order byte of the DES_INT32).  We won't.
     */
    return right & 0xFFFFFFFFUL;
}
