GCM: Add support for split data buffers and online operation
authorJussi Kivilinna <jussi.kivilinna@iki.fi>
Wed, 20 Nov 2013 13:06:03 +0000 (15:06 +0200)
committerJussi Kivilinna <jussi.kivilinna@iki.fi>
Wed, 20 Nov 2013 16:45:46 +0000 (18:45 +0200)
* cipher/cipher-gcm.c (do_ghash_buf): Add buffering for less than
blocksize length input and padding handling.
(_gcry_cipher_gcm_encrypt, _gcry_cipher_gcm_decrypt): Add handling
for AAD padding and check if data has already being padded.
(_gcry_cipher_gcm_authenticate): Check that AAD or data has not being
padded yet.
(_gcry_cipher_gcm_initiv): Clear padding marks.
(_gcry_cipher_gcm_tag): Add finalization and padding; Clear sensitive
data from cipher handle, since they are not used after generating tag.
* cipher/cipher-internal.h (gcry_cipher_handle): Add 'u_mode.gcm.macbuf',
'u_mode.gcm.mac_unused', 'u_mode.gcm.ghash_data_finalized' and
'u_mode.gcm.ghash_aad_finalized'.
* tests/basic.c (check_gcm_cipher): Rename to...
(_check_gcm_cipher): ...this and add handling for different buffer step
lengths; Enable per byte buffer testing.
(check_gcm_cipher): Call _check_gcm_cipher with different buffer step
sizes.
--

Until now, GCM was expecting full data to be input in one go. This patch adds
support for feeding data continuously (for encryption/decryption/aad).

Signed-off-by: Jussi Kivilinna <jussi.kivilinna@iki.fi>
cipher/cipher-gcm.c
cipher/cipher-internal.h
tests/basic.c

index 62855b1..42cfc1e 100644 (file)
@@ -806,30 +806,60 @@ gcm_check_aadlen_or_ivlen (u32 ctr[2])
 
 
 static void
-do_ghash_buf(gcry_cipher_hd_t c, byte *hash, const byte * buf,
-             size_t buflen)
+do_ghash_buf(gcry_cipher_hd_t c, byte *hash, const byte *buf,
+             size_t buflen, int do_padding)
 {
-  unsigned char tmp[MAX_BLOCKSIZE];
   unsigned int blocksize = GCRY_GCM_BLOCK_LEN;
-  size_t nblocks;
+  unsigned int unused = c->u_mode.gcm.mac_unused;
+  size_t nblocks, n;
   unsigned int burn = 0;
 
-  nblocks = buflen / blocksize;
+  if (buflen == 0 && (unused == 0 || !do_padding))
+    return;
 
-  if (nblocks)
+  do
     {
-      burn = ghash (c, hash, buf, nblocks);
-      buf += blocksize * nblocks;
-      buflen -= blocksize * nblocks;
-    }
+      if (buflen + unused < blocksize || unused > 0)
+        {
+          n = blocksize - unused;
+          n = n < buflen ? n : buflen;
 
-  if (buflen)
-    {
-      buf_cpy (tmp, buf, buflen);
-      memset (tmp + buflen, 0, blocksize - buflen);
-      burn = ghash (c, hash, tmp, 1);
-      wipememory (tmp, sizeof(tmp));
+          buf_cpy (&c->u_mode.gcm.macbuf[unused], buf, n);
+
+          unused += n;
+          buf += n;
+          buflen -= n;
+        }
+      if (!buflen)
+        {
+          if (!do_padding)
+            break;
+
+          while (unused < blocksize)
+            c->u_mode.gcm.macbuf[unused++] = 0;
+        }
+
+      if (unused > 0)
+        {
+          gcry_assert (unused == blocksize);
+
+          /* Process one block from macbuf.  */
+          burn = ghash (c, hash, c->u_mode.gcm.macbuf, 1);
+          unused = 0;
+        }
+
+      nblocks = buflen / blocksize;
+
+      if (nblocks)
+        {
+          burn = ghash (c, hash, buf, nblocks);
+          buf += blocksize * nblocks;
+          buflen -= blocksize * nblocks;
+        }
     }
+  while (buflen > 0);
+
+  c->u_mode.gcm.mac_unused = unused;
 
   if (burn)
     _gcry_burn_stack (burn);
@@ -850,7 +880,7 @@ _gcry_cipher_gcm_encrypt (gcry_cipher_hd_t c,
     return GPG_ERR_BUFFER_TOO_SHORT;
   if (c->u_mode.gcm.datalen_over_limits)
     return GPG_ERR_INV_LENGTH;
-  if (c->marks.tag)
+  if (c->marks.tag || c->u_mode.gcm.ghash_data_finalized)
     return GPG_ERR_INV_STATE;
 
   if (!c->marks.iv)
@@ -859,6 +889,13 @@ _gcry_cipher_gcm_encrypt (gcry_cipher_hd_t c,
   if (c->u_mode.gcm.disallow_encryption_because_of_setiv_in_fips_mode)
     return GPG_ERR_INV_STATE;
 
+  if (!c->u_mode.gcm.ghash_aad_finalized)
+    {
+      /* Start of encryption marks end of AAD stream. */
+      do_ghash_buf(c, c->u_mode.gcm.u_tag.tag, NULL, 0, 1);
+      c->u_mode.gcm.ghash_aad_finalized = 1;
+    }
+
   gcm_bytecounter_add(c->u_mode.gcm.datalen, inbuflen);
   if (!gcm_check_datalen(c->u_mode.gcm.datalen))
     {
@@ -870,7 +907,7 @@ _gcry_cipher_gcm_encrypt (gcry_cipher_hd_t c,
   if (err != 0)
     return err;
 
-  do_ghash_buf(c, c->u_mode.gcm.u_tag.tag, outbuf, inbuflen);
+  do_ghash_buf(c, c->u_mode.gcm.u_tag.tag, outbuf, inbuflen, 0);
 
   return 0;
 }
@@ -889,12 +926,19 @@ _gcry_cipher_gcm_decrypt (gcry_cipher_hd_t c,
     return GPG_ERR_BUFFER_TOO_SHORT;
   if (c->u_mode.gcm.datalen_over_limits)
     return GPG_ERR_INV_LENGTH;
-  if (c->marks.tag)
+  if (c->marks.tag || c->u_mode.gcm.ghash_data_finalized)
     return GPG_ERR_INV_STATE;
 
   if (!c->marks.iv)
     _gcry_cipher_gcm_setiv (c, zerobuf, GCRY_GCM_BLOCK_LEN);
 
+  if (!c->u_mode.gcm.ghash_aad_finalized)
+    {
+      /* Start of decryption marks end of AAD stream. */
+      do_ghash_buf(c, c->u_mode.gcm.u_tag.tag, NULL, 0, 1);
+      c->u_mode.gcm.ghash_aad_finalized = 1;
+    }
+
   gcm_bytecounter_add(c->u_mode.gcm.datalen, inbuflen);
   if (!gcm_check_datalen(c->u_mode.gcm.datalen))
     {
@@ -902,7 +946,7 @@ _gcry_cipher_gcm_decrypt (gcry_cipher_hd_t c,
       return GPG_ERR_INV_LENGTH;
     }
 
-  do_ghash_buf(c, c->u_mode.gcm.u_tag.tag, inbuf, inbuflen);
+  do_ghash_buf(c, c->u_mode.gcm.u_tag.tag, inbuf, inbuflen, 0);
 
   return _gcry_cipher_ctr_encrypt(c, outbuf, outbuflen, inbuf, inbuflen);
 }
@@ -918,7 +962,8 @@ _gcry_cipher_gcm_authenticate (gcry_cipher_hd_t c,
     return GPG_ERR_CIPHER_ALGO;
   if (c->u_mode.gcm.datalen_over_limits)
     return GPG_ERR_INV_LENGTH;
-  if (c->marks.tag)
+  if (c->marks.tag || c->u_mode.gcm.ghash_aad_finalized ||
+      c->u_mode.gcm.ghash_data_finalized)
     return GPG_ERR_INV_STATE;
 
   if (!c->marks.iv)
@@ -931,7 +976,7 @@ _gcry_cipher_gcm_authenticate (gcry_cipher_hd_t c,
       return GPG_ERR_INV_LENGTH;
     }
 
-  do_ghash_buf(c, c->u_mode.gcm.u_tag.tag, aadbuf, aadbuflen);
+  do_ghash_buf(c, c->u_mode.gcm.u_tag.tag, aadbuf, aadbuflen, 0);
 
   return 0;
 }
@@ -944,6 +989,8 @@ _gcry_cipher_gcm_initiv (gcry_cipher_hd_t c, const byte *iv, size_t ivlen)
   memset (c->u_mode.gcm.datalen, 0, sizeof(c->u_mode.gcm.datalen));
   memset (c->u_mode.gcm.u_tag.tag, 0, GCRY_GCM_BLOCK_LEN);
   c->u_mode.gcm.datalen_over_limits = 0;
+  c->u_mode.gcm.ghash_data_finalized = 0;
+  c->u_mode.gcm.ghash_aad_finalized = 0;
 
   if (ivlen == 0)
     return GPG_ERR_INV_LENGTH;
@@ -966,7 +1013,7 @@ _gcry_cipher_gcm_initiv (gcry_cipher_hd_t c, const byte *iv, size_t ivlen)
           return GPG_ERR_INV_LENGTH;
         }
 
-      do_ghash_buf(c, c->u_ctr.ctr, iv, ivlen);
+      do_ghash_buf(c, c->u_ctr.ctr, iv, ivlen, 1);
 
       /* iv length, 64-bit */
       bitlengths[1][1] = be_bswap32(iv_bytes[0] << 3);
@@ -976,10 +1023,10 @@ _gcry_cipher_gcm_initiv (gcry_cipher_hd_t c, const byte *iv, size_t ivlen)
       bitlengths[0][1] = 0;
       bitlengths[0][0] = 0;
 
-      do_ghash_buf(c, c->u_ctr.ctr, (byte*)bitlengths, GCRY_GCM_BLOCK_LEN);
+      do_ghash_buf(c, c->u_ctr.ctr, (byte*)bitlengths, GCRY_GCM_BLOCK_LEN, 1);
 
-      wipememory(iv_bytes, sizeof iv_bytes);
-      wipememory(bitlengths, sizeof bitlengths);
+      wipememory (iv_bytes, sizeof iv_bytes);
+      wipememory (bitlengths, sizeof bitlengths);
     }
   else
     {
@@ -1073,13 +1120,23 @@ _gcry_cipher_gcm_tag (gcry_cipher_hd_t c,
       bitlengths[1][0] = be_bswap32((c->u_mode.gcm.datalen[0] >> 29) |
                                     (c->u_mode.gcm.datalen[1] << 3));
 
+      /* Finalize data-stream. */
+      do_ghash_buf(c, c->u_mode.gcm.u_tag.tag, NULL, 0, 1);
+      c->u_mode.gcm.ghash_aad_finalized = 1;
+      c->u_mode.gcm.ghash_data_finalized = 1;
+
+      /* Add bitlengths to tag. */
       do_ghash_buf(c, c->u_mode.gcm.u_tag.tag, (byte*)bitlengths,
-                   GCRY_GCM_BLOCK_LEN);
+                   GCRY_GCM_BLOCK_LEN, 1);
       buf_xor (c->u_mode.gcm.u_tag.tag, c->u_mode.gcm.tagiv,
                c->u_mode.gcm.u_tag.tag, GCRY_GCM_BLOCK_LEN);
       c->marks.tag = 1;
 
-      wipememory(bitlengths, sizeof bitlengths);
+      wipememory (bitlengths, sizeof (bitlengths));
+      wipememory (c->u_mode.gcm.macbuf, GCRY_GCM_BLOCK_LEN);
+      wipememory (c->u_mode.gcm.tagiv, GCRY_GCM_BLOCK_LEN);
+      wipememory (c->u_mode.gcm.aadlen, sizeof (c->u_mode.gcm.aadlen));
+      wipememory (c->u_mode.gcm.datalen, sizeof (c->u_mode.gcm.datalen));
     }
 
   if (!check)
index 225f699..ede6f75 100644 (file)
@@ -168,6 +168,10 @@ struct gcry_cipher_handle
         unsigned char tag[MAX_BLOCKSIZE];
       } u_tag;
 
+      /* Space to save partial input lengths for MAC. */
+      unsigned char macbuf[GCRY_CCM_BLOCK_LEN];
+      int mac_unused;  /* Number of unprocessed bytes in MACBUF. */
+
       /* byte counters for GCM */
       u32 aadlen[2];
       u32 datalen[2];
@@ -187,6 +191,9 @@ struct gcry_cipher_handle
  #endif
 #endif
 
+      unsigned int ghash_data_finalized:1;
+      unsigned int ghash_aad_finalized:1;
+
       unsigned int datalen_over_limits:1;
       unsigned int disallow_encryption_because_of_setiv_in_fips_mode:1;
 #ifdef GCM_USE_INTEL_PCLMUL
index b4541a0..a205f48 100644 (file)
@@ -1138,7 +1138,7 @@ check_ofb_cipher (void)
 }
 
 static void
-check_gcm_cipher (void)
+_check_gcm_cipher (unsigned int step)
 {
   struct tv
   {
@@ -1275,8 +1275,11 @@ check_gcm_cipher (void)
 
   gcry_cipher_hd_t hde, hdd;
   unsigned char out[MAX_DATA_LEN];
+  unsigned char tag[GCRY_GCM_BLOCK_LEN];
   int i, keylen;
   gcry_error_t err = 0;
+  size_t pos, poslen;
+  int byteNum;
 
   if (verbose)
     fprintf (stderr, "  Starting GCM checks.\n");
@@ -1327,56 +1330,68 @@ check_gcm_cipher (void)
           return;
         }
 
-      err = gcry_cipher_authenticate(hde, tv[i].aad, tv[i].aadlen);
-      if (err)
-        {
-          fail ("aes-gcm, gcry_cipher_authenticate (%d) failed: %s\n",
-                i, gpg_strerror (err));
-          gcry_cipher_close (hde);
-          gcry_cipher_close (hdd);
-          return;
-        }
-      if (!err)
-        err = gcry_cipher_authenticate(hdd, tv[i].aad, tv[i].aadlen);
-      if (err)
+      for (pos = 0; pos < tv[i].aadlen; pos += step)
         {
-          fail ("aes-gcm, de gcry_cipher_authenticate (%d) failed: %s\n",
-                i, gpg_strerror (err));
-          gcry_cipher_close (hde);
-          gcry_cipher_close (hdd);
-          return;
+          poslen = (pos + step < tv[i].aadlen) ? step : tv[i].aadlen - pos;
+
+          err = gcry_cipher_authenticate(hde, tv[i].aad + pos, poslen);
+          if (err)
+            {
+              fail ("aes-gcm, gcry_cipher_authenticate (%d) (%d:%d) failed: "
+                    "%s\n", i, pos, step, gpg_strerror (err));
+              gcry_cipher_close (hde);
+              gcry_cipher_close (hdd);
+              return;
+            }
+          err = gcry_cipher_authenticate(hdd, tv[i].aad + pos, poslen);
+          if (err)
+            {
+              fail ("aes-gcm, de gcry_cipher_authenticate (%d) (%d:%d) failed: "
+                   "%s\n", i, pos, step, gpg_strerror (err));
+              gcry_cipher_close (hde);
+              gcry_cipher_close (hdd);
+              return;
+            }
         }
 
-      err = gcry_cipher_encrypt (hde, out, MAX_DATA_LEN,
-                                 tv[i].plaintext,
-                                 tv[i].inlen);
-      if (err)
+      for (pos = 0; pos < tv[i].inlen; pos += step)
         {
-          fail ("aes-gcm, gcry_cipher_encrypt (%d) failed: %s\n",
-                i, gpg_strerror (err));
-          gcry_cipher_close (hde);
-          gcry_cipher_close (hdd);
-          return;
+          poslen = (pos + step < tv[i].inlen) ? step : tv[i].inlen - pos;
+
+          err = gcry_cipher_encrypt (hde, out + pos, poslen,
+                                     tv[i].plaintext + pos, poslen);
+          if (err)
+            {
+              fail ("aes-gcm, gcry_cipher_encrypt (%d) (%d:%d) failed: %s\n",
+                    i, pos, step, gpg_strerror (err));
+              gcry_cipher_close (hde);
+              gcry_cipher_close (hdd);
+              return;
+            }
         }
 
       if (memcmp (tv[i].out, out, tv[i].inlen))
-        fail ("aes-gcm, encrypt mismatch entry %d\n", i);
+        fail ("aes-gcm, encrypt mismatch entry %d (step %d)\n", i, step);
 
-      err = gcry_cipher_decrypt (hdd, out, tv[i].inlen, NULL, 0);
-      if (err)
+      for (pos = 0; pos < tv[i].inlen; pos += step)
         {
-          fail ("aes-gcm, gcry_cipher_decrypt (%d) failed: %s\n",
-                i, gpg_strerror (err));
-          gcry_cipher_close (hde);
-          gcry_cipher_close (hdd);
-          return;
+          poslen = (pos + step < tv[i].inlen) ? step : tv[i].inlen - pos;
+
+          err = gcry_cipher_decrypt (hdd, out + pos, poslen, NULL, 0);
+          if (err)
+            {
+              fail ("aes-gcm, gcry_cipher_decrypt (%d) (%d:%d) failed: %s\n",
+                    i, pos, step, gpg_strerror (err));
+              gcry_cipher_close (hde);
+              gcry_cipher_close (hdd);
+              return;
+            }
         }
 
       if (memcmp (tv[i].plaintext, out, tv[i].inlen))
-        fail ("aes-gcm, decrypt mismatch entry %d\n", i);
+        fail ("aes-gcm, decrypt mismatch entry %d (step %d)\n", i, step);
 
-#define TAGLEN 16
-      err = gcry_cipher_gettag (hde, out, TAGLEN); /* FIXME */
+      err = gcry_cipher_gettag (hde, out, GCRY_GCM_BLOCK_LEN);
       if (err)
         {
           fail ("aes-gcm, gcry_cipher_gettag(%d) failed: %s\n",
@@ -1386,11 +1401,11 @@ check_gcm_cipher (void)
           return;
         }
 
-      if (memcmp (tv[i].tag, out, TAGLEN))
+      if (memcmp (tv[i].tag, out, GCRY_GCM_BLOCK_LEN))
         fail ("aes-gcm, encrypt tag mismatch entry %d\n", i);
 
 
-      err = gcry_cipher_checktag (hdd, out, TAGLEN);
+      err = gcry_cipher_checktag (hdd, out, GCRY_GCM_BLOCK_LEN);
       if (err)
         {
           fail ("aes-gcm, gcry_cipher_checktag(%d) failed: %s\n",
@@ -1412,8 +1427,6 @@ check_gcm_cipher (void)
           return;
         }
 
-
-#if 0
       /* gcry_cipher_reset clears the IV */
       err = gcry_cipher_setiv (hde, tv[i].iv, tv[i].ivlen);
       if (!err)
@@ -1427,8 +1440,29 @@ check_gcm_cipher (void)
           return;
         }
 
-      /* this time we encrypt and decrypt one byte at a time */
-      int byteNum;
+      /* this time we authenticate, encrypt and decrypt one byte at a time */
+      for (byteNum = 0; byteNum < tv[i].aadlen; ++byteNum)
+        {
+          err = gcry_cipher_authenticate(hde, tv[i].aad + byteNum, 1);
+          if (err)
+            {
+              fail ("aes-gcm, gcry_cipher_authenticate (%d) (byte-buf) failed: "
+                    "%s\n", i, gpg_strerror (err));
+              gcry_cipher_close (hde);
+              gcry_cipher_close (hdd);
+              return;
+            }
+          err = gcry_cipher_authenticate(hdd, tv[i].aad + byteNum, 1);
+          if (err)
+            {
+              fail ("aes-gcm, de gcry_cipher_authenticate (%d) (byte-buf) "
+                   "failed: %s\n", i, gpg_strerror (err));
+              gcry_cipher_close (hde);
+              gcry_cipher_close (hdd);
+              return;
+            }
+        }
+
       for (byteNum = 0; byteNum < tv[i].inlen; ++byteNum)
         {
           err = gcry_cipher_encrypt (hde, out+byteNum, 1,
@@ -1436,7 +1470,7 @@ check_gcm_cipher (void)
                                      1);
           if (err)
             {
-              fail ("aes-gcm, gcry_cipher_encrypt (%d) failed: %s\n",
+              fail ("aes-gcm, gcry_cipher_encrypt (%d) (byte-buf) failed: %s\n",
                     i,  gpg_strerror (err));
               gcry_cipher_close (hde);
               gcry_cipher_close (hdd);
@@ -1445,14 +1479,27 @@ check_gcm_cipher (void)
         }
 
       if (memcmp (tv[i].out, out, tv[i].inlen))
-        fail ("aes-gcm, encrypt mismatch entry %d\n", i);
+        fail ("aes-gcm, encrypt mismatch entry %d, (byte-buf)\n", i);
+
+      err = gcry_cipher_gettag (hde, tag, GCRY_GCM_BLOCK_LEN);
+      if (err)
+        {
+          fail ("aes-gcm, gcry_cipher_gettag(%d) (byte-buf) failed: %s\n",
+                i, gpg_strerror (err));
+          gcry_cipher_close (hde);
+          gcry_cipher_close (hdd);
+          return;
+        }
+
+      if (memcmp (tv[i].tag, tag, GCRY_GCM_BLOCK_LEN))
+        fail ("aes-gcm, encrypt tag mismatch entry %d, (byte-buf)\n", i);
 
       for (byteNum = 0; byteNum < tv[i].inlen; ++byteNum)
         {
           err = gcry_cipher_decrypt (hdd, out+byteNum, 1, NULL, 0);
           if (err)
             {
-              fail ("aes-gcm, gcry_cipher_decrypt (%d) failed: %s\n",
+              fail ("aes-gcm, gcry_cipher_decrypt (%d) (byte-buf) failed: %s\n",
                     i, gpg_strerror (err));
               gcry_cipher_close (hde);
               gcry_cipher_close (hdd);
@@ -1462,7 +1509,16 @@ check_gcm_cipher (void)
 
       if (memcmp (tv[i].plaintext, out, tv[i].inlen))
         fail ("aes-gcm, decrypt mismatch entry %d\n", i);
-#endif
+
+      err = gcry_cipher_checktag (hdd, tag, GCRY_GCM_BLOCK_LEN);
+      if (err)
+        {
+          fail ("aes-gcm, gcry_cipher_checktag(%d) (byte-buf) failed: %s\n",
+                i, gpg_strerror (err));
+          gcry_cipher_close (hde);
+          gcry_cipher_close (hdd);
+          return;
+        }
 
       gcry_cipher_close (hde);
       gcry_cipher_close (hdd);
@@ -1473,6 +1529,20 @@ check_gcm_cipher (void)
 
 
 static void
+check_gcm_cipher (void)
+{
+  /* Large buffers, no splitting. */
+  _check_gcm_cipher(0xffffffff);
+  /* Split input to one byte buffers. */
+  _check_gcm_cipher(1);
+  /* Split input to 7 byte buffers. */
+  _check_gcm_cipher(7);
+  /* Split input to 16 byte buffers. */
+  _check_gcm_cipher(16);
+}
+
+
+static void
 check_ccm_cipher (void)
 {
   static const struct tv