Skip to content

Commit 4f8d057

Browse files
committed
validate aead tag length against algorithm's allowed sizes (gcm/ccm)
1 parent 3032fbe commit 4f8d057

4 files changed

Lines changed: 226 additions & 0 deletions

File tree

src/wp_aes_aead.c

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,37 @@ static int wp_aead_get_ctx_params(wp_AeadCtx* ctx, OSSL_PARAM params[])
458458
return ok;
459459
}
460460

461+
/**
462+
* Check whether a tag length is one of the algorithm's allowed sizes.
463+
*
464+
* GCM tags must be 4, 8, 12, 13, 14, 15 or 16 bytes (NIST SP 800-38D).
465+
* CCM tags must be 4, 6, 8, 10, 12, 14 or 16 bytes (NIST SP 800-38C /
466+
* RFC 3610).
467+
*
468+
* @param [in] mode Cipher mode: EVP_CIPH_GCM_MODE or EVP_CIPH_CCM_MODE.
469+
* @param [in] sz Tag length in bytes to check.
470+
* @return 1 if sz is an allowed length for mode.
471+
* @return 0 otherwise.
472+
*/
473+
static int wp_aead_tag_len_valid(int mode, size_t sz)
474+
{
475+
int ok;
476+
477+
if (mode == EVP_CIPH_GCM_MODE) {
478+
ok = (sz == 4) || (sz == 8) || (sz == 12) || (sz == 13) ||
479+
(sz == 14) || (sz == 15) || (sz == 16);
480+
}
481+
else if (mode == EVP_CIPH_CCM_MODE) {
482+
ok = (sz == 4) || (sz == 6) || (sz == 8) || (sz == 10) ||
483+
(sz == 12) || (sz == 14) || (sz == 16);
484+
}
485+
else {
486+
ok = 0;
487+
}
488+
489+
return ok;
490+
}
491+
461492
/**
462493
* Set the AEAD tag from the parameters.
463494
*
@@ -488,6 +519,9 @@ static int wp_aead_set_param_tag(wp_AeadCtx* ctx,
488519
if (ok && ((sz == 0) || ((p->data != NULL) && ctx->enc))) {
489520
ok = 0;
490521
}
522+
if (ok && !wp_aead_tag_len_valid(ctx->mode, sz)) {
523+
ok = 0;
524+
}
491525
if (ok) {
492526
ctx->tagLen = sz;
493527
}

test/test_aestag.c

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1484,6 +1484,91 @@ int test_aes_gcm_tls_iv_fixed_oversized(void *data)
14841484
return err;
14851485
}
14861486

1487+
/*
1488+
* Test that GCM tags smaller than the 32-bit minimum are rejected.
1489+
*/
1490+
static int test_aes_gcm_tag_len_undersized_helper(OSSL_LIB_CTX *libCtx,
1491+
const char *cipherName, int keyLen)
1492+
{
1493+
int err;
1494+
EVP_CIPHER *cipher = NULL;
1495+
EVP_CIPHER_CTX *ctx = NULL;
1496+
unsigned char key[32];
1497+
unsigned char iv[12];
1498+
unsigned char aad[] = "additional data";
1499+
unsigned char pt[] = "GCM plaintext for tag length test";
1500+
int ptLen = (int)(sizeof(pt) - 1);
1501+
unsigned char ct[64];
1502+
unsigned char tag[16];
1503+
int outLen = 0, fLen = 0;
1504+
1505+
memset(key, 0xAA, keyLen);
1506+
memset(iv, 0xBB, sizeof(iv));
1507+
1508+
cipher = EVP_CIPHER_fetch(libCtx, cipherName, "");
1509+
err = cipher == NULL;
1510+
1511+
/* Encrypt normally to get a valid ciphertext/full-size tag pair. */
1512+
if (err == 0) {
1513+
ctx = EVP_CIPHER_CTX_new();
1514+
err = ctx == NULL;
1515+
}
1516+
if (err == 0) {
1517+
err = EVP_EncryptInit(ctx, cipher, key, iv) != 1;
1518+
}
1519+
if (err == 0) {
1520+
err = EVP_EncryptUpdate(ctx, NULL, &outLen, aad,
1521+
(int)(sizeof(aad) - 1)) != 1;
1522+
}
1523+
if (err == 0) {
1524+
err = EVP_EncryptUpdate(ctx, ct, &outLen, pt, ptLen) != 1;
1525+
}
1526+
if (err == 0) {
1527+
err = EVP_EncryptFinal_ex(ctx, ct + outLen, &fLen) != 1;
1528+
outLen += fLen;
1529+
}
1530+
if (err == 0) {
1531+
err = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, sizeof(tag),
1532+
tag) != 1;
1533+
}
1534+
EVP_CIPHER_CTX_free(ctx);
1535+
ctx = NULL;
1536+
1537+
/* A 1-byte tag length is below the NIST-mandated minimum (4 bytes)
1538+
* and must be rejected here. */
1539+
if (err == 0) {
1540+
ctx = EVP_CIPHER_CTX_new();
1541+
err = ctx == NULL;
1542+
}
1543+
if (err == 0) {
1544+
err = EVP_DecryptInit(ctx, cipher, NULL, NULL) != 1;
1545+
}
1546+
if (err == 0) {
1547+
if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 1, tag) == 1) {
1548+
PRINT_ERR_MSG("%s: EVP_CTRL_AEAD_SET_TAG incorrectly accepted "
1549+
"a 1-byte tag length", cipherName);
1550+
err = 1;
1551+
}
1552+
}
1553+
1554+
EVP_CIPHER_CTX_free(ctx);
1555+
EVP_CIPHER_free(cipher);
1556+
return err;
1557+
}
1558+
1559+
int test_aes_gcm_tag_len_undersized(void *data)
1560+
{
1561+
int err;
1562+
1563+
(void)data;
1564+
1565+
PRINT_MSG("AES-128-GCM undersized tag length rejection");
1566+
err = test_aes_gcm_tag_len_undersized_helper(wpLibCtx, "AES-128-GCM",
1567+
16);
1568+
1569+
return err;
1570+
}
1571+
14871572
#endif /* WP_HAVE_AESGCM */
14881573

