Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions doc/dox_comments/header_files/aes.h
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,59 @@ int wc_AesSetKeyDirect(Aes* aes, const byte* key, word32 len,
*/
int wc_AesGcmSetKey(Aes* aes, const byte* key, word32 len);

/*!
\ingroup AES
\brief This function associates one tag length with the key held by an
AES object, as SP 800-38D section 5.2.1.2 and SP 800-38C section 5.3
require. Once set, AES-GCM and AES-CCM calls with that key accept only
that tag length. wc_AesGcmSetKey() and wc_AesCcmSetKey() clear the
association. The mode still applies its own list of allowed tag lengths
on top of this.

The first use of a key fixes its tag length even without this call. Set
the key again through one of those two, or pass WC_NO_TAG_ASSOCIATION, to
clear it. wc_AesSetKey() clears it as well on builds that use the software
key schedule, but a backend with its own key setter does not. Ports that
replace the AES-GCM or AES-CCM entry points enforce their own tag rules,
and such a build would be validated as a hybrid module.

A crypto callback is checked here before the work is handed over, because
the key it forwards is the one this object holds. A callback that builds a
fresh context per call cannot see the association, so this is the only
place it can be caught. A device key installed by id or label is the
exception, it is never seen here, so its first tag length stays associated
until the object is freed.

An OpenSSL-compat EVP context re-initialized without a new key keeps the
association, so a later message asking for a different tag length fails.
That is the one key one tag length rule, not a caller error.

\return 0 On success.
\return BAD_FUNC_ARG Returned if aes is NULL, or the length is larger
than the AES block size.

\param aes pointer to the AES object holding the key
\param tagLen tag length to associate with the key, or
WC_NO_TAG_ASSOCIATION to clear it

_Example_
\code
Aes enc;
byte key[] = { some 16, 24, 32 byte key };
if (wc_AesGcmSetKey(&enc, key, sizeof(key)) != 0) {
// failed to set aes key
}
if (wc_AesSetTagLen(&enc, 16) != 0) {
// failed to associate the tag length
}
\endcode

\sa wc_AesGcmSetKey
\sa wc_AesCcmSetKey
\sa wc_CmacSetTagLen
*/
int wc_AesSetTagLen(Aes* aes, word32 tagLen);

/*!
\ingroup AES
\brief This function encrypts the input message, held in the buffer in,
Expand Down
34 changes: 34 additions & 0 deletions doc/dox_comments/header_files/cmac.h
Original file line number Diff line number Diff line change
Expand Up @@ -292,3 +292,37 @@ int wc_AesCmacVerify_ex(Cmac* cmac, const byte* check, word32 checkSz,
const byte* in, word32 inSz,
const byte* key, word32 keySz,
void* heap, int devId);

/*!
\ingroup CMAC
\brief This function associates one tag length with the key held by a
Cmac object, as SP 800-38B section 5.4 requires. Once set, the final and
verify calls for that key accept only that tag length. Starting a new
Cmac clears the association. The length is kept on the AES object the
Cmac already holds.

\return 0 On success.
\return BAD_FUNC_ARG Returned if cmac is NULL, or the length is outside
WC_CMAC_TAG_MIN_SZ to WC_CMAC_TAG_MAX_SZ.

\param cmac pointer to the Cmac object holding the key
\param tagLen tag length to associate with the key, or
WC_NO_TAG_ASSOCIATION to clear it

_Example_
\code
Cmac cmac;
byte key[] = { some 16, 24, 32 byte key };
if (wc_InitCmac(&cmac, key, sizeof(key), WC_CMAC_AES, NULL) != 0) {
// failed to set up cmac
}
if (wc_CmacSetTagLen(&cmac, 16) != 0) {
// failed to associate the tag length
}
\endcode

\sa wc_InitCmac
\sa wc_CmacFinal
\sa wc_AesSetTagLen
*/
int wc_CmacSetTagLen(Cmac* cmac, word32 tagLen);
211 changes: 209 additions & 2 deletions tests/api/test_aes.c
Original file line number Diff line number Diff line change
Expand Up @@ -3519,6 +3519,196 @@ int test_wc_AesGcmEncryptDecrypt(void)

} /* END test_wc_AesGcmEncryptDecrypt */


