/*
 * aeskeywrap.c - implement AES Key Wrap algorithm from RFC 3394
 *
 * 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 "prcpucfg.h"
#if defined(IS_LITTLE_ENDIAN) || defined(SHA_NO_LONG_LONG)
#define BIG_ENDIAN_WITH_64_BIT_REGISTERS 0
#else
#define BIG_ENDIAN_WITH_64_BIT_REGISTERS 1
#endif
#include "prtypes.h"	/* for PRUintXX */
#include "secport.h"	/* for PORT_XXX */
#include "secerr.h"
#include "blapi.h"	/* for AES_ functions */
#include "rijndael.h"

struct AESKeyWrapContextStr {
     unsigned char iv[AES_KEY_WRAP_IV_BYTES];
     AESContext    aescx;
};

/******************************************/
/*
** AES key wrap algorithm, RFC 3394
*/

AESKeyWrapContext * 
AESKeyWrap_AllocateContext(void)
{
    AESKeyWrapContext * cx = PORT_New(AESKeyWrapContext);
    return cx;
}

SECStatus  
AESKeyWrap_InitContext(AESKeyWrapContext *cx, 
		       const unsigned char *key, 
		       unsigned int keylen,
		       const unsigned char *iv, 
		       int x1,
		       unsigned int encrypt,
		       unsigned int x2)
{
    SECStatus rv = SECFailure;
    if (!cx) {
	PORT_SetError(SEC_ERROR_INVALID_ARGS);
    	return SECFailure;
    }
    if (iv) {
    	memcpy(cx->iv, iv, sizeof cx->iv);
    } else {
	memset(cx->iv, 0xA6, sizeof cx->iv);
    }
    rv = AES_InitContext(&cx->aescx, key, keylen, NULL, NSS_AES, encrypt, 
                                  AES_BLOCK_SIZE);
    return rv;
}

/*
** Create a new AES context suitable for AES encryption/decryption.
** 	"key" raw key data
** 	"keylen" the number of bytes of key data (16, 24, or 32)
*/
extern AESKeyWrapContext *
AESKeyWrap_CreateContext(const unsigned char *key, const unsigned char *iv, 
                         int encrypt, unsigned int keylen)
{
    SECStatus rv;
    AESKeyWrapContext * cx = AESKeyWrap_AllocateContext();
    if (!cx) 
    	return NULL;	/* error is already set */
    rv = AESKeyWrap_InitContext(cx, key, keylen, iv, 0, encrypt, 0);
    if (rv != SECSuccess) {
        PORT_Free(cx);
	cx = NULL; 	/* error should already be set */
    }
    return cx;
}

/*
** Destroy a AES KeyWrap context.
**	"cx" the context
**	"freeit" if PR_TRUE then free the object as well as its sub-objects
*/
extern void 
AESKeyWrap_DestroyContext(AESKeyWrapContext *cx, PRBool freeit)
{
    if (cx) {
	AES_DestroyContext(&cx->aescx, PR_FALSE);
/*	memset(cx, 0, sizeof *cx); */
	if (freeit)
	    PORT_Free(cx);
    }
}

#if !BIG_ENDIAN_WITH_64_BIT_REGISTERS

/* The AES Key Wrap algorithm has 64-bit values that are ALWAYS big-endian
** (Most significant byte first) in memory.  The only ALU operations done
** on them are increment, decrement, and XOR.  So, on little-endian CPUs,
** and on CPUs that lack 64-bit registers, these big-endian 64-bit operations
** are simulated in the following code.  This is thought to be faster and
** simpler than trying to convert the data to little-endian and back.
*/

/* A and T point to two 64-bit values stored most signficant byte first
** (big endian).  This function increments the 64-bit value T, and then
** XORs it with A, changing A.
*/ 
static void
increment_and_xor(unsigned char *A, unsigned char *T)
{
    if (!++T[7])
        if (!++T[6])
	    if (!++T[5])
		if (!++T[4])
		    if (!++T[3])
			if (!++T[2])
			    if (!++T[1])
				 ++T[0];

    A[0] ^= T[0];
    A[1] ^= T[1];
    A[2] ^= T[2];
    A[3] ^= T[3];
    A[4] ^= T[4];
    A[5] ^= T[5];
    A[6] ^= T[6];
    A[7] ^= T[7];
}

