diff --git a/src/wp_aes_aead.c b/src/wp_aes_aead.c index fb0893cb..75700cfc 100644 --- a/src/wp_aes_aead.c +++ b/src/wp_aes_aead.c @@ -458,6 +458,37 @@ static int wp_aead_get_ctx_params(wp_AeadCtx* ctx, OSSL_PARAM params[]) return ok; } +/** + * Check whether a tag length is one of the algorithm's allowed sizes. + * + * GCM tags must be 4, 8, 12, 13, 14, 15 or 16 bytes (NIST SP 800-38D). + * CCM tags must be 4, 6, 8, 10, 12, 14 or 16 bytes (NIST SP 800-38C / + * RFC 3610). + * + * @param [in] mode Cipher mode: EVP_CIPH_GCM_MODE or EVP_CIPH_CCM_MODE. + * @param [in] sz Tag length in bytes to check. + * @return 1 if sz is an allowed length for mode. + * @return 0 otherwise. + */ +static int wp_aead_tag_len_valid(int mode, size_t sz) +{ + int ok; + + if (mode == EVP_CIPH_GCM_MODE) { + ok = (sz == 4) || (sz == 8) || (sz == 12) || (sz == 13) || + (sz == 14) || (sz == 15) || (sz == 16); + } + else if (mode == EVP_CIPH_CCM_MODE) { + ok = (sz == 4) || (sz == 6) || (sz == 8) || (sz == 10) || + (sz == 12) || (sz == 14) || (sz == 16); + } + else { + ok = 0; + } + + return ok; +} + /** * Set the AEAD tag from the parameters. * @@ -488,6 +519,9 @@ static int wp_aead_set_param_tag(wp_AeadCtx* ctx, if (ok && ((sz == 0) || ((p->data != NULL) && ctx->enc))) { ok = 0; } + if (ok && !wp_aead_tag_len_valid(ctx->mode, sz)) { + ok = 0; + } if (ok) { ctx->tagLen = sz; } @@ -959,9 +993,10 @@ static int wp_aesgcm_tls_iv_set_fixed(wp_AeadCtx* ctx, unsigned char* iv, } else { /* Fixed field must be at least 4 bytes and invocation field at least 8 - */ - if ((len < EVP_GCM_TLS_FIXED_IV_LEN) || - (ctx->ivLen - (int)len) < EVP_GCM_TLS_EXPLICIT_IV_LEN) { + * bytes */ + if ((len < EVP_GCM_TLS_FIXED_IV_LEN) || (len > ctx->ivLen) || + (len > sizeof(ctx->iv)) || + (ctx->ivLen - len) < EVP_GCM_TLS_EXPLICIT_IV_LEN) { return 0; } if (ctx->enc) { diff --git a/test/test_aestag.c b/test/test_aestag.c index ee9e2062..90edb00c 100644 --- a/test/test_aestag.c +++ b/test/test_aestag.c @@ -1416,6 +1416,159 @@ int test_aes_gcm_bad_tag(void *data) return err; } +static int test_aes_gcm_tls_iv_fixed_oversized_helper(OSSL_LIB_CTX *libCtx, + const char *cipherName, int keyLen) +{ + int err; + EVP_CIPHER *cipher = NULL; + EVP_CIPHER_CTX *ctx = NULL; + unsigned char key[32]; + /* ivLen (12, the GCM default) + EVP_GCM_TLS_EXPLICIT_IV_LEN (8) = 20: */ + unsigned char iv[20]; + + memset(key, 0xCC, keyLen); + memset(iv, 0xDD, sizeof(iv)); + + cipher = EVP_CIPHER_fetch(libCtx, cipherName, ""); + err = cipher == NULL; + + /* A fixed len that leaves exactly the 8-byte + * explicit/invocation field must still be accepted. */ + if (err == 0) { + ctx = EVP_CIPHER_CTX_new(); + err = ctx == NULL; + } + if (err == 0) { + err = EVP_DecryptInit_ex(ctx, cipher, NULL, key, NULL) != 1; + } + if (err == 0) { + err = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IV_FIXED, + EVP_GCM_TLS_FIXED_IV_LEN, iv) != 1; + } + EVP_CIPHER_CTX_free(ctx); + ctx = NULL; + + /* Oversized fixed len (> ctx->ivLen, default 12) must be rejected. */ + if (err == 0) { + ctx = EVP_CIPHER_CTX_new(); + err = ctx == NULL; + } + if (err == 0) { + err = EVP_DecryptInit_ex(ctx, cipher, NULL, key, NULL) != 1; + } + if (err == 0) { + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IV_FIXED, + (int)sizeof(iv), iv) == 1) { + PRINT_ERR_MSG("%s: EVP_CTRL_GCM_SET_IV_FIXED incorrectly " + "accepted a fixed IV length (%d) larger than " + "the IV length", cipherName, (int)sizeof(iv)); + err = 1; + } + } + + EVP_CIPHER_CTX_free(ctx); + EVP_CIPHER_free(cipher); + return err; +} + +int test_aes_gcm_tls_iv_fixed_oversized(void *data) +{ + int err; + + (void)data; + + PRINT_MSG("AES-128-GCM TLS1 fixed-IV oversized length rejection"); + err = test_aes_gcm_tls_iv_fixed_oversized_helper(wpLibCtx, + "AES-128-GCM", 16); + + return err; +} + +/* + * Test that GCM tags smaller than the 32-bit minimum are rejected. + */ +static int test_aes_gcm_tag_len_undersized_helper(OSSL_LIB_CTX *libCtx, + const char *cipherName, int keyLen) +{ + int err; + EVP_CIPHER *cipher = NULL; + EVP_CIPHER_CTX *ctx = NULL; + unsigned char key[32]; + unsigned char iv[12]; + unsigned char aad[] = "additional data"; + unsigned char pt[] = "GCM plaintext for tag length test"; + int ptLen = (int)(sizeof(pt) - 1); + unsigned char ct[64]; + unsigned char tag[16]; + int outLen = 0, fLen = 0; + + memset(key, 0xAA, keyLen); + memset(iv, 0xBB, sizeof(iv)); + + cipher = EVP_CIPHER_fetch(libCtx, cipherName, ""); + err = cipher == NULL; + + /* Encrypt normally to get a valid ciphertext/full-size tag pair. */ + if (err == 0) { + ctx = EVP_CIPHER_CTX_new(); + err = ctx == NULL; + } + if (err == 0) { + err = EVP_EncryptInit(ctx, cipher, key, iv) != 1; + } + if (err == 0) { + err = EVP_EncryptUpdate(ctx, NULL, &outLen, aad, + (int)(sizeof(aad) - 1)) != 1; + } + if (err == 0) { + err = EVP_EncryptUpdate(ctx, ct, &outLen, pt, ptLen) != 1; + } + if (err == 0) { + err = EVP_EncryptFinal_ex(ctx, ct + outLen, &fLen) != 1; + outLen += fLen; + } + if (err == 0) { + err = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, sizeof(tag), + tag) != 1; + } + EVP_CIPHER_CTX_free(ctx); + ctx = NULL; + + /* A 1-byte tag length is below the NIST-mandated minimum (4 bytes) + * and must be rejected here. */ + if (err == 0) { + ctx = EVP_CIPHER_CTX_new(); + err = ctx == NULL; + } + if (err == 0) { + err = EVP_DecryptInit(ctx, cipher, NULL, NULL) != 1; + } + if (err == 0) { + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 1, tag) == 1) { + PRINT_ERR_MSG("%s: EVP_CTRL_AEAD_SET_TAG incorrectly accepted " + "a 1-byte tag length", cipherName); + err = 1; + } + } + + EVP_CIPHER_CTX_free(ctx); + EVP_CIPHER_free(cipher); + return err; +} + +int test_aes_gcm_tag_len_undersized(void *data) +{ + int err; + + (void)data; + + PRINT_MSG("AES-128-GCM undersized tag length rejection"); + err = test_aes_gcm_tag_len_undersized_helper(wpLibCtx, "AES-128-GCM", + 16); + + return err; +} + #endif /* WP_HAVE_AESGCM */ /******************************************************************************/ @@ -1601,5 +1754,108 @@ int test_aes_ccm_bad_tag(void *data) return err; } +/* + * Test that CCM tags are at least 32 bits. + */ +static int test_aes_ccm_tag_len_undersized_helper(OSSL_LIB_CTX *libCtx, + const char *cipherName, int keyLen) +{ + int err; + EVP_CIPHER *cipher = NULL; + EVP_CIPHER_CTX *ctx = NULL; + unsigned char key[32]; + unsigned char iv[13]; + unsigned char aad[] = "additional data"; + unsigned char pt[] = "CCM plaintext for tag length test"; + int ptLen = (int)(sizeof(pt) - 1); + unsigned char ct[64]; + unsigned char tag[16]; + int outLen = 0, fLen = 0; + + memset(key, 0xAA, keyLen); + memset(iv, 0xBB, sizeof(iv)); + + cipher = EVP_CIPHER_fetch(libCtx, cipherName, ""); + err = cipher == NULL; + + /* Encrypt normally to get a valid ciphertext/full-size tag pair. */ + if (err == 0) { + ctx = EVP_CIPHER_CTX_new(); + err = ctx == NULL; + } + if (err == 0) { + err = EVP_EncryptInit(ctx, cipher, NULL, NULL) != 1; + } + if (err == 0) { + err = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, + (int)sizeof(iv), NULL) != 1; + } + if (err == 0) { + err = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, sizeof(tag), + NULL) != 1; + } + if (err == 0) { + err = EVP_EncryptInit(ctx, NULL, key, iv) != 1; + } + if (err == 0) { + err = EVP_EncryptUpdate(ctx, NULL, &outLen, NULL, ptLen) != 1; + } + if (err == 0) { + err = EVP_EncryptUpdate(ctx, NULL, &outLen, aad, + (int)(sizeof(aad) - 1)) != 1; + } + if (err == 0) { + err = EVP_EncryptUpdate(ctx, ct, &outLen, pt, ptLen) != 1; + } + if (err == 0) { + err = EVP_EncryptFinal_ex(ctx, ct + outLen, &fLen) != 1; + outLen += fLen; + } + if (err == 0) { + err = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, sizeof(tag), + tag) != 1; + } + EVP_CIPHER_CTX_free(ctx); + ctx = NULL; + + /* A 1-byte tag length is below the NIST-mandated minimum (4 bytes) + * and must be rejected here. */ + if (err == 0) { + ctx = EVP_CIPHER_CTX_new(); + err = ctx == NULL; + } + if (err == 0) { + err = EVP_DecryptInit(ctx, cipher, NULL, NULL) != 1; + } + if (err == 0) { + err = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, + (int)sizeof(iv), NULL) != 1; + } + if (err == 0) { + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 1, tag) == 1) { + PRINT_ERR_MSG("%s: EVP_CTRL_AEAD_SET_TAG incorrectly accepted " + "a 1-byte tag length", cipherName); + err = 1; + } + } + + EVP_CIPHER_CTX_free(ctx); + EVP_CIPHER_free(cipher); + return err; +} + +int test_aes_ccm_tag_len_undersized(void *data) +{ + int err; + + (void)data; + + PRINT_MSG("AES-128-CCM undersized tag length rejection"); + err = test_aes_ccm_tag_len_undersized_helper(wpLibCtx, "AES-128-CCM", + 16); + + return err; +} + #endif /* WP_HAVE_AESCCM */ diff --git a/test/unit.c b/test/unit.c index e116f9b5..cb02a4b9 100644 --- a/test/unit.c +++ b/test/unit.c @@ -288,6 +288,8 @@ TEST_CASE test_case[] = { TEST_DECL(test_aes128_gcm_set_iv_inv, NULL), TEST_DECL(test_aes128_gcm_key_then_iv, NULL), TEST_DECL(test_aes_gcm_bad_tag, NULL), + TEST_DECL(test_aes_gcm_tls_iv_fixed_oversized, NULL), + TEST_DECL(test_aes_gcm_tag_len_undersized, NULL), #endif #ifdef WP_HAVE_AESCCM TEST_DECL(test_aes128_ccm, NULL), @@ -296,6 +298,7 @@ TEST_CASE test_case[] = { #if OPENSSL_VERSION_NUMBER >= 0x10100000L TEST_DECL(test_aes128_ccm_tls, NULL), TEST_DECL(test_aes_ccm_bad_tag, NULL), + TEST_DECL(test_aes_ccm_tag_len_undersized, NULL), #endif #endif #ifdef WP_HAVE_RANDOM diff --git a/test/unit.h b/test/unit.h index 9aab6828..26b6349d 100644 --- a/test/unit.h +++ b/test/unit.h @@ -219,6 +219,8 @@ int test_aes128_gcm_tls(void *data); int test_aes128_gcm_set_iv_inv(void *data); int test_aes128_gcm_key_then_iv(void *data); int test_aes_gcm_bad_tag(void *data); +int test_aes_gcm_tls_iv_fixed_oversized(void *data); +int test_aes_gcm_tag_len_undersized(void *data); #endif /* WP_HAVE_AESGCM */ @@ -229,6 +231,7 @@ int test_aes192_ccm(void *data); int test_aes256_ccm(void *data); int test_aes128_ccm_tls(void *data); int test_aes_ccm_bad_tag(void *data); +int test_aes_ccm_tag_len_undersized(void *data); #endif /* WP_HAVE_AESCCM */