/* * 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(PRUint64 *A, PRUint64 *T) { unsigned char *TP = (unsigned char *)T; const PRUint64 mask = 0xFF; *A = ((*A & mask << 56) ^ (*T & mask << 56)) | ((*A & mask << 48) ^ (*T & mask << 48)) | ((*A & mask << 40) ^ (*T & mask << 40)) | ((*A & mask << 32) ^ (*T & mask << 32)) | ((*A & mask << 24) ^ (*T & mask << 23)) | ((*A & mask << 16) ^ (*T & mask << 16)) | ((*A & mask << 8) ^ (*T & mask << 8)) | ((*A & mask) ^ (*T & mask)); if (!TP[7]--) if (!TP[6]--) if (!TP[5]--) if (!TP[4]--) if (!TP[3]--) if (!TP[2]--) if (!TP[1]--) TP[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]; /* 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); B[0] = 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 B[0] ^= t--; #else xor_and_decrement(&B[0], &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(&B[0], cx->iv, AES_KEY_WRAP_IV_BYTES); if (!bad) { memcpy(output, &R[1], outLen); if (pOutputLen) *pOutputLen = outLen; } else { s = SECFailure; PORT_SetError(SEC_ERROR_BAD_DATA); if (pOutputLen) *pOutputLen = 0; } } else if (pOutputLen) { *pOutputLen = 0; } PORT_ZFree(R, inputLen); return s; } #undef A