/* A and T point to two 64-bit values stored most signficant byte first
** (big endian).  This function XORs T with A, giving a new A, then 
** decrements the 64-bit value T.
*/ 
static void
xor_and_decrement(unsigned char *A, unsigned char *T)
{
    A[0] ^= T[0];
    A[1] ^= T[1];
    A[2] ^= T[2];
    A[3] ^= T[3];
    A[4] ^= T[4];
    A[5] ^= T[5];
    A[6] ^= T[6];
    A[7] ^= T[7];

    if (!T[7]--)
        if (!T[6]--)
	    if (!T[5]--)
		if (!T[4]--)
		    if (!T[3]--)
			if (!T[2]--)
			    if (!T[1]--)
				 T[0]--;

}

/* Given an unsigned long t (in host byte order), store this value as a
** 64-bit big-endian value (MSB first) in *pt.
*/
static void
set_t(unsigned char *pt, unsigned long t)
{
    pt[7] = (unsigned char)t; t >>= 8;
    pt[6] = (unsigned char)t; t >>= 8;
    pt[5] = (unsigned char)t; t >>= 8;
    pt[4] = (unsigned char)t; t >>= 8;
    pt[3] = (unsigned char)t; t >>= 8;
    pt[2] = (unsigned char)t; t >>= 8;
    pt[1] = (unsigned char)t; t >>= 8;
    pt[0] = (unsigned char)t;
}

#endif

/*
** Perform AES key wrap.
**	"cx" the context
**	"output" the output buffer to store the encrypted data.
**	"outputLen" how much data is stored in "output". Set by the routine
**	   after some data is stored in output.
**	"maxOutputLen" the maximum amount of data that can ever be
**	   stored in "output"
**	"input" the input data
**	"inputLen" the amount of input data
*/
extern SECStatus 
AESKeyWrap_Encrypt(AESKeyWrapContext *cx, unsigned char *output,
            unsigned int *pOutputLen, unsigned int maxOutputLen,
            const unsigned char *input, unsigned int inputLen)
{
    PRUint64 *     R          = NULL;
    unsigned int   nBlocks;
    unsigned int   i, j;
    unsigned int   aesLen     = AES_BLOCK_SIZE;
    unsigned int   outLen     = inputLen + AES_KEY_WRAP_BLOCK_SIZE;
    SECStatus      s          = SECFailure;
    /* These PRUint64s are ALWAYS big endian, regardless of CPU orientation. */
    PRUint64       t;
    PRUint64       B[2];

#define A B[0]

    /* Check args */
    if (!inputLen || 0 != inputLen % AES_KEY_WRAP_BLOCK_SIZE) {
	PORT_SetError(SEC_ERROR_INPUT_LEN);
	return s;
    }
#ifdef maybe
    if (!output && pOutputLen) {	/* caller is asking for output size */
    	*pOutputLen = outLen;
	return SECSuccess;
    }
#endif
    if (maxOutputLen < outLen) {
	PORT_SetError(SEC_ERROR_OUTPUT_LEN);
	return s;
    }
    if (cx == NULL || output == NULL || input == NULL) {
	PORT_SetError(SEC_ERROR_INVALID_ARGS);
	return s;
    }
    nBlocks = inputLen / AES_KEY_WRAP_BLOCK_SIZE;
    R = PORT_NewArray(PRUint64, nBlocks + 1);
    if (!R)
    	return s;	/* error is already set. */
    /* 
    ** 1) Initialize variables.
    */
    memcpy(&A, cx->iv, AES_KEY_WRAP_IV_BYTES);
    memcpy(&R[1], input, inputLen);
#if BIG_ENDIAN_WITH_64_BIT_REGISTERS
    t = 0;
#else
    memset(&t, 0, sizeof t);
#endif
    /* 
    ** 2) Calculate intermediate values.
    */
    for (j = 0; j < 6; ++j) {
    	for (i = 1; i <= nBlocks; ++i) {
	    B[1] = R[i];
	    s = AES_Encrypt(&cx->aescx, (unsigned char *)B, &aesLen, 
	                    sizeof B,  (unsigned char *)B, sizeof B);
	    if (s != SECSuccess) 
	        break;
	    R[i] = B[1];
	    /* here, increment t and XOR A with t (in big endian order); */
#if BIG_ENDIAN_WITH_64_BIT_REGISTERS
   	    A ^= ++t; 
#else
	    increment_and_xor((unsigned char *)&A, (unsigned char *)&t);
#endif
	}
    }
    /* 
    ** 3) Output the results.
    */
    if (s == SECSuccess) {
    	R[0] =  A;
	memcpy(output, &R[0], outLen);
	if (pOutputLen)
	    *pOutputLen = outLen;
    } else if (pOutputLen) {
    	*pOutputLen = 0;
    }
    PORT_ZFree(R, outLen);
    return s;
}
#undef A

