summaryrefslogtreecommitdiffstats
path: root/security/nss/lib/freebl/cmac.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /security/nss/lib/freebl/cmac.c
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--security/nss/lib/freebl/cmac.c323
1 files changed, 323 insertions, 0 deletions
diff --git a/security/nss/lib/freebl/cmac.c b/security/nss/lib/freebl/cmac.c
new file mode 100644
index 0000000000..222cef1b4c
--- /dev/null
+++ b/security/nss/lib/freebl/cmac.c
@@ -0,0 +1,323 @@
+/* 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;
+ unsigned 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;
+ ctx->cipher.aes = AES_CreateContext(key, NULL, NSS_AES, 1, key_len,
+ ctx->blockSize);
+ if (ctx->cipher.aes == NULL) {
+ 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)
+{
+ unsigned 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 && ctx->cipher.aes != NULL) {
+ AES_DestroyContext(ctx->cipher.aes, PR_TRUE);
+ }
+
+ /* 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);
+ }
+}