/**
 * XML Security Library (http://www.aleksey.com/xmlsec).
 *
 * Implementation of AES/DES Key Transport algorithm
 *
 * This is free software; see Copyright file in the source
 * distribution for preciese wording.
 *
 * Copyright (C) 2002-2003 Aleksey Sanin <aleksey@aleksey.com>
 */
#include "globals.h"

#include <stdlib.h>
#include <string.h>

#include <libxml/tree.h>

#include <xmlsec/xmlsec.h>
#include <xmlsec/errors.h>

#include "kw_aes_des.h"

#ifndef XMLSEC_NO_DES

static int      xmlSecKWDes3BufferReverse                       (xmlSecByte *buf, 
                                                                 xmlSecSize size);

/********************************************************************
 *
 * CMS Triple DES Key Wrap
 *
 * http://www.w3.org/TR/xmlenc-core/#sec-Alg-SymmetricKeyWrap
 *
 * The following algorithm wraps (encrypts) a key (the wrapped key, WK)
 * under a TRIPLEDES key-encryption-key (KEK) as specified in [CMS-Algorithms]:
 *
 * 1. Represent the key being wrapped as an octet sequence. If it is a
 *    TRIPLEDES key, this is 24 octets (192 bits) with odd parity bit as
 *    the bottom bit of each octet.
 * 2. Compute the CMS key checksum (section 5.6.1) call this CKS.
 * 3. Let WKCKS = WK || CKS, where || is concatenation.
 * 4. Generate 8 random octets [RANDOM] and call this IV.
 * 5. Encrypt WKCKS in CBC mode using KEK as the key and IV as the
 *    initialization vector. Call the results TEMP1.
 * 6. Left TEMP2 = IV || TEMP1.
 * 7. Reverse the order of the octets in TEMP2 and call the result TEMP3.
 * 8. Encrypt TEMP3 in CBC mode using the KEK and an initialization vector
 *    of 0x4adda22c79e82105. The resulting cipher text is the desired result.
 *    It is 40 octets long if a 168 bit key is being wrapped.
 *
 * The following algorithm unwraps (decrypts) a key as specified in
 * [CMS-Algorithms]:
 *
 * 1. Check if the length of the cipher text is reasonable given the key type.
 *    It must be 40 bytes for a 168 bit key and either 32, 40, or 48 bytes for
 *    a 128, 192, or 256 bit key. If the length is not supported or inconsistent
 *    with the algorithm for which the key is intended, return error.
 * 2. Decrypt the cipher text with TRIPLEDES in CBC mode using the KEK and
 *    an initialization vector (IV) of 0x4adda22c79e82105. Call the output TEMP3.
 * 3. Reverse the order of the octets in TEMP3 and call the result TEMP2.
 * 4. Decompose TEMP2 into IV, the first 8 octets, and TEMP1, the remaining
 *    octets.
 * 5. Decrypt TEMP1 using TRIPLEDES in CBC mode using the KEK and the IV found
 *    in the previous step. Call the result WKCKS.
 * 6. Decompose WKCKS. CKS is the last 8 octets and WK, the wrapped key, are
 *    those octets before the CKS.
 * 7. Calculate a CMS key checksum (section 5.6.1) over the WK and compare
 *    with the CKS extracted in the above step. If they are not equal, return
 *    error.
 * 8. WK is the wrapped key, now extracted for use in data decryption.
 *
 ********************************************************************/
static xmlSecByte xmlSecKWDes3Iv[XMLSEC_KW_DES3_IV_LENGTH] = {
    0x4a, 0xdd, 0xa2, 0x2c, 0x79, 0xe8, 0x21, 0x05
};

