cipher: GCM: check that length of supplied tag is one of valid lengths
authorJussi Kivilinna <jussi.kivilinna@iki.fi>
Sun, 27 Mar 2016 08:17:39 +0000 (11:17 +0300)
committerJussi Kivilinna <jussi.kivilinna@iki.fi>
Sun, 27 Mar 2016 08:17:39 +0000 (11:17 +0300)
* cipher/cipher-gcm.c (is_tag_length_valid): New.
(_gcry_cipher_gcm_tag): Check that 'outbuflen' has valid tag length.
* tests/basic.c (_check_gcm_cipher): Add test-vectors with different
valid tag lengths and negative test vectors with invalid lengths.
--

NIST SP 800-38D allows following tag lengths:
 128, 120, 112, 104, 96, 64 and 32 bits.

[v2: allow larger buffer when outputting tag. 128-bit tag is written
     to target buffer in this case]
Signed-off-by: Jussi Kivilinna <jussi.kivilinna@iki.fi>
cipher/cipher-gcm.c
tests/basic.c

index 712641e..6e0959a 100644 (file)
@@ -769,12 +769,32 @@ _gcry_cipher_gcm_geniv (gcry_cipher_hd_t c,
 #endif
 
 
+static int
+is_tag_length_valid(size_t taglen)
+{
+  switch (taglen)
+    {
+    /* Allowed tag lengths from NIST SP 800-38D.  */
+    case 128 / 8: /* GCRY_GCM_BLOCK_LEN */
+    case 120 / 8:
+    case 112 / 8:
+    case 104 / 8:
+    case 96 / 8:
+    case 64 / 8:
+    case 32 / 8:
+      return 1;
+
+    default:
+      return 0;
+    }
+}
+
 static gcry_err_code_t
 _gcry_cipher_gcm_tag (gcry_cipher_hd_t c,
                       byte * outbuf, size_t outbuflen, int check)
 {
-  if (outbuflen < GCRY_GCM_BLOCK_LEN)
-    return GPG_ERR_BUFFER_TOO_SHORT;
+  if (!(is_tag_length_valid (outbuflen) || outbuflen >= GCRY_GCM_BLOCK_LEN))
+    return GPG_ERR_INV_LENGTH;
   if (c->u_mode.gcm.datalen_over_limits)
     return GPG_ERR_INV_LENGTH;
 
@@ -815,17 +835,19 @@ _gcry_cipher_gcm_tag (gcry_cipher_hd_t c,
 
   if (!check)
     {
+      if (outbuflen > GCRY_GCM_BLOCK_LEN)
+        outbuflen = GCRY_GCM_BLOCK_LEN;
+
       /* NB: We already checked that OUTBUF is large enough to hold
-         the result.  */
-      memcpy (outbuf, c->u_mode.gcm.u_tag.tag, GCRY_GCM_BLOCK_LEN);
+       * the result or has valid truncated length.  */
+      memcpy (outbuf, c->u_mode.gcm.u_tag.tag, outbuflen);
     }
   else
     {
       /* OUTBUFLEN gives the length of the user supplied tag in OUTBUF
        * and thus we need to compare its length first.  */
-      if (outbuflen != GCRY_GCM_BLOCK_LEN
-          || !buf_eq_const (outbuf, c->u_mode.gcm.u_tag.tag,
-                            GCRY_GCM_BLOCK_LEN))
+      if (!is_tag_length_valid (outbuflen)
+          || !buf_eq_const (outbuf, c->u_mode.gcm.u_tag.tag, outbuflen))
         return GPG_ERR_CHECKSUM;
     }
 
index 36a83d0..25d31ee 100644 (file)
@@ -1304,6 +1304,8 @@ _check_gcm_cipher (unsigned int step)
     int inlen;
     char out[MAX_DATA_LEN];
     char tag[MAX_DATA_LEN];
+    int taglen;
+    int should_fail;
   } tv[] =
     {
       /* http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-revised-spec.pdf */
@@ -1319,6 +1321,78 @@ _check_gcm_cipher (unsigned int step)
         "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
         "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 12,
         "", 0,
+        "",
+        0,
+        "",
+        "\x58\xe2\xfc\xce\xfa\x7e\x30\x61\x36\x7f\x1d\x57\xa4\xe7\x45",
+        15 },
+      { GCRY_CIPHER_AES,
+        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 12,
+        "", 0,
+        "",
+        0,
+        "",
+        "\x58\xe2\xfc\xce\xfa\x7e\x30\x61\x36\x7f\x1d\x57\xa4\xe7",
+        14 },
+      { GCRY_CIPHER_AES,
+        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 12,
+        "", 0,
+        "",
+        0,
+        "",
+        "\x58\xe2\xfc\xce\xfa\x7e\x30\x61\x36\x7f\x1d\x57\xa4",
+        13 },
+      { GCRY_CIPHER_AES,
+        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 12,
+        "", 0,
+        "",
+        0,
+        "",
+        "\x58\xe2\xfc\xce\xfa\x7e\x30\x61\x36\x7f\x1d\x57",
+        12 },
+      { GCRY_CIPHER_AES,
+        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 12,
+        "", 0,
+        "",
+        0,
+        "",
+        "\x58\xe2\xfc\xce\xfa\x7e\x30\x61\x36\x7f\x1d",
+        11, 1 },
+      { GCRY_CIPHER_AES,
+        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 12,
+        "", 0,
+        "",
+        0,
+        "",
+        "\x58\xe2\xfc\xce\xfa\x7e\x30\x61",
+        8 },
+      { GCRY_CIPHER_AES,
+        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 12,
+        "", 0,
+        "",
+        0,
+        "",
+        "\x58\xe2\xfc\xce",
+        4 },
+      { GCRY_CIPHER_AES,
+        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 12,
+        "", 0,
+        "",
+        0,
+        "",
+        "\x58",
+        1, 1 },
+      { GCRY_CIPHER_AES,
+        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 12,
+        "", 0,
         "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
         16,
         "\x03\x88\xda\xce\x60\xb6\xa3\x92\xf3\x28\xc2\xb9\x71\xb2\xfe\x78",
@@ -1570,9 +1644,14 @@ _check_gcm_cipher (unsigned int step)
       if (memcmp (tv[i].plaintext, out, tv[i].inlen))
         fail ("aes-gcm, decrypt mismatch entry %d (step %d)\n", i, step);
 
-      err = gcry_cipher_gettag (hde, out, GCRY_GCM_BLOCK_LEN);
+      taglen2 = tv[i].taglen ? tv[i].taglen : GCRY_GCM_BLOCK_LEN;
+
+      err = gcry_cipher_gettag (hde, out, taglen2);
       if (err)
         {
+          if (tv[i].should_fail)
+            goto next_tv;
+
           fail ("aes-gcm, gcry_cipher_gettag(%d) failed: %s\n",
                 i, gpg_strerror (err));
           gcry_cipher_close (hde);
@@ -1580,11 +1659,10 @@ _check_gcm_cipher (unsigned int step)
           return;
         }
 
-      if (memcmp (tv[i].tag, out, GCRY_GCM_BLOCK_LEN))
+      if (memcmp (tv[i].tag, out, taglen2))
         fail ("aes-gcm, encrypt tag mismatch entry %d\n", i);
 
-
-      err = gcry_cipher_checktag (hdd, out, GCRY_GCM_BLOCK_LEN);
+      err = gcry_cipher_checktag (hdd, out, taglen2);
       if (err)
         {
           fail ("aes-gcm, gcry_cipher_checktag(%d) failed: %s\n",
@@ -1660,17 +1738,25 @@ _check_gcm_cipher (unsigned int step)
       if (memcmp (tv[i].out, out, tv[i].inlen))
         fail ("aes-gcm, encrypt mismatch entry %d, (byte-buf)\n", i);
 
-      err = gcry_cipher_gettag (hde, tag, GCRY_GCM_BLOCK_LEN);
+      /* Test output to larger than 16-byte buffer. */
+      taglen2 = tv[i].taglen ? tv[i].taglen : GCRY_GCM_BLOCK_LEN + 1;
+
+      err = gcry_cipher_gettag (hde, tag, taglen2);
       if (err)
         {
-          fail ("aes-gcm, gcry_cipher_gettag(%d) (byte-buf) failed: %s\n",
-                i, gpg_strerror (err));
+          if (tv[i].should_fail)
+            goto next_tv;
+
+          fail ("aes-gcm, gcry_cipher_gettag(%d, %d) (byte-buf) failed: %s\n",
+                i, taglen2, gpg_strerror (err));
           gcry_cipher_close (hde);
           gcry_cipher_close (hdd);
           return;
         }
 
-      if (memcmp (tv[i].tag, tag, GCRY_GCM_BLOCK_LEN))
+      taglen2 = tv[i].taglen ? tv[i].taglen : GCRY_GCM_BLOCK_LEN;
+
+      if (memcmp (tv[i].tag, tag, taglen2))
         fail ("aes-gcm, encrypt tag mismatch entry %d, (byte-buf)\n", i);
 
       for (byteNum = 0; byteNum < tv[i].inlen; ++byteNum)
@@ -1689,7 +1775,7 @@ _check_gcm_cipher (unsigned int step)
       if (memcmp (tv[i].plaintext, out, tv[i].inlen))
         fail ("aes-gcm, decrypt mismatch entry %d\n", i);
 
-      err = gcry_cipher_checktag (hdd, tag, GCRY_GCM_BLOCK_LEN);
+      err = gcry_cipher_checktag (hdd, tag, taglen2);
       if (err)
         {
           fail ("aes-gcm, gcry_cipher_checktag(%d) (byte-buf) failed: %s\n",
@@ -1699,6 +1785,34 @@ _check_gcm_cipher (unsigned int step)
           return;
         }
 
+      err = gcry_cipher_checktag (hdd, tag, 1);
+      if (!err)
+        {
+          fail ("aes-gcm, gcry_cipher_checktag(%d) did not fail for invalid "
+               " tag length of '%d'\n", i, 1);
+          gcry_cipher_close (hde);
+          gcry_cipher_close (hdd);
+          return;
+        }
+      err = gcry_cipher_checktag (hdd, tag, 17);
+      if (!err)
+        {
+          fail ("aes-gcm, gcry_cipher_checktag(%d) did not fail for invalid "
+               " tag length of '%d'\n", i, 17);
+          gcry_cipher_close (hde);
+          gcry_cipher_close (hdd);
+          return;
+        }
+
+      if (tv[i].should_fail)
+        {
+          fail ("aes-gcm, negative test succeeded %d\n", i);
+          gcry_cipher_close (hde);
+          gcry_cipher_close (hdd);
+          return;
+        }
+
+next_tv:
       gcry_cipher_close (hde);
       gcry_cipher_close (hdd);
     }