diff options
author | Jussi Kivilinna <jussi.kivilinna@iki.fi> | 2013-11-20 15:06:03 +0200 |
---|---|---|
committer | Jussi Kivilinna <jussi.kivilinna@iki.fi> | 2013-11-20 18:45:46 +0200 |
commit | fb1e52e3fe231671de546eacd6becd31c26c4f7b (patch) | |
tree | 7d99e8d25341febcb8d66c8cf8b1229ae590e3a6 | |
parent | 2d870a9142e8c8b3f008e1ad8e83e4bdf7a8e4e7 (diff) | |
download | libgcrypt-fb1e52e3fe231671de546eacd6becd31c26c4f7b.tar.gz |
GCM: Add support for split data buffers and online operation
* 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>
-rw-r--r-- | cipher/cipher-gcm.c | 113 | ||||
-rw-r--r-- | cipher/cipher-internal.h | 7 | ||||
-rw-r--r-- | tests/basic.c | 166 |
3 files changed, 210 insertions, 76 deletions
diff --git a/cipher/cipher-gcm.c b/cipher/cipher-gcm.c index 62855b15..42cfc1ea 100644 --- a/cipher/cipher-gcm.c +++ b/cipher/cipher-gcm.c @@ -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) diff --git a/cipher/cipher-internal.h b/cipher/cipher-internal.h index 225f6993..ede6f757 100644 --- a/cipher/cipher-internal.h +++ b/cipher/cipher-internal.h @@ -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 diff --git a/tests/basic.c b/tests/basic.c index b4541a0f..a205f48e 100644 --- a/tests/basic.c +++ b/tests/basic.c @@ -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 |