]> git.ipfire.org Git - thirdparty/gnutls.git/commitdiff
cipher: add restriction on CCM tag length under FIPS mode
authorDaiki Ueno <ueno@gnu.org>
Fri, 21 Oct 2022 06:48:39 +0000 (15:48 +0900)
committerDaiki Ueno <ueno@gnu.org>
Mon, 24 Oct 2022 23:59:23 +0000 (08:59 +0900)
This change prohibits any use of tag length other than 4, 6, 8, 10,
12, 14, and 16 bytes in CCM used under FIPS mode, in accordance with
SP800-38C A.1.  While use of tag lengths smaller than 8 bytes is not
recommended, we simply allow 4 and 6 bytes tags for now.

Signed-off-by: Daiki Ueno <ueno@gnu.org>
lib/accelerated/aarch64/aes-ccm-aarch64.c
lib/accelerated/x86/aes-ccm-x86-aesni.c
lib/nettle/cipher.c
tests/fips-test.c

index a2ba259e9949e2fdf856acc0c293e07a88d57854..b415d4ddfbd19cce86a066ac6a3da96c2ed5d1f6 100644 (file)
@@ -36,6 +36,7 @@
 #include <byteswap.h>
 #include <nettle/ccm.h>
 #include <aes-aarch64.h>
