/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifdef FREEBL_NO_DEPEND
#include "stubs.h"
#endif
#include "blapit.h"
#include "blapii.h"
#include "cts.h"
#include "secerr.h"

struct CTSContextStr {
    freeblCipherFunc cipher;
    void *context;
    /* iv stores the last ciphertext block of the previous message.
     * Only used by decrypt. */
    unsigned char iv[MAX_BLOCK_SIZE];
};

CTSContext *
CTS_CreateContext(void *context, freeblCipherFunc cipher,
		  const unsigned char *iv, unsigned int blocksize)
{
    CTSContext  *cts;

    if (blocksize > MAX_BLOCK_SIZE) {
	PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
	return NULL;
    }
    cts = PORT_ZNew(CTSContext);
    if (cts == NULL) {
	return NULL;
    }
    PORT_Memcpy(cts->iv, iv, blocksize);
    cts->cipher = cipher;
    cts->context = context;
    return cts;
}

void
CTS_DestroyContext(CTSContext *cts, PRBool freeit)
{
    if (freeit) {
	PORT_Free(cts);
    }
}
	
/*
 * See addemdum to NIST SP 800-38A
 * Generically handle cipher text stealing. Basically this is doing CBC
 * operations except someone can pass us a partial block.
 *
 *  Output Order:
 *  CS-1:  C1||C2||C3..Cn-1(could be partial)||Cn   (NIST)
 *  CS-2: pad == 0 C1||C2||C3...Cn-1(is full)||Cn   (Schneier)
 *  CS-2: pad != 0 C1||C2||C3...Cn||Cn-1(is partial)(Schneier)
 *  CS-3: C1||C2||C3...Cn||Cn-1(could be partial)   (Kerberos)
 *
 * The characteristics of these three options:
 *  - NIST & Schneier (CS-1 & CS-2) are identical to CBC if there are no
 * partial blocks on input.
 *  - Scheier and Kerberos (CS-2 and CS-3) have no embedded partial blocks,
 * which make decoding easier.
 *  - NIST & Kerberos (CS-1 and CS-3) have consistent block order independent
 * of padding.
 *
 * PKCS #11 did not specify which version to implement, but points to the NIST
 * spec, so this code implements CTS-CS-1 from NIST.
 *
 * To convert the returned buffer to:
 *   CS-2 (Schneier): do
 *       unsigned char tmp[MAX_BLOCK_SIZE];
 *       pad = *outlen % blocksize;
 *       if (pad) {
 *          memcpy(tmp, outbuf+*outlen-blocksize, blocksize);
 *          memcpy(outbuf+*outlen-pad,outbuf+*outlen-blocksize-pad, pad);
 *	    memcpy(outbuf+*outlen-blocksize-pad, tmp, blocksize);
 *       }
 *   CS-3 (Kerberos): do
 *       unsigned char tmp[MAX_BLOCK_SIZE];
 *       pad = *outlen % blocksize;
 *       if (pad == 0) {
 *           pad = blocksize;
 *       }
 *       memcpy(tmp, outbuf+*outlen-blocksize, blocksize);
 *       memcpy(outbuf+*outlen-pad,outbuf+*outlen-blocksize-pad, pad);
 *	 memcpy(outbuf+*outlen-blocksize-pad, tmp, blocksize);
 */
SECStatus
CTS_EncryptUpdate(CTSContext *cts, unsigned char *outbuf,
		unsigned int *outlen, unsigned int maxout,
		const unsigned char *inbuf, unsigned int inlen,
		unsigned int blocksize)
{
    unsigned char lastBlock[MAX_BLOCK_SIZE];
    unsigned int tmp;
    int fullblocks;
    int written;
    SECStatus rv;

    if (inlen < blocksize) {
	PORT_SetError(SEC_ERROR_INPUT_LEN);
	return SECFailure;
    }

    if (maxout < inlen) {
	*outlen = inlen;
	PORT_SetError(SEC_ERROR_OUTPUT_LEN);
	return SECFailure;
    }
    fullblocks = (inlen/blocksize)*blocksize;
    rv = (*cts->cipher)(cts->context, outbuf, outlen, maxout, inbuf,
	 fullblocks, blocksize);
    if (rv != SECSuccess) {
	return SECFailure;
    }
    *outlen = fullblocks; /* AES low level doesn't set outlen */
    inbuf += fullblocks;
    inlen -= fullblocks;
    if (inlen == 0) {
	return SECSuccess;
    }
    written = *outlen - (blocksize - inlen);
    outbuf += written;
    maxout -= written;

    /*
     * here's the CTS magic, we pad our final block with zeros,
     * then do a CBC encrypt. CBC will xor our plain text with
     * the previous block (Cn-1), capturing part of that block (Cn-1**) as it
     * xors with the zero pad. We then write this full block, overwritting
     * (Cn-1**) in our buffer. This allows us to have input data == output
     * data since Cn contains enough information to reconver Cn-1** when
     * we decrypt (at the cost of some complexity as you can see in decrypt
     * below */
    PORT_Memcpy(lastBlock, inbuf, inlen);
    PORT_Memset(lastBlock + inlen, 0, blocksize - inlen);
    rv = (*cts->cipher)(cts->context, outbuf, &tmp, maxout, lastBlock,
			blocksize, blocksize);
    PORT_Memset(lastBlock, 0, blocksize);
    if (rv == SECSuccess) {
	*outlen = written + blocksize;
    }
    return rv;
}