14891574
/******************************************************************************/
@@ -1669,5 +1754,108 @@ int test_aes_ccm_bad_tag(void *data)
16691754
return err;
16701755
}
16711756

1757+
/*
1758+
* Test that CCM tags are at least 32 bits.
1759+
*/
1760+
static int test_aes_ccm_tag_len_undersized_helper(OSSL_LIB_CTX *libCtx,
1761+
const char *cipherName, int keyLen)
1762+
{
1763+
int err;
1764+
EVP_CIPHER *cipher = NULL;
1765+
EVP_CIPHER_CTX *ctx = NULL;
1766+
unsigned char key[32];
1767+
unsigned char iv[13];
1768+
unsigned char aad[] = "additional data";
1769+
unsigned char pt[] = "CCM plaintext for tag length test";
1770+
int ptLen = (int)(sizeof(pt) - 1);
1771+
unsigned char ct[64];
1772+
unsigned char tag[16];
1773+
int outLen = 0, fLen = 0;
1774+
1775+
memset(key, 0xAA, keyLen);
1776+
memset(iv, 0xBB, sizeof(iv));
1777+
1778+
cipher = EVP_CIPHER_fetch(libCtx, cipherName, "");
1779+
err = cipher == NULL;
1780+
1781+
/* Encrypt normally to get a valid ciphertext/full-size tag pair. */
1782+
if (err == 0) {
1783+
ctx = EVP_CIPHER_CTX_new();
1784+
err = ctx == NULL;
1785+
}
1786+
if (err == 0) {
1787+
err = EVP_EncryptInit(ctx, cipher, NULL, NULL) != 1;
1788+
}
1789+
if (err == 0) {
1790+
err = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN,
1791+
(int)sizeof(iv), NULL) != 1;
1792+
}
1793+
if (err == 0) {
1794+
err = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, sizeof(tag),
1795+
NULL) != 1;
1796+
}
1797+
if (err == 0) {
1798+
err = EVP_EncryptInit(ctx, NULL, key, iv) != 1;
1799+
}
1800+
if (err == 0) {
1801+
err = EVP_EncryptUpdate(ctx, NULL, &outLen, NULL, ptLen) != 1;
1802+
}
1803+
if (err == 0) {
1804+
err = EVP_EncryptUpdate(ctx, NULL, &outLen, aad,
1805+
(int)(sizeof(aad) - 1)) != 1;
1806+
}
1807+
if (err == 0) {
1808+
err = EVP_EncryptUpdate(ctx, ct, &outLen, pt, ptLen) != 1;
1809+
}
1810+
if (err == 0) {
1811+
err = EVP_EncryptFinal_ex(ctx, ct + outLen, &fLen) != 1;
1812+
outLen += fLen;
1813+
}
1814+
if (err == 0) {
1815+
err = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, sizeof(tag),
1816+
tag) != 1;
1817+
}
1818+
EVP_CIPHER_CTX_free(ctx);
1819+
ctx = NULL;
1820+
1821+
/* A 1-byte tag length is below the NIST-mandated minimum (4 bytes)
1822+
* and must be rejected here. */
1823+
if (err == 0) {
1824+
ctx = EVP_CIPHER_CTX_new();
1825+
err = ctx == NULL;
1826+
}
1827+
if (err == 0) {
1828+
err = EVP_DecryptInit(ctx, cipher, NULL, NULL) != 1;
1829+
}
1830+
if (err == 0) {
1831+
err = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN,
1832+
(int)sizeof(iv), NULL) != 1;
1833+
}
1834+
if (err == 0) {
1835+
if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 1, tag) == 1) {
1836+
PRINT_ERR_MSG("%s: EVP_CTRL_AEAD_SET_TAG incorrectly accepted "
1837+
"a 1-byte tag length", cipherName);
1838+
err = 1;
1839+
}
1840+
}
1841+
1842+
EVP_CIPHER_CTX_free(ctx);
1843+
EVP_CIPHER_free(cipher);
1844+
return err;
1845+
}
1846+
1847+
int test_aes_ccm_tag_len_undersized(void *data)
1848+
{
1849+
int err;
1850+
1851+
(void)data;
1852+
1853+
PRINT_MSG("AES-128-CCM undersized tag length rejection");
1854+
err = test_aes_ccm_tag_len_undersized_helper(wpLibCtx, "AES-128-CCM",
1855+
16);
1856+
1857+
return err;
1858+
}
1859+
16721860
#endif /* WP_HAVE_AESCCM */
16731861

