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