diff --git a/src/wp_cmac.c b/src/wp_cmac.c index c3889a46..ad25f497 100644 --- a/src/wp_cmac.c +++ b/src/wp_cmac.c @@ -104,7 +104,7 @@ static void wp_cmac_free(wp_CmacCtx* macCtx) * Allocates space for the key in the wolfSSL CMAC object. * * @param [in, out] macCtx CMAC context object. - * @param [in] key Key data to set. + * @param [in] key Key data to set. NULL restarts with the cached key. * @param [in] keyLen Length of key data in bytes. * @param [in] restart Restart CMAC calculation. * @return 1 on success. @@ -117,34 +117,43 @@ static int wp_cmac_set_key(wp_CmacCtx* macCtx, const unsigned char* key, WOLFPROV_ENTER(WP_LOG_COMP_MAC, "wp_cmac_set_key"); - if (keyLen > AES_256_KEY_SIZE) { + /* A NULL key restarts with the key already cached. */ + if (key == NULL) { + keyLen = macCtx->keyLen; + } + + if (keyLen == 0) { ok = 0; } - if (ok && (macCtx->expKeySize != 0) && (keyLen != macCtx->expKeySize)) { + if (ok && (key != NULL) && (keyLen > AES_256_KEY_SIZE)) { ok = 0; } - if (ok) { - if (macCtx->keyLen > 0) { - OPENSSL_cleanse(macCtx->key, macCtx->keyLen); - } - macCtx->keyLen = keyLen; + if (ok && (key != NULL) && (macCtx->expKeySize != 0) && + (keyLen != macCtx->expKeySize)) { + ok = 0; + } + if (ok && (key != NULL)) { + /* The cached length stays zero until the key is installed. */ + OPENSSL_cleanse(macCtx->key, macCtx->keyLen); + macCtx->keyLen = 0; XMEMCPY(macCtx->key, key, keyLen); - - if (restart) { - #if LIBWOLFSSL_VERSION_HEX >= 0x05000000 - int rc = wc_InitCmac_ex(&macCtx->cmac, macCtx->key, - (word32)macCtx->keyLen, macCtx->type, NULL, NULL, - INVALID_DEVID); - #else - int rc = wc_InitCmac(&macCtx->cmac, macCtx->key, - (word32)macCtx->keyLen, macCtx->type, NULL); - #endif - if (rc != 0) { - WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_InitCmac/wc_InitCmac_ex", rc); - ok = 0; - } + } + if (ok && restart) { + #if LIBWOLFSSL_VERSION_HEX >= 0x05000000 + int rc = wc_InitCmac_ex(&macCtx->cmac, macCtx->key, (word32)keyLen, + macCtx->type, NULL, NULL, INVALID_DEVID); + #else + int rc = wc_InitCmac(&macCtx->cmac, macCtx->key, (word32)keyLen, + macCtx->type, NULL); + #endif + if (rc != 0) { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_InitCmac/wc_InitCmac_ex", rc); + ok = 0; } } + if (ok) { + macCtx->keyLen = keyLen; + } WOLFPROV_LEAVE(WP_LOG_COMP_MAC, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); return ok; @@ -190,7 +199,7 @@ static wp_CmacCtx* wp_cmac_dup(wp_CmacCtx* src) * Initializes an CMAC context object with a key and parameters. * * @param [in, out] macCtx CMAC context object to initialize. - * @param [in] key Key data to set. + * @param [in] key Key data to set. NULL restarts with the cached key. * @param [in] keyLen Length of key data in bytes. * @param [in] params Extra parameters to set. * @return 1 on success. @@ -211,7 +220,7 @@ static int wp_cmac_init(wp_CmacCtx* macCtx, const unsigned char* key, } if (ok) { macCtx->size = AES_BLOCK_SIZE; - if ((key != NULL) && (!wp_cmac_set_key(macCtx, key, keyLen, 1))) { + if (!wp_cmac_set_key(macCtx, key, keyLen, 1)) { ok = 0; } } @@ -236,6 +245,10 @@ static int wp_cmac_update(wp_CmacCtx* macCtx, const unsigned char* data, WOLFPROV_ENTER(WP_LOG_COMP_MAC, "wp_cmac_update"); + if (macCtx->keyLen == 0) { + ok = 0; + } + while (ok && (dataLen > 0)) { word32 chunk = (!WP_FITS_WORD32(dataLen)) ? 0xFFFFFFFFU : (word32)dataLen; @@ -278,6 +291,9 @@ static int wp_cmac_final(wp_CmacCtx* macCtx, unsigned char* out, size_t* outl, if (ok && (outSize < macCtx->size)) { ok = 0; } + if (ok && (macCtx->keyLen == 0)) { + ok = 0; + } if (ok) { outSz = (word32)macCtx->size; diff --git a/test/test_cmac.c b/test/test_cmac.c index 6b022352..954da38c 100644 --- a/test/test_cmac.c +++ b/test/test_cmac.c @@ -358,6 +358,225 @@ int test_cmac_multi_update(void *data) return err; } +/** + * Test that one CMAC context can be reset by calling EVP_MAC_init() with no + * key, both after EVP_MAC_final() and mid-stream, reusing the cached key. + */ +static int test_cmac_reinit_helper(OSSL_LIB_CTX *libCtx, unsigned char *macA, + unsigned char *macB) +{ + int err; + EVP_MAC *emac = NULL; + EVP_MAC_CTX *ctx = NULL; + OSSL_PARAM params[3]; + char cipher[] = "AES-256-CBC"; + unsigned char key[32] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 + }; + unsigned char msgA[32]; + unsigned char msgB[21]; + unsigned char macC[AES_BLOCK_SIZE]; + unsigned char macD[AES_BLOCK_SIZE]; + size_t macASz = AES_BLOCK_SIZE; + size_t macBSz = AES_BLOCK_SIZE; + size_t macCSz = sizeof(macC); + size_t macDSz = sizeof(macD); + + memset(msgA, 0x41, sizeof(msgA)); + memset(msgB, 0x5a, sizeof(msgB)); + + params[0] = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_CIPHER, + cipher, 0); + params[1] = OSSL_PARAM_construct_octet_string(OSSL_MAC_PARAM_KEY, + (void *)key, sizeof(key)); + params[2] = OSSL_PARAM_construct_end(); + + err = (emac = EVP_MAC_fetch(libCtx, "CMAC", NULL)) == NULL; + if (err == 0) { + err = (ctx = EVP_MAC_CTX_new(emac)) == NULL; + } + if (err == 0) { + err = EVP_MAC_CTX_set_params(ctx, params) != 1; + } + + /* First round with installed key by EVP_MAC_CTX_set_params() above. */ + if (err == 0) { + err = EVP_MAC_init(ctx, NULL, 0, NULL) != 1; + } + if (err == 0) { + err = EVP_MAC_update(ctx, msgA, sizeof(msgA)) != 1; + } + if (err == 0) { + err = EVP_MAC_final(ctx, macA, &macASz, AES_BLOCK_SIZE) != 1; + } + + /* Reset after final and MAC a different message with the cached key. */ + if (err == 0) { + err = EVP_MAC_init(ctx, NULL, 0, NULL) != 1; + } + if (err == 0) { + err = EVP_MAC_update(ctx, msgB, sizeof(msgB)) != 1; + } + if (err == 0) { + err = EVP_MAC_final(ctx, macB, &macBSz, AES_BLOCK_SIZE) != 1; + } + + /* The first message must produce the first MAC again. */ + if (err == 0) { + err = EVP_MAC_init(ctx, NULL, 0, NULL) != 1; + } + if (err == 0) { + err = EVP_MAC_update(ctx, msgA, sizeof(msgA)) != 1; + } + if (err == 0) { + err = EVP_MAC_final(ctx, macC, &macCSz, sizeof(macC)) != 1; + } + if ((err == 0) && ((macCSz != macASz) || + (memcmp(macC, macA, macASz) != 0))) { + PRINT_ERR_MSG("CMAC after reset doesn't match the first MAC"); + err = 1; + } + + /* A reset mid-stream must discard the data buffered so far. */ + if (err == 0) { + err = EVP_MAC_init(ctx, NULL, 0, NULL) != 1; + } + if (err == 0) { + err = EVP_MAC_update(ctx, msgA, sizeof(msgA)) != 1; + } + if (err == 0) { + err = EVP_MAC_init(ctx, NULL, 0, NULL) != 1; + } + if (err == 0) { + err = EVP_MAC_update(ctx, msgB, sizeof(msgB)) != 1; + } + if (err == 0) { + err = EVP_MAC_final(ctx, macD, &macDSz, sizeof(macD)) != 1; + } + if ((err == 0) && ((macDSz != macBSz) || + (memcmp(macD, macB, macBSz) != 0))) { + PRINT_ERR_MSG("CMAC after mid-stream reset covers stale data"); + err = 1; + } + + EVP_MAC_CTX_free(ctx); + EVP_MAC_free(emac); + return err; +} + +/** + * A key that never made it into the CMAC object must not be usable on a + * later keyless restart. + */ +static int test_cmac_reinit_stale_key(OSSL_LIB_CTX *libCtx) +{ + int err; + EVP_MAC *emac = NULL; + EVP_MAC_CTX *ctx = NULL; + OSSL_PARAM keyOnly[2]; + OSSL_PARAM cipherOnly[2]; + char cipher[] = "AES-128-CBC"; + unsigned char key[32]; + unsigned char msg[32]; + unsigned char mac[AES_BLOCK_SIZE]; + size_t macSz = sizeof(mac); + + memset(key, 0x0b, sizeof(key)); + memset(msg, 0x41, sizeof(msg)); + + keyOnly[0] = OSSL_PARAM_construct_octet_string(OSSL_MAC_PARAM_KEY, + (void *)key, sizeof(key)); + keyOnly[1] = OSSL_PARAM_construct_end(); + cipherOnly[0] = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_CIPHER, + cipher, 0); + cipherOnly[1] = OSSL_PARAM_construct_end(); + + err = (emac = EVP_MAC_fetch(libCtx, "CMAC", NULL)) == NULL; + if (err == 0) { + err = (ctx = EVP_MAC_CTX_new(emac)) == NULL; + } + /* No cipher is set yet, so installing the key cannot succeed. */ + if (err == 0) { + err = EVP_MAC_CTX_set_params(ctx, keyOnly) == 1; + if (err != 0) { + PRINT_ERR_MSG("Setting a CMAC key with no cipher succeeded"); + } + } + if (err == 0) { + err = EVP_MAC_CTX_set_params(ctx, cipherOnly) != 1; + } + /* The rejected key must not be picked up by a keyless restart. */ + if (err == 0) { + err = EVP_MAC_init(ctx, NULL, 0, NULL) == 1; + if (err != 0) { + PRINT_ERR_MSG("CMAC restarted with a key that was never set"); + } + } + if ((err == 0) && (EVP_MAC_update(ctx, msg, sizeof(msg)) == 1) && + (EVP_MAC_final(ctx, mac, &macSz, sizeof(mac)) == 1)) { + PRINT_ERR_MSG("CMAC produced a MAC from a key that was never set"); + err = 1; + } + + /* EVP_MAC_final on its own must fail the same way. */ + if (err == 0) { + EVP_MAC_CTX_free(ctx); + err = (ctx = EVP_MAC_CTX_new(emac)) == NULL; + } + if (err == 0) { + err = EVP_MAC_CTX_set_params(ctx, cipherOnly) != 1; + } + if (err == 0) { + macSz = sizeof(mac); + err = EVP_MAC_final(ctx, mac, &macSz, sizeof(mac)) == 1; + if (err != 0) { + PRINT_ERR_MSG("CMAC finalized with a key that was never set"); + } + } + + EVP_MAC_CTX_free(ctx); + EVP_MAC_free(emac); + return err; +} + +int test_cmac_reinit(void *data) +{ + int err; + unsigned char osslMacA[AES_BLOCK_SIZE]; + unsigned char osslMacB[AES_BLOCK_SIZE]; + unsigned char wpMacA[AES_BLOCK_SIZE]; + unsigned char wpMacB[AES_BLOCK_SIZE]; + + (void)data; + + PRINT_MSG("CMAC context reset with OpenSSL"); + err = test_cmac_reinit_helper(osslLibCtx, osslMacA, osslMacB); + if (err == 0) { + PRINT_MSG("CMAC context reset with wolfProvider"); + err = test_cmac_reinit_helper(wpLibCtx, wpMacA, wpMacB); + } + if ((err == 0) && (memcmp(osslMacA, wpMacA, AES_BLOCK_SIZE) != 0)) { + PRINT_ERR_MSG("First CMAC doesn't match OpenSSL"); + err = 1; + } + if ((err == 0) && (memcmp(osslMacB, wpMacB, AES_BLOCK_SIZE) != 0)) { + PRINT_ERR_MSG("CMAC after reset doesn't match OpenSSL"); + err = 1; + } + if (err == 0) { + PRINT_MSG("CMAC keyless restart with a rejected key, OpenSSL"); + err = test_cmac_reinit_stale_key(osslLibCtx); + } + if (err == 0) { + PRINT_MSG("CMAC keyless restart with a rejected key, wolfProvider"); + err = test_cmac_reinit_stale_key(wpLibCtx); + } + return err; +} + int test_cmac_dup(void *data) { int ret = 0; diff --git a/test/unit.c b/test/unit.c index 26f42804..82d55ba9 100644 --- a/test/unit.c +++ b/test/unit.c @@ -231,6 +231,7 @@ TEST_CASE test_case[] = { #ifdef WP_HAVE_CMAC TEST_DECL(test_cmac_create, &flags), TEST_DECL(test_cmac_multi_update, &flags), + TEST_DECL(test_cmac_reinit, &flags), TEST_DECL(test_cmac_dup, &flags), TEST_DECL(test_cmac_size_query, &flags), TEST_DECL(test_cmac_large_buffer, &flags), diff --git a/test/unit.h b/test/unit.h index 9e2cf097..c75c4aa7 100644 --- a/test/unit.h +++ b/test/unit.h @@ -166,6 +166,7 @@ int test_mac_sig_dup(void *data); #ifdef WP_HAVE_CMAC int test_cmac_create(void *data); int test_cmac_multi_update(void *data); +int test_cmac_reinit(void *data); int test_cmac_dup(void *data); int test_cmac_size_query(void *data); int test_cmac_large_buffer(void *data);