diff --git a/src/wp_aes_aead.c b/src/wp_aes_aead.c index f4f9018d..ae60325e 100644 --- a/src/wp_aes_aead.c +++ b/src/wp_aes_aead.c @@ -61,6 +61,8 @@ typedef struct wp_AeadCtx { /** Length of key. */ size_t keyLen; + /** Key data. */ + unsigned char key[AES_256_KEY_SIZE]; /** Length of iv/nonce. */ size_t ivLen; /** Authentication tag length. */ @@ -83,7 +85,7 @@ typedef struct wp_AeadCtx { unsigned int ivGen:1; /** CCM needs to set IV specially. */ unsigned int ivSet:1; - /** GCM needs to know if key has been set. */ + /** Key has been set. */ unsigned int keySet:1; /** Cache of authentication status. */ unsigned int authErr:1; @@ -132,6 +134,106 @@ typedef struct wp_AeadCtx { /** Uninitialized value for a field of type size_t. */ #define UNINITIALISED_SIZET ((size_t)-1) +/** + * Save the key for dupctx. + * + * @param [in, out] ctx AEAD context object. + * @param [in] key Key data. + * @param [in] keyLen Length of key in bytes. + */ +static void wp_aead_save_key(wp_AeadCtx* ctx, const unsigned char* key, + size_t keyLen) +{ + XMEMCPY(ctx->key, key, keyLen); + ctx->keySet = 1; +} + +/** + * Duplicate the AEAD context object. + * + * @param [in] src AEAD context object to copy. + * @return NULL on failure. + * @return AEAD context object. + */ +static void *wp_aead_dupctx(wp_AeadCtx* src) +{ + wp_AeadCtx* dst = NULL; + int ok = 1; + + if (!wolfssl_prov_is_running()) { + ok = 0; + } +#if defined(WP_HAVE_AESGCM) && defined(WOLFSSL_AESGCM_STREAM) + /* wolfCrypt's GCM stream state can't be copied. */ + if (ok && (src->mode == EVP_CIPH_GCM_MODE) && + (src->ivState == IV_STATE_COPIED)) { + ok = 0; + } +#endif + if (ok) { + dst = OPENSSL_malloc(sizeof(*dst)); + if (dst == NULL) { + ok = 0; + } + } + if (ok) { + XMEMCPY(dst, src, sizeof(*dst)); + dst->aad = NULL; + dst->aadLen = 0; +#if defined(WP_HAVE_AESGCM) && !defined(WOLFSSL_AESGCM_STREAM) + dst->in = NULL; + dst->inLen = 0; + dst->bufSize = 0; +#endif + XMEMSET(&dst->aes, 0, sizeof(dst->aes)); + if (wc_AesInit(&dst->aes, NULL, INVALID_DEVID) != 0) { + ok = 0; + } + } +#ifdef WP_HAVE_AESGCM + if (ok && src->keySet && (src->mode == EVP_CIPH_GCM_MODE) && + (wc_AesGcmSetKey(&dst->aes, src->key, (word32)src->keyLen) != 0)) { + ok = 0; + } +#endif +#ifdef WP_HAVE_AESCCM + if (ok && src->keySet && (src->mode == EVP_CIPH_CCM_MODE) && + (wc_AesCcmSetKey(&dst->aes, src->key, (word32)src->keyLen) != 0)) { + ok = 0; + } +#endif + if (ok && (src->aad != NULL) && (src->aadLen > 0)) { + dst->aad = OPENSSL_memdup(src->aad, src->aadLen); + if (dst->aad == NULL) { + ok = 0; + } + else { + dst->aadLen = src->aadLen; + } + } +#if defined(WP_HAVE_AESGCM) && !defined(WOLFSSL_AESGCM_STREAM) + if (ok && (src->in != NULL)) { + dst->in = OPENSSL_malloc(src->bufSize); + if (dst->in == NULL) { + ok = 0; + } + else { + XMEMCPY(dst->in, src->in, src->inLen); + dst->inLen = src->inLen; + dst->bufSize = src->bufSize; + } + } +#endif + if ((!ok) && (dst != NULL)) { + OPENSSL_free(dst->aad); + wc_AesFree(&dst->aes); + OPENSSL_clear_free(dst, sizeof(*dst)); + dst = NULL; + } + + return dst; +} + /** AEAD cipher flags. */ #define AEAD_FLAGS (WP_CIPHER_FLAG_AEAD | WP_CIPHER_FLAG_CUSTOM_IV) @@ -171,6 +273,7 @@ const OSSL_DISPATCH wp_aes##kbits##lc##_functions[] = { \ { OSSL_FUNC_CIPHER_NEWCTX, \ (DFUNC)wp_aes_##lc##_##kbits##lc##_newctx }, \ { OSSL_FUNC_CIPHER_FREECTX, (DFUNC)wp_aes_##lc##_freectx }, \ + { OSSL_FUNC_CIPHER_DUPCTX, (DFUNC)wp_aead_dupctx }, \ { OSSL_FUNC_CIPHER_ENCRYPT_INIT, (DFUNC)wp_aes##lc##_einit }, \ { OSSL_FUNC_CIPHER_DECRYPT_INIT, (DFUNC)wp_aes##lc##_dinit }, \ { OSSL_FUNC_CIPHER_UPDATE, (DFUNC)wp_aes##lc##_stream_update }, \ @@ -1145,6 +1248,9 @@ static int wp_aesgcm_einit(wp_AeadCtx* ctx, const unsigned char *key, if (ok) { WP_CHECK_FIPS_ALGO(WP_CAST_ALGO_AES); } + if (ok && (key != NULL) && (keyLen != ctx->keyLen)) { + ok = 0; + } #ifdef WOLFSSL_AESGCM_STREAM if (ok) { int rc; @@ -1166,6 +1272,9 @@ static int wp_aesgcm_einit(wp_AeadCtx* ctx, const unsigned char *key, WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_AesGcmSetKey", rc); ok = 0; } + else { + wp_aead_save_key(ctx, key, keyLen); + } } else if (key != NULL) { rc = wc_AesGcmEncryptInit(aes, key, (word32)keyLen, iv, (word32)ivLen); @@ -1173,6 +1282,9 @@ static int wp_aesgcm_einit(wp_AeadCtx* ctx, const unsigned char *key, WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_AesGcmEncryptInit", rc); ok = 0; } + else { + wp_aead_save_key(ctx, key, keyLen); + } } } #else @@ -1182,6 +1294,9 @@ static int wp_aesgcm_einit(wp_AeadCtx* ctx, const unsigned char *key, WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_AesGcmSetKey", rc); ok = 0; } + else { + wp_aead_save_key(ctx, key, keyLen); + } } if (ok && (iv != NULL)) { if (ivLen != ctx->ivLen) { @@ -1249,6 +1364,9 @@ static int wp_aesgcm_dinit(wp_AeadCtx *ctx, const unsigned char *key, if (ok) { WP_CHECK_FIPS_ALGO(WP_CAST_ALGO_AES); } + if (ok && (key != NULL) && (keyLen != ctx->keyLen)) { + ok = 0; + } #ifdef WOLFSSL_AESGCM_STREAM if (ok && key != NULL) { int rc = wc_AesGcmDecryptInit(aes, key, (word32)keyLen, iv, (word32)ivLen); @@ -1256,6 +1374,9 @@ static int wp_aesgcm_dinit(wp_AeadCtx *ctx, const unsigned char *key, WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_AesGcmDecryptInit", rc); ok = 0; } + else { + wp_aead_save_key(ctx, key, keyLen); + } } if (ok && (iv != NULL)) { XMEMCPY(ctx->iv, iv, ivLen); @@ -1269,6 +1390,9 @@ static int wp_aesgcm_dinit(wp_AeadCtx *ctx, const unsigned char *key, WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_AesGcmSetKey", rc); ok = 0; } + else { + wp_aead_save_key(ctx, key, keyLen); + } } if (ok && (iv != NULL)) { if (ivLen != ctx->ivLen) { @@ -1535,6 +1659,13 @@ static int wp_aesgcm_stream_final(wp_AeadCtx *ctx, unsigned char *out, if ((!done) && ok && (ctx->ivState == IV_STATE_UNINITIALISED)) { ok = 0; } + if ((!done) && ok && (ctx->ivState == IV_STATE_BUFFERED)) { + rc = wc_AesGcmInit(aes, NULL, 0, ctx->iv, (word32)ctx->ivLen); + if (rc != 0) { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_AesGcmInit", rc); + ok = 0; + } + } if ((!done) && ok) { if (outSize == 0) { @@ -1892,10 +2023,10 @@ static void wp_aes_gcm_freectx(wp_AeadCtx* ctx) { OPENSSL_free(ctx->aad); #if defined(WP_HAVE_AESGCM) && !defined(WOLFSSL_AESGCM_STREAM) - OPENSSL_free(ctx->in); + OPENSSL_clear_free(ctx->in, ctx->inLen); #endif wc_AesFree(&ctx->aes); - OPENSSL_free(ctx); + OPENSSL_clear_free(ctx, sizeof(*ctx)); } @@ -1970,12 +2101,18 @@ static int wp_aesccm_init(wp_AeadCtx* ctx, const unsigned char *key, if (ok) { WP_CHECK_FIPS_ALGO(WP_CAST_ALGO_AES); } + if (ok && (key != NULL) && (keyLen != ctx->keyLen)) { + ok = 0; + } if (ok && (key != NULL)) { rc = wc_AesCcmSetKey(&ctx->aes, key, (word32)keyLen); if (rc != 0) { WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_AesCcmSetKey", rc); ok = 0; } + else { + wp_aead_save_key(ctx, key, keyLen); + } } if (ok && (iv != NULL)) { if (ivLen != ctx->ivLen) { @@ -2411,7 +2548,7 @@ static void wp_aes_ccm_freectx(wp_AeadCtx* ctx) { OPENSSL_free(ctx->aad); wc_AesFree(&ctx->aes); - OPENSSL_free(ctx); + OPENSSL_clear_free(ctx, sizeof(*ctx)); } /* Implement AES CCM for key sizes: 128, 192 and 256 bits. */ diff --git a/test/test_aestag.c b/test/test_aestag.c index ab639e0c..80a5bf5d 100644 --- a/test/test_aestag.c +++ b/test/test_aestag.c @@ -2510,6 +2510,90 @@ int test_aes_gcm_final_no_update_no_iv(void *data) return err; } +static EVP_CIPHER_CTX *aead_base(EVP_CIPHER *cipher, int enc, int ccm, + const unsigned char *key) +{ + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + + if (ctx != NULL && (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, + enc) != 1 || + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, NULL) != 1 || + (ccm && EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 16, + NULL) != 1) || + EVP_CipherInit_ex(ctx, NULL, NULL, key, NULL, enc) != 1)) { + EVP_CIPHER_CTX_free(ctx); + ctx = NULL; + } + return ctx; +} + +/* pyca/cryptography copies a keyed base context for every operation. */ +static int aead_copy_op(EVP_CIPHER_CTX *base, int copy, int enc, int ccm, + int len, const unsigned char *in, unsigned char *out, unsigned char *tag) +{ + static const unsigned char iv[12] = { 1, 2, 3 }; + static const unsigned char aad[] = "aad"; + EVP_CIPHER_CTX *ctx = copy ? EVP_CIPHER_CTX_new() : base; + int n = 0, fin, err; + + err = ctx == NULL || (copy && EVP_CIPHER_CTX_copy(ctx, base) != 1) || + EVP_CipherInit_ex(ctx, NULL, NULL, NULL, iv, enc) != 1 || + (!enc && EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 16, + tag) != 1) || + (ccm && EVP_CipherUpdate(ctx, NULL, &n, NULL, len) != 1) || + (len > 0 && (EVP_CipherUpdate(ctx, NULL, &n, aad, sizeof(aad)) != 1 || + EVP_CipherUpdate(ctx, out, &n, in, len) != 1 || n != len)) || + EVP_CipherFinal_ex(ctx, out + n, &fin) != 1 || + (enc && EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, 16, tag) != 1); + if (copy) { + EVP_CIPHER_CTX_free(ctx); + } + return err; +} + +int test_aes_aead_ctx_copy(void *data) +{ + static const char *names[] = { "AES-128-GCM", "AES-192-GCM", "AES-256-GCM", +#ifdef WP_HAVE_AESCCM + "AES-128-CCM", "AES-192-CCM", "AES-256-CCM", +#endif + }; + unsigned char key[32] = { 4, 5, 6 }, pt[32] = { 7, 8, 9 }; + unsigned char ct[2][32], tag[2][16], dec[32]; + EVP_CIPHER *cipher[2]; + EVP_CIPHER_CTX *base[2], *ref; + int i, j, ccm, len, err = 0; + + (void)data; + + for (i = 0; i < (int)(sizeof(names) / sizeof(*names)) && !err; i++) { + PRINT_MSG("%s: copy keyed context", names[i]); + ccm = strstr(names[i], "CCM") != NULL; + cipher[0] = EVP_CIPHER_fetch(osslLibCtx, names[i], ""); + cipher[1] = EVP_CIPHER_fetch(wpLibCtx, names[i], ""); + base[0] = aead_base(cipher[1], 1, ccm, key); + base[1] = aead_base(cipher[1], 0, ccm, key); + /* Round two copies the bases again; GCM goes straight to Final. */ + for (j = 0; j < 2 && !err; j++) { + len = (ccm || j == 0) ? 32 : 0; + /* Default can't copy GCM/CCM before 3.0.13/3.1.5. */ + ref = aead_base(cipher[0], 1, ccm, key); + err = aead_copy_op(ref, 0, 1, ccm, len, pt, ct[0], tag[0]) || + aead_copy_op(base[0], 1, 1, ccm, len, pt, ct[1], tag[1]) || + memcmp(ct[0], ct[1], len) != 0 || + memcmp(tag[0], tag[1], 16) != 0 || + aead_copy_op(base[1], 1, 0, ccm, len, ct[1], dec, tag[1]) || + memcmp(dec, pt, len) != 0; + EVP_CIPHER_CTX_free(ref); + } + EVP_CIPHER_CTX_free(base[0]); + EVP_CIPHER_CTX_free(base[1]); + EVP_CIPHER_free(cipher[0]); + EVP_CIPHER_free(cipher[1]); + } + return err; +} + #endif /* WP_HAVE_AESGCM */ /******************************************************************************/ diff --git a/test/unit.c b/test/unit.c index be7f72a2..4299320f 100644 --- a/test/unit.c +++ b/test/unit.c @@ -337,6 +337,7 @@ TEST_CASE test_case[] = { TEST_DECL(test_aes_gcm_key_no_iv, NULL), TEST_DECL(test_aes_gcm_stream_key_no_iv, NULL), TEST_DECL(test_aes_gcm_final_no_update_no_iv, NULL), + TEST_DECL(test_aes_aead_ctx_copy, NULL), #endif #ifdef WP_HAVE_AESCCM TEST_DECL(test_aes128_ccm, NULL), diff --git a/test/unit.h b/test/unit.h index 35bc245b..8f0d3b62 100644 --- a/test/unit.h +++ b/test/unit.h @@ -298,6 +298,7 @@ int test_aes_gcm_tag_len_undersized(void *data); int test_aes_gcm_key_no_iv(void *data); int test_aes_gcm_stream_key_no_iv(void *data); int test_aes_gcm_final_no_update_no_iv(void *data); +int test_aes_aead_ctx_copy(void *data); #endif /* WP_HAVE_AESGCM */