int
xmlSecKWDes3Encode(xmlSecKWDes3Id kwDes3Id, void *context,
                  const xmlSecByte *in, xmlSecSize inSize,
                  xmlSecByte *out, xmlSecSize outSize) {
    xmlSecByte sha1[XMLSEC_KW_DES3_SHA_DIGEST_LENGTH];
    xmlSecByte iv[XMLSEC_KW_DES3_IV_LENGTH];
    xmlSecSize s;
    int ret;

    xmlSecAssert2(xmlSecKWDes3CheckId(kwDes3Id), -1);
    xmlSecAssert2(context != NULL, -1);
    xmlSecAssert2(in != NULL, -1);
    xmlSecAssert2(inSize > 0, -1);
    xmlSecAssert2(out != NULL, -1);
    xmlSecAssert2(outSize >= inSize + XMLSEC_KW_DES3_BLOCK_LENGTH + XMLSEC_KW_DES3_IV_LENGTH, -1);

    /* step 2: calculate sha1 and CMS */
    ret = kwDes3Id->sha1(context, in, inSize, sha1, sizeof(sha1));
    if((ret < 0) || (ret != sizeof(sha1))) {
        xmlSecError(XMLSEC_ERRORS_HERE,
                    NULL,
                    "kwDes3Id->sha1",
                    XMLSEC_ERRORS_R_XMLSEC_FAILED,
                    "ret=%d", ret);
        return(-1);
    }

    /* step 3: construct WKCKS as WK || CKS */
    memcpy(out, in, inSize);
    memcpy(out + inSize, sha1, XMLSEC_KW_DES3_BLOCK_LENGTH);

    /* step 4: generate random iv */
    ret = kwDes3Id->generateRandom(context, iv, sizeof(iv));
    if((ret < 0) || (ret != sizeof(iv))) {
        xmlSecError(XMLSEC_ERRORS_HERE,
                    NULL,
                    "kwDes3Id->generateRandom",
                    XMLSEC_ERRORS_R_XMLSEC_FAILED,
                    "ret=%d", ret);
        return(-1);
    }

    /* step 5: first encryption, result is TEMP1 */
    ret = kwDes3Id->encrypt(context,
                           iv, sizeof(iv),
                           out, inSize + XMLSEC_KW_DES3_BLOCK_LENGTH,
                           out, outSize);
    if((ret < 0) || ((xmlSecSize)ret != inSize + XMLSEC_KW_DES3_BLOCK_LENGTH)) {
        xmlSecError(XMLSEC_ERRORS_HERE,
                    NULL,
                    "kwDes3Id->encrypt",
                    XMLSEC_ERRORS_R_XMLSEC_FAILED,
                    "ret=%d", ret);
        return(-1);
    }

    /* step 6: construct TEMP2=IV || TEMP1 */
    memmove(out + XMLSEC_KW_DES3_IV_LENGTH, out, inSize + XMLSEC_KW_DES3_BLOCK_LENGTH);
    memcpy(out, iv, XMLSEC_KW_DES3_IV_LENGTH);
    s = inSize + XMLSEC_KW_DES3_BLOCK_LENGTH + XMLSEC_KW_DES3_IV_LENGTH;

    /* step 7: reverse octets order, result is TEMP3 */
    ret = xmlSecKWDes3BufferReverse(out, s);
    if(ret < 0) {
        xmlSecError(XMLSEC_ERRORS_HERE,
                    NULL,
                    "xmlSecKWDes3BufferReverse",
                    XMLSEC_ERRORS_R_XMLSEC_FAILED,
                    "ret=%d", ret);
        return(-1);
    }

    /* step 8: second encryption with static IV */
    ret = kwDes3Id->encrypt(context,
                           xmlSecKWDes3Iv, sizeof(xmlSecKWDes3Iv),
                           out, s,
                           out, outSize);
    if((ret < 0) || ((xmlSecSize)ret != s)) {
        xmlSecError(XMLSEC_ERRORS_HERE,
                    NULL,
                    "kwDes3Id->encrypt",
                    XMLSEC_ERRORS_R_XMLSEC_FAILED,
                    "ret=%d", ret);
        return(-1);
    }

    s = ret;
    return(s);
}

