diff --git a/doc/dox_comments/header_files/aes.h b/doc/dox_comments/header_files/aes.h index d76125cce1a..8bde17ab00b 100644 --- a/doc/dox_comments/header_files/aes.h +++ b/doc/dox_comments/header_files/aes.h @@ -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, diff --git a/doc/dox_comments/header_files/cmac.h b/doc/dox_comments/header_files/cmac.h index b2bac936a4f..bb7ac40e8d0 100644 --- a/doc/dox_comments/header_files/cmac.h +++ b/doc/dox_comments/header_files/cmac.h @@ -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); diff --git a/tests/api/test_aes.c b/tests/api/test_aes.c index f557f130cc9..ae68ad74a25 100644 --- a/tests/api/test_aes.c +++ b/tests/api/test_aes.c @@ -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 ******************************************************************************/ @@ -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 @@ -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); @@ -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); diff --git a/tests/api/test_aes.h b/tests/api/test_aes.h index a3f8cd11400..5b3490f69a5 100644 --- a/tests/api/test_aes.h +++ b/tests/api/test_aes.h @@ -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); @@ -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), \ diff --git a/tests/api/test_cmac.c b/tests/api/test_cmac.c index 1179cf3e238..ef54dd05598 100644 --- a/tests/api/test_cmac.c +++ b/tests/api/test_cmac.c @@ -328,6 +328,58 @@ int test_wc_AesCmacGenerate(void) } /* END test_wc_AesCmacGenerate */ + +/* A tag length associated with the key must be the only one it accepts. */ +int test_wc_CmacSetTagLen(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_CMAC) && !defined(NO_AES) && defined(WOLFSSL_AES_DIRECT) \ + && !defined(HAVE_SELFTEST) \ + && (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) + Cmac cmac; + byte key[WC_AES_BLOCK_SIZE]; + byte msg[WC_AES_BLOCK_SIZE]; + byte tag[WC_AES_BLOCK_SIZE]; + word32 tagSz; + /* smallest length CMAC allows, so it never equals the one tied below */ + word32 otherSz = WC_CMAC_TAG_MIN_SZ; + + XMEMSET(key, 0, sizeof(key)); + XMEMSET(msg, 0, sizeof(msg)); + + /* generate side: only the associated size is taken */ + ExpectIntEQ(wc_InitCmac(&cmac, key, sizeof(key), WC_CMAC_AES, NULL), 0); + if (EXPECT_SUCCESS()) { + ExpectIntEQ(wc_CmacSetTagLen(NULL, (word32)sizeof(tag)), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_CmacSetTagLen(&cmac, WC_CMAC_TAG_MAX_SZ + 1), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_CmacSetTagLen(&cmac, (word32)sizeof(tag)), 0); + ExpectIntEQ(wc_CmacUpdate(&cmac, msg, sizeof(msg)), 0); + tagSz = otherSz; + ExpectIntEQ(wc_CmacFinalNoFree(&cmac, tag, &tagSz), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + tagSz = (word32)sizeof(tag); + ExpectIntEQ(wc_CmacFinalNoFree(&cmac, tag, &tagSz), 0); + ExpectIntEQ(tagSz, (word32)sizeof(tag)); + /* NoFree leaves the cmac to us */ + wc_CmacFree(&cmac); + } + + /* verify side: a check value of another size is refused */ + ExpectIntEQ(wc_InitCmac(&cmac, key, sizeof(key), WC_CMAC_AES, NULL), 0); + if (EXPECT_SUCCESS()) { + ExpectIntEQ(wc_CmacSetTagLen(&cmac, (word32)sizeof(tag)), 0); + /* key NULL keeps the cmac that was just keyed, so it applies */ + ExpectIntEQ(wc_AesCmacVerify_ex(&cmac, tag, otherSz, msg, sizeof(msg), + NULL, 0, HEAP_HINT, testDevId), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* refused before any work, so the cmac is still ours to free */ + wc_CmacFree(&cmac); + } +#endif + return EXPECT_RESULT(); +} /* END test_wc_CmacSetTagLen */ + /* * MC/DC: wc_CMAC_Grow()'s (cmac == NULL) || (in == NULL && inSz != 0) * guard. Compiled out entirely unless WOLFSSL_HASH_KEEP is defined. diff --git a/tests/api/test_cmac.h b/tests/api/test_cmac.h index 28272c59a64..25de2292832 100644 --- a/tests/api/test_cmac.h +++ b/tests/api/test_cmac.h @@ -28,6 +28,7 @@ int test_wc_InitCmac(void); int test_wc_CmacUpdate(void); int test_wc_CmacFinal(void); int test_wc_AesCmacGenerate(void); +int test_wc_CmacSetTagLen(void); int test_wc_CMAC_Grow(void); int test_wc_InitCmac_Id(void); int test_wc_InitCmac_Label(void); @@ -41,6 +42,7 @@ int test_wc_CryptoCb_CmacFree(void); TEST_DECL_GROUP("cmac", test_wc_CmacUpdate), \ TEST_DECL_GROUP("cmac", test_wc_CmacFinal), \ TEST_DECL_GROUP("cmac", test_wc_AesCmacGenerate), \ + TEST_DECL_GROUP("cmac", test_wc_CmacSetTagLen), \ TEST_DECL_GROUP("cmac", test_wc_CMAC_Grow), \ TEST_DECL_GROUP("cmac", test_wc_InitCmac_Id), \ TEST_DECL_GROUP("cmac", test_wc_InitCmac_Label), \ diff --git a/wolfcrypt/src/aes.c b/wolfcrypt/src/aes.c index 562e69c5974..dab73cbc700 100644 --- a/wolfcrypt/src/aes.c +++ b/wolfcrypt/src/aes.c @@ -188,6 +188,66 @@ block cipher mechanism that uses n-bit binary string parameter key with 128-bits #endif #endif +#if defined(HAVE_AESGCM) || defined(HAVE_AESCCM) || defined(WOLFSSL_CMAC) + +/* One tag length per key, per SP 800-38D 5.2.1.2, SP 800-38C 5.3 and + * SP 800-38B 5.4. Pass WC_NO_TAG_ASSOCIATION to clear it. + */ +int wc_AesSetTagLen(Aes* aes, word32 tagLen) +{ + if (aes == NULL || tagLen > WC_AES_BLOCK_SIZE) { + return BAD_FUNC_ARG; + } + + aes->tagLen = tagLen; + + return 0; +} + +#endif /* HAVE_AESGCM || HAVE_AESCCM || WOLFSSL_CMAC */ + +#if defined(HAVE_AESGCM) || defined(HAVE_AESCCM) + +/* Ports that bring their own entry points enforce their own tag rules. */ + +/* only where aes.c itself implements a GCM or CCM entry point, the ports + * below carry their own + */ +#if !defined(WOLFSSL_TI_CRYPT) && \ + ((defined(HAVE_AESGCM) && !defined(WOLFSSL_AFALG) && \ + !defined(WOLFSSL_KCAPI_AES) && !defined(WOLFSSL_DEVCRYPTO_AES) && \ + !defined(WOLFSSL_XILINX_CRYPT) && !defined(WOLFSSL_AFALG_XILINX_AES)) || \ + (defined(HAVE_AESCCM) && \ + !(defined(WOLFSSL_IMX6_CAAM) && !defined(NO_IMX6_CAAM_AES) && \ + !defined(WOLFSSL_QNX_CAAM)))) + +/* ties the length to the key on first use, then requires a match + */ +static int AesAssociateTagSz(Aes* aes, word32 authTagSz) +{ + if (aes == NULL) { + return BAD_FUNC_ARG; + } + + /* first use of the key fixes the length, per SP 800-38D 5.2.1.2, + * SP 800-38C 5.3 and SP 800-38B 5.4 */ + if (aes->tagLen == WC_NO_TAG_ASSOCIATION) { + aes->tagLen = authTagSz; + return 0; + } + + if (authTagSz != aes->tagLen) { + WOLFSSL_MSG("AES tag size differs from the one associated with the key"); + return BAD_FUNC_ARG; + } + + return 0; +} + +#endif /* aes.c implements a GCM or CCM entry point */ + +#endif /* HAVE_AESGCM || HAVE_AESCCM */ + #if defined(WOLFSSL_TI_CRYPT) #include @@ -5649,6 +5709,14 @@ static void AesSetKey_C(Aes* aes, const byte* key, word32 keySz, int dir) ret = AesSetKeyLocal_body(aes, userKey, keylen, iv, dir, checkKeyLen); aes->keyInstalled = (ret == 0) ? 1 : 0; +/* A new key drops the old one's tag length. Built out entirely unless a mode + * that carries one is on, so plain AES pays nothing. */ +#if defined(HAVE_AESGCM) || defined(HAVE_AESCCM) || defined(WOLFSSL_CMAC) + if (ret == 0) { + aes->tagLen = WC_NO_TAG_ASSOCIATION; + } +#endif + return ret; } @@ -8757,6 +8825,10 @@ int wc_AesGcmSetKey(Aes* aes, const byte* key, word32 len) #endif XMEMSET(iv, 0, WC_AES_BLOCK_SIZE); ret = wc_AesSetKey(aes, key, len, iv, AES_ENCRYPTION); + /* new key, so the tag length of the old one no longer applies */ + if (ret == 0) { + aes->tagLen = WC_NO_TAG_ASSOCIATION; + } #ifdef WOLF_CRYPTO_CB_ONLY_AES /* do key scheduling so that ECB-only devices can still do GCM */ if (ret == 0) { @@ -10868,6 +10940,10 @@ int wc_AesGcmEncrypt(Aes* aes, byte* out, const byte* in, word32 sz, if (status) return status; + if (AesAssociateTagSz(aes, authTagSz) != 0) { + return BAD_FUNC_ARG; + } + status = wolfSSL_CryptHwMutexLock(); if (status != 0) return status; @@ -11483,6 +11559,10 @@ int wc_AesGcmEncrypt(Aes* aes, byte* out, const byte* in, word32 sz, return FIPS_BAD_VALUE_E; #endif + if (AesAssociateTagSz(aes, authTagSz) != 0) { + return BAD_FUNC_ARG; + } + #ifdef WOLF_CRYPTO_CB #ifndef WOLF_CRYPTO_CB_FIND if (aes->devId != INVALID_DEVID) @@ -11757,6 +11837,10 @@ int wc_AesGcmDecrypt(Aes* aes, byte* out, const byte* in, word32 sz, return ret; } + if (AesAssociateTagSz(aes, authTagSz) != 0) { + return BAD_FUNC_ARG; + } + status = wolfSSL_CryptHwMutexLock(); if (status != 0) return status; @@ -12338,6 +12422,9 @@ int wc_AesGcmDecrypt(Aes* aes, byte* out, const byte* in, word32 sz, ret = wc_local_AesGcmCheckTagSz(authTagSz); if (ret != 0) return ret; + if (AesAssociateTagSz(aes, authTagSz) != 0) { + return BAD_FUNC_ARG; + } /* No FIPS check on ivSz in decrypt mode -- SP 800-38D IV * construction requirements bind encryption only; decryption must @@ -14872,6 +14959,10 @@ int wc_AesGcmEncryptFinal(Aes* aes, byte* authTag, word32 authTagSz) ret = FIPS_BAD_VALUE_E; #endif + if (ret == 0) { + ret = AesAssociateTagSz(aes, authTagSz); + } + if (ret == 0) { /* Calculate authentication tag. */ #ifdef WOLFSSL_AESNI @@ -15027,6 +15118,10 @@ int wc_AesGcmDecryptFinal(Aes* aes, const byte* authTag, word32 authTagSz) ret = MISSING_IV; } + if (ret == 0) { + ret = AesAssociateTagSz(aes, authTagSz); + } + if (ret == 0) { /* Calculate authentication tag and compare with one passed in.. */ #ifdef WOLFSSL_AESNI @@ -15299,10 +15394,18 @@ int wc_GmacUpdate(Gmac* gmac, const byte* iv, word32 ivSz, int wc_AesCcmSetKey(Aes* aes, const byte* key, word32 keySz) { + int ret; + if (!((keySz == 16) || (keySz == 24) || (keySz == 32))) return BAD_FUNC_ARG; - return wc_AesSetKey(aes, key, keySz, NULL, AES_ENCRYPTION); + ret = wc_AesSetKey(aes, key, keySz, NULL, AES_ENCRYPTION); + /* new key, so the tag length of the old one no longer applies */ + if (ret == 0) { + aes->tagLen = WC_NO_TAG_ASSOCIATION; + } + + return ret; } @@ -15334,6 +15437,14 @@ int wc_AesCcmEncrypt(Aes* aes, byte* out, const byte* in, word32 inSz, byte* authTag, word32 authTagSz, const byte* authIn, word32 authInSz) { + if (wc_AesCcmCheckTagSize((int)authTagSz) != 0) { + return BAD_FUNC_ARG; + } + + if (AesAssociateTagSz(aes, authTagSz) != 0) { + return BAD_FUNC_ARG; + } + return wc_AesCcmEncrypt_silabs( aes, out, in, inSz, nonce, nonceSz, @@ -15347,6 +15458,14 @@ int wc_AesCcmDecrypt(Aes* aes, byte* out, const byte* in, word32 inSz, const byte* authTag, word32 authTagSz, const byte* authIn, word32 authInSz) { + if (wc_AesCcmCheckTagSize((int)authTagSz) != 0) { + return BAD_FUNC_ARG; + } + + if (AesAssociateTagSz(aes, authTagSz) != 0) { + return BAD_FUNC_ARG; + } + return wc_AesCcmDecrypt_silabs( aes, out, in, inSz, nonce, nonceSz, @@ -15399,6 +15518,10 @@ int wc_AesCcmEncrypt(Aes* aes, byte* out, const byte* in, word32 inSz, } } + if (AesAssociateTagSz(aes, authTagSz) != 0) { + return BAD_FUNC_ARG; + } + status = wolfSSL_CryptHwMutexLock(); if (status != 0) return status; @@ -15426,6 +15549,11 @@ int wc_AesCcmDecrypt(Aes* aes, byte* out, const byte* in, word32 inSz, return BAD_FUNC_ARG; } + if (wc_AesCcmCheckTagSize((int)authTagSz) != 0) { + return BAD_FUNC_ARG; + } + + key = (byte*)aes->key; status = wc_AesGetKeySize(aes, &keySize); @@ -15445,6 +15573,10 @@ int wc_AesCcmDecrypt(Aes* aes, byte* out, const byte* in, word32 inSz, } } + if (AesAssociateTagSz(aes, authTagSz) != 0) { + return BAD_FUNC_ARG; + } + status = wolfSSL_CryptHwMutexLock(); if (status != 0) return status; @@ -15654,6 +15786,10 @@ int wc_AesCcmEncrypt(Aes* aes, byte* out, const byte* in, word32 inSz, return AES_CCM_OVERFLOW_E; } + if (AesAssociateTagSz(aes, authTagSz) != 0) { + return BAD_FUNC_ARG; + } + #ifdef WOLF_CRYPTO_CB #ifndef WOLF_CRYPTO_CB_FIND if (aes->devId != INVALID_DEVID) @@ -15824,6 +15960,10 @@ int wc_AesCcmDecrypt(Aes* aes, byte* out, const byte* in, word32 inSz, return AES_CCM_OVERFLOW_E; } + if (AesAssociateTagSz(aes, authTagSz) != 0) { + return BAD_FUNC_ARG; + } + #ifdef WOLF_CRYPTO_CB #ifndef WOLF_CRYPTO_CB_FIND if (aes->devId != INVALID_DEVID) diff --git a/wolfcrypt/src/cmac.c b/wolfcrypt/src/cmac.c index 28a8b50e824..1a27c0c0d49 100644 --- a/wolfcrypt/src/cmac.c +++ b/wolfcrypt/src/cmac.c @@ -410,6 +410,50 @@ int wc_CmacFree(Cmac* cmac) return 0; } +#ifdef WOLFSSL_CMAC_TAG_ASSOCIATION +/* first use of the key fixes the length, per SP 800-38B 5.4 + */ +static int CmacAssociateTagSz(Cmac* cmac, word32 tagSz) +{ + if (cmac == NULL) { + return BAD_FUNC_ARG; + } + + if (cmac->aes.tagLen == WC_NO_TAG_ASSOCIATION) { + cmac->aes.tagLen = tagSz; + return 0; + } + + if (tagSz != cmac->aes.tagLen) { + WOLFSSL_MSG("CMAC tag size differs from the one associated with the key"); + return BAD_FUNC_ARG; + } + + return 0; +} +#endif + + +#ifdef WOLFSSL_CMAC_TAG_ASSOCIATION +/* One tag length per key, per SP 800-38B 5.4, which gives a range not a + * list. Pass WC_NO_TAG_ASSOCIATION to clear it. + */ +int wc_CmacSetTagLen(Cmac* cmac, word32 tagLen) +{ + if (cmac == NULL) { + return BAD_FUNC_ARG; + } + + if (tagLen != WC_NO_TAG_ASSOCIATION && + (tagLen < WC_CMAC_TAG_MIN_SZ || tagLen > WC_CMAC_TAG_MAX_SZ)) { + return BAD_FUNC_ARG; + } + + return wc_AesSetTagLen(&cmac->aes, tagLen); +} +#endif + + int wc_CmacFinalNoFree(Cmac* cmac, byte* out, word32* outSz) { int ret = 0; @@ -420,6 +464,11 @@ int wc_CmacFinalNoFree(Cmac* cmac, byte* out, word32* outSz) if (*outSz < WC_CMAC_TAG_MIN_SZ || *outSz > WC_CMAC_TAG_MAX_SZ) { return BUFFER_E; } +#ifdef WOLFSSL_CMAC_TAG_ASSOCIATION + if (CmacAssociateTagSz(cmac, *outSz) != 0) { + return BAD_FUNC_ARG; + } +#endif #ifdef WOLF_CRYPTO_CB #ifndef WOLF_CRYPTO_CB_FIND @@ -519,6 +568,13 @@ int wc_AesCmacGenerate_ex(Cmac* cmac, if (devId != INVALID_DEVID) #endif { + #ifdef WOLFSSL_CMAC_TAG_ASSOCIATION + /* this path returns without reaching wc_CmacFinal() */ + if (key == NULL && outSz != NULL && + CmacAssociateTagSz(cmac, *outSz) != 0) { + return BAD_FUNC_ARG; + } + #endif ret = wc_CryptoCb_Cmac(cmac, key, keySz, in, inSz, out, outSz, WC_CMAC_AES, NULL); if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) @@ -616,6 +672,14 @@ int wc_AesCmacVerify_ex(Cmac* cmac, return BAD_FUNC_ARG; } +#ifdef WOLFSSL_CMAC_TAG_ASSOCIATION + /* only tie a length when the caller hands in a keyed cmac, a key here + * means this call sets the key and clears any association */ + if (key == NULL && CmacAssociateTagSz(cmac, checkSz) != 0) { + return BAD_FUNC_ARG; + } +#endif + aSz = checkSz; XMEMSET(a, 0, sizeof(a)); ret = wc_AesCmacGenerate_ex(cmac, diff --git a/wolfssl/wolfcrypt/aes.h b/wolfssl/wolfcrypt/aes.h index 455f30088b7..011c7a9a66c 100644 --- a/wolfssl/wolfcrypt/aes.h +++ b/wolfssl/wolfcrypt/aes.h @@ -537,6 +537,11 @@ struct Aes { * path, which is exactly the case that would otherwise encrypt with an * all-zero key. */ WC_BITFIELD keyInstalled:1; + +#if defined(HAVE_AESGCM) || defined(HAVE_AESCCM) || defined(WOLFSSL_CMAC) + /* tag length this key is tied to, at the end so offsets do not move */ + word32 tagLen; +#endif }; #ifndef WC_AES_TYPE_DEFINED @@ -622,6 +627,16 @@ typedef int (*wc_AesAuthDecryptFunc)(Aes* aes, byte* out, const byte* authTag, word32 authTagSz, const byte* authIn, word32 authInSz); +#if defined(HAVE_AESGCM) || defined(HAVE_AESCCM) || defined(WOLFSSL_CMAC) +/* no tag length is tied to the key yet, also pass to wc_AesSetTagLen() or + * wc_CmacSetTagLen() to clear one */ +enum { + WC_NO_TAG_ASSOCIATION = 0 +}; + +WOLFSSL_API int wc_AesSetTagLen(Aes* aes, word32 tagLen); +#endif + /* AES-CBC */ WOLFSSL_API int wc_AesSetKey(Aes* aes, const byte* key, word32 len, const byte* iv, int dir); diff --git a/wolfssl/wolfcrypt/cmac.h b/wolfssl/wolfcrypt/cmac.h index 58cb8c080a1..6d55741b6dc 100644 --- a/wolfssl/wolfcrypt/cmac.h +++ b/wolfssl/wolfcrypt/cmac.h @@ -128,6 +128,16 @@ int wc_InitCmac(Cmac* cmac, const byte* key, word32 keySz, int type, void* unused); +/* the selftest build pins an older aes.h that has no tag length field */ +#if !defined(NO_AES) && !defined(HAVE_SELFTEST) + #define WOLFSSL_CMAC_TAG_ASSOCIATION +#endif + +#ifdef WOLFSSL_CMAC_TAG_ASSOCIATION +WOLFSSL_API +int wc_CmacSetTagLen(Cmac* cmac, word32 tagLen); +#endif + WOLFSSL_API int wc_InitCmac_ex(Cmac* cmac, const byte* key, word32 keySz,