#include "rivide/crypto/aes_gcm.h"
#include "rivide/crypto/aes.h"
#include "rivide/crypto/ghash.h"
#include "rivide/rivide_config.h"
#include "rivide/utils/mem.h"
static inline uint32_t load32_be(const uint8_t *p) {
return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | (uint32_t)p[3];
}
static inline void store32_be(uint8_t *p, uint32_t v) {
p[0] = (uint8_t)(v >> 24);
p[1] = (uint8_t)(v >> 16);
p[2] = (uint8_t)(v >> 8);
p[3] = (uint8_t)v;
}
static inline void store64_be(uint8_t *p, uint64_t v) {
int i;
for (i = 7; i >= 0; i--) {
p[i] = (uint8_t)(v & 0xFF);
v >>= 8;
}
}
static inline void gcm_inc_counter(uint8_t counter[16]) {
uint32_t c = load32_be(counter + 12);
c++;
store32_be(counter + 12, c);
}
static inline int buffers_overlap_partial(const void *a, const void *b, size_t len) {
if (len == 0 || a == b || !a || !b) {
return 0;
}
const uintptr_t p1 = (uintptr_t)a;
const uintptr_t p2 = (uintptr_t)b;
return (p1 < p2 + len && p2 < p1 + len);
}
static inline int buffer_ranges_overlap(const void *a, size_t len_a, const void *b, size_t len_b) {
if (len_a == 0 || len_b == 0 || !a || !b) {
return 0;
}
const uintptr_t p1 = (uintptr_t)a;
const uintptr_t p2 = (uintptr_t)b;
return (p1 < p2 + len_b && p2 < p1 + len_a);
}
rivide_status_t rivide_aes_gcm_encrypt(const rivide_aes_key_t *key, const uint8_t *iv,
const uint8_t *aad, size_t aad_len, const uint8_t *pt,
size_t pt_len, uint8_t *ct, uint8_t *tag) {
uint8_t h[16] = {0};
uint8_t j0[16];
uint8_t counter[16];
uint8_t enc_block[16];
uint8_t ghash_tag[16] = {0};
uint8_t len_block[16];
size_t i;
if (!key || !iv || !tag) {
return RIVIDE_ERR_NULL_PTR;
}
if (pt_len > 0 && (!pt || !ct)) {
return RIVIDE_ERR_NULL_PTR;
}
if (aad_len > 0 && !aad) {
return RIVIDE_ERR_NULL_PTR;
}
if ((uint64_t)pt_len > RIVIDE_GCM_MAX_PAYLOAD_BYTES ||
(uint64_t)aad_len > RIVIDE_GCM_MAX_AAD_BYTES) {
return RIVIDE_ERR_INVALID_PARAM;
}
if (buffers_overlap_partial(pt, ct, pt_len)) {
return RIVIDE_ERR_INVALID_PARAM;
}
if (pt_len > 0 && buffer_ranges_overlap(tag, RIVIDE_GCM_TAG_BYTES, pt, pt_len) && tag != pt) {
return RIVIDE_ERR_INVALID_PARAM;
}
if (pt_len > 0 && buffer_ranges_overlap(tag, RIVIDE_GCM_TAG_BYTES, ct, pt_len) && tag != ct) {
return RIVIDE_ERR_INVALID_PARAM;
}
if (aad_len > 0 && buffer_ranges_overlap(tag, RIVIDE_GCM_TAG_BYTES, aad, aad_len)) {
return RIVIDE_ERR_INVALID_PARAM;
}
rivide_aes_encrypt_block(key, h, h);
for (i = 0; i < 12; i++) {
j0[i] = iv[i];
}
j0[12] = 0x00;
j0[13] = 0x00;
j0[14] = 0x00;
j0[15] = 0x01;
for (i = 0; i < 16; i++) {
counter[i] = j0[i];
}
{
size_t remaining = pt_len;
size_t offset = 0;
while (remaining > 0) {
size_t chunk = (remaining >= 16) ? 16 : remaining;
size_t k;
gcm_inc_counter(counter);
rivide_aes_encrypt_block(key, counter, enc_block);
for (k = 0; k < chunk; k++) {
ct[offset + k] = (uint8_t)(pt[offset + k] ^ enc_block[k]);
}
offset += chunk;
remaining -= chunk;
}
}
if (aad_len > 0 && aad) {
rivide_ghash_update(h, aad, aad_len, ghash_tag);
}
if (pt_len > 0) {
rivide_ghash_update(h, ct, pt_len, ghash_tag);
}
store64_be(len_block, (uint64_t)aad_len * 8);
store64_be(len_block + 8, (uint64_t)pt_len * 8);
rivide_ghash_update(h, len_block, 16, ghash_tag);
rivide_aes_encrypt_block(key, j0, enc_block);
for (i = 0; i < 16; i++) {
tag[i] = (uint8_t)(ghash_tag[i] ^ enc_block[i]);
}
rivide_cleanse(h, sizeof(h));
rivide_cleanse(j0, sizeof(j0));
rivide_cleanse(counter, sizeof(counter));
rivide_cleanse(enc_block, sizeof(enc_block));
rivide_cleanse(ghash_tag, sizeof(ghash_tag));
rivide_cleanse(len_block, sizeof(len_block));
return RIVIDE_SUCCESS;
}
rivide_status_t rivide_aes_gcm_decrypt(const rivide_aes_key_t *key, const uint8_t *iv,
const uint8_t *aad, size_t aad_len, const uint8_t *ct,
size_t ct_len, const uint8_t *tag, uint8_t *pt) {
uint8_t h[16] = {0};
uint8_t j0[16];
uint8_t counter[16];
uint8_t enc_block[16];
uint8_t ghash_tag[16] = {0};
uint8_t len_block[16];
uint8_t computed_tag[16];
size_t i;
if (!key || !iv || !tag) {
return RIVIDE_ERR_NULL_PTR;
}
if (ct_len > 0 && (!ct || !pt)) {
return RIVIDE_ERR_NULL_PTR;
}
if (aad_len > 0 && !aad) {
return RIVIDE_ERR_NULL_PTR;
}
if ((uint64_t)ct_len > RIVIDE_GCM_MAX_PAYLOAD_BYTES ||
(uint64_t)aad_len > RIVIDE_GCM_MAX_AAD_BYTES) {
return RIVIDE_ERR_INVALID_PARAM;
}
if (buffers_overlap_partial(ct, pt, ct_len)) {
return RIVIDE_ERR_INVALID_PARAM;
}
if (ct_len > 0 && buffer_ranges_overlap(tag, RIVIDE_GCM_TAG_BYTES, pt, ct_len) && tag != pt) {
return RIVIDE_ERR_INVALID_PARAM;
}
if (ct_len > 0 && buffer_ranges_overlap(tag, RIVIDE_GCM_TAG_BYTES, ct, ct_len) && tag != ct) {
return RIVIDE_ERR_INVALID_PARAM;
}
if (aad_len > 0 && buffer_ranges_overlap(tag, RIVIDE_GCM_TAG_BYTES, aad, aad_len)) {
return RIVIDE_ERR_INVALID_PARAM;
}
rivide_aes_encrypt_block(key, h, h);
for (i = 0; i < 12; i++) {
j0[i] = iv[i];
}
j0[12] = 0x00;
j0[13] = 0x00;
j0[14] = 0x00;
j0[15] = 0x01;
if (aad_len > 0 && aad) {
rivide_ghash_update(h, aad, aad_len, ghash_tag);
}
if (ct_len > 0) {
rivide_ghash_update(h, ct, ct_len, ghash_tag);
}
store64_be(len_block, (uint64_t)aad_len * 8);
store64_be(len_block + 8, (uint64_t)ct_len * 8);
rivide_ghash_update(h, len_block, 16, ghash_tag);
rivide_aes_encrypt_block(key, j0, enc_block);
for (i = 0; i < 16; i++) {
computed_tag[i] = (uint8_t)(ghash_tag[i] ^ enc_block[i]);
}
for (i = 0; i < 16; i++) {
counter[i] = j0[i];
}
{
size_t remaining = ct_len;
size_t offset = 0;
while (remaining > 0) {
size_t chunk = (remaining >= 16) ? 16 : remaining;
size_t k;
gcm_inc_counter(counter);
rivide_aes_encrypt_block(key, counter, enc_block);
for (k = 0; k < chunk; k++) {
pt[offset + k] = (uint8_t)(ct[offset + k] ^ enc_block[k]);
}
offset += chunk;
remaining -= chunk;
}
}
rivide_cleanse(h, sizeof(h));
rivide_cleanse(j0, sizeof(j0));
rivide_cleanse(counter, sizeof(counter));
rivide_cleanse(enc_block, sizeof(enc_block));
rivide_cleanse(ghash_tag, sizeof(ghash_tag));
rivide_cleanse(len_block, sizeof(len_block));
if (rivide_ct_memcmp(computed_tag, tag, 16) != 0) {
if (pt && ct_len > 0) {
rivide_cleanse(pt, ct_len);
}
rivide_cleanse(computed_tag, sizeof(computed_tag));
return RIVIDE_ERR_VERIFICATION_FAILED;
}
rivide_cleanse(computed_tag, sizeof(computed_tag));
return RIVIDE_SUCCESS;
}