1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-22 14:28:07 +00:00
trezor-firmware/crypto/aes/aesccm.c
2023-08-29 10:59:34 +02:00

331 lines
10 KiB
C

/**
* Copyright (c) 2023 Andrew Kozlik
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* For a specification of AES-CCM see one of the following:
* https://datatracker.ietf.org/doc/html/rfc3610
* https://doi.org/10.6028/NIST.SP.800-38C
*/
#include <string.h>
#include "aesccm.h"
#include "memzero.h"
typedef struct {
const aes_encrypt_ctx *encrypt_ctx;
union {
// Ensure 32-bit alignment.
uint8_t state[AES_BLOCK_SIZE];
uint32_t state32[AES_BLOCK_SIZE / 4];
};
// Next position in the state where data will be added.
// Valid values are 0 to 15.
uint8_t pos;
} cbc_mac_context;
// WARNING: Caller must ensure that encrypt_ctx remains valid for the lifetime
// of ctx.
static void cbc_mac_init(cbc_mac_context *ctx,
const aes_encrypt_ctx *encrypt_ctx) {
memzero(ctx, sizeof(cbc_mac_context));
ctx->encrypt_ctx = encrypt_ctx;
}
static AES_RETURN cbc_mac_update(cbc_mac_context *ctx, const uint8_t *data,
size_t size) {
if (ctx->pos != 0) {
while (size > 0 && ctx->pos < AES_BLOCK_SIZE) {
ctx->state[ctx->pos] ^= *data;
ctx->pos++;
data++;
size--;
}
if (ctx->pos != AES_BLOCK_SIZE) {
return EXIT_SUCCESS;
}
if (aes_encrypt(ctx->state, ctx->state, ctx->encrypt_ctx) != EXIT_SUCCESS) {
memzero(ctx, sizeof(*ctx));
return EXIT_FAILURE;
}
ctx->pos = 0;
}
size_t block_count = size >> AES_BLOCK_SIZE_P2;
size %= AES_BLOCK_SIZE;
if (!ALIGN_OFFSET(data, 4)) {
while (block_count != 0) {
ctx->state32[0] ^= ((uint32_t *)data)[0];
ctx->state32[1] ^= ((uint32_t *)data)[1];
ctx->state32[2] ^= ((uint32_t *)data)[2];
ctx->state32[3] ^= ((uint32_t *)data)[3];
if (aes_encrypt(ctx->state, ctx->state, ctx->encrypt_ctx) !=
EXIT_SUCCESS) {
memzero(ctx, sizeof(*ctx));
return EXIT_FAILURE;
}
data += AES_BLOCK_SIZE;
block_count--;
}
} else {
while (block_count != 0) {
ctx->state[0] ^= data[0];
ctx->state[1] ^= data[1];
ctx->state[2] ^= data[2];
ctx->state[3] ^= data[3];
ctx->state[4] ^= data[4];
ctx->state[5] ^= data[5];
ctx->state[6] ^= data[6];
ctx->state[7] ^= data[7];
ctx->state[8] ^= data[8];
ctx->state[9] ^= data[9];
ctx->state[10] ^= data[10];
ctx->state[11] ^= data[11];
ctx->state[12] ^= data[12];
ctx->state[13] ^= data[13];
ctx->state[14] ^= data[14];
ctx->state[15] ^= data[15];
if (aes_encrypt(ctx->state, ctx->state, ctx->encrypt_ctx) !=
EXIT_SUCCESS) {
memzero(ctx, sizeof(*ctx));
return EXIT_FAILURE;
}
data += AES_BLOCK_SIZE;
block_count--;
}
}
while (size > 0) {
ctx->state[ctx->pos] ^= *data;
ctx->pos++;
data++;
size--;
}
return EXIT_SUCCESS;
}
static AES_RETURN cbc_mac_update_zero_padding(cbc_mac_context *ctx) {
if (ctx->pos != 0) {
if (aes_encrypt(ctx->state, ctx->state, ctx->encrypt_ctx) != EXIT_SUCCESS) {
memzero(ctx, sizeof(*ctx));
return EXIT_FAILURE;
}
ctx->pos = 0;
}
return EXIT_SUCCESS;
}
static AES_RETURN cbc_mac_final(cbc_mac_context *ctx, uint8_t *mac,
size_t mac_len) {
if (ctx->pos != 0 || mac_len > AES_BLOCK_SIZE) {
memzero(ctx, sizeof(*ctx));
return EXIT_FAILURE;
}
memcpy(mac, ctx->state, mac_len);
memzero(ctx, sizeof(*ctx));
return EXIT_SUCCESS;
}
static AES_RETURN aes_ccm_init(aes_encrypt_ctx *encrypt_ctx,
const uint8_t *nonce, size_t nonce_len,
const uint8_t *adata, size_t adata_len,
size_t plaintext_len, size_t mac_len,
cbc_mac_context *cbc_ctx, uint8_t *ctr_block) {
if (mac_len < 4 || mac_len > AES_BLOCK_SIZE || mac_len % 2 != 0) {
return EXIT_FAILURE;
}
if (nonce_len < 7 || nonce_len > 13) {
return EXIT_FAILURE;
}
// Length of the binary representation of plaintext_len.
const size_t q = 15 - nonce_len;
uint8_t flags = (adata_len != 0) << 6;
flags |= ((mac_len - 2) / 2) << 3;
flags |= q - 1;
// Encode the first block.
uint8_t block[AES_BLOCK_SIZE] = {0};
block[0] = flags;
memcpy(&block[1], nonce, nonce_len);
size_t shifted_len = plaintext_len;
for (size_t i = 0; i < q; ++i) {
block[15 - i] = shifted_len & 0xff;
shifted_len >>= 8;
}
if (shifted_len != 0) {
// plaintext_len does not fit into q octets.
return EXIT_FAILURE;
}
aes_mode_reset(encrypt_ctx);
cbc_mac_init(cbc_ctx, encrypt_ctx);
if (cbc_mac_update(cbc_ctx, block, sizeof(block)) != EXIT_SUCCESS) {
return EXIT_FAILURE;
}
// Format the associated data length.
if (adata_len != 0) {
size_t block_size = 0;
if (adata_len < 65536 - 256) {
block[0] = adata_len >> 8;
block[1] = adata_len & 0xff;
block_size = 2;
} else {
shifted_len = adata_len;
block[5] = shifted_len & 0xff;
shifted_len >>= 8;
block[4] = shifted_len & 0xff;
shifted_len >>= 8;
block[3] = shifted_len & 0xff;
shifted_len >>= 8;
block[2] = shifted_len & 0xff;
block[1] = 0xfe;
block[0] = 0xff;
block_size = 6;
if ((shifted_len >> 8) != 0) {
// Associated data over 4 GB not supported.
return EXIT_FAILURE;
}
}
if (cbc_mac_update(cbc_ctx, block, block_size) != EXIT_SUCCESS ||
cbc_mac_update(cbc_ctx, adata, adata_len) != EXIT_SUCCESS ||
cbc_mac_update_zero_padding(cbc_ctx) != EXIT_SUCCESS) {
return EXIT_FAILURE;
}
}
// Initialize counter.
memzero(ctr_block, AES_BLOCK_SIZE);
ctr_block[0] = q - 1;
memcpy(&ctr_block[1], nonce, nonce_len);
return EXIT_SUCCESS;
}
// The length of data written to the ciphertext array is plaintext_len +
// mac_len.
AES_RETURN aes_ccm_encrypt(aes_encrypt_ctx *encrypt_ctx, const uint8_t *nonce,
size_t nonce_len, const uint8_t *adata,
size_t adata_len, const uint8_t *plaintext,
size_t plaintext_len, size_t mac_len,
uint8_t *ciphertext) {
cbc_mac_context cbc_ctx = {0};
uint8_t ctr_block[AES_BLOCK_SIZE] = {0};
if (aes_ccm_init(encrypt_ctx, nonce, nonce_len, adata, adata_len,
plaintext_len, mac_len, &cbc_ctx,
ctr_block) != EXIT_SUCCESS) {
return EXIT_FAILURE;
}
if (cbc_mac_update(&cbc_ctx, plaintext, plaintext_len) != EXIT_SUCCESS ||
cbc_mac_update_zero_padding(&cbc_ctx) != EXIT_SUCCESS ||
cbc_mac_final(&cbc_ctx, &ciphertext[plaintext_len], mac_len) !=
EXIT_SUCCESS) {
return EXIT_FAILURE;
}
uint8_t s0[AES_BLOCK_SIZE] = {0};
if (aes_ecb_encrypt(ctr_block, s0, AES_BLOCK_SIZE, encrypt_ctx) !=
EXIT_SUCCESS) {
memzero(s0, sizeof(s0));
return EXIT_FAILURE;
}
for (size_t i = 0; i < mac_len; ++i) {
ciphertext[plaintext_len + i] ^= s0[i];
}
memzero(s0, sizeof(s0));
ctr_block[AES_BLOCK_SIZE - 1] = 1;
if (aes_ctr_crypt(plaintext, ciphertext, plaintext_len, ctr_block,
aes_ctr_cbuf_inc, encrypt_ctx) != EXIT_SUCCESS) {
memzero(ciphertext, plaintext_len + mac_len);
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
// The length of data written to the plaintext array is ciphertext_len -
// mac_len.
AES_RETURN aes_ccm_decrypt(aes_encrypt_ctx *encrypt_ctx, const uint8_t *nonce,
size_t nonce_len, const uint8_t *adata,
size_t adata_len, const uint8_t *ciphertext,
size_t ciphertext_len, size_t mac_len,
uint8_t *plaintext) {
cbc_mac_context cbc_ctx = {0};
uint8_t ctr_block[AES_BLOCK_SIZE] = {0};
size_t plaintext_len = ciphertext_len - mac_len;
if (ciphertext_len < mac_len ||
aes_ccm_init(encrypt_ctx, nonce, nonce_len, adata, adata_len,
plaintext_len, mac_len, &cbc_ctx,
ctr_block) != EXIT_SUCCESS) {
return EXIT_FAILURE;
}
uint8_t s0[AES_BLOCK_SIZE] = {0};
if (aes_ecb_encrypt(ctr_block, s0, AES_BLOCK_SIZE, encrypt_ctx) !=
EXIT_SUCCESS) {
memzero(&cbc_ctx, sizeof(cbc_ctx));
return EXIT_FAILURE;
}
ctr_block[AES_BLOCK_SIZE - 1] = 1;
if (aes_ctr_crypt(ciphertext, plaintext, plaintext_len, ctr_block,
aes_ctr_cbuf_inc, encrypt_ctx) != EXIT_SUCCESS) {
memzero(&cbc_ctx, sizeof(cbc_ctx));
memzero(s0, sizeof(s0));
memzero(plaintext, plaintext_len);
return EXIT_FAILURE;
}
uint8_t cbc_mac[AES_BLOCK_SIZE] = {0};
if (cbc_mac_update(&cbc_ctx, plaintext, plaintext_len) != EXIT_SUCCESS ||
cbc_mac_update_zero_padding(&cbc_ctx) != EXIT_SUCCESS ||
cbc_mac_final(&cbc_ctx, cbc_mac, mac_len) != EXIT_SUCCESS) {
memzero(s0, sizeof(s0));
memzero(plaintext, plaintext_len);
return EXIT_FAILURE;
}
uint8_t diff = 0;
for (size_t i = 0; i < mac_len; ++i) {
diff |= ciphertext[plaintext_len + i] ^ s0[i] ^ cbc_mac[i];
}
memzero(cbc_mac, sizeof(cbc_mac));
memzero(s0, sizeof(s0));
if (diff != 0) {
memzero(plaintext, plaintext_len);
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}