/**************************************************************************
 *{@C
 *      Copyright:      1988-2023 Paul Obermeier (obermeier@poSoft.de)
 *
 *      Module:         Utilities
 *      Filename:       UT_CryptCommon.c
 *
 *      Author:         Paul Obermeier
 *
 *      Description:    Functions to encrypt and decrypt blocks of data.
 *
 *      Additional documentation:
 *
 *      Exported functions:
 *                      UT_CryptEncode
 *                      UT_CryptDecode
 *                      UT_CryptUuEncode
 *
 **************************************************************************/

#include <stdio.h>
#include <string.h>

#include "UT_Compat.h"
#include "UT_Const.h"
#include "UT_Random.h"
#include "UT_Error.h"
#include "UT_Crypt.h"

/* String constants */
#define str_err_plain_size "Plain text size (%d) is incorrect. " \
                           "Must be divisible by 3"

/* Compute a checksum for a block of data. */

static UInt16 cry_checksum (Int32 n, UInt8 data[])
{
    UInt8  byte, *current, *end;
    UInt16 checksum;
    Int32  i;

    current = data;
    end = data + n;
    checksum = 0x0000;
    byte = 0;
    i = 0;
    while (current < end) {
        if (i-- == 0) {
            byte = *(current++);
            i = 8;
        }
        checksum = (checksum << 1) |
                    (((checksum >> 15) & 0x0001) ^
                    ((checksum >> 12) & 0x0001) ^
                    ((checksum >> 5) & 0x0001) ^
                    (byte & 0x01));
        byte >>= 1;
    }
    return checksum & 0x0000ffff;
}

/* Exclusive-or a sequence of bytes with a pseudo-random byte sequence. */

static void cry_xor (UInt32 key, Int32 n, UInt8 src[], UInt8 dest[])
{
    UInt8        *stop;
    UT_RandomGen rgen;

    stop = src + n;
    rgen = UT_RandomNew (key);
    while (src < stop) {
        *(dest++) = *(src++) ^ (UT_RandomGetInt32(rgen, 0, MaxInt32) >> 24);
    }
    UT_RandomDelete (rgen);
    return;
}

/***************************************************************************
 *[@e
 *      Name:           UT_CryptEncode
 *
 *      Usage:          Encrypt a block of data.
 *
 *      Synopsis:       void UT_CryptEncode (UInt8 key[8], UInt32 seq, Int32 n,
 *                                        UInt8 *pt, UInt8 *ct)
 *
 *      Description:    Pt must point to a block of n bytes of plaintext
 *                      which is encrypted using a secret key, k, and a
 *                      sequence number, seq.
 *                      The ciphertext, which will be n+2 bytes long,
 *                      is stored in the location pointed to by ct.
 *
 *                      Note: If secret data have to be stored in a file,
 *                      they are usually encrypted block by block and
 *                      then written into the file.
 *                      If all data blocks were encrypted using the same key,
 *                      it would be easy to detect blocks with equal contents.
 *                      Therefore each block should be assigned a sequence
 *                      number, seq, which is used by UT_CryptEncode to modify
 *                      the key, k, before encrypting the data.
 *
 *      Return value:   None.
 *
 *      See also:
 *
 ***************************************************************************/

void UT_CryptEncode (UInt8 key[8], UInt32 seq, Int32 n, UInt8 *pt, UInt8 *ct)
{
    UInt16 checksum;
    UInt32 key1, key2;

    key1        = key[0] | (key[2] << 8) | (key[4] << 16) | (key[6] << 24);
    key2        = key[1] | (key[3] << 8) | (key[5] << 16) | (key[7] << 24);
    checksum = cry_checksum (n, pt);
    key1 ^= ((UInt32)checksum & 0x0000ffff);
    key2 ^= seq;
    cry_xor (key1, n, pt, ct + 2);
    ct[0] = (UInt8) (checksum);
    ct[1] = (UInt8) (checksum >> 8);
    cry_xor (key2, n + 2, ct, ct);
    return;
}

/***************************************************************************
 *[@e
 *      Name:           UT_CryptDecode
 *
 *      Usage:          Decrypt a block of data.
 *
 *      Synopsis:       UT_CryptStatus UT_CryptDecode (UInt8 key[8], UInt32 seq,
 *                                             Int32 n, UInt8 *ct, UInt8 *pt)
 *
 *      Description:    Ct must point to a block of n+2 bytes of ciphertext
 *                      encrypted with UT_CryptEncode. K, the encryption key,
 *                      and s, the sequence number, are used to transform
 *                      the ciphertext into n bytes of plaintext.
 *                      The plaintext is stored at the location indicated
 *                      by pt.
 *
 *                      Note: K and seq must be those used when the data
 *                      were encrypted. Of course.
 *
 *      Return value:   UT_CryptStatusOK, if the ciphertext could be decrypted,
 *                      otherwise UT_CryptStatusWrong.
 *
 *      See also:
 *
 ***************************************************************************/

