Files
wolfssl/wolfcrypt/src/chacha20_poly1305.c
2021-03-11 13:42:46 +07:00

470 lines
14 KiB
C

/* chacha.c
*
* Copyright (C) 2006-2021 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
*/
/*
DESCRIPTION
This library contains implementation for the ChaCha20 stream cipher and
the Poly1305 authenticator, both as as combined-mode,
or Authenticated Encryption with Additional Data (AEAD) algorithm.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <wolfssl/wolfcrypt/settings.h>
#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305)
#include <wolfssl/wolfcrypt/chacha20_poly1305.h>
#include <wolfssl/wolfcrypt/error-crypt.h>
#include <wolfssl/wolfcrypt/logging.h>
#ifdef NO_INLINE
#include <wolfssl/wolfcrypt/misc.h>
#else
#define WOLFSSL_MISC_INCLUDED
#include <wolfcrypt/src/misc.c>
#endif
#define CHACHA20_POLY1305_AEAD_INITIAL_COUNTER 0
int wc_ChaCha20Poly1305_Encrypt(
const byte inKey[CHACHA20_POLY1305_AEAD_KEYSIZE],
const byte inIV[CHACHA20_POLY1305_AEAD_IV_SIZE],
const byte* inAAD, const word32 inAADLen,
const byte* inPlaintext, const word32 inPlaintextLen,
byte* outCiphertext,
byte outAuthTag[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE])
{
int ret;
ChaChaPoly_Aead aead;
/* Validate function arguments */
if (!inKey || !inIV ||
!inPlaintext || !inPlaintextLen ||
!outCiphertext ||
!outAuthTag)
{
return BAD_FUNC_ARG;
}
ret = wc_ChaCha20Poly1305_Init(&aead, inKey, inIV,
CHACHA20_POLY1305_AEAD_ENCRYPT);
if (ret == 0)
ret = wc_ChaCha20Poly1305_UpdateAad(&aead, inAAD, inAADLen);
if (ret == 0)
ret = wc_ChaCha20Poly1305_UpdateData(&aead, inPlaintext, outCiphertext,
inPlaintextLen);
if (ret == 0)
ret = wc_ChaCha20Poly1305_Final(&aead, outAuthTag);
return ret;
}
int wc_ChaCha20Poly1305_Decrypt(
const byte inKey[CHACHA20_POLY1305_AEAD_KEYSIZE],
const byte inIV[CHACHA20_POLY1305_AEAD_IV_SIZE],
const byte* inAAD, const word32 inAADLen,
const byte* inCiphertext, const word32 inCiphertextLen,
const byte inAuthTag[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE],
byte* outPlaintext)
{
int ret;
ChaChaPoly_Aead aead;
byte calculatedAuthTag[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE];
/* Validate function arguments */
if (!inKey || !inIV ||
!inCiphertext || !inCiphertextLen ||
!inAuthTag ||
!outPlaintext)
{
return BAD_FUNC_ARG;
}
XMEMSET(calculatedAuthTag, 0, sizeof(calculatedAuthTag));
ret = wc_ChaCha20Poly1305_Init(&aead, inKey, inIV,
CHACHA20_POLY1305_AEAD_DECRYPT);
if (ret == 0)
ret = wc_ChaCha20Poly1305_UpdateAad(&aead, inAAD, inAADLen);
if (ret == 0)
ret = wc_ChaCha20Poly1305_UpdateData(&aead, inCiphertext, outPlaintext,
inCiphertextLen);
if (ret == 0)
ret = wc_ChaCha20Poly1305_Final(&aead, calculatedAuthTag);
if (ret == 0)
ret = wc_ChaCha20Poly1305_CheckTag(inAuthTag, calculatedAuthTag);
return ret;
}
int wc_ChaCha20Poly1305_CheckTag(
const byte authTag[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE],
const byte authTagChk[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE])
{
int ret = 0;
if (authTag == NULL || authTagChk == NULL) {
return BAD_FUNC_ARG;
}
if (ConstantCompare(authTag, authTagChk,
CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE) != 0) {
ret = MAC_CMP_FAILED_E;
}
return ret;
}
int wc_ChaCha20Poly1305_Init(ChaChaPoly_Aead* aead,
const byte inKey[CHACHA20_POLY1305_AEAD_KEYSIZE],
const byte inIV[CHACHA20_POLY1305_AEAD_IV_SIZE],
int isEncrypt)
{
int ret;
byte authKey[CHACHA20_POLY1305_AEAD_KEYSIZE];
/* check arguments */
if (aead == NULL || inKey == NULL || inIV == NULL) {
return BAD_FUNC_ARG;
}
/* setup aead context */
XMEMSET(aead, 0, sizeof(ChaChaPoly_Aead));
XMEMSET(authKey, 0, sizeof(authKey));
aead->isEncrypt = (byte)isEncrypt;
/* Initialize the ChaCha20 context (key and iv) */
ret = wc_Chacha_SetKey(&aead->chacha, inKey,
CHACHA20_POLY1305_AEAD_KEYSIZE);
if (ret == 0) {
ret = wc_Chacha_SetIV(&aead->chacha, inIV,
CHACHA20_POLY1305_AEAD_INITIAL_COUNTER);
}
/* Create the Poly1305 key */
if (ret == 0) {
ret = wc_Chacha_Process(&aead->chacha, authKey, authKey,
CHACHA20_POLY1305_AEAD_KEYSIZE);
}
/* Initialize Poly1305 context */
if (ret == 0) {
ret = wc_Poly1305SetKey(&aead->poly, authKey,
CHACHA20_POLY1305_AEAD_KEYSIZE);
}
/* advance counter by 1 after creating Poly1305 key */
if (ret == 0) {
ret = wc_Chacha_SetIV(&aead->chacha, inIV,
CHACHA20_POLY1305_AEAD_INITIAL_COUNTER + 1);
}
if (ret == 0) {
aead->state = CHACHA20_POLY1305_STATE_READY;
}
return ret;
}
/* optional additional authentication data */
int wc_ChaCha20Poly1305_UpdateAad(ChaChaPoly_Aead* aead,
const byte* inAAD, word32 inAADLen)
{
int ret = 0;
if (aead == NULL || (inAAD == NULL && inAADLen > 0)) {
return BAD_FUNC_ARG;
}
if (aead->state != CHACHA20_POLY1305_STATE_READY &&
aead->state != CHACHA20_POLY1305_STATE_AAD) {
return BAD_STATE_E;
}
if (inAADLen > CHACHA20_POLY1305_MAX - aead->aadLen)
return CHACHA_POLY_OVERFLOW;
if (inAAD && inAADLen > 0) {
ret = wc_Poly1305Update(&aead->poly, inAAD, inAADLen);
if (ret == 0) {
aead->aadLen += inAADLen;
aead->state = CHACHA20_POLY1305_STATE_AAD;
}
}
return ret;
}
/* inData and outData can be same pointer (inline) */
int wc_ChaCha20Poly1305_UpdateData(ChaChaPoly_Aead* aead,
const byte* inData, byte* outData, word32 dataLen)
{
int ret = 0;
if (aead == NULL || inData == NULL || outData == NULL) {
return BAD_FUNC_ARG;
}
if (aead->state != CHACHA20_POLY1305_STATE_READY &&
aead->state != CHACHA20_POLY1305_STATE_AAD &&
aead->state != CHACHA20_POLY1305_STATE_DATA) {
return BAD_STATE_E;
}
if (dataLen > CHACHA20_POLY1305_MAX - aead->dataLen)
return CHACHA_POLY_OVERFLOW;
/* Pad the AAD */
if (aead->state == CHACHA20_POLY1305_STATE_AAD) {
ret = wc_Poly1305_Pad(&aead->poly, aead->aadLen);
}
/* advance state */
aead->state = CHACHA20_POLY1305_STATE_DATA;
/* Perform ChaCha20 encrypt/decrypt and Poly1305 auth calc */
if (ret == 0) {
if (aead->isEncrypt) {
ret = wc_Chacha_Process(&aead->chacha, outData, inData, dataLen);
if (ret == 0)
ret = wc_Poly1305Update(&aead->poly, outData, dataLen);
}
else {
ret = wc_Poly1305Update(&aead->poly, inData, dataLen);
if (ret == 0)
ret = wc_Chacha_Process(&aead->chacha, outData, inData, dataLen);
}
}
if (ret == 0) {
aead->dataLen += dataLen;
}
return ret;
}
int wc_ChaCha20Poly1305_Final(ChaChaPoly_Aead* aead,
byte outAuthTag[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE])
{
int ret = 0;
if (aead == NULL || outAuthTag == NULL) {
return BAD_FUNC_ARG;
}
if (aead->state != CHACHA20_POLY1305_STATE_AAD &&
aead->state != CHACHA20_POLY1305_STATE_DATA) {
return BAD_STATE_E;
}
/* Pad the AAD - Make sure it is done */
if (aead->state == CHACHA20_POLY1305_STATE_AAD) {
ret = wc_Poly1305_Pad(&aead->poly, aead->aadLen);
}
/* Pad the plaintext/ciphertext to 16 bytes */
if (ret == 0) {
ret = wc_Poly1305_Pad(&aead->poly, aead->dataLen);
}
/* Add the aad length and plaintext/ciphertext length */
if (ret == 0) {
ret = wc_Poly1305_EncodeSizes(&aead->poly, aead->aadLen,
aead->dataLen);
}
/* Finalize the auth tag */
if (ret == 0) {
ret = wc_Poly1305Final(&aead->poly, outAuthTag);
}
/* reset and cleanup sensitive context */
ForceZero(aead, sizeof(ChaChaPoly_Aead));
return ret;
}
#ifdef HAVE_XCHACHA
int wc_XChaCha20Poly1305_Init(
ChaChaPoly_Aead *aead,
const byte *ad, word32 ad_len,
const byte *nonce, word32 nonce_len,
const byte *key, word32 key_len,
int isEncrypt)
{
byte authKey[CHACHA20_POLY1305_AEAD_KEYSIZE];
int ret;
if ((ad == NULL) || (nonce == NULL) || (key == NULL))
return BAD_FUNC_ARG;
if ((key_len != CHACHA20_POLY1305_AEAD_KEYSIZE) ||
(nonce_len != XCHACHA20_POLY1305_AEAD_NONCE_SIZE))
return BAD_FUNC_ARG;
if ((ret = wc_XChacha_SetKey(&aead->chacha,
key, key_len,
nonce, nonce_len,
0 /* counter */)) < 0)
return ret;
XMEMSET(authKey, 0, sizeof authKey);
/* Create the Poly1305 key */
if ((ret = wc_Chacha_Process(&aead->chacha, authKey, authKey,
(word32)sizeof authKey)) < 0)
return ret;
/* advance to start of the next ChaCha block. */
wc_Chacha_purge_current_block(&aead->chacha);
/* Initialize Poly1305 context */
if ((ret = wc_Poly1305SetKey(&aead->poly, authKey,
(word32)sizeof authKey)) < 0)
return ret;
if ((ret = wc_Poly1305Update(&aead->poly, ad, (word32)ad_len)) < 0)
return ret;
if ((ret = wc_Poly1305_Pad(&aead->poly, (word32)ad_len)) < 0)
return ret;
aead->isEncrypt = (byte)isEncrypt;
aead->state = CHACHA20_POLY1305_STATE_AAD;
return 0;
}
static WC_INLINE int wc_XChaCha20Poly1305_crypt_oneshot(
byte *dst, const size_t dst_space,
const byte *src, const size_t src_len,
const byte *ad, const size_t ad_len,
const byte *nonce, const size_t nonce_len,
const byte *key, const size_t key_len,
int isEncrypt)
{
int ret;
ssize_t dst_len = isEncrypt ?
(ssize_t)src_len + POLY1305_DIGEST_SIZE :
(ssize_t)src_len - POLY1305_DIGEST_SIZE;
const byte *src_i;
byte *dst_i;
size_t src_len_rem;
#if defined(WOLFSSL_SMALL_STACK) && !defined(WOLFSSL_NO_MALLOC)
ChaChaPoly_Aead *aead = (ChaChaPoly_Aead *)XMALLOC(sizeof *aead, NULL, DYNAMIC_TYPE_TMP_BUFFER);
if (aead == NULL)
return MEMORY_E;
#else
ChaChaPoly_Aead aead_buf, *aead = &aead_buf;
#endif
if ((dst == NULL) || (src == NULL)) {
ret = BAD_FUNC_ARG;
goto out;
}
if ((ssize_t)dst_space < dst_len) {
ret = BUFFER_E;
goto out;
}
if ((ret = wc_XChaCha20Poly1305_Init(aead, ad, (word32)ad_len,
nonce, (word32)nonce_len,
key, (word32)key_len, 1)) < 0)
goto out;
/* process the input in 16k pieces to accommodate src_lens that don't fit in a word32,
* and to exploit hot cache for the input data.
*/
src_i = src;
src_len_rem = isEncrypt ? src_len : (size_t)dst_len;
dst_i = dst;
while (src_len_rem > 0) {
word32 this_src_len =
(src_len_rem > 16384) ?
16384 :
(word32)src_len_rem;
if ((ret = wc_Chacha_Process(&aead->chacha, dst_i, src_i, this_src_len)) < 0)
goto out;
if ((ret = wc_Poly1305Update(&aead->poly, isEncrypt ? dst_i : src_i, this_src_len)) < 0)
goto out;
src_len_rem -= (size_t)this_src_len;
src_i += this_src_len;
dst_i += this_src_len;
}
if (aead->poly.leftover) {
if ((ret = wc_Poly1305_Pad(&aead->poly, (word32)aead->poly.leftover)) < 0)
return ret;
}
#ifdef WORD64_AVAILABLE
ret = wc_Poly1305_EncodeSizes64(&aead->poly, ad_len, isEncrypt ? src_len : (size_t)dst_len);
#else
ret = wc_Poly1305_EncodeSizes(&aead->poly, ad_len, isEncrypt ? src_len : (size_t)dst_len);
#endif
if (ret < 0)
goto out;
if (isEncrypt)
ret = wc_Poly1305Final(&aead->poly, dst + src_len);
else {
byte outAuthTag[POLY1305_DIGEST_SIZE];
if ((ret = wc_Poly1305Final(&aead->poly, outAuthTag)) < 0)
goto out;
if (ConstantCompare(outAuthTag, src + dst_len, POLY1305_DIGEST_SIZE) != 0) {
ret = MAC_CMP_FAILED_E;
goto out;
}
}
out:
ForceZero(aead, sizeof *aead);
#if defined(WOLFSSL_SMALL_STACK) && !defined(WOLFSSL_NO_MALLOC)
XFREE(aead, NULL, DYNAMIC_TYPE_TMP_BUFFER);
#endif
return ret;
}
int wc_XChaCha20Poly1305_Encrypt(
byte *dst, const size_t dst_space,
const byte *src, const size_t src_len,
const byte *ad, const size_t ad_len,
const byte *nonce, const size_t nonce_len,
const byte *key, const size_t key_len)
{
return wc_XChaCha20Poly1305_crypt_oneshot(dst, dst_space, src, src_len, ad, ad_len, nonce, nonce_len, key, key_len, 1);
}
int wc_XChaCha20Poly1305_Decrypt(
byte *dst, const size_t dst_space,
const byte *src, const size_t src_len,
const byte *ad, const size_t ad_len,
const byte *nonce, const size_t nonce_len,
const byte *key, const size_t key_len)
{
return wc_XChaCha20Poly1305_crypt_oneshot(dst, dst_space, src, src_len, ad, ad_len, nonce, nonce_len, key, key_len, 0);
}
#endif /* HAVE_XCHACHA */
#endif /* HAVE_CHACHA && HAVE_POLY1305 */