int
xmlSecKWDes3Decode(xmlSecKWDes3Id kwDes3Id, void *context,
                  const xmlSecByte *in, xmlSecSize inSize,
                  xmlSecByte *out, xmlSecSize outSize) 
{
    xmlSecByte sha1[XMLSEC_KW_DES3_SHA_DIGEST_LENGTH];
    xmlSecSize s;
    int ret;

    xmlSecAssert2(xmlSecKWDes3CheckId(kwDes3Id), -1);
    xmlSecAssert2(context != NULL, -1);
    xmlSecAssert2(in != NULL, -1);
    xmlSecAssert2(inSize > 0, -1);
    xmlSecAssert2(out != NULL, -1);
    xmlSecAssert2(outSize >= inSize, -1);


    /* step 2: first decryption with static IV, result is TEMP3 */
    ret = kwDes3Id->decrypt(context,
                           xmlSecKWDes3Iv, sizeof(xmlSecKWDes3Iv),
                           in, inSize, 
                           out, outSize);
    if((ret < 0) || (ret < XMLSEC_KW_DES3_IV_LENGTH)) {
        xmlSecError(XMLSEC_ERRORS_HERE,
                    NULL,
                    "kwDes3Id->decrypt",
                    XMLSEC_ERRORS_R_XMLSEC_FAILED,
                    "ret=%d", ret);
        return(-1);
    }
    s = ret;

    /* step 3: reverse octets order in TEMP3, result is TEMP2 */
    ret = xmlSecKWDes3BufferReverse(out, s);
    if(ret < 0) {
        xmlSecError(XMLSEC_ERRORS_HERE,
                    NULL,
                    "xmlSecKWDes3BufferReverse",
                    XMLSEC_ERRORS_R_XMLSEC_FAILED,
                    "ret=%d", ret);
        return(-1);
    }

    /* steps 4 and 5: get IV and decrypt second time, result is WKCKS */
    ret = kwDes3Id->decrypt(context,
                           out, XMLSEC_KW_DES3_IV_LENGTH,
                           out + XMLSEC_KW_DES3_IV_LENGTH, s - XMLSEC_KW_DES3_IV_LENGTH,
                           out, outSize);
    if((ret < 0) || (ret < XMLSEC_KW_DES3_BLOCK_LENGTH)) {
        xmlSecError(XMLSEC_ERRORS_HERE,
                    NULL,
                    "kwDes3Id->decrypt",
                    XMLSEC_ERRORS_R_XMLSEC_FAILED,
                    "ret=%d", ret);
        return(-1);
    }
    s = ret - XMLSEC_KW_DES3_BLOCK_LENGTH;

    /* steps 6 and 7: calculate SHA1 and validate it */
    ret = kwDes3Id->sha1(context,
                        out, s,
                        sha1, sizeof(sha1));
    if((ret < 0) || (ret != sizeof(sha1))) {
        xmlSecError(XMLSEC_ERRORS_HERE,
                    NULL,
                    "kwDes3Id->sha1",
                    XMLSEC_ERRORS_R_XMLSEC_FAILED,
                    "ret=%d", ret);
        return(-1);
    }

    /* check sha1 */
    xmlSecAssert2(XMLSEC_KW_DES3_BLOCK_LENGTH <= sizeof(sha1), -1);
    if(memcmp(sha1, out + s, XMLSEC_KW_DES3_BLOCK_LENGTH) != 0) {
        xmlSecError(XMLSEC_ERRORS_HERE,
                    NULL,
                    NULL,
                    XMLSEC_ERRORS_R_INVALID_DATA,
                    "SHA1 does not match");
        return(-1);
    }

    /* done */
    return(s);
}

static int
xmlSecKWDes3BufferReverse(xmlSecByte *buf, xmlSecSize size) 
{
    xmlSecByte * p;
    xmlSecByte ch;

    xmlSecAssert2(buf != NULL, -1);
    xmlSecAssert2(size > 0, -1);

    for(p = buf + size - 1; p >= buf; ++buf, --p) {
        ch = (*p);
        (*p) = (*buf);
        (*buf) = ch;
    }
    return (0);
}

#endif /* XMLSEC_NO_DES */