/* must match the call sites in test_wc_AesSetTagLen(), a build that compiles
* these helpers without calling them fails -Wunused-function */
#if ((defined(HAVE_AESGCM) && WOLFSSL_MIN_AUTH_TAG_SZ <= 12) || \
defined(HAVE_AESCCM)) && !defined(NO_AES) && \
defined(WOLFSSL_AES_128) && !defined(HAVE_SELFTEST) && \
(!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0))

#define TEST_AES_TAG_GCM 1
#define TEST_AES_TAG_CCM 2

/* The three calls below are the only difference between the two modes.
*/
static int test_aes_tag_setkey(int type, Aes* aes, const byte* key, word32 len)
{
#ifdef HAVE_AESGCM
if (type == TEST_AES_TAG_GCM)
return wc_AesGcmSetKey(aes, key, len);
#endif
#ifdef HAVE_AESCCM
if (type == TEST_AES_TAG_CCM)
return wc_AesCcmSetKey(aes, key, len);
#endif
return NOT_COMPILED_IN;
}

static int test_aes_tag_enc(int type, Aes* aes, byte* out, const byte* in,
word32 sz, const byte* iv, word32 ivSz, byte* tag, word32 tagSz)
{
#ifdef HAVE_AESGCM
if (type == TEST_AES_TAG_GCM)
return wc_AesGcmEncrypt(aes, out, in, sz, iv, ivSz, tag, tagSz,
NULL, 0);
#endif
#ifdef HAVE_AESCCM
if (type == TEST_AES_TAG_CCM)
return wc_AesCcmEncrypt(aes, out, in, sz, iv, ivSz, tag, tagSz,
NULL, 0);
#endif
return NOT_COMPILED_IN;
}

/* aes.c only defines these decrypt entry points when the build keeps them
*/
#ifdef HAVE_AES_DECRYPT
static int test_aes_tag_dec(int type, Aes* aes, byte* out, const byte* in,
word32 sz, const byte* iv, word32 ivSz, const byte* tag, word32 tagSz)
{
#ifdef HAVE_AESGCM
if (type == TEST_AES_TAG_GCM)
return wc_AesGcmDecrypt(aes, out, in, sz, iv, ivSz, tag, tagSz,
NULL, 0);
#endif
#ifdef HAVE_AESCCM
if (type == TEST_AES_TAG_CCM)
return wc_AesCcmDecrypt(aes, out, in, sz, iv, ivSz, tag, tagSz,
NULL, 0);
#endif
return NOT_COMPILED_IN;
}
#endif