UT_CryptStatus UT_CryptDecode (UInt8 key[8], UInt32 seq, Int32 n, UInt8 *ct, UInt8 *pt)
{
    UInt16 checksum;
    UInt32 key1, key2;

    key1 = key[0] | (key[2] << 8) | (key[4] << 16) | (key[6] << 24);
    key2 = key[1] | (key[3] << 8) | (key[5] << 16) | (key[7] << 24);
    key2 ^= seq;
    cry_xor (key2, n + 2, ct, ct);
    checksum = ((UInt16)ct[0] & 0x00ff) | ((UInt16)ct[1] << 8);
    key1 ^= ((UInt32)checksum & 0x0000ffff);
    cry_xor (key1, n, ct + 2, pt);
    if (checksum != cry_checksum (n, pt)) {
        return UT_CryptStatusWrong;
    }
    return UT_CryptStatusOK;
}

/* The two currently defined translation tables.
   The first is the standard uuencoding, the second is base64 encoding. 
*/

static const UInt8 uu_std[64] =
{
  '`', '!', '"', '#', '$', '%', '&', '\'',
  '(', ')', '*', '+', ',', '-', '.', '/',
  '0', '1', '2', '3', '4', '5', '6', '7',
  '8', '9', ':', ';', '<', '=', '>', '?',
  '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
  'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
  'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
  'X', 'Y', 'Z', '[', '\\', ']', '^', '_'
};

static const UInt8 uu_base64[64] =
{
  'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
  'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
  'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
  'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
  'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
  'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
  'w', 'x', 'y', 'z', '0', '1', '2', '3',
  '4', '5', '6', '7', '8', '9', '+', '/'
};

/***************************************************************************
 *[@e
 *      Name:           UT_CryptUuEncode
 *
 *      Usage:          UUencode a block of data.
 *
 *      Synopsis:       Int32 UT_CryptUuEncode (Int32 n, UInt8 *pt, UInt8 *ct,
 *                                          UT_CryptMode mode)
 *
 *      Description:    Pt must point to a block of n bytes of plaintext
 *                      which is uuencoded either with standard mode
 *                      (UT_CryptModeStandard) or in BASE64 mode
 *                      (UT_CryptModeBase64).
 *
 *                      The ciphertext is stored in the location pointed to
 *                      by ct.
 *
 *                      Note: 
 *                          - 3 Bytes of plain text are encoded into 
 *                            4 bytes of ciphertext.
 *                          - n must be divisible by 3.
 *                          - A newline character is appended to the end of the
 *                            ciphertext.
 *                          - ct must be at least of size (n / 3 * 4 + 1).
 *
 *      Return value:   The number of bytes of the ciphertext.
 *                      In case of failure -1 is returned.
 *
 *      See also:
 *
 ***************************************************************************/

Int32 UT_CryptUuEncode (Int32 n, UInt8 *pt, UInt8 *ct, UT_CryptMode mode)
{
    #define ENC(Char) (trans_ptr[(Char) & 077])
    const UInt8 *trans_ptr;
    UInt8 *inPtr, *outPtr;
    int ch;

    if ( n % 3 ) {
        UT_ErrSetNum (UT_ErrParamRange, str_err_plain_size, n);
        /* return -1; */
    }

    trans_ptr = (mode == UT_CryptModeStandard? uu_std: uu_base64);
    inPtr  = pt;
    outPtr = ct;

    if (trans_ptr == uu_std) {
        *outPtr++ = ENC (n);
    }
    for (; n > 2; n -= 3, inPtr += 3) {
        ch = *inPtr >> 2;
        ch = ENC (ch);
        *outPtr++ = ch;

        ch = ((*inPtr << 4) & 060) | ((inPtr[1] >> 4) & 017);
        ch = ENC (ch);
        *outPtr++ = ch;

        ch = ((inPtr[1] << 2) & 074) | ((inPtr[2] >> 6) & 03);
        ch = ENC (ch);
        *outPtr++ = ch;

        ch = inPtr[2] & 077;
        ch = ENC (ch);
        *outPtr++ = ch;
    }

    if (n == 0) {
        *outPtr++ = '\n';
        return outPtr - ct;
    }

    while (n != 0) {
        UInt8 c1 = *inPtr;
        UInt8 c2 = n == 1 ? 0 : inPtr[1];

        ch = c1 >> 2;
        ch = ENC (ch);
        *outPtr++ = ch;

        ch = ((c1 << 4) & 060) | ((c2 >> 4) & 017);
        ch = ENC (ch);
        *outPtr++ = ch;

        if (n == 1) {
            ch = trans_ptr == uu_std ? ENC ('\0') : '=';
        } else {
            ch = (c2 << 2) & 074;
            ch = ENC (ch);
        }
        *outPtr++ = ch;

        ch = trans_ptr == uu_std ? ENC ('\0') : '=';
        *outPtr++ = ch;
        *outPtr++ = '\n';
        break;
    }

    if (trans_ptr == uu_std) {
        *outPtr++ = ENC ('\0');
        *outPtr++ = '\n';
    }
    return outPtr - ct;
}