#ifndef XMLSEC_NO_AES
/********************************************************************
 *
 * KT AES
 *
 * http://www.w3.org/TR/xmlenc-core/#sec-Alg-SymmetricKeyWrap:
 *
 * Assume that the data to be wrapped consists of N 64-bit data blocks
 * denoted P(1), P(2), P(3) ... P(N). The result of wrapping will be N+1
 * 64-bit blocks denoted C(0), C(1), C(2), ... C(N). The key encrypting
 * key is represented by K. Assume integers i, j, and t and intermediate
 * 64-bit register A, 128-bit register B, and array of 64-bit quantities
 * R(1) through R(N).
 *
 * "|" represents concatentation so x|y, where x and y and 64-bit quantities,
 * is the 128-bit quantity with x in the most significant bits and y in the
 * least significant bits. AES(K)enc(x) is the operation of AES encrypting
 * the 128-bit quantity x under the key K. AES(K)dec(x) is the corresponding
 * decryption opteration. XOR(x,y) is the bitwise exclusive or of x and y.
 * MSB(x) and LSB(y) are the most significant 64 bits and least significant
 * 64 bits of x and y respectively.
 *
 * If N is 1, a single AES operation is performed for wrap or unwrap.
 * If N>1, then 6*N AES operations are performed for wrap or unwrap.
 *
 * The key wrap algorithm is as follows:
 *
 *   1. If N is 1:
 *          * B=AES(K)enc(0xA6A6A6A6A6A6A6A6|P(1))
 *          * C(0)=MSB(B)
 *          * C(1)=LSB(B)
 *      If N>1, perform the following steps:
 *   2. Initialize variables:
 *          * Set A to 0xA6A6A6A6A6A6A6A6
 *          * Fori=1 to N,
 *            R(i)=P(i)
 *   3. Calculate intermediate values:
 *          * Forj=0 to 5,
 *                o For i=1 to N,
 *                  t= i + j*N
 *                  B=AES(K)enc(A|R(i))
 *                  A=XOR(t,MSB(B))
 *                  R(i)=LSB(B)
 *   4. Output the results:
 *          * Set C(0)=A
 *          * For i=1 to N,
 *            C(i)=R(i)
 *
 * The key unwrap algorithm is as follows:
 *
 *   1. If N is 1:
 *          * B=AES(K)dec(C(0)|C(1))
 *          * P(1)=LSB(B)
 *          * If MSB(B) is 0xA6A6A6A6A6A6A6A6, return success. Otherwise,
 *            return an integrity check failure error.
 *      If N>1, perform the following steps:
 *   2. Initialize the variables:
 *          * A=C(0)
 *          * For i=1 to N,
 *            R(i)=C(i)
 *   3. Calculate intermediate values:
 *          * For j=5 to 0,
 *                o For i=N to 1,
 *                  t= i + j*N
 *                  B=AES(K)dec(XOR(t,A)|R(i))
 *                  A=MSB(B)
 *                  R(i)=LSB(B)
 *   4. Output the results:
 *          * For i=1 to N,
 *            P(i)=R(i)
 *          * If A is 0xA6A6A6A6A6A6A6A6, return success. Otherwise, return
 *            an integrity check failure error.
 ********************************************************************/
static const xmlSecByte xmlSecKWAesMagicBlock[XMLSEC_KW_AES_MAGIC_BLOCK_SIZE] = {
    0xA6,  0xA6,  0xA6,  0xA6,  0xA6,  0xA6,  0xA6,  0xA6
};

int
xmlSecKWAesEncode(xmlSecKWAesId kwAesId, void *context,
                  const xmlSecByte *in, xmlSecSize inSize,
                  xmlSecByte *out, xmlSecSize outSize) {
    xmlSecByte block[XMLSEC_KW_AES_BLOCK_SIZE];
    xmlSecByte *p;
    int N, i, j, t;
    int ret;

    xmlSecAssert2(kwAesId != NULL, -1);
    xmlSecAssert2(kwAesId->encrypt != NULL, -1);
    xmlSecAssert2(kwAesId->decrypt != NULL, -1);
    xmlSecAssert2(context != NULL, -1);
    xmlSecAssert2(in != NULL, -1);
    xmlSecAssert2(inSize > 0, -1);
    xmlSecAssert2(out != NULL, -1);
    xmlSecAssert2(outSize >= inSize + XMLSEC_KW_AES_MAGIC_BLOCK_SIZE, -1);

    /* prepend magic block */
    if(in != out) {
        memcpy(out + XMLSEC_KW_AES_MAGIC_BLOCK_SIZE, in, inSize);
    } else {
        memmove(out + XMLSEC_KW_AES_MAGIC_BLOCK_SIZE, out, inSize);
    }
    memcpy(out, xmlSecKWAesMagicBlock, XMLSEC_KW_AES_MAGIC_BLOCK_SIZE);

    N = (inSize / 8);
    if(N == 1) {
        ret = kwAesId->encrypt(out, inSize + XMLSEC_KW_AES_MAGIC_BLOCK_SIZE, out, outSize, context);
        if(ret < 0) {
            xmlSecError(XMLSEC_ERRORS_HERE,
                        NULL,
                        "kwAesId->encrypt",
                        XMLSEC_ERRORS_R_XMLSEC_FAILED,
                        XMLSEC_ERRORS_NO_MESSAGE);
            return(-1);
        }
    } else {
        for(j = 0; j <= 5; ++j) {
            for(i = 1; i <= N; ++i) {
                t = i + (j * N);
                p = out + i * 8;

                memcpy(block, out, 8);
                memcpy(block + 8, p, 8);

                ret = kwAesId->encrypt(block, sizeof(block), block, sizeof(block), context);
                if(ret < 0) {
                    xmlSecError(XMLSEC_ERRORS_HERE,
                                NULL,
                                "kwAesId->encrypt",
                                XMLSEC_ERRORS_R_XMLSEC_FAILED,
                                XMLSEC_ERRORS_NO_MESSAGE);
                    return(-1);
                }
                block[7] ^=  t;
                memcpy(out, block, 8);
                memcpy(p, block + 8, 8);
            }
        }
    }

    return(inSize + 8);
}

