diff options
Diffstat (limited to 'security/nss/lib/freebl/aeskeywrap.c')
-rw-r--r-- | security/nss/lib/freebl/aeskeywrap.c | 642 |
1 files changed, 642 insertions, 0 deletions
diff --git a/security/nss/lib/freebl/aeskeywrap.c b/security/nss/lib/freebl/aeskeywrap.c new file mode 100644 index 0000000000..09c0667c7a --- /dev/null +++ b/security/nss/lib/freebl/aeskeywrap.c @@ -0,0 +1,642 @@ +/* + * 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 <stddef.h> + +#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 { + AESContext aescx; + unsigned char iv[AES_KEY_WRAP_IV_BYTES]; + void *mem; /* Pointer to beginning of allocated memory. */ +}; + +/******************************************/ +/* +** AES key wrap algorithm, RFC 3394 +*/ + +AESKeyWrapContext * +AESKeyWrap_AllocateContext(void) +{ + /* aligned_alloc is C11 so we have to do it the old way. */ + AESKeyWrapContext *ctx = PORT_ZAlloc(sizeof(AESKeyWrapContext) + 15); + if (ctx == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; + } + ctx->mem = ctx; + return (AESKeyWrapContext *)(((uintptr_t)ctx + 15) & ~(uintptr_t)0x0F); +} + +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->mem); + 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->mem); + } + } +} + +#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 + +static void +encode_PRUint32_BE(unsigned char *data, PRUint32 val) +{ + size_t i; + for (i = 0; i < sizeof(PRUint32); i++) { + data[i] = PORT_GET_BYTE_BE(val, i, sizeof(PRUint32)); + } +} + +static PRUint32 +decode_PRUint32_BE(unsigned char *data) +{ + PRUint32 val = 0; + size_t i; + + for (i = 0; i < sizeof(PRUint32); i++) { + val = (val << PR_BITS_PER_BYTE) | data[i]; + } + return val; +} + +/* +** Perform AES key wrap W function. +** "cx" the context +** "iv" the iv is concatenated to the plain text for for executing the function +** "output" the output buffer to store the encrypted data. +** "pOutputLen" 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_W(AESKeyWrapContext *cx, unsigned char *iv, 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 < 2 * AES_KEY_WRAP_BLOCK_SIZE || + 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, 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 wrap W^-1 function. +** "cx" the context +** "iv" the input IV to verify against. If NULL, then skip verification. +** "ivOut" the output buffer to store the IV (optional). +** "output" the output buffer to store the decrypted data. +** "pOutputLen" 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_Winv(AESKeyWrapContext *cx, unsigned char *iv, + unsigned char *ivOut, 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 = (iv) && memcmp(&B[0], iv, AES_KEY_WRAP_IV_BYTES); + if (!bad) { + memcpy(output, &R[1], outLen); + if (pOutputLen) + *pOutputLen = outLen; + if (ivOut) { + memcpy(ivOut, &B[0], AES_KEY_WRAP_IV_BYTES); + } + } 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 + +/* +** Perform AES key wrap. +** "cx" the context +** "output" the output buffer to store the encrypted data. +** "pOutputLen" 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) +{ + return AESKeyWrap_W(cx, cx->iv, output, pOutputLen, maxOutputLen, + input, inputLen); +} + +/* +** Perform AES key unwrap. +** "cx" the context +** "output" the output buffer to store the decrypted data. +** "pOutputLen" 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) +{ + return AESKeyWrap_Winv(cx, cx->iv, NULL, output, pOutputLen, maxOutputLen, + input, inputLen); +} + +#define BLOCK_PAD_POWER2(x, bs) (((bs) - ((x) & ((bs)-1))) & ((bs)-1)) +#define AES_KEY_WRAP_ICV2 0xa6, 0x59, 0x59, 0xa6 +#define AES_KEY_WRAP_ICV2_INT32 0xa65959a6 +#define AES_KEY_WRAP_ICV2_LEN 4 + +/* +** Perform AES key wrap with padding. +** "cx" the context +** "output" the output buffer to store the encrypted data. +** "pOutputLen" 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_EncryptKWP(AESKeyWrapContext *cx, unsigned char *output, + unsigned int *pOutputLen, unsigned int maxOutputLen, + const unsigned char *input, unsigned int inputLen) +{ + unsigned int padLen = BLOCK_PAD_POWER2(inputLen, AES_KEY_WRAP_BLOCK_SIZE); + unsigned int paddedInputLen = inputLen + padLen; + unsigned int outLen = paddedInputLen + AES_KEY_WRAP_BLOCK_SIZE; + unsigned char iv[AES_BLOCK_SIZE] = { AES_KEY_WRAP_ICV2 }; + unsigned char *newBuf; + SECStatus rv; + + *pOutputLen = outLen; + if (maxOutputLen < outLen) { + PORT_SetError(SEC_ERROR_OUTPUT_LEN); + return SECFailure; + } + PORT_Assert((AES_KEY_WRAP_ICV2_LEN + sizeof(PRUint32)) == AES_KEY_WRAP_BLOCK_SIZE); + encode_PRUint32_BE(iv + AES_KEY_WRAP_ICV2_LEN, inputLen); + + /* If we can fit in an AES Block, just do and AES Encrypt, + * iv is big enough to handle this on the stack, so no need to allocate + */ + if (outLen == AES_BLOCK_SIZE) { + PORT_Assert(inputLen <= AES_KEY_WRAP_BLOCK_SIZE); + PORT_Memset(iv + AES_KEY_WRAP_BLOCK_SIZE, 0, AES_KEY_WRAP_BLOCK_SIZE); + PORT_Memcpy(iv + AES_KEY_WRAP_BLOCK_SIZE, input, inputLen); + rv = AES_Encrypt(&cx->aescx, output, pOutputLen, maxOutputLen, iv, + outLen); + PORT_Memset(iv, 0, sizeof(iv)); + return rv; + } + + /* add padding to our input block */ + newBuf = PORT_ZAlloc(paddedInputLen); + if (newBuf == NULL) { + return SECFailure; + } + PORT_Memcpy(newBuf, input, inputLen); + + rv = AESKeyWrap_W(cx, iv, output, pOutputLen, maxOutputLen, + newBuf, paddedInputLen); + PORT_ZFree(newBuf, paddedInputLen); + /* a little overkill, we only need to clear out the length, but this + * is easier to verify we got it all */ + PORT_Memset(iv, 0, sizeof(iv)); + return rv; +} + +/* +** Perform AES key unwrap with padding. +** "cx" the context +** "output" the output buffer to store the decrypted data. +** "pOutputLen" 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_DecryptKWP(AESKeyWrapContext *cx, unsigned char *output, + unsigned int *pOutputLen, unsigned int maxOutputLen, + const unsigned char *input, unsigned int inputLen) +{ + unsigned int padLen; + unsigned int padLen2; + unsigned int outLen; + unsigned int paddedLen; + unsigned int good; + unsigned char *newBuf = NULL; + unsigned char *allocBuf = NULL; + int i; + unsigned char iv[AES_BLOCK_SIZE]; + PRUint32 magic; + SECStatus rv = SECFailure; + + paddedLen = inputLen - AES_KEY_WRAP_BLOCK_SIZE; + /* unwrap the padded result */ + if (inputLen == AES_BLOCK_SIZE) { + rv = AES_Decrypt(&cx->aescx, iv, &outLen, inputLen, input, inputLen); + newBuf = &iv[AES_KEY_WRAP_BLOCK_SIZE]; + outLen -= AES_KEY_WRAP_BLOCK_SIZE; + } else { + /* if the caller supplied enough space to hold the unpadded buffer, + * we can unwrap directly into that unpadded buffer. Otherwise + * we allocate a buffer that can hold the padding, and we'll copy + * the result in a later step */ + newBuf = output; + if (maxOutputLen < paddedLen) { + allocBuf = newBuf = PORT_Alloc(paddedLen); + if (!allocBuf) { + return SECFailure; + } + } + /* We pass NULL for the first IV argument because we don't know + * what the IV has since in includes the length, so we don't have + * Winv verify it. We pass iv in the second argument to get the + * iv, which we verify below before we return anything */ + rv = AESKeyWrap_Winv(cx, NULL, iv, newBuf, &outLen, + paddedLen, input, inputLen); + } + if (rv != SECSuccess) { + goto loser; + } + rv = SECFailure; + if (outLen != paddedLen) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + goto loser; + } + + /* we verify the result in a constant time manner */ + /* verify ICV magic */ + magic = decode_PRUint32_BE(iv); + good = PORT_CT_EQ(magic, AES_KEY_WRAP_ICV2_INT32); + /* fetch and verify plain text length */ + outLen = decode_PRUint32_BE(iv + AES_KEY_WRAP_ICV2_LEN); + good &= PORT_CT_LE(outLen, paddedLen); + /* now verify the padding */ + padLen = paddedLen - outLen; + padLen2 = BLOCK_PAD_POWER2(outLen, AES_KEY_WRAP_BLOCK_SIZE); + good &= PORT_CT_EQ(padLen, padLen2); + for (i = 0; i < AES_KEY_WRAP_BLOCK_SIZE; i++) { + unsigned int doTest = PORT_CT_GT(padLen, i); + unsigned int result = PORT_CT_ZERO(newBuf[paddedLen - i - 1]); + good &= PORT_CT_SEL(doTest, result, PORT_CT_TRUE); + } + + /* now if anything was wrong, fail. At this point we will leak timing + * information, but we also 'leak' the error code as well. */ + if (!good) { + PORT_SetError(SEC_ERROR_BAD_DATA); + goto loser; + } + + /* now copy out the result */ + *pOutputLen = outLen; + if (maxOutputLen < outLen) { + PORT_SetError(SEC_ERROR_OUTPUT_LEN); + goto loser; + } + if (output != newBuf) { + PORT_Memcpy(output, newBuf, outLen); + } + rv = SECSuccess; +loser: + /* if we failed, make sure we don't return any data to the user */ + if ((rv != SECSuccess) && (output == newBuf)) { + PORT_Memset(newBuf, 0, paddedLen); + } + /* clear out CSP sensitive data from the heap and stack */ + if (allocBuf) { + PORT_ZFree(allocBuf, paddedLen); + } + PORT_Memset(iv, 0, sizeof(iv)); + return rv; +} |