#define XOR_BLOCK(x,y,count) for(i=0; i < count; i++) x[i] = x[i] ^ y[i]

/*
 * See addemdum to NIST SP 800-38A
 * Decrypt, Expect CS-1: input. See the comment on the encrypt side
 * to understand what CS-2 and CS-3 mean.
 *
 * To convert the input buffer to CS-1 from ...
 *   CS-2 (Schneier): do
 *       unsigned char tmp[MAX_BLOCK_SIZE];
 *       pad = inlen % blocksize;
 *       if (pad) {
 *          memcpy(tmp, inbuf+inlen-blocksize-pad, blocksize);
 *          memcpy(inbuf+inlen-blocksize-pad,inbuf+inlen-pad, pad);
 *	    memcpy(inbuf+inlen-blocksize, tmp, blocksize);
 *       }
 *   CS-3 (Kerberos): do
 *       unsigned char tmp[MAX_BLOCK_SIZE];
 *       pad = inlen % blocksize;
 *       if (pad == 0) {
 *           pad = blocksize;
 *       }
 *       memcpy(tmp, inbuf+inlen-blocksize-pad, blocksize);
 *       memcpy(inbuf+inlen-blocksize-pad,inbuf+inlen-pad, pad);
 *	 memcpy(inbuf+inlen-blocksize, tmp, blocksize);
 */
