diff options
Diffstat (limited to 'lib/freebl')
-rw-r--r-- | lib/freebl/blapi.h | 1 | ||||
-rw-r--r-- | lib/freebl/cmac.c | 322 | ||||
-rw-r--r-- | lib/freebl/cmac.h | 47 | ||||
-rw-r--r-- | lib/freebl/exports.gyp | 1 | ||||
-rw-r--r-- | lib/freebl/freebl_base.gypi | 1 | ||||
-rw-r--r-- | lib/freebl/ldvector.c | 11 | ||||
-rw-r--r-- | lib/freebl/loader.c | 51 | ||||
-rw-r--r-- | lib/freebl/loader.h | 16 | ||||
-rw-r--r-- | lib/freebl/manifest.mn | 3 |
9 files changed, 451 insertions, 2 deletions
diff --git a/lib/freebl/blapi.h b/lib/freebl/blapi.h index d646b8a15..3b404992b 100644 --- a/lib/freebl/blapi.h +++ b/lib/freebl/blapi.h @@ -10,6 +10,7 @@ #include "blapit.h" #include "hasht.h" +#include "cmac.h" #include "alghmac.h" SEC_BEGIN_PROTOS diff --git a/lib/freebl/cmac.c b/lib/freebl/cmac.c new file mode 100644 index 000000000..73237c03a --- /dev/null +++ b/lib/freebl/cmac.c @@ -0,0 +1,322 @@ +/* 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 "rijndael.h" +#include "blapi.h" +#include "cmac.h" +#include "secerr.h" +#include "nspr.h" + +struct CMACContextStr { + /* Information about the block cipher to use internally. The cipher should + * be placed in ECB mode so that we can use it to directly encrypt blocks. + * + * + * To add a new cipher, add an entry to CMACCipher, update CMAC_Init, + * cmac_Encrypt, and CMAC_Destroy methods to handle the new cipher, and + * add a new Context pointer to the cipher union with the correct type. */ + CMACCipher cipherType; + union { + AESContext aes; + } cipher; + int blockSize; + + /* Internal keys which are conditionally used by the algorithm. Derived + * from encrypting the NULL block. We leave the storing of (and the + * cleanup of) the CMAC key to the underlying block cipher. */ + unsigned char k1[MAX_BLOCK_SIZE]; + unsigned char k2[MAX_BLOCK_SIZE]; + + /* When Update is called with data which isn't a multiple of the block + * size, we need a place to put it. HMAC handles this by passing it to + * the underlying hash function right away; we can't do that as the + * contract on the cipher object is different. */ + unsigned int partialIndex; + unsigned char partialBlock[MAX_BLOCK_SIZE]; + + /* Last encrypted block. This gets xor-ed with partialBlock prior to + * encrypting it. NIST defines this to be the empty string to begin. */ + unsigned char lastBlock[MAX_BLOCK_SIZE]; +}; + +static void +cmac_ShiftLeftOne(unsigned char *out, const unsigned char *in, int length) +{ + int i = 0; + for (; i < length - 1; i++) { + out[i] = in[i] << 1; + out[i] |= in[i + 1] >> 7; + } + out[i] = in[i] << 1; +} + +static SECStatus +cmac_Encrypt(CMACContext *ctx, unsigned char *output, + const unsigned char *input, + unsigned int inputLen) +{ + if (ctx->cipherType == CMAC_AES) { + unsigned int tmpOutputLen; + SECStatus rv = AES_Encrypt(&ctx->cipher.aes, output, &tmpOutputLen, + ctx->blockSize, input, inputLen); + + /* Assumption: AES_Encrypt (when in ECB mode) always returns an + * output of length equal to blockSize (what was pass as the value + * of the maxOutputLen parameter). */ + PORT_Assert(tmpOutputLen == ctx->blockSize); + return rv; + } + + return SECFailure; +} + +/* NIST SP.800-38B, 6.1 Subkey Generation */ +static SECStatus +cmac_GenerateSubkeys(CMACContext *ctx) +{ + unsigned char null_block[MAX_BLOCK_SIZE] = { 0 }; + unsigned char L[MAX_BLOCK_SIZE]; + unsigned char v; + unsigned char i; + + /* Step 1: L = AES(key, null_block) */ + if (cmac_Encrypt(ctx, L, null_block, ctx->blockSize) != SECSuccess) { + return SECFailure; + } + + /* In the following, some effort has been made to be constant time. Rather + * than conditioning on the value of the MSB (of L or K1), we use the loop + * to build a mask for the conditional constant. */ + + /* Step 2: If MSB(L) = 0, K1 = L << 1. Else, K1 = (L << 1) ^ R_b. */ + cmac_ShiftLeftOne(ctx->k1, L, ctx->blockSize); + v = L[0] >> 7; + for (i = 1; i <= 7; i <<= 1) { + v |= (v << i); + } + ctx->k1[ctx->blockSize - 1] ^= (0x87 & v); + + /* Step 3: If MSB(K1) = 0, K2 = K1 << 1. Else, K2 = (K1 <, 1) ^ R_b. */ + cmac_ShiftLeftOne(ctx->k2, ctx->k1, ctx->blockSize); + v = ctx->k1[0] >> 7; + for (i = 1; i <= 7; i <<= 1) { + v |= (v << i); + } + ctx->k2[ctx->blockSize - 1] ^= (0x87 & v); + + /* Any intermediate value in the computation of the subkey shall be + * secret. */ + PORT_Memset(null_block, 0, MAX_BLOCK_SIZE); + PORT_Memset(L, 0, MAX_BLOCK_SIZE); + + /* Step 4: Return the values. */ + return SECSuccess; +} + +/* NIST SP.800-38B, 6.2 MAC Generation step 6 */ +static SECStatus +cmac_UpdateState(CMACContext *ctx) +{ + if (ctx == NULL || ctx->partialIndex != ctx->blockSize) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + /* Step 6: C_i = CIPHER(key, C_{i-1} ^ M_i) for 1 <= i <= n, and + * C_0 is defined as the empty string. */ + + for (unsigned int index = 0; index < ctx->blockSize; index++) { + ctx->partialBlock[index] ^= ctx->lastBlock[index]; + } + + return cmac_Encrypt(ctx, ctx->lastBlock, ctx->partialBlock, ctx->blockSize); +} + +SECStatus +CMAC_Init(CMACContext *ctx, CMACCipher type, + const unsigned char *key, unsigned int key_len) +{ + if (ctx == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return SECFailure; + } + + /* We only currently support AES-CMAC. */ + if (type != CMAC_AES) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + PORT_Memset(ctx, 0, sizeof(*ctx)); + + ctx->blockSize = AES_BLOCK_SIZE; + ctx->cipherType = CMAC_AES; + if (AES_InitContext(&ctx->cipher.aes, key, key_len, NULL, NSS_AES, 1, + ctx->blockSize) != SECSuccess) { + return SECFailure; + } + + return CMAC_Begin(ctx); +} + +CMACContext * +CMAC_Create(CMACCipher type, const unsigned char *key, + unsigned int key_len) +{ + CMACContext *result = PORT_New(CMACContext); + + if (CMAC_Init(result, type, key, key_len) != SECSuccess) { + CMAC_Destroy(result, PR_TRUE); + return NULL; + } + + return result; +} + +SECStatus +CMAC_Begin(CMACContext *ctx) +{ + if (ctx == NULL) { + return SECFailure; + } + + /* Ensure that our blockSize is less than the maximum. When this fails, + * a cipher with a larger block size was added and MAX_BLOCK_SIZE needs + * to be updated accordingly. */ + PORT_Assert(ctx->blockSize <= MAX_BLOCK_SIZE); + + if (cmac_GenerateSubkeys(ctx) != SECSuccess) { + return SECFailure; + } + + /* Set the index to write partial blocks at to zero. This saves us from + * having to clear ctx->partialBlock. */ + ctx->partialIndex = 0; + + /* Step 5: Let C_0 = 0^b. */ + PORT_Memset(ctx->lastBlock, 0, ctx->blockSize); + + return SECSuccess; +} + +/* NIST SP.800-38B, 6.2 MAC Generation */ +SECStatus +CMAC_Update(CMACContext *ctx, const unsigned char *data, + unsigned int data_len) +{ + int data_index = 0; + if (ctx == NULL) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (data == NULL || data_len == 0) { + return SECSuccess; + } + + /* Copy as many bytes from data into ctx->partialBlock as we can, up to + * the maximum of the remaining data and the remaining space in + * ctx->partialBlock. + * + * Note that we swap the order (encrypt *then* copy) because the last + * block is different from the rest. If we end on an even multiple of + * the block size, we have to be able to XOR it with K1. But we won't know + * that it is the last until CMAC_Finish is called (and by then, CMAC_Update + * has already returned). */ + while (data_index < data_len) { + if (ctx->partialIndex == ctx->blockSize) { + if (cmac_UpdateState(ctx) != SECSuccess) { + return SECFailure; + } + + ctx->partialIndex = 0; + } + + unsigned int copy_len = data_len - data_index; + if (copy_len > (ctx->blockSize - ctx->partialIndex)) { + copy_len = ctx->blockSize - ctx->partialIndex; + } + + PORT_Memcpy(ctx->partialBlock + ctx->partialIndex, data + data_index, copy_len); + data_index += copy_len; + ctx->partialIndex += copy_len; + } + + return SECSuccess; +} + +/* NIST SP.800-38B, 6.2 MAC Generation */ +SECStatus +CMAC_Finish(CMACContext *ctx, unsigned char *result, + unsigned int *result_len, + unsigned int max_result_len) +{ + if (ctx == NULL || result == NULL || max_result_len == 0) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (max_result_len > ctx->blockSize) { + /* This is a weird situation. The PKCS #11 soft tokencode passes + * sizeof(result) here, which is hard-coded as SFTK_MAX_MAC_LENGTH. + * This later gets truncated to min(SFTK_MAX_MAC_LENGTH, requested). */ + max_result_len = ctx->blockSize; + } + + /* Step 4: If M_n* is a complete block, M_n = K1 ^ M_n*. Else, + * M_n = K2 ^ (M_n* || 10^j). */ + if (ctx->partialIndex == ctx->blockSize) { + /* XOR in K1. */ + for (unsigned int index = 0; index < ctx->blockSize; index++) { + ctx->partialBlock[index] ^= ctx->k1[index]; + } + } else { + /* Use 10* padding on the partial block. */ + ctx->partialBlock[ctx->partialIndex++] = 0x80; + PORT_Memset(ctx->partialBlock + ctx->partialIndex, 0, + ctx->blockSize - ctx->partialIndex); + ctx->partialIndex = ctx->blockSize; + + /* XOR in K2. */ + for (unsigned int index = 0; index < ctx->blockSize; index++) { + ctx->partialBlock[index] ^= ctx->k2[index]; + } + } + + /* Encrypt the block. */ + if (cmac_UpdateState(ctx) != SECSuccess) { + return SECFailure; + } + + /* Step 7 & 8: T = MSB_tlen(C_n); return T. */ + PORT_Memcpy(result, ctx->lastBlock, max_result_len); + if (result_len != NULL) { + *result_len = max_result_len; + } + return SECSuccess; +} + +void +CMAC_Destroy(CMACContext *ctx, PRBool free_it) +{ + if (ctx == NULL) { + return; + } + + if (ctx->cipherType == CMAC_AES) { + AES_DestroyContext(&ctx->cipher.aes, PR_FALSE); + } + + /* Destroy everything in the context. This includes sensitive data in + * K1, K2, and lastBlock. */ + PORT_Memset(ctx, 0, sizeof(*ctx)); + + if (free_it == PR_TRUE) { + PORT_Free(ctx); + } +} diff --git a/lib/freebl/cmac.h b/lib/freebl/cmac.h new file mode 100644 index 000000000..6a6f42c79 --- /dev/null +++ b/lib/freebl/cmac.h @@ -0,0 +1,47 @@ +/* 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/. */ + +#ifndef _CMAC_H_ +#define _CMAC_H_ + +typedef struct CMACContextStr CMACContext; + +SEC_BEGIN_PROTOS + +/* Enum for identifying the underlying block cipher we're using internally. */ +typedef enum { + CMAC_AES = 0 +} CMACCipher; + +/* Initialize an existing CMACContext struct. */ +SECStatus CMAC_Init(CMACContext *ctx, CMACCipher type, + const unsigned char *key, unsigned int key_len); + +/* Allocate and initialize a new CMAC context with the specified cipher and + * key. */ +CMACContext *CMAC_Create(CMACCipher type, const unsigned char *key, + unsigned int key_len); + +/* Called automatically by CMAC_*{Create,Init}(...). Only useful for restarting + * an already-started CMAC instance. */ +SECStatus CMAC_Begin(CMACContext *ctx); + +/* Add the specified bytes into the CMAC state. */ +SECStatus CMAC_Update(CMACContext *ctx, const unsigned char *data, + unsigned int data_len); + +/* Finalize the CMAC state and return the result. */ +SECStatus CMAC_Finish(CMACContext *ctx, unsigned char *result, + unsigned int *result_len, + unsigned int max_result_len); + +/* Note: CMAC_Clone isn't implemented here because AES doesn't expose a + * context-cloning operation. */ + +/* Destroy a CMAC context, optionally freeing it. */ +void CMAC_Destroy(CMACContext *ctx, PRBool free_it); + +SEC_END_PROTOS + +#endif diff --git a/lib/freebl/exports.gyp b/lib/freebl/exports.gyp index ca0b6dafd..af5c782a5 100644 --- a/lib/freebl/exports.gyp +++ b/lib/freebl/exports.gyp @@ -27,6 +27,7 @@ }, { 'files': [ + 'cmac.h', 'alghmac.h', 'blapi.h', 'blake2b.h', diff --git a/lib/freebl/freebl_base.gypi b/lib/freebl/freebl_base.gypi index 84f16d682..f2d1304fd 100644 --- a/lib/freebl/freebl_base.gypi +++ b/lib/freebl/freebl_base.gypi @@ -5,6 +5,7 @@ 'sources': [ 'aeskeywrap.c', 'alg2268.c', + 'cmac.c', 'alghmac.c', 'arcfive.c', 'arcfour.c', diff --git a/lib/freebl/ldvector.c b/lib/freebl/ldvector.c index d2fcbb514..6ae9ef68d 100644 --- a/lib/freebl/ldvector.c +++ b/lib/freebl/ldvector.c @@ -10,6 +10,7 @@ extern int FREEBL_InitStubs(void); #endif #include "loader.h" +#include "cmac.h" #include "alghmac.h" #include "hmacct.h" #include "blapii.h" @@ -317,10 +318,18 @@ static const struct FREEBLVectorStr vector = /* End of Version 3.020 */ - ChaCha20_Xor + ChaCha20_Xor, /* End of version 3.021 */ + CMAC_Init, + CMAC_Create, + CMAC_Begin, + CMAC_Update, + CMAC_Finish, + CMAC_Destroy + + /* End of version 3.022 */ }; const FREEBLVector* diff --git a/lib/freebl/loader.c b/lib/freebl/loader.c index ba9a56430..945ecf171 100644 --- a/lib/freebl/loader.c +++ b/lib/freebl/loader.c @@ -2245,3 +2245,54 @@ BLAKE2B_Resurrect(unsigned char *space, void *arg) } return (vector->p_BLAKE2B_Resurrect)(space, arg); } + +/* == New for CMAC == */ +SECStatus +CMAC_Init(CMACContext *ctx, CMACCipher type, const unsigned char *key, + unsigned int key_len) +{ + if (!vector && PR_SUCCESS != freebl_RunLoaderOnce()) + return SECFailure; + return (vector->p_CMAC_Init)(ctx, type, key, key_len); +} + +CMACContext * +CMAC_Create(CMACCipher type, const unsigned char *key, unsigned int key_len) +{ + if (!vector && PR_SUCCESS != freebl_RunLoaderOnce()) + return NULL; + return (vector->p_CMAC_Create)(type, key, key_len); +} + +SECStatus +CMAC_Begin(CMACContext *ctx) +{ + if (!vector && PR_SUCCESS != freebl_RunLoaderOnce()) + return SECFailure; + return (vector->p_CMAC_Begin)(ctx); +} + +SECStatus +CMAC_Update(CMACContext *ctx, const unsigned char *data, unsigned int data_len) +{ + if (!vector && PR_SUCCESS != freebl_RunLoaderOnce()) + return SECFailure; + return (vector->p_CMAC_Update)(ctx, data, data_len); +} + +SECStatus +CMAC_Finish(CMACContext *ctx, unsigned char *result, unsigned int *result_len, + unsigned int max_result_len) +{ + if (!vector && PR_SUCCESS != freebl_RunLoaderOnce()) + return SECFailure; + return (vector->p_CMAC_Finish)(ctx, result, result_len, max_result_len); +} + +void +CMAC_Destroy(CMACContext *ctx, PRBool free_it) +{ + if (!vector && PR_SUCCESS != freebl_RunLoaderOnce()) + return; + (vector->p_CMAC_Destroy)(ctx, free_it); +} diff --git a/lib/freebl/loader.h b/lib/freebl/loader.h index f7e965143..ff1a5d1a4 100644 --- a/lib/freebl/loader.h +++ b/lib/freebl/loader.h @@ -10,7 +10,7 @@ #include "blapi.h" -#define FREEBL_VERSION 0x0315 +#define FREEBL_VERSION 0x0316 struct FREEBLVectorStr { @@ -765,6 +765,20 @@ struct FREEBLVectorStr { /* Version 3.021 came to here */ + SECStatus (*p_CMAC_Init)(CMACContext *ctx, CMACCipher type, + const unsigned char *key, unsigned int key_len); + CMACContext *(*p_CMAC_Create)(CMACCipher type, const unsigned char *key, + unsigned int key_len); + SECStatus (*p_CMAC_Begin)(CMACContext *ctx); + SECStatus (*p_CMAC_Update)(CMACContext *ctx, const unsigned char *data, + unsigned int data_len); + SECStatus (*p_CMAC_Finish)(CMACContext *ctx, unsigned char *result, + unsigned int *result_len, + unsigned int max_result_len); + void (*p_CMAC_Destroy)(CMACContext *ctx, PRBool free_it); + + /* Version 3.022 came to here */ + /* Add new function pointers at the end of this struct and bump * FREEBL_VERSION at the beginning of this file. */ }; diff --git a/lib/freebl/manifest.mn b/lib/freebl/manifest.mn index e4c9ab0b7..8915ac474 100644 --- a/lib/freebl/manifest.mn +++ b/lib/freebl/manifest.mn @@ -85,6 +85,7 @@ EXPORTS = \ $(NULL) PRIVATE_EXPORTS = \ + cmac.h \ alghmac.h \ blake2b.h \ blapi.h \ @@ -119,6 +120,7 @@ CSRCS = \ md2.c \ md5.c \ sha512.c \ + cmac.c \ alghmac.c \ rawhash.c \ alg2268.c \ @@ -162,6 +164,7 @@ CSRCS = \ ALL_CSRCS := $(CSRCS) ALL_HDRS = \ + cmac.h \ alghmac.h \ blake2b.h \ blapi.h \ |