wolfssl/wolfcrypt/src/ascon.c

522 lines
15 KiB
C

/* ascon.c
*
* Copyright (C) 2006-2025 wolfSSL Inc.
*
* This file is part of wolfSSL.
*
* wolfSSL is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* wolfSSL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
*/
#include <wolfssl/wolfcrypt/libwolfssl_sources.h>
#ifdef HAVE_ASCON
#include <wolfssl/wolfcrypt/ascon.h>
#ifdef NO_INLINE
#include <wolfssl/wolfcrypt/misc.h>
#else
#define WOLFSSL_MISC_INCLUDED
#include <wolfcrypt/src/misc.c>
#endif
/*
* Implementation of the ASCON AEAD and HASH algorithms. Based on the NIST
* Initial Public Draft "NIST SP 800-232 ipd" and reference implementation found
* at https://github.com/ascon/ascon-c.
*/
/*
* TODO
* - Add support for big-endian systems
* - Add support for 32-bit and smaller systems */
#ifndef WORD64_AVAILABLE
#error "Ascon implementation requires a 64-bit word"
#endif
/* Data block size in bytes */
#define ASCON_HASH256_RATE 8
#define ASCON_HASH256_ROUNDS 12
#define ASCON_HASH256_IV 0x0000080100CC0002ULL
#define ASCON_AEAD128_ROUNDS_PA 12
#define ASCON_AEAD128_ROUNDS_PB 8
#define ASCON_AEAD128_IV 0x00001000808C0001ULL
#define ASCON_AEAD128_RATE 16
#define MAX_ROUNDS 12
#ifndef WOLFSSL_ASCON_UNROLL
/* Table 5 */
static const byte round_constants[MAX_ROUNDS] = {
0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b
};
static byte start_index(byte rounds)
{
switch (rounds) {
case 8:
return 4;
case 12:
return 0;
default:
WOLFSSL_MSG("Something went wrong in wolfCrypt logic. Wrong ASCON "
"rounds value.");
return MAX_ROUNDS;
}
}
static WC_INLINE void ascon_round(AsconState* a, byte round)
{
word64 tmp0, tmp1, tmp2, tmp3, tmp4;
/* 3.2 Constant-Addition Layer */
a->s64[2] ^= round_constants[round];
/* 3.3 Substitution Layer */
a->s64[0] ^= a->s64[4];
a->s64[4] ^= a->s64[3];
a->s64[2] ^= a->s64[1];
tmp0 = a->s64[0] ^ (~a->s64[1] & a->s64[2]);
tmp2 = a->s64[2] ^ (~a->s64[3] & a->s64[4]);
tmp4 = a->s64[4] ^ (~a->s64[0] & a->s64[1]);
tmp1 = a->s64[1] ^ (~a->s64[2] & a->s64[3]);
tmp3 = a->s64[3] ^ (~a->s64[4] & a->s64[0]);
tmp1 ^= tmp0;
tmp3 ^= tmp2;
tmp0 ^= tmp4;
tmp2 = ~tmp2;
/* 3.4 Linear Diffusion Layer */
a->s64[4] = tmp4 ^ rotrFixed64(tmp4, 7) ^ rotrFixed64(tmp4, 41);
a->s64[1] = tmp1 ^ rotrFixed64(tmp1, 61) ^ rotrFixed64(tmp1, 39);
a->s64[3] = tmp3 ^ rotrFixed64(tmp3, 10) ^ rotrFixed64(tmp3, 17);
a->s64[0] = tmp0 ^ rotrFixed64(tmp0, 19) ^ rotrFixed64(tmp0, 28);
a->s64[2] = tmp2 ^ rotrFixed64(tmp2, 1) ^ rotrFixed64(tmp2, 6);
}
static void permutation(AsconState* a, byte rounds)
{
byte i = start_index(rounds);
for (; i < MAX_ROUNDS; i++) {
ascon_round(a, i);
}
}
#else
#define p(a, c) do { \
word64 tmp0, tmp1, tmp2, tmp3, tmp4; \
/* 3.2 Constant-Addition Layer */ \
(a)->s64[2] ^= c; \
/* 3.3 Substitution Layer */ \
(a)->s64[0] ^= (a)->s64[4]; \
(a)->s64[4] ^= (a)->s64[3]; \
(a)->s64[2] ^= (a)->s64[1]; \
tmp0 = (a)->s64[0] ^ (~(a)->s64[1] & (a)->s64[2]); \
tmp2 = (a)->s64[2] ^ (~(a)->s64[3] & (a)->s64[4]); \
tmp4 = (a)->s64[4] ^ (~(a)->s64[0] & (a)->s64[1]); \
tmp1 = (a)->s64[1] ^ (~(a)->s64[2] & (a)->s64[3]); \
tmp3 = (a)->s64[3] ^ (~(a)->s64[4] & (a)->s64[0]); \
tmp1 ^= tmp0; \
tmp3 ^= tmp2; \
tmp0 ^= tmp4; \
tmp2 = ~tmp2; \
/* 3.4 Linear Diffusion Layer */ \
(a)->s64[4] = tmp4 ^ rotrFixed64(tmp4, 7) ^ rotrFixed64(tmp4, 41); \
(a)->s64[1] = tmp1 ^ rotrFixed64(tmp1, 61) ^ rotrFixed64(tmp1, 39); \
(a)->s64[3] = tmp3 ^ rotrFixed64(tmp3, 10) ^ rotrFixed64(tmp3, 17); \
(a)->s64[0] = tmp0 ^ rotrFixed64(tmp0, 19) ^ rotrFixed64(tmp0, 28); \
(a)->s64[2] = tmp2 ^ rotrFixed64(tmp2, 1) ^ rotrFixed64(tmp2, 6); \
} while (0)
#define p8(a) \
p(a, 0xb4); \
p(a, 0xa5); \
p(a, 0x96); \
p(a, 0x87); \
p(a, 0x78); \
p(a, 0x69); \
p(a, 0x5a); \
p(a, 0x4b)
#define p12(a) \
p(a, 0xf0); \
p(a, 0xe1); \
p(a, 0xd2); \
p(a, 0xc3); \
p8(a)
/* Needed layer to evaluate the macro values */
#define _permutation(a, rounds) \
p ## rounds(a)
#define permutation(a, rounds) \
_permutation(a, rounds)
#endif
/* AsconHash API */
wc_AsconHash256* wc_AsconHash256_New(void)
{
wc_AsconHash256* ret = (wc_AsconHash256*)XMALLOC(sizeof(wc_AsconHash256),
NULL, DYNAMIC_TYPE_ASCON);
if (ret != NULL) {
if (wc_AsconHash256_Init(ret) != 0) {
wc_AsconHash256_Free(ret);
ret = NULL;
}
}
return ret;
}
void wc_AsconHash256_Free(wc_AsconHash256* a)
{
if (a != NULL) {
wc_AsconHash256_Clear(a);
XFREE(a, NULL, DYNAMIC_TYPE_ASCON);
}
}
int wc_AsconHash256_Init(wc_AsconHash256* a)
{
if (a == NULL)
return BAD_FUNC_ARG;
XMEMSET(a, 0, sizeof(*a));
a->state.s64[0] = ASCON_HASH256_IV;
permutation(&a->state, ASCON_HASH256_ROUNDS);
return 0;
}
void wc_AsconHash256_Clear(wc_AsconHash256* a)
{
if (a != NULL) {
ForceZero(a, sizeof(*a));
}
}
int wc_AsconHash256_Update(wc_AsconHash256* a, const byte* data, word32 dataSz)
{
if (a == NULL || (data == NULL && dataSz != 0))
return BAD_FUNC_ARG;
if (dataSz == 0)
return 0;
/* Process leftover block */
if (a->lastBlkSz != 0) {
word32 toProcess = min(ASCON_HASH256_RATE - a->lastBlkSz, dataSz);
xorbuf(a->state.s8 + a->lastBlkSz, data, toProcess);
data += toProcess;
dataSz -= toProcess;
a->lastBlkSz += toProcess;
if (a->lastBlkSz < ASCON_HASH256_RATE)
return 0;
permutation(&a->state, ASCON_HASH256_ROUNDS);
/* Reset the counter */
a->lastBlkSz = 0;
}
while (dataSz >= ASCON_HASH256_RATE) {
/* Read in input as little endian numbers */
xorbuf(a->state.s64, data, ASCON_HASH256_RATE);
permutation(&a->state, ASCON_HASH256_ROUNDS);
data += ASCON_HASH256_RATE;
dataSz -= ASCON_HASH256_RATE;
}
xorbuf(a->state.s64, data, dataSz);
a->lastBlkSz = dataSz;
return 0;
}
int wc_AsconHash256_Final(wc_AsconHash256* a, byte* hash)
{
byte i;
if (a == NULL || hash == NULL)
return BAD_FUNC_ARG;
/* Process last block */
a->state.s8[a->lastBlkSz] ^= 1;
for (i = 0; i < ASCON_HASH256_SZ; i += ASCON_HASH256_RATE) {
permutation(&a->state, ASCON_HASH256_ROUNDS);
XMEMCPY(hash, a->state.s64, ASCON_HASH256_RATE);
hash += ASCON_HASH256_RATE;
}
/* Clear state as soon as possible */
wc_AsconHash256_Clear(a);
return 0;
}
/* AsconAEAD API */
wc_AsconAEAD128* wc_AsconAEAD128_New(void)
{
wc_AsconAEAD128 *ret = (wc_AsconAEAD128*) XMALLOC(sizeof(wc_AsconAEAD128),
NULL, DYNAMIC_TYPE_ASCON);
if (ret != NULL) {
if (wc_AsconAEAD128_Init(ret) != 0) {
wc_AsconAEAD128_Free(ret);
ret = NULL;
}
}
return ret;
}
void wc_AsconAEAD128_Free(wc_AsconAEAD128 *a)
{
if (a != NULL) {
wc_AsconAEAD128_Clear(a);
XFREE(a, NULL, DYNAMIC_TYPE_ASCON);
}
}
int wc_AsconAEAD128_Init(wc_AsconAEAD128 *a)
{
if (a == NULL)
return BAD_FUNC_ARG;
XMEMSET(a, 0, sizeof(*a));
a->state.s64[0] = ASCON_AEAD128_IV;
return 0;
}
void wc_AsconAEAD128_Clear(wc_AsconAEAD128 *a)
{
if (a != NULL) {
ForceZero(a, sizeof(*a));
}
}
int wc_AsconAEAD128_SetKey(wc_AsconAEAD128* a, const byte* key)
{
if (a == NULL || key == NULL)
return BAD_FUNC_ARG;
if (a->keySet)
return BAD_STATE_E;
XMEMCPY(a->key, key, ASCON_AEAD128_KEY_SZ);
a->state.s64[1] = a->key[0];
a->state.s64[2] = a->key[1];
a->keySet = 1;
return 0;
}
int wc_AsconAEAD128_SetNonce(wc_AsconAEAD128* a, const byte* nonce)
{
if (a == NULL || nonce == NULL)
return BAD_FUNC_ARG;
if (a->nonceSet)
return BAD_STATE_E;
XMEMCPY(&a->state.s64[3], nonce, ASCON_AEAD128_NONCE_SZ);
a->nonceSet = 1;
return 0;
}
int wc_AsconAEAD128_SetAD(wc_AsconAEAD128* a, const byte* ad,
word32 adSz)
{
if (a == NULL || (ad == NULL && adSz > 0))
return BAD_FUNC_ARG;
if (!a->keySet || !a->nonceSet) /* key and nonce must be set before */
return BAD_STATE_E;
permutation(&a->state, ASCON_AEAD128_ROUNDS_PA);
a->state.s64[3] ^= a->key[0];
a->state.s64[4] ^= a->key[1];
if (adSz > 0) {
while (adSz >= ASCON_AEAD128_RATE) {
xorbuf(a->state.s64, ad, ASCON_AEAD128_RATE);
permutation(&a->state, ASCON_AEAD128_ROUNDS_PB);
ad += ASCON_AEAD128_RATE;
adSz -= ASCON_AEAD128_RATE;
}
xorbuf(a->state.s64, ad, adSz);
/* Pad the last block */
a->state.s8[adSz] ^= 1;
permutation(&a->state, ASCON_AEAD128_ROUNDS_PB);
}
a->state.s64[4] ^= 1ULL << 63;
a->adSet = 1;
return 0;
}
int wc_AsconAEAD128_EncryptUpdate(wc_AsconAEAD128* a, byte* out,
const byte* in, word32 inSz)
{
if (a == NULL || (in == NULL && inSz > 0))
return BAD_FUNC_ARG;
if (!a->keySet || !a->nonceSet || !a->adSet)
return BAD_STATE_E;
if (a->op == ASCON_AEAD128_NOTSET)
a->op = ASCON_AEAD128_ENCRYPT;
else if (a->op != ASCON_AEAD128_ENCRYPT)
return BAD_STATE_E;
/* Process leftover from last block */
if (a->lastBlkSz != 0) {
word32 toProcess = min(ASCON_AEAD128_RATE - a->lastBlkSz, inSz);
xorbuf(&a->state.s8[a->lastBlkSz], in, toProcess);
XMEMCPY(out, &a->state.s8[a->lastBlkSz], toProcess);
a->lastBlkSz += toProcess;
in += toProcess;
out += toProcess;
inSz -= toProcess;
if (a->lastBlkSz < ASCON_AEAD128_RATE)
return 0;
permutation(&a->state, ASCON_AEAD128_ROUNDS_PB);
a->lastBlkSz = 0;
}
while (inSz >= ASCON_AEAD128_RATE) {
xorbuf(a->state.s64, in, ASCON_AEAD128_RATE);
XMEMCPY(out, a->state.s64, ASCON_AEAD128_RATE);
permutation(&a->state, ASCON_AEAD128_ROUNDS_PB);
in += ASCON_AEAD128_RATE;
out += ASCON_AEAD128_RATE;
inSz -= ASCON_AEAD128_RATE;
}
/* Store leftover */
xorbuf(a->state.s64, in, inSz);
XMEMCPY(out, a->state.s64, inSz);
a->lastBlkSz = inSz;
return 0;
}
int wc_AsconAEAD128_EncryptFinal(wc_AsconAEAD128* a, byte* tag)
{
if (a == NULL || tag == NULL)
return BAD_FUNC_ARG;
if (!a->keySet || !a->nonceSet || !a->adSet)
return BAD_STATE_E;
if (a->op != ASCON_AEAD128_ENCRYPT)
return BAD_STATE_E;
/* Process leftover from last block */
a->state.s8[a->lastBlkSz] ^= 1;
a->state.s64[2] ^= a->key[0];
a->state.s64[3] ^= a->key[1];
permutation(&a->state, ASCON_AEAD128_ROUNDS_PA);
a->state.s64[3] ^= a->key[0];
a->state.s64[4] ^= a->key[1];
XMEMCPY(tag, &a->state.s64[3], ASCON_AEAD128_TAG_SZ);
/* Clear state as soon as possible */
wc_AsconAEAD128_Clear(a);
return 0;
}
int wc_AsconAEAD128_DecryptUpdate(wc_AsconAEAD128* a, byte* out,
const byte* in, word32 inSz)
{
if (a == NULL || (in == NULL && inSz > 0))
return BAD_FUNC_ARG;
if (!a->keySet || !a->nonceSet || !a->adSet)
return BAD_STATE_E;
if (a->op == ASCON_AEAD128_NOTSET)
a->op = ASCON_AEAD128_DECRYPT;
else if (a->op != ASCON_AEAD128_DECRYPT)
return BAD_STATE_E;
/* Process leftover block */
if (a->lastBlkSz != 0) {
word32 toProcess = min(ASCON_AEAD128_RATE - a->lastBlkSz, inSz);
xorbufout(out, a->state.s8 + a->lastBlkSz, in, toProcess);
XMEMCPY(a->state.s8 + a->lastBlkSz, in, toProcess);
in += toProcess;
out += toProcess;
inSz -= toProcess;
a->lastBlkSz += toProcess;
if (a->lastBlkSz < ASCON_AEAD128_RATE)
return 0;
permutation(&a->state, ASCON_AEAD128_ROUNDS_PB);
a->lastBlkSz = 0;
}
while (inSz >= ASCON_AEAD128_RATE) {
xorbufout(out, a->state.s64, in, ASCON_AEAD128_RATE);
XMEMCPY(a->state.s64, in, ASCON_AEAD128_RATE);
permutation(&a->state, ASCON_AEAD128_ROUNDS_PB);
in += ASCON_AEAD128_RATE;
out += ASCON_AEAD128_RATE;
inSz -= ASCON_AEAD128_RATE;
}
/* Store leftover */
xorbufout(out, a->state.s64, in, inSz);
XMEMCPY(a->state.s64, in, inSz);
a->lastBlkSz = inSz;
return 0;
}
int wc_AsconAEAD128_DecryptFinal(wc_AsconAEAD128* a, const byte* tag)
{
if (a == NULL || tag == NULL)
return BAD_FUNC_ARG;
if (!a->keySet || !a->nonceSet || !a->adSet)
return BAD_STATE_E;
if (a->op != ASCON_AEAD128_DECRYPT)
return BAD_STATE_E;
/* Pad last block */
a->state.s8[a->lastBlkSz] ^= 1;
a->state.s64[2] ^= a->key[0];
a->state.s64[3] ^= a->key[1];
permutation(&a->state, ASCON_AEAD128_ROUNDS_PA);
a->state.s64[3] ^= a->key[0];
a->state.s64[4] ^= a->key[1];
if (ConstantCompare(tag, (const byte*)&a->state.s64[3],
ASCON_AEAD128_TAG_SZ) != 0)
return ASCON_AUTH_E;
/* Clear state as soon as possible */
wc_AsconAEAD128_Clear(a);
return 0;
}
#endif /* HAVE_ASCON */