SECStatus
CTS_DecryptUpdate(CTSContext *cts, unsigned char *outbuf,
		unsigned int *outlen, unsigned int maxout,
		const unsigned char *inbuf, unsigned int inlen,
		unsigned int blocksize)
{
    unsigned char *Pn;
    unsigned char Cn_2[MAX_BLOCK_SIZE]; /* block Cn-2 */
    unsigned char Cn_1[MAX_BLOCK_SIZE]; /* block Cn-1 */
    unsigned char Cn[MAX_BLOCK_SIZE];   /* block Cn   */
    unsigned char lastBlock[MAX_BLOCK_SIZE];
    const unsigned char *tmp;
    unsigned int tmpLen;
    int fullblocks, pad;
    unsigned int i;
    SECStatus rv;

    if (inlen < blocksize) {
	PORT_SetError(SEC_ERROR_INPUT_LEN);
	return SECFailure;
    }

    if (maxout < inlen) {
	*outlen = inlen;
	PORT_SetError(SEC_ERROR_OUTPUT_LEN);
	return SECFailure;
    }

    fullblocks = (inlen/blocksize)*blocksize;

    /* even though we expect the input to be CS-1, CS-2 is easier to parse,
     * so convert to CS-2 immediately. NOTE: this is the same code as in
     * the comment for encrypt. NOTE2: since we can't modify inbuf unless
     * inbuf and outbuf overlap, just copy inbuf to outbuf and modify it there
     */
    pad = inlen - fullblocks;
    if (pad != 0) {
	if (inbuf != outbuf) {
	    memcpy(outbuf, inbuf, inlen);
	    /* keep the names so we logically know how we are using the
	     * buffers */
	    inbuf = outbuf;
	}
	memcpy(lastBlock, inbuf+inlen-blocksize, blocksize);
	/* we know inbuf == outbuf now, inbuf is declared const and can't
	 * be the target, so use outbuf for the target here */
	memcpy(outbuf+inlen-pad, inbuf+inlen-blocksize-pad, pad);
	memcpy(outbuf+inlen-blocksize-pad, lastBlock, blocksize);
    }
    /* save the previous to last block so we can undo the misordered
     * chaining */
    tmp =  (fullblocks < blocksize*2) ? cts->iv :
			inbuf+fullblocks-blocksize*2;
    PORT_Memcpy(Cn_2, tmp, blocksize);
    PORT_Memcpy(Cn, inbuf+fullblocks-blocksize, blocksize);
    rv = (*cts->cipher)(cts->context, outbuf, outlen, maxout, inbuf,
	 fullblocks, blocksize);
    if (rv != SECSuccess) {
	return SECFailure;
    }
    *outlen = fullblocks; /* AES low level doesn't set outlen */
    inbuf += fullblocks;
    inlen -= fullblocks;
    if (inlen == 0) {
	return SECSuccess;
    }
    outbuf += fullblocks;

    /* recover the stolen text */
    PORT_Memset(lastBlock, 0, blocksize);
    PORT_Memcpy(lastBlock, inbuf, inlen);
    PORT_Memcpy(Cn_1, inbuf, inlen);
    Pn = outbuf-blocksize;
    /* inbuf points to Cn-1* in the input buffer */
    /* NOTE: below there are 2 sections marked "make up for the out of order
     * cbc decryption". You may ask, what is going on here.
     *   Short answer: CBC automatically xors the plain text with the previous
     * encrypted block. We are decrypting the last 2 blocks out of order, so
     * we have to 'back out' the decrypt xor and 'add back' the encrypt xor.
     *   Long answer: When we encrypted, we encrypted as follows:
     *       Pn-2, Pn-1, (Pn || 0), but on decryption we can't
     *  decrypt Cn-1 until we decrypt Cn because part of Cn-1 is stored in
     *  Cn (see below).  So above we decrypted all the full blocks:
     *       Cn-2, Cn,
     *  to get:
     *       Pn-2, Pn, Except that Pn is not yet corect. On encrypt, we
     *  xor'd Pn || 0  with Cn-1, but on decrypt we xor'd it with Cn-2
     *  To recover Pn, we xor the block with Cn-1* || 0 (in last block) and
     *  Cn-2 to get Pn || Cn-1**. Pn can then be written to the output buffer
     *  and we can now reunite Cn-1. With the full Cn-1 we can decrypt it,
     *  but now decrypt is going to xor the decrypted data with Cn instead of
     *  Cn-2. xoring Cn and Cn-2 restores the original Pn-1 and we can now
     *  write that oout to the buffer */

    /* make up for the out of order CBC decryption */
    XOR_BLOCK(lastBlock, Cn_2, blocksize);
    XOR_BLOCK(lastBlock, Pn, blocksize);
    /* last buf now has Pn || Cn-1**, copy out Pn */
    PORT_Memcpy(outbuf, lastBlock, inlen);
    *outlen += inlen;
    /* copy Cn-1* into last buf to recover Cn-1 */
    PORT_Memcpy(lastBlock, Cn_1, inlen);
    /* note: because Cn and Cn-1 were out of order, our pointer to Pn also
     * points to where Pn-1 needs to reside. From here on out read Pn in
     * the code as really Pn-1. */
    rv = (*cts->cipher)(cts->context, Pn, &tmpLen, blocksize, lastBlock,
	 blocksize, blocksize);
    if (rv != SECSuccess) {
	return SECFailure;
    }
    /* make up for the out of order CBC decryption */
    XOR_BLOCK(Pn, Cn_2, blocksize);
    XOR_BLOCK(Pn, Cn, blocksize);
    /* reset iv to Cn  */
    PORT_Memcpy(cts->iv, Cn, blocksize);
    /* This makes Cn the last block for the next decrypt operation, which
     * matches the encrypt. We don't care about the contexts of last block,
     * only the side effect of setting the internal IV */
    (void) (*cts->cipher)(cts->context, lastBlock, &tmpLen, blocksize, Cn,
		blocksize, blocksize);
    /* clear last block. At this point last block contains Pn xor Cn_1 xor
     * Cn_2, both of with an attacker would know, so we need to clear this
     * buffer out */
    PORT_Memset(lastBlock, 0, blocksize);
    /* Cn, Cn_1, and Cn_2 have encrypted data, so no need to clear them */
    return SECSuccess;
}
