#include "atca_basic_aes_gcm.h"
#include "atca_compiler.h"
const char* atca_basic_aes_gcm_version = "1.0";
static ATCA_STATUS atcab_aes_ghash(const uint8_t* h, const uint8_t* data, size_t data_size, uint8_t* y)
{
ATCA_STATUS status;
uint8_t pad_bytes[AES_DATA_SIZE];
size_t xor_index;
if (h == NULL || data == NULL || y == NULL)
{
RETURN(ATCA_BAD_PARAM, "Null pointer");
}
if (data_size == 0)
{
return ATCA_SUCCESS;
}
while (data_size / AES_DATA_SIZE)
{
for (xor_index = 0; xor_index < AES_DATA_SIZE; xor_index++)
{
y[xor_index] ^= *data++;
}
if (ATCA_SUCCESS != (status = atcab_aes_gfm(h, y, y)))
{
RETURN(status, "GHASH GFM (full block) failed");
}
data_size -= AES_DATA_SIZE;
}
if (data_size)
{
memcpy(pad_bytes, data, data_size);
memset(&pad_bytes[data_size], 0, sizeof(pad_bytes) - data_size);
for (xor_index = 0; xor_index < AES_DATA_SIZE; xor_index++)
{
y[xor_index] ^= pad_bytes[xor_index];
}
if (ATCA_SUCCESS != (status = atcab_aes_gfm(h, y, y)))
{
RETURN(status, "GHASH GFM (partial block) failed");
}
}
return ATCA_SUCCESS;
}
static ATCA_STATUS atcab_aes_gcm_increment(uint8_t* cb, size_t counter_size)
{
size_t i;
if (cb == NULL)
{
return ATCA_BAD_PARAM;
}
for (i = 0; i < counter_size; i++)
{
if (++(cb[AES_DATA_SIZE - i - 1]) != 0)
{
break;
}
}
if (i >= counter_size)
{
memset(&cb[AES_DATA_SIZE - counter_size], 0, counter_size);
}
return ATCA_SUCCESS;
}
ATCA_STATUS atcab_aes_gcm_init(atca_aes_gcm_ctx_t* ctx, uint16_t key_id, uint8_t key_block, const uint8_t* iv, size_t iv_size)
{
ATCA_STATUS status;
uint8_t ghash_data[AES_DATA_SIZE];
uint32_t length;
if (ctx == NULL || iv == NULL || iv_size == 0)
{
RETURN(ATCA_BAD_PARAM, "GCM init failed; Either null pointer or 0 iv_size");
}
memset(ctx, 0, sizeof(*ctx));
if (ATCA_SUCCESS != (status = atcab_aes_encrypt(key_id, key_block, ctx->h, ctx->h)))
{
RETURN(status, "GCM - H failed");
}
if (iv_size == ATCA_AES_GCM_IV_STD_LENGTH)
{
memcpy(ctx->j0, iv, iv_size);
ctx->j0[AES_DATA_SIZE - 1] = 0x01;
}
else
{
if (ATCA_SUCCESS != (status = atcab_aes_ghash(ctx->h, iv, iv_size, ctx->j0)))
{
RETURN(status, "GCM - J0 (IV) failed");
}
memset(ghash_data, 0, AES_DATA_SIZE);
length = ATCA_UINT32_HOST_TO_BE((uint32_t)(iv_size * 8));
memcpy(&ghash_data[12], &length, sizeof(length));
if (ATCA_SUCCESS != (status = atcab_aes_ghash(ctx->h, ghash_data, sizeof(ghash_data), ctx->j0)))
{
RETURN(status, "GCM - J0 (IV Size) failed");
}
}
ctx->key_id = key_id;
ctx->key_block = key_block;
memcpy(ctx->cb, ctx->j0, AES_DATA_SIZE);
if (ATCA_SUCCESS != (status = atcab_aes_gcm_increment(ctx->cb, 4)))
{
RETURN(status, "GCM CTR increment failed");
}
return ATCA_SUCCESS;
}
ATCA_STATUS atcab_aes_gcm_init_rand(atca_aes_gcm_ctx_t* ctx, uint16_t key_id, uint8_t key_block, size_t rand_size,
const uint8_t* free_field, size_t free_field_size, uint8_t* iv)
{
ATCA_STATUS status;
uint8_t random[RANDOM_NUM_SIZE];
if (ctx == NULL || iv == NULL || (free_field_size > 0 && free_field == NULL))
{
RETURN(ATCA_BAD_PARAM, "Null pointer");
}
if (rand_size < 12 || rand_size > RANDOM_NUM_SIZE)
{
RETURN(ATCA_BAD_PARAM, "Bad rand_size");
}
if (ATCA_SUCCESS != (status = atcab_random(random)))
{
RETURN(status, "GCM init rand - Random Generation failed");
}
memcpy(iv, random, rand_size);
memcpy(&iv[rand_size], free_field, free_field_size);
if (ATCA_SUCCESS != (status = atcab_aes_gcm_init(ctx, key_id, key_block, iv, rand_size + free_field_size)))
{
RETURN(status, "GCM init failed");
}
return ATCA_SUCCESS;
}
ATCA_STATUS atcab_aes_gcm_aad_update(atca_aes_gcm_ctx_t* ctx, const uint8_t* aad, uint32_t aad_size)
{
ATCA_STATUS status;
uint32_t block_count;
uint32_t rem_size;
uint32_t copy_size;
if (ctx == NULL || (aad_size > 0 && aad == NULL))
{
RETURN(ATCA_BAD_PARAM, "Null pointer");
}
if (aad_size == 0)
{
return ATCA_SUCCESS;
}
rem_size = AES_DATA_SIZE - (uint32_t)ctx->partial_aad_size;
copy_size = aad_size > rem_size ? rem_size : aad_size;
memcpy(&ctx->partial_aad[ctx->partial_aad_size], aad, copy_size);
if (ctx->partial_aad_size + aad_size < AES_DATA_SIZE)
{
ctx->partial_aad_size += aad_size;
return ATCA_SUCCESS;
}
if (ATCA_SUCCESS != (status = atcab_aes_ghash(ctx->h, ctx->partial_aad, AES_DATA_SIZE, ctx->y)))
{
RETURN(status, "GCM - S (AAD) failed");
}
aad_size -= copy_size; block_count = aad_size / AES_DATA_SIZE;
if (ATCA_SUCCESS != (status = atcab_aes_ghash(ctx->h, &aad[copy_size], block_count * AES_DATA_SIZE, ctx->y)))
{
RETURN(status, "GCM - S (AAD) failed");
}
ctx->aad_size += (block_count + 1) * AES_DATA_SIZE;
ctx->partial_aad_size = aad_size % AES_DATA_SIZE;
memcpy(ctx->partial_aad, &aad[copy_size + block_count * AES_DATA_SIZE], ctx->partial_aad_size);
return ATCA_SUCCESS;
}
static ATCA_STATUS atcab_aes_gcm_update(atca_aes_gcm_ctx_t* ctx, const uint8_t* input, uint32_t input_size,
uint8_t* output, bool is_encrypt)
{
ATCA_STATUS status;
uint32_t data_idx;
uint32_t i;
if (ctx == NULL || (input_size > 0 && (input == NULL || output == NULL)))
{
RETURN(ATCA_BAD_PARAM, "Null pointer");
}
if (ctx->partial_aad_size > 0)
{
if (ATCA_SUCCESS != (status = atcab_aes_ghash(ctx->h, ctx->partial_aad, ctx->partial_aad_size, ctx->y)))
{
RETURN(status, "GCM - S (AAD partial) failed");
}
ctx->aad_size += ctx->partial_aad_size;
ctx->partial_aad_size = 0;
}
if (input_size == 0)
{
return ATCA_SUCCESS;
}
data_idx = 0;
while (data_idx < input_size)
{
if (ctx->data_size % AES_DATA_SIZE == 0)
{
if (ATCA_SUCCESS != (status = atcab_aes_encrypt(ctx->key_id, ctx->key_block, ctx->cb, ctx->enc_cb)))
{
RETURN(status, "AES GCM CB encrypt failed");
}
if (ATCA_SUCCESS != (status = atcab_aes_gcm_increment(ctx->cb, 4)))
{
RETURN(status, "AES GCM counter increment failed");
}
}
for (i = ctx->data_size % AES_DATA_SIZE; i < AES_DATA_SIZE && data_idx < input_size; i++, data_idx++)
{
output[data_idx] = input[data_idx] ^ ctx->enc_cb[i];
ctx->ciphertext_block[i] = is_encrypt ? output[data_idx] : input[data_idx];
ctx->data_size += 1;
}
if (ctx->data_size % AES_DATA_SIZE == 0)
{
if (ATCA_SUCCESS != (status = atcab_aes_ghash(ctx->h, ctx->ciphertext_block, AES_DATA_SIZE, ctx->y)))
{
RETURN(status, "GCM - S (data) failed");
}
}
}
return ATCA_SUCCESS;
}
ATCA_STATUS atcab_aes_gcm_encrypt_update(atca_aes_gcm_ctx_t* ctx, const uint8_t* plaintext, uint32_t plaintext_size, uint8_t* ciphertext)
{
return atcab_aes_gcm_update(ctx, plaintext, plaintext_size, ciphertext, true);
}
static ATCA_STATUS atcab_aes_gcm_calc_auth_tag(atca_aes_gcm_ctx_t* ctx, uint8_t* tag, size_t tag_size)
{
ATCA_STATUS status;
size_t xor_index;
uint8_t temp_data[AES_DATA_SIZE];
uint64_t length;
if (ctx == NULL || tag == NULL)
{
RETURN(ATCA_BAD_PARAM, "Null pointer");
}
if (tag_size < 12 || tag_size > 16)
{
RETURN(ATCA_BAD_PARAM, "Invalid tag size");
}
memset(temp_data, 0, AES_DATA_SIZE);
length = ATCA_UINT64_HOST_TO_BE(((uint64_t)ctx->aad_size) * 8);
memcpy(&temp_data[0], &length, sizeof(length));
length = ATCA_UINT64_HOST_TO_BE(((uint64_t)ctx->data_size) * 8);
memcpy(&temp_data[8], &length, sizeof(length));
if (ATCA_SUCCESS != (status = atcab_aes_ghash(ctx->h, temp_data, AES_DATA_SIZE, ctx->y)))
{
RETURN(status, "GCM - S (lengths) failed");
}
if (ATCA_SUCCESS != (status = atcab_aes_encrypt(ctx->key_id, ctx->key_block, ctx->j0, temp_data)))
{
RETURN(status, "Tag GCTR Encryption failed");
}
for (xor_index = 0; xor_index < tag_size; xor_index++)
{
tag[xor_index] = temp_data[xor_index] ^ ctx->y[xor_index];
}
return ATCA_SUCCESS;
}
ATCA_STATUS atcab_aes_gcm_encrypt_finish(atca_aes_gcm_ctx_t* ctx, uint8_t* tag, size_t tag_size)
{
ATCA_STATUS status;
if (ctx == NULL)
{
RETURN(ATCA_BAD_PARAM, "Null pointer");
}
if (ctx->partial_aad_size > 0)
{
if (ATCA_SUCCESS != (status = atcab_aes_gcm_update(ctx, NULL, 0, NULL, true)))
{
RETURN(status, "GCM - S (AAD) failed");
}
}
if (ATCA_SUCCESS != (status = atcab_aes_ghash(ctx->h, ctx->ciphertext_block, ctx->data_size % AES_DATA_SIZE, ctx->y)))
{
RETURN(status, "GCM - S (C - encrypt update) failed");
}
if (ATCA_SUCCESS != (status = atcab_aes_gcm_calc_auth_tag(ctx, tag, tag_size)))
{
RETURN(status, "GCM encrypt tag calculation failed");
}
return ATCA_SUCCESS;
}
ATCA_STATUS atcab_aes_gcm_decrypt_update(atca_aes_gcm_ctx_t* ctx, const uint8_t* ciphertext, uint32_t ciphertext_size, uint8_t* plaintext)
{
return atcab_aes_gcm_update(ctx, ciphertext, ciphertext_size, plaintext, false);
}
ATCA_STATUS atcab_aes_gcm_decrypt_finish(atca_aes_gcm_ctx_t* ctx, const uint8_t* tag, size_t tag_size, bool* is_verified)
{
ATCA_STATUS status;
uint8_t calc_tag[AES_DATA_SIZE];
if (ctx == NULL || is_verified == NULL)
{
RETURN(ATCA_BAD_PARAM, "Null pointer");
}
*is_verified = false;
if (ctx->partial_aad_size > 0)
{
if (ATCA_SUCCESS != (status = atcab_aes_gcm_update(ctx, NULL, 0, NULL, false)))
{
RETURN(status, "GCM - S (AAD) failed");
}
}
if (ATCA_SUCCESS != (status = atcab_aes_ghash(ctx->h, ctx->ciphertext_block, ctx->data_size % AES_DATA_SIZE, ctx->y)))
{
RETURN(status, "GCM - S (C - encrypt update) failed");
}
if (ATCA_SUCCESS != (status = atcab_aes_gcm_calc_auth_tag(ctx, calc_tag, tag_size)))
{
RETURN(status, "Tag calculation failed");
}
*is_verified = (memcmp(calc_tag, tag, tag_size) == 0);
return ATCA_SUCCESS;
}