int
xmlSecKWAesDecode(xmlSecKWAesId kwAesId, void *context,
                  const xmlSecByte *in, xmlSecSize inSize,
                  xmlSecByte *out, xmlSecSize outSize) {
    xmlSecByte block[XMLSEC_KW_AES_BLOCK_SIZE];
    xmlSecByte *p;
    int N, i, j, t;
    int ret;

    xmlSecAssert2(kwAesId != NULL, -1);
    xmlSecAssert2(kwAesId->encrypt != NULL, -1);
    xmlSecAssert2(kwAesId->decrypt != NULL, -1);
    xmlSecAssert2(context != NULL, -1);
    xmlSecAssert2(in != NULL, -1);
    xmlSecAssert2(inSize > 0, -1);
    xmlSecAssert2(out != NULL, -1);
    xmlSecAssert2(outSize >= inSize, -1);

    /* copy input */
    if(in != out) {
        memcpy(out, in, inSize);
    }

    N = (inSize / 8) - 1;
    if(N == 1) {
        ret = kwAesId->decrypt(out, inSize, out, outSize, context);
        if(ret < 0) {
            xmlSecError(XMLSEC_ERRORS_HERE,
                        NULL,
                        "kwAesId->decrypt",
                        XMLSEC_ERRORS_R_XMLSEC_FAILED,
                        XMLSEC_ERRORS_NO_MESSAGE);
            return(-1);
        }
    } else {
        for(j = 5; j >= 0; --j) {
            for(i = N; i > 0; --i) {
                t = i + (j * N);
                p = out + i * 8;

                memcpy(block, out, 8);
                memcpy(block + 8, p, 8);
                block[7] ^= t;

                ret = kwAesId->decrypt(block, sizeof(block), block, sizeof(block), context);
                if(ret < 0) {
                    xmlSecError(XMLSEC_ERRORS_HERE,
                                NULL,
                                "kwAesId->decrypt",
                                XMLSEC_ERRORS_R_XMLSEC_FAILED,
                                XMLSEC_ERRORS_NO_MESSAGE);
                    return(-1);
                }
                memcpy(out, block, 8);
                memcpy(p, block + 8, 8);
            }
        }
    }
    /* do not left data in memory */
    memset(block, 0, sizeof(block));

    /* check the output */
    if(memcmp(xmlSecKWAesMagicBlock, out, XMLSEC_KW_AES_MAGIC_BLOCK_SIZE) != 0) {
        xmlSecError(XMLSEC_ERRORS_HERE,
                    NULL,
                    NULL,
                    XMLSEC_ERRORS_R_INVALID_DATA,
                    "bad magic block");
        return(-1);
    }

    /* get rid of magic block */
    memmove(out, out + XMLSEC_KW_AES_MAGIC_BLOCK_SIZE, inSize - XMLSEC_KW_AES_MAGIC_BLOCK_SIZE);
    return(inSize - XMLSEC_KW_AES_MAGIC_BLOCK_SIZE);
}

#endif /* XMLSEC_NO_AES */