/* Runs the same checks for either mode, otherSz being a size it allows
*/
static int test_aes_tag_bind(int type, word32 otherSz, word32 ivSz)
{
EXPECT_DECLS;
Aes aes;
byte key[16];
byte iv[GCM_NONCE_MID_SZ];
byte plain[16];
byte cipher[16];
byte tag[16];
int aesInit = 0;

XMEMSET(key, 0, sizeof(key));
XMEMSET(iv, 0, sizeof(iv));
XMEMSET(plain, 0, sizeof(plain));

ExpectIntEQ(wc_AesInit(&aes, HEAP_HINT, testDevId), 0);
if (EXPECT_SUCCESS())
aesInit = 1;
ExpectIntEQ(test_aes_tag_setkey(type, &aes, key, sizeof(key)), 0);

/* nothing associated yet, so this first use fixes the length */
ExpectIntEQ(test_aes_tag_enc(type, &aes, cipher, plain, sizeof(plain), iv,
ivSz, tag, otherSz), 0);
ExpectIntEQ(test_aes_tag_enc(type, &aes, cipher, plain, sizeof(plain), iv,
ivSz, tag, sizeof(tag)), WC_NO_ERR_TRACE(BAD_FUNC_ARG));

ExpectIntEQ(wc_AesSetTagLen(NULL, sizeof(tag)),
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
ExpectIntEQ(wc_AesSetTagLen(&aes, WC_AES_BLOCK_SIZE + 1),
WC_NO_ERR_TRACE(BAD_FUNC_ARG));

/* associate the full length with this key, then otherSz must fail both
* ways */
ExpectIntEQ(wc_AesSetTagLen(&aes, sizeof(tag)), 0);
ExpectIntEQ(test_aes_tag_enc(type, &aes, cipher, plain, sizeof(plain), iv,
ivSz, tag, sizeof(tag)), 0);
ExpectIntEQ(test_aes_tag_enc(type, &aes, cipher, plain, sizeof(plain), iv,
ivSz, tag, otherSz), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
#ifdef HAVE_AES_DECRYPT
ExpectIntEQ(test_aes_tag_dec(type, &aes, plain, cipher, sizeof(cipher), iv,
ivSz, tag, otherSz), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
#endif

/* resetting the association drops it, and so does a new key */
ExpectIntEQ(wc_AesSetTagLen(&aes, WC_NO_TAG_ASSOCIATION), 0);
ExpectIntEQ(test_aes_tag_enc(type, &aes, cipher, plain, sizeof(plain), iv,
ivSz, tag, otherSz), 0);
ExpectIntEQ(wc_AesSetTagLen(&aes, sizeof(tag)), 0);
ExpectIntEQ(test_aes_tag_setkey(type, &aes, key, sizeof(key)), 0);
ExpectIntEQ(test_aes_tag_enc(type, &aes, cipher, plain, sizeof(plain), iv,
ivSz, tag, otherSz), 0);

if (aesInit)
wc_AesFree(&aes);

return EXPECT_RESULT();
}
#endif

/* A tag length associated with the key must be the only one it accepts.
*/
int test_wc_AesSetTagLen(void)
{
EXPECT_DECLS;
#if !defined(NO_AES) && defined(WOLFSSL_AES_128) && \
!defined(HAVE_SELFTEST) && \
(!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0))
#if defined(HAVE_AESGCM) && WOLFSSL_MIN_AUTH_TAG_SZ <= 12
ExpectIntEQ(test_aes_tag_bind(TEST_AES_TAG_GCM, 12, GCM_NONCE_MID_SZ),
TEST_SUCCESS);
#endif
#ifdef HAVE_AESCCM
ExpectIntEQ(test_aes_tag_bind(TEST_AES_TAG_CCM, 8, 12), TEST_SUCCESS);
#endif
#endif
return EXPECT_RESULT();
} /* END test_wc_AesSetTagLen */


/* Streaming final must honour the length and still reject a NULL aes
*/
int test_wc_AesGcmStreamTagLen(void)
{
EXPECT_DECLS;
#if defined(HAVE_AESGCM) && defined(WOLFSSL_AESGCM_STREAM) && \
!defined(NO_AES) && defined(WOLFSSL_AES_128) && \
!defined(HAVE_SELFTEST) && \
(!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0))
Aes aes;
byte key[16];
byte iv[GCM_NONCE_MID_SZ];
byte plain[16];
byte cipher[16];
byte tag[16];
word32 shortTagSz = 12;
int aesInit = 0;

XMEMSET(key, 0, sizeof(key));
XMEMSET(iv, 0, sizeof(iv));
XMEMSET(plain, 0, sizeof(plain));

ExpectIntEQ(wc_AesInit(&aes, HEAP_HINT, testDevId), 0);
if (EXPECT_SUCCESS())
aesInit = 1;
ExpectIntEQ(wc_AesGcmInit(&aes, key, sizeof(key), iv, sizeof(iv)), 0);
ExpectIntEQ(wc_AesSetTagLen(&aes, sizeof(tag)), 0);
ExpectIntEQ(wc_AesGcmEncryptUpdate(&aes, cipher, plain, sizeof(plain),
NULL, 0), 0);
/* RFC 5084 section 3.2 allows this length, but the key is tied to the
* full one */
ExpectIntEQ(wc_AesGcmEncryptFinal(&aes, tag, shortTagSz),
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
ExpectIntEQ(wc_AesGcmEncryptFinal(&aes, tag, sizeof(tag)), 0);

/* a NULL aes is an argument error, never a read through the pointer */
ExpectIntEQ(wc_AesGcmEncryptFinal(NULL, tag, sizeof(tag)),
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
ExpectIntEQ(wc_AesGcmDecryptFinal(NULL, tag, sizeof(tag)),
WC_NO_ERR_TRACE(BAD_FUNC_ARG));

if (aesInit)
wc_AesFree(&aes);
#endif
return EXPECT_RESULT();
} /* END test_wc_AesGcmStreamTagLen */

/*******************************************************************************
* AES-GCM overlapping (in-place) buffers
******************************************************************************/
Expand Down Expand Up @@ -4970,6 +5160,19 @@ int test_wc_GmacUpdate(void)
ExpectIntEQ(wc_GmacUpdate(&gmac, iv, sizeof(iv), authIn, sizeof(authIn),
tagOut, sizeof(tag1)), 0);
ExpectIntEQ(XMEMCMP(tag1, tagOut, sizeof(tag1)), 0);
#if !defined(HAVE_SELFTEST) && \
(!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0))
/* a gmac holds an aes, so a tag length associates the same way */
ExpectIntEQ(wc_AesSetTagLen(&gmac.aes, sizeof(tag1)), 0);
ExpectIntEQ(wc_GmacUpdate(&gmac, iv, sizeof(iv), authIn, sizeof(authIn),
tagOut, sizeof(tag1)), 0);
ExpectIntEQ(wc_GmacUpdate(&gmac, iv, sizeof(iv), authIn, sizeof(authIn),
tagOut, sizeof(tag1) - 4), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
/* a new key resets the association */
ExpectIntEQ(wc_GmacSetKey(&gmac, key16, sizeof(key16)), 0);
ExpectIntEQ(wc_GmacUpdate(&gmac, iv, sizeof(iv), authIn, sizeof(authIn),
tagOut, sizeof(tag1) - 4), 0);
#endif
wc_AesFree(&gmac.aes);
#endif

Expand Down Expand Up @@ -8675,7 +8878,9 @@ int test_wc_AesFeatureCoverage(void)
ccmTag, 16, ccmAad, sizeof(ccmAad)), 0);
ExpectBufEQ(ccmRecovered, ccmPlain, sizeof(ccmPlain));

/* 7-byte nonce, 8-byte tag, no AAD. */
/* 7-byte nonce, 8-byte tag, no AAD. A different tag length means a
* different key, per SP 800-38C section 5.3. */
ExpectIntEQ(wc_AesCcmSetKey(&aes, ccmKey, sizeof(ccmKey)), 0);
ExpectIntEQ(wc_AesCcmEncrypt(&aes, ccmCipher, ccmPlain,
sizeof(ccmPlain), ccmNonce7, sizeof(ccmNonce7),
ccmTag, 8, NULL, 0), 0);
Expand All @@ -8690,7 +8895,9 @@ int test_wc_AesFeatureCoverage(void)
sizeof(ccmPlain), ccmNonce7, sizeof(ccmNonce7),
ccmTag, 8, NULL, 0), 0);