+#include <fips.h>
 
 typedef struct ccm_aarch64_aes_ctx {
        AES_KEY key;
@@ -103,6 +104,25 @@ aes_ccm_aead_encrypt(void *_ctx,
        if (unlikely(encr_size < plain_size + tag_size))
                return gnutls_assert_val(GNUTLS_E_SHORT_MEMORY_BUFFER);
 
+       /* SP800-38C A.1 says Tlen must be a multiple of 16 between 32
+        * and 128.
+        */
+       switch (tag_size) {
+       case 4: case 6:
+               /* SP800-38C B.2 says Tlen smaller than 64 should not be used
+                * under sufficient restriction. We simply allow those for now.
+                */
+               FALLTHROUGH;
+       case 8: case 10: case 12: case 14: case 16:
+               break;
+       default:
+               if (_gnutls_fips_mode_enabled()) {
+                       _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR);
+                       return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
+               }
+               break;
+       }
+
        ccm_encrypt_message(&ctx->key, aarch64_aes_encrypt,
                            nonce_size, nonce,
                            auth_size, auth,
@@ -129,6 +149,25 @@ aes_ccm_aead_decrypt(void *_ctx,
        if (unlikely(plain_size < encr_size - tag_size))
                return gnutls_assert_val(GNUTLS_E_SHORT_MEMORY_BUFFER);
 
+       /* SP800-38C A.1 says Tlen must be a multiple of 16 between 32
+        * and 128.
+        */
+       switch (tag_size) {
+       case 4: case 6:
+               /* SP800-38C B.2 says Tlen smaller than 64 should not be used
+                * under sufficient restriction. We simply allow those for now.
+                */
+               FALLTHROUGH;
+       case 8: case 10: case 12: case 14: case 16:
+               break;
+       default:
+               if (_gnutls_fips_mode_enabled()) {
+                       _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR);
+                       return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
+               }
+               break;
+       }
+
        ret = ccm_decrypt_message(&ctx->key, aarch64_aes_encrypt,
                                  nonce_size, nonce,
                                  auth_size, auth,
index 701c0f992a5703d3e8e65b4d31a895c38fdc1e32..9ebbdd7b2afcb2bdc1adcfed4525beb24a4bc287 100644 (file)
@@ -37,6 +37,7 @@
 #include <byteswap.h>
 #include <nettle/ccm.h>
 #include <aes-x86.h>
+#include <fips.h>
 
 typedef struct ccm_x86_aes_ctx {
        AES_KEY key;
@@ -95,6 +96,25 @@ aes_ccm_aead_encrypt(void *_ctx,
        if (unlikely(encr_size < plain_size + tag_size))
                return gnutls_assert_val(GNUTLS_E_SHORT_MEMORY_BUFFER);
 
+       /* SP800-38C A.1 says Tlen must be a multiple of 16 between 32
+        * and 128.
+        */
+       switch (tag_size) {
+       case 4: case 6:
+               /* SP800-38C B.2 says Tlen smaller than 64 should not be used
+                * under sufficient restriction. We simply allow those for now.
+                */
+               FALLTHROUGH;
+       case 8: case 10: case 12: case 14: case 16:
+               break;
+       default:
+               if (_gnutls_fips_mode_enabled()) {
+                       _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR);
+                       return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
+               }
+               break;
+       }
+
        ccm_encrypt_message(&ctx->key, x86_aes_encrypt,
                            nonce_size, nonce,
                            auth_size, auth,
@@ -121,6 +141,25 @@ aes_ccm_aead_decrypt(void *_ctx,
        if (unlikely(plain_size < encr_size - tag_size))
                return gnutls_assert_val(GNUTLS_E_SHORT_MEMORY_BUFFER);
 
+       /* SP800-38C A.1 says Tlen must be a multiple of 16 between 32
+        * and 128.
+        */
+       switch (tag_size) {
+       case 4: case 6:
+               /* SP800-38C B.2 says Tlen smaller than 64 should not be used
+                * under sufficient restriction. We simply allow those for now.
+                */
+               FALLTHROUGH;
+       case 8: case 10: case 12: case 14: case 16:
+               break;
+       default:
+               if (_gnutls_fips_mode_enabled()) {
+                       _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR);
+                       return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
+               }
+               break;
+       }
+
        ret = ccm_decrypt_message(&ctx->key, x86_aes_encrypt,
                                  nonce_size, nonce,
                                  auth_size, auth,
index 9c2ce19e7e72219e47dc21b3edcf88ddd04641a7..8c23d11252a546bf5756742955bb30e8c6780099 100644 (file)
@@ -1253,6 +1253,34 @@ wrap_nettle_cipher_aead_encrypt(void *_ctx,
                ctx->cipher->tag(ctx->ctx_ptr, tag_size, ((uint8_t*)encr) + plain_size);
        } else {
                /* CCM-style cipher */
+
+               switch (ctx->cipher->algo) {
+               case GNUTLS_CIPHER_AES_128_CCM:
+               case GNUTLS_CIPHER_AES_256_CCM:
+                       /* SP800-38C A.1 says Tlen must be a multiple of 16
+                        * between 32 and 128.
+                        */
+                       switch (tag_size) {
+                       case 4: case 6:
+                               /* SP800-38C B.2 says Tlen smaller than 64
+                                * should not be used under sufficient
+                                * restriction. We simply allow those for now.
+                                */
+                               FALLTHROUGH;
+                       case 8: case 10: case 12: case 14: case 16:
+                               break;
+                       default:
+                               if (_gnutls_fips_mode_enabled()) {
+                                       _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR);
+                                       return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
+                               }
+                               break;
+                       }
+                       break;
+               default:
+                       break;
+               }
+
                ctx->cipher->aead_encrypt(ctx,
                                          nonce_size, nonce,
                                          auth_size, auth,
@@ -1302,6 +1330,33 @@ wrap_nettle_cipher_aead_decrypt(void *_ctx,
                if (unlikely(plain_size < encr_size))
                        return gnutls_assert_val(GNUTLS_E_SHORT_MEMORY_BUFFER);
 
+               switch (ctx->cipher->algo) {
+               case GNUTLS_CIPHER_AES_128_CCM:
+               case GNUTLS_CIPHER_AES_256_CCM:
+                       /* SP800-38C A.1 says Tlen must be a multiple of 16
+                        * between 32 and 128.
+                        */
+                       switch (tag_size) {
+                       case 4: case 6:
+                               /* SP800-38C B.2 says Tlen smaller than 64
+                                * should not be used under sufficient
+                                * restriction. We simply allow those for now.
+                                */
+                               FALLTHROUGH;
+                       case 8: case 10: case 12: case 14: case 16:
+                               break;
+                       default:
+                               if (_gnutls_fips_mode_enabled()) {
+                                       _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR);
+                                       return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
+                               }
+                               break;
+                       }
+                       break;
+               default:
+                       break;
+               }
+
                ret = ctx->cipher->aead_decrypt(ctx,
                                                nonce_size, nonce,
                                                auth_size, auth,
index f7556d7bbbc080da093b87918e9d3563be0fffd4..c43503fba01dcdd0a3714028b38f9c9bbb5b093f 100644 (file)
@@ -1,4 +1,5 @@
 #include <config.h>
+#include <stdbool.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <string.h>
@@ -213,14 +214,96 @@ test_cipher_disallowed(gnutls_cipher_algorithm_t cipher)
        FIPS_POP_CONTEXT(ERROR);
 }
 
+static void
+test_ccm_cipher(gnutls_cipher_algorithm_t cipher, size_t tag_length,
+               bool expect_encryption_fail,
+               gnutls_fips140_operation_state_t expected_state)
+{
+       int ret;
+       unsigned key_size = gnutls_cipher_get_key_size(cipher);
+       gnutls_aead_cipher_hd_t h;
+       gnutls_datum_t key = { key_data, key_size };
+       unsigned char buffer[256];
+       size_t length;
+       gnutls_memset(key_data, 0, key_size);
+
+       FIPS_PUSH_CONTEXT();
+       ret = gnutls_aead_cipher_init(&h, cipher, &key);
+       if (ret < 0) {
+               fail("gnutls_aead_cipher_init failed for %s\n",
+                    gnutls_cipher_get_name(cipher));
+       }
+       FIPS_POP_CONTEXT(APPROVED);
+
+       fips_push_context(fips_context);
+       memset(buffer, 0, sizeof(buffer));
+       length = sizeof(buffer);
+       ret = gnutls_aead_cipher_encrypt(h, iv_data,
+                                        gnutls_cipher_get_iv_size(cipher),
+                                        NULL, 0, tag_length,
+                                        buffer, length - tag_length,
+                                        buffer, &length);
+       if (expect_encryption_fail) {
+               if (ret != GNUTLS_E_INVALID_REQUEST) {
+                       fail("gnutls_aead_cipher_encrypt(%s) returned %d "
+                            "while %d is expected\n",
+                            gnutls_cipher_get_name(cipher),
+                            ret, GNUTLS_E_INVALID_REQUEST);
+               }
+       } else if (ret < 0) {
+               fail("gnutls_aead_cipher_encrypt failed for %s\n",
+                    gnutls_cipher_get_name(cipher));
+       }
+       fips_pop_context(fips_context, expected_state);
+
+       fips_push_context(fips_context);
+       length = sizeof(buffer);
+       ret = gnutls_aead_cipher_decrypt(h, iv_data,
+                                        gnutls_cipher_get_iv_size(cipher),
+                                        NULL, 0, tag_length,
+                                        buffer, length,
+                                        buffer, &length);
+       if (expect_encryption_fail) {
+               if (ret != GNUTLS_E_INVALID_REQUEST) {
+                       fail("gnutls_aead_cipher_decrypt(%s) returned %d "
+                            "while %d is expected\n",
+                            gnutls_cipher_get_name(cipher),
+                            ret, GNUTLS_E_INVALID_REQUEST);
+               }
+       } else if (ret < 0) {
+               fail("gnutls_aead_cipher_decrypt failed for %s\n",
+                    gnutls_cipher_get_name(cipher));
+       }
+       fips_pop_context(fips_context, expected_state);
+
+       gnutls_aead_cipher_deinit(h);
+}
+
 static inline void
 test_ciphers(void)
 {
+       size_t i;
+
        test_cipher_approved(GNUTLS_CIPHER_AES_128_CBC);
        test_cipher_approved(GNUTLS_CIPHER_AES_192_CBC);
        test_cipher_approved(GNUTLS_CIPHER_AES_256_CBC);
-       test_aead_cipher_approved(GNUTLS_CIPHER_AES_128_CCM);
-       test_aead_cipher_approved(GNUTLS_CIPHER_AES_256_CCM);
+
+       /* Check for all allowed Tlen */
+       for (i = 4; i <= 16; i += 2) {
+               test_ccm_cipher(GNUTLS_CIPHER_AES_128_CCM, i,
+                               false, GNUTLS_FIPS140_OP_APPROVED);
+               test_ccm_cipher(GNUTLS_CIPHER_AES_256_CCM, i,
+                               false, GNUTLS_FIPS140_OP_APPROVED);
+       }
+       test_ccm_cipher(GNUTLS_CIPHER_AES_128_CCM, 3,
+                       true, GNUTLS_FIPS140_OP_ERROR);
+       test_ccm_cipher(GNUTLS_CIPHER_AES_256_CCM, 3,
+                       true, GNUTLS_FIPS140_OP_ERROR);
+       test_ccm_cipher(GNUTLS_CIPHER_AES_128_CCM, 5,
+                       true, GNUTLS_FIPS140_OP_ERROR);
+       test_ccm_cipher(GNUTLS_CIPHER_AES_256_CCM, 5,
+                       true, GNUTLS_FIPS140_OP_ERROR);
+
        test_aead_cipher_approved(GNUTLS_CIPHER_AES_128_CCM_8);
        test_aead_cipher_approved(GNUTLS_CIPHER_AES_256_CCM_8);
        test_cipher_approved(GNUTLS_CIPHER_AES_128_CFB8);