test/unit.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ TEST_CASE test_case[] = {
289289
TEST_DECL(test_aes128_gcm_key_then_iv, NULL),
290290
TEST_DECL(test_aes_gcm_bad_tag, NULL),
291291
TEST_DECL(test_aes_gcm_tls_iv_fixed_oversized, NULL),
292+
TEST_DECL(test_aes_gcm_tag_len_undersized, NULL),
292293
#endif
293294
#ifdef WP_HAVE_AESCCM
294295
TEST_DECL(test_aes128_ccm, NULL),
@@ -297,6 +298,7 @@ TEST_CASE test_case[] = {
297298
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
298299
TEST_DECL(test_aes128_ccm_tls, NULL),
299300
TEST_DECL(test_aes_ccm_bad_tag, NULL),
301+
TEST_DECL(test_aes_ccm_tag_len_undersized, NULL),
300302
#endif
301303
#endif
302304
#ifdef WP_HAVE_RANDOM

test/unit.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ int test_aes128_gcm_set_iv_inv(void *data);
220220
int test_aes128_gcm_key_then_iv(void *data);
221221
int test_aes_gcm_bad_tag(void *data);
222222
int test_aes_gcm_tls_iv_fixed_oversized(void *data);
223+
int test_aes_gcm_tag_len_undersized(void *data);
223224

224225
#endif /* WP_HAVE_AESGCM */
225226

@@ -230,6 +231,7 @@ int test_aes192_ccm(void *data);
230231
int test_aes256_ccm(void *data);
231232
int test_aes128_ccm_tls(void *data);
232233
int test_aes_ccm_bad_tag(void *data);
234+
int test_aes_ccm_tag_len_undersized(void *data);
233235

234236
#endif /* WP_HAVE_AESCCM */
235237

0 commit comments

Comments
 (0)