/* Empty plaintext: AAD-only authentication. */
/* Empty plaintext: AAD-only authentication. Back to a 16-byte tag,
* so set the key again. */
ExpectIntEQ(wc_AesCcmSetKey(&aes, ccmKey, sizeof(ccmKey)), 0);
ExpectIntEQ(wc_AesCcmEncrypt(&aes, NULL, NULL, 0,
ccmNonce13, sizeof(ccmNonce13),
ccmTag, 16, ccmAad, sizeof(ccmAad)), 0);
Expand Down
4 changes: 4 additions & 0 deletions tests/api/test_aes.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ int test_wc_AesCtrCounterOverflow(void);
int test_wc_AesGcmSetKey(void);
int test_wc_AesGcmEncryptDecrypt_Sizes(void);
int test_wc_AesGcmEncryptDecrypt(void);
int test_wc_AesSetTagLen(void);
int test_wc_AesGcmStreamTagLen(void);
int test_wc_AesGcmEncryptDecrypt_InPlace(void);
int test_wc_AesGcmEncryptDecrypt_UnalignedBuffers(void);
int test_wc_AesGcm_CrossCipher(void);
Expand Down Expand Up @@ -208,6 +210,8 @@ int test_wc_CryptoCb_AesKeyWrapEcbCompose(void);
TEST_DECL_GROUP("aes", test_wc_AesGcmSetKey), \
TEST_DECL_GROUP("aes", test_wc_AesGcmEncryptDecrypt_Sizes), \
TEST_DECL_GROUP("aes", test_wc_AesGcmEncryptDecrypt), \
TEST_DECL_GROUP("aes", test_wc_AesSetTagLen), \
TEST_DECL_GROUP("aes", test_wc_AesGcmStreamTagLen), \
TEST_DECL_GROUP("aes", test_wc_AesGcmEncryptDecrypt_InPlace), \
TEST_DECL_GROUP("aes", test_wc_AesGcmEncryptDecrypt_UnalignedBuffers), \
TEST_DECL_GROUP("aes", test_wc_AesGcm_CrossCipher), \
Expand Down
Loading
Loading