/*
** Perform AES key unwrap.
**	"cx" the context
**	"output" the output buffer to store the decrypted data.
**	"outputLen" how much data is stored in "output". Set by the routine
**	   after some data is stored in output.
**	"maxOutputLen" the maximum amount of data that can ever be
**	   stored in "output"
**	"input" the input data
**	"inputLen" the amount of input data
*/
extern SECStatus 
AESKeyWrap_Decrypt(AESKeyWrapContext *cx, unsigned char *output,
            unsigned int *pOutputLen, unsigned int maxOutputLen,
            const unsigned char *input, unsigned int inputLen)
{
    PRUint64 *     R          = NULL;
    unsigned int   nBlocks;
    unsigned int   i, j;
    unsigned int   aesLen     = AES_BLOCK_SIZE;
    unsigned int   outLen;
    SECStatus      s          = SECFailure;
    /* These PRUint64s are ALWAYS big endian, regardless of CPU orientation. */
    PRUint64       t;
    PRUint64       B[2];

#define A B[0]

    /* Check args */
    if (inputLen < 3 * AES_KEY_WRAP_BLOCK_SIZE || 
        0 != inputLen % AES_KEY_WRAP_BLOCK_SIZE) {
	PORT_SetError(SEC_ERROR_INPUT_LEN);
	return s;
    }
    outLen = inputLen - AES_KEY_WRAP_BLOCK_SIZE;
#ifdef maybe
    if (!output && pOutputLen) {	/* caller is asking for output size */
    	*pOutputLen = outLen;
	return SECSuccess;
    }
#endif
    if (maxOutputLen < outLen) {
	PORT_SetError(SEC_ERROR_OUTPUT_LEN);
	return s;
    }
    if (cx == NULL || output == NULL || input == NULL) {
	PORT_SetError(SEC_ERROR_INVALID_ARGS);
	return s;
    }
    nBlocks = inputLen / AES_KEY_WRAP_BLOCK_SIZE;
    R = PORT_NewArray(PRUint64, nBlocks);
    if (!R)
    	return s;	/* error is already set. */
    nBlocks--;
    /* 
    ** 1) Initialize variables.
    */
    memcpy(&R[0], input, inputLen);
    A = R[0];
#if BIG_ENDIAN_WITH_64_BIT_REGISTERS
    t = 6UL * nBlocks;
#else
    set_t((unsigned char *)&t, 6UL * nBlocks);
#endif
    /* 
    ** 2) Calculate intermediate values.
    */
    for (j = 0; j < 6; ++j) {
    	for (i = nBlocks; i; --i) {
	    /* here, XOR A with t (in big endian order) and decrement t; */
#if BIG_ENDIAN_WITH_64_BIT_REGISTERS
   	    A ^= t--; 
#else
	    xor_and_decrement((unsigned char *)&A, (unsigned char *)&t);
#endif
	    B[1] = R[i];
	    s = AES_Decrypt(&cx->aescx, (unsigned char *)B, &aesLen, 
	                    sizeof B,  (unsigned char *)B, sizeof B);
	    if (s != SECSuccess) 
	        break;
	    R[i] = B[1];
	}
    }
    /* 
    ** 3) Output the results.
    */
    if (s == SECSuccess) {
	int bad = memcmp(&A, cx->iv, AES_KEY_WRAP_IV_BYTES);
	if (!bad) {
	    memcpy(output, &R[1], outLen);
	    if (pOutputLen)
		*pOutputLen = outLen;
	} else {
	    PORT_SetError(SEC_ERROR_BAD_DATA);
	    if (pOutputLen) 
		*pOutputLen = 0;
    	}
    } else if (pOutputLen) {
    	*pOutputLen = 0;
    }
    PORT_ZFree(R, inputLen);
    return s;
}
#undef A
