From 90d8b5a42ed36a2474bc59fa2b21a8269d3b1e07 Mon Sep 17 00:00:00 2001 From: Andrew Kozlik Date: Mon, 26 Jun 2023 21:17:51 +0200 Subject: [PATCH] feat(crypto): Implement AES-CCM. --- crypto/Makefile | 3 +- crypto/aes/aesccm.c | 323 ++++++++++++++++++++++++++++++++++++++++++ crypto/aes/aesccm.h | 43 ++++++ tools/style.c.exclude | 2 +- 4 files changed, 369 insertions(+), 2 deletions(-) create mode 100644 crypto/aes/aesccm.c create mode 100644 crypto/aes/aesccm.h diff --git a/crypto/Makefile b/crypto/Makefile index 7a5d7e35b..c7ff9e6b3 100644 --- a/crypto/Makefile +++ b/crypto/Makefile @@ -83,6 +83,7 @@ CFLAGS += -DUSE_MONERO=1 CFLAGS += -DUSE_NEM=1 CFLAGS += -DUSE_CARDANO=1 CFLAGS += -DUSE_INSECURE_PRNG=1 +CFLAGS += -DAES_128 CFLAGS += $(shell pkg-config --cflags openssl) # disable certain optimizations and features when small footprint is required @@ -97,7 +98,7 @@ SRCS += ripemd160.c SRCS += sha2.c SRCS += sha3.c SRCS += hasher.c -SRCS += aes/aescrypt.c aes/aeskey.c aes/aestab.c aes/aes_modes.c +SRCS += aes/aesccm.c aes/aescrypt.c aes/aeskey.c aes/aestab.c aes/aes_modes.c SRCS += ed25519-donna/curve25519-donna-32bit.c ed25519-donna/curve25519-donna-helpers.c ed25519-donna/modm-donna-32bit.c SRCS += ed25519-donna/ed25519-donna-basepoint-table.c ed25519-donna/ed25519-donna-32bit-tables.c ed25519-donna/ed25519-donna-impl-base.c SRCS += ed25519-donna/ed25519.c ed25519-donna/curve25519-donna-scalarmult-base.c ed25519-donna/ed25519-sha3.c ed25519-donna/ed25519-keccak.c diff --git a/crypto/aes/aesccm.c b/crypto/aes/aesccm.c new file mode 100644 index 000000000..6f01006c7 --- /dev/null +++ b/crypto/aes/aesccm.c @@ -0,0 +1,323 @@ +/** + * 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. + */ + +#include + +#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. + 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; +} diff --git a/crypto/aes/aesccm.h b/crypto/aes/aesccm.h new file mode 100644 index 000000000..d03cf8738 --- /dev/null +++ b/crypto/aes/aesccm.h @@ -0,0 +1,43 @@ +/** + * 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. + */ + +#ifndef __AESCCM_H__ +#define __AESCCM_H__ + +#include +#include + +#include "aes/aes.h" + +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); + +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); + +#endif diff --git a/tools/style.c.exclude b/tools/style.c.exclude index 5ef29b507..e643d7575 100644 --- a/tools/style.c.exclude +++ b/tools/style.c.exclude @@ -1,5 +1,5 @@ ^\./core/embed/bootloader/protob/ -^\./crypto/aes/ +^\./crypto/aes/aes\(\|crypt\|key\|_modes\|opt\|tab\|tst\)\. ^\./crypto/chacha20poly1305/ ^\./crypto/ed25519-donna/ ^\./crypto/gui/