7e1ffc
From 26b2caef673aba8bfd10db3b1b8117f941c18e58 Mon Sep 17 00:00:00 2001
7e1ffc
From: Daiki Ueno <ueno@gnu.org>
7e1ffc
Date: Fri, 21 Oct 2022 15:48:39 +0900
7e1ffc
Subject: [PATCH] cipher: add restriction on CCM tag length under FIPS mode
7e1ffc
7e1ffc
This change prohibits any use of tag length other than 4, 6, 8, 10,
7e1ffc
12, 14, and 16 bytes in CCM used under FIPS mode, in accordance with
7e1ffc
SP800-38C A.1.  While use of tag lengths smaller than 8 bytes is not
7e1ffc
recommended, we simply allow 4 and 6 bytes tags for now.
7e1ffc
7e1ffc
Signed-off-by: Daiki Ueno <ueno@gnu.org>
7e1ffc
---
7e1ffc
 lib/accelerated/aarch64/aes-ccm-aarch64.c | 39 ++++++++++
7e1ffc
 lib/accelerated/x86/aes-ccm-x86-aesni.c   | 39 ++++++++++
7e1ffc
 lib/nettle/cipher.c                       | 55 ++++++++++++++
7e1ffc
 tests/fips-test.c                         | 87 ++++++++++++++++++++++-
7e1ffc
 4 files changed, 218 insertions(+), 2 deletions(-)
7e1ffc
7e1ffc
diff --git a/lib/accelerated/aarch64/aes-ccm-aarch64.c b/lib/accelerated/aarch64/aes-ccm-aarch64.c
7e1ffc
index a2ba259e99..b415d4ddfb 100644
7e1ffc
--- a/lib/accelerated/aarch64/aes-ccm-aarch64.c
7e1ffc
+++ b/lib/accelerated/aarch64/aes-ccm-aarch64.c
7e1ffc
@@ -36,6 +36,7 @@
7e1ffc
 #include <byteswap.h>
7e1ffc
 #include <nettle/ccm.h>
7e1ffc
 #include <aes-aarch64.h>
7e1ffc
+#include <fips.h>
7e1ffc
 
7e1ffc
 typedef struct ccm_aarch64_aes_ctx {
7e1ffc
 	AES_KEY key;
7e1ffc
@@ -103,6 +104,25 @@ aes_ccm_aead_encrypt(void *_ctx,
7e1ffc
 	if (unlikely(encr_size < plain_size + tag_size))
7e1ffc
 		return gnutls_assert_val(GNUTLS_E_SHORT_MEMORY_BUFFER);
7e1ffc
 
7e1ffc
+	/* SP800-38C A.1 says Tlen must be a multiple of 16 between 32
7e1ffc
+	 * and 128.
7e1ffc
+	 */
7e1ffc
+	switch (tag_size) {
7e1ffc
+	case 4: case 6:
7e1ffc
+		/* SP800-38C B.2 says Tlen smaller than 64 should not be used
7e1ffc
+		 * under sufficient restriction. We simply allow those for now.
7e1ffc
+		 */
7e1ffc
+		FALLTHROUGH;
7e1ffc
+	case 8: case 10: case 12: case 14: case 16:
7e1ffc
+		break;
7e1ffc
+	default:
7e1ffc
+		if (_gnutls_fips_mode_enabled()) {
7e1ffc
+			_gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR);
7e1ffc
+			return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
7e1ffc
+		}
7e1ffc
+		break;
7e1ffc
+	}
7e1ffc
+
7e1ffc
 	ccm_encrypt_message(&ctx->key, aarch64_aes_encrypt,
7e1ffc
 			    nonce_size, nonce,
7e1ffc
 			    auth_size, auth,
7e1ffc
@@ -129,6 +149,25 @@ aes_ccm_aead_decrypt(void *_ctx,
7e1ffc
 	if (unlikely(plain_size < encr_size - tag_size))
7e1ffc
 		return gnutls_assert_val(GNUTLS_E_SHORT_MEMORY_BUFFER);
7e1ffc
 
7e1ffc
+	/* SP800-38C A.1 says Tlen must be a multiple of 16 between 32
7e1ffc
+	 * and 128.
7e1ffc
+	 */
7e1ffc
+	switch (tag_size) {
7e1ffc
+	case 4: case 6:
7e1ffc
+		/* SP800-38C B.2 says Tlen smaller than 64 should not be used
7e1ffc
+		 * under sufficient restriction. We simply allow those for now.
7e1ffc
+		 */
7e1ffc
+		FALLTHROUGH;
7e1ffc
+	case 8: case 10: case 12: case 14: case 16:
7e1ffc
+		break;
7e1ffc
+	default:
7e1ffc
+		if (_gnutls_fips_mode_enabled()) {
7e1ffc
+			_gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR);
7e1ffc
+			return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
7e1ffc
+		}
7e1ffc
+		break;
7e1ffc
+	}
7e1ffc
+
7e1ffc
 	ret = ccm_decrypt_message(&ctx->key, aarch64_aes_encrypt,
7e1ffc
 				  nonce_size, nonce,
7e1ffc
 				  auth_size, auth,
7e1ffc
diff --git a/lib/accelerated/x86/aes-ccm-x86-aesni.c b/lib/accelerated/x86/aes-ccm-x86-aesni.c
7e1ffc
index 701c0f992a..9ebbdd7b2a 100644
7e1ffc
--- a/lib/accelerated/x86/aes-ccm-x86-aesni.c
7e1ffc
+++ b/lib/accelerated/x86/aes-ccm-x86-aesni.c
7e1ffc
@@ -37,6 +37,7 @@
7e1ffc
 #include <byteswap.h>
7e1ffc
 #include <nettle/ccm.h>
7e1ffc
 #include <aes-x86.h>
7e1ffc
+#include <fips.h>
7e1ffc
 
7e1ffc
 typedef struct ccm_x86_aes_ctx {
7e1ffc
 	AES_KEY key;
7e1ffc
@@ -95,6 +96,25 @@ aes_ccm_aead_encrypt(void *_ctx,
7e1ffc
 	if (unlikely(encr_size < plain_size + tag_size))
7e1ffc
 		return gnutls_assert_val(GNUTLS_E_SHORT_MEMORY_BUFFER);
7e1ffc
 
7e1ffc
+	/* SP800-38C A.1 says Tlen must be a multiple of 16 between 32
7e1ffc
+	 * and 128.
7e1ffc
+	 */
7e1ffc
+	switch (tag_size) {
7e1ffc
+	case 4: case 6:
7e1ffc
+		/* SP800-38C B.2 says Tlen smaller than 64 should not be used
7e1ffc
+		 * under sufficient restriction. We simply allow those for now.
7e1ffc
+		 */
7e1ffc
+		FALLTHROUGH;
7e1ffc
+	case 8: case 10: case 12: case 14: case 16:
7e1ffc
+		break;
7e1ffc
+	default:
7e1ffc
+		if (_gnutls_fips_mode_enabled()) {
7e1ffc
+			_gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR);
7e1ffc
+			return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
7e1ffc
+		}
7e1ffc
+		break;
7e1ffc
+	}
7e1ffc
+
7e1ffc
 	ccm_encrypt_message(&ctx->key, x86_aes_encrypt,
7e1ffc
 			    nonce_size, nonce,
7e1ffc
 			    auth_size, auth,
7e1ffc
@@ -121,6 +141,25 @@ aes_ccm_aead_decrypt(void *_ctx,
7e1ffc
 	if (unlikely(plain_size < encr_size - tag_size))
7e1ffc
 		return gnutls_assert_val(GNUTLS_E_SHORT_MEMORY_BUFFER);
7e1ffc
 
7e1ffc
+	/* SP800-38C A.1 says Tlen must be a multiple of 16 between 32
7e1ffc
+	 * and 128.
7e1ffc
+	 */
7e1ffc
+	switch (tag_size) {
7e1ffc
+	case 4: case 6:
7e1ffc
+		/* SP800-38C B.2 says Tlen smaller than 64 should not be used
7e1ffc
+		 * under sufficient restriction. We simply allow those for now.
7e1ffc
+		 */
7e1ffc
+		FALLTHROUGH;
7e1ffc
+	case 8: case 10: case 12: case 14: case 16:
7e1ffc
+		break;
7e1ffc
+	default:
7e1ffc
+		if (_gnutls_fips_mode_enabled()) {
7e1ffc
+			_gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR);
7e1ffc
+			return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
7e1ffc
+		}
7e1ffc
+		break;
7e1ffc
+	}
7e1ffc
+
7e1ffc
 	ret = ccm_decrypt_message(&ctx->key, x86_aes_encrypt,
7e1ffc
 				  nonce_size, nonce,
7e1ffc
 				  auth_size, auth,
7e1ffc
diff --git a/lib/nettle/cipher.c b/lib/nettle/cipher.c
7e1ffc
index 9c2ce19e7e..8c23d11252 100644
7e1ffc
--- a/lib/nettle/cipher.c
7e1ffc
+++ b/lib/nettle/cipher.c
7e1ffc
@@ -1253,6 +1253,34 @@ wrap_nettle_cipher_aead_encrypt(void *_ctx,
7e1ffc
 		ctx->cipher->tag(ctx->ctx_ptr, tag_size, ((uint8_t*)encr) + plain_size);
7e1ffc
 	} else {
7e1ffc
 		/* CCM-style cipher */
7e1ffc
+
7e1ffc
+		switch (ctx->cipher->algo) {
7e1ffc
+		case GNUTLS_CIPHER_AES_128_CCM:
7e1ffc
+		case GNUTLS_CIPHER_AES_256_CCM:
7e1ffc
+			/* SP800-38C A.1 says Tlen must be a multiple of 16
7e1ffc
+			 * between 32 and 128.
7e1ffc
+			 */
7e1ffc
+			switch (tag_size) {
7e1ffc
+			case 4: case 6:
7e1ffc
+				/* SP800-38C B.2 says Tlen smaller than 64
7e1ffc
+				 * should not be used under sufficient
7e1ffc
+				 * restriction. We simply allow those for now.
7e1ffc
+				 */
7e1ffc
+				FALLTHROUGH;
7e1ffc
+			case 8: case 10: case 12: case 14: case 16:
7e1ffc
+				break;
7e1ffc
+			default:
7e1ffc
+				if (_gnutls_fips_mode_enabled()) {
7e1ffc
+					_gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR);
7e1ffc
+					return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
7e1ffc
+				}
7e1ffc
+				break;
7e1ffc
+			}
7e1ffc
+			break;
7e1ffc
+		default:
7e1ffc
+			break;
7e1ffc
+		}
7e1ffc
+
7e1ffc
 		ctx->cipher->aead_encrypt(ctx,
7e1ffc
 					  nonce_size, nonce,
7e1ffc
 					  auth_size, auth,
7e1ffc
@@ -1302,6 +1330,33 @@ wrap_nettle_cipher_aead_decrypt(void *_ctx,
7e1ffc
 		if (unlikely(plain_size < encr_size))
7e1ffc
 			return gnutls_assert_val(GNUTLS_E_SHORT_MEMORY_BUFFER);
7e1ffc
 
7e1ffc
+		switch (ctx->cipher->algo) {
7e1ffc
+		case GNUTLS_CIPHER_AES_128_CCM:
7e1ffc
+		case GNUTLS_CIPHER_AES_256_CCM:
7e1ffc
+			/* SP800-38C A.1 says Tlen must be a multiple of 16
7e1ffc
+			 * between 32 and 128.
7e1ffc
+			 */
7e1ffc
+			switch (tag_size) {
7e1ffc
+			case 4: case 6:
7e1ffc
+				/* SP800-38C B.2 says Tlen smaller than 64
7e1ffc
+				 * should not be used under sufficient
7e1ffc
+				 * restriction. We simply allow those for now.
7e1ffc
+				 */
7e1ffc
+				FALLTHROUGH;
7e1ffc
+			case 8: case 10: case 12: case 14: case 16:
7e1ffc
+				break;
7e1ffc
+			default:
7e1ffc
+				if (_gnutls_fips_mode_enabled()) {
7e1ffc
+					_gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR);
7e1ffc
+					return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
7e1ffc
+				}
7e1ffc
+				break;
7e1ffc
+			}
7e1ffc
+			break;
7e1ffc
+		default:
7e1ffc
+			break;
7e1ffc
+		}
7e1ffc
+
7e1ffc
 		ret = ctx->cipher->aead_decrypt(ctx,
7e1ffc
 						nonce_size, nonce,
7e1ffc
 						auth_size, auth,
7e1ffc
diff --git a/tests/fips-test.c b/tests/fips-test.c
7e1ffc
index f7556d7bbb..c43503fba0 100644
7e1ffc
--- a/tests/fips-test.c
7e1ffc
+++ b/tests/fips-test.c
7e1ffc
@@ -1,4 +1,5 @@
7e1ffc
 #include <config.h>
7e1ffc
+#include <stdbool.h>
7e1ffc
 #include <stdint.h>
7e1ffc
 #include <stdio.h>
7e1ffc
 #include <string.h>
7e1ffc
@@ -213,14 +214,96 @@ test_cipher_disallowed(gnutls_cipher_algorithm_t cipher)
7e1ffc
 	FIPS_POP_CONTEXT(ERROR);
7e1ffc
 }
7e1ffc
 
7e1ffc
+static void
7e1ffc
+test_ccm_cipher(gnutls_cipher_algorithm_t cipher, size_t tag_length,
7e1ffc
+		bool expect_encryption_fail,
7e1ffc
+		gnutls_fips140_operation_state_t expected_state)
7e1ffc
+{
7e1ffc
+	int ret;
7e1ffc
+	unsigned key_size = gnutls_cipher_get_key_size(cipher);
7e1ffc
+	gnutls_aead_cipher_hd_t h;
7e1ffc
+	gnutls_datum_t key = { key_data, key_size };
7e1ffc
+	unsigned char buffer[256];
7e1ffc
+	size_t length;
7e1ffc
+	gnutls_memset(key_data, 0, key_size);
7e1ffc
+
7e1ffc
+	FIPS_PUSH_CONTEXT();
7e1ffc
+	ret = gnutls_aead_cipher_init(&h, cipher, &key);
7e1ffc
+	if (ret < 0) {
7e1ffc
+		fail("gnutls_aead_cipher_init failed for %s\n",
7e1ffc
+		     gnutls_cipher_get_name(cipher));
7e1ffc
+	}
7e1ffc
+	FIPS_POP_CONTEXT(APPROVED);
7e1ffc
+
7e1ffc
+	fips_push_context(fips_context);
7e1ffc
+	memset(buffer, 0, sizeof(buffer));
7e1ffc
+	length = sizeof(buffer);
7e1ffc
+	ret = gnutls_aead_cipher_encrypt(h, iv_data,
7e1ffc
+					 gnutls_cipher_get_iv_size(cipher),
7e1ffc
+					 NULL, 0, tag_length,
7e1ffc
+					 buffer, length - tag_length,
7e1ffc
+					 buffer, &length);
7e1ffc
+	if (expect_encryption_fail) {
7e1ffc
+		if (ret != GNUTLS_E_INVALID_REQUEST) {
7e1ffc
+			fail("gnutls_aead_cipher_encrypt(%s) returned %d "
7e1ffc
+			     "while %d is expected\n",
7e1ffc
+			     gnutls_cipher_get_name(cipher),
7e1ffc
+			     ret, GNUTLS_E_INVALID_REQUEST);
7e1ffc
+		}
7e1ffc
+	} else if (ret < 0) {
7e1ffc
+		fail("gnutls_aead_cipher_encrypt failed for %s\n",
7e1ffc
+		     gnutls_cipher_get_name(cipher));
7e1ffc
+	}
7e1ffc
+	fips_pop_context(fips_context, expected_state);
7e1ffc
+
7e1ffc
+	fips_push_context(fips_context);
7e1ffc
+	length = sizeof(buffer);
7e1ffc
+	ret = gnutls_aead_cipher_decrypt(h, iv_data,
7e1ffc
+					 gnutls_cipher_get_iv_size(cipher),
7e1ffc
+					 NULL, 0, tag_length,
7e1ffc
+					 buffer, length,
7e1ffc
+					 buffer, &length);
7e1ffc
+	if (expect_encryption_fail) {
7e1ffc
+		if (ret != GNUTLS_E_INVALID_REQUEST) {
7e1ffc
+			fail("gnutls_aead_cipher_decrypt(%s) returned %d "
7e1ffc
+			     "while %d is expected\n",
7e1ffc
+			     gnutls_cipher_get_name(cipher),
7e1ffc
+			     ret, GNUTLS_E_INVALID_REQUEST);
7e1ffc
+		}
7e1ffc
+	} else if (ret < 0) {
7e1ffc
+		fail("gnutls_aead_cipher_decrypt failed for %s\n",
7e1ffc
+		     gnutls_cipher_get_name(cipher));
7e1ffc
+	}
7e1ffc
+	fips_pop_context(fips_context, expected_state);
7e1ffc
+
7e1ffc
+	gnutls_aead_cipher_deinit(h);
7e1ffc
+}
7e1ffc
+
7e1ffc
 static inline void
7e1ffc
 test_ciphers(void)
7e1ffc
 {
7e1ffc
+	size_t i;
7e1ffc
+
7e1ffc
 	test_cipher_approved(GNUTLS_CIPHER_AES_128_CBC);
7e1ffc
 	test_cipher_approved(GNUTLS_CIPHER_AES_192_CBC);
7e1ffc
 	test_cipher_approved(GNUTLS_CIPHER_AES_256_CBC);
7e1ffc
-	test_aead_cipher_approved(GNUTLS_CIPHER_AES_128_CCM);
7e1ffc
-	test_aead_cipher_approved(GNUTLS_CIPHER_AES_256_CCM);
7e1ffc
+
7e1ffc
+	/* Check for all allowed Tlen */
7e1ffc
+	for (i = 4; i <= 16; i += 2) {
7e1ffc
+		test_ccm_cipher(GNUTLS_CIPHER_AES_128_CCM, i,
7e1ffc
+				false, GNUTLS_FIPS140_OP_APPROVED);
7e1ffc
+		test_ccm_cipher(GNUTLS_CIPHER_AES_256_CCM, i,
7e1ffc
+				false, GNUTLS_FIPS140_OP_APPROVED);
7e1ffc
+	}
7e1ffc
+	test_ccm_cipher(GNUTLS_CIPHER_AES_128_CCM, 3,
7e1ffc
+			true, GNUTLS_FIPS140_OP_ERROR);
7e1ffc
+	test_ccm_cipher(GNUTLS_CIPHER_AES_256_CCM, 3,
7e1ffc
+			true, GNUTLS_FIPS140_OP_ERROR);
7e1ffc
+	test_ccm_cipher(GNUTLS_CIPHER_AES_128_CCM, 5,
7e1ffc
+			true, GNUTLS_FIPS140_OP_ERROR);
7e1ffc
+	test_ccm_cipher(GNUTLS_CIPHER_AES_256_CCM, 5,
7e1ffc
+			true, GNUTLS_FIPS140_OP_ERROR);
7e1ffc
+
7e1ffc
 	test_aead_cipher_approved(GNUTLS_CIPHER_AES_128_CCM_8);
7e1ffc
 	test_aead_cipher_approved(GNUTLS_CIPHER_AES_256_CCM_8);
7e1ffc
 	test_cipher_approved(GNUTLS_CIPHER_AES_128_CFB8);
7e1ffc
-- 
7e1ffc
2.38.1
7e1ffc