mirror of
git://git.gnupg.org/gnupg.git
synced 2024-12-22 10:19:57 +01:00
gpg: Fix AEAD encryption for chunk sizes other than 64 KiB.
* g10/cipher-aead.c (do_flush): Init ERR. Fix remaining chunklen computation. (do_free): Add dummy encryption. Close the cipher handle. * g10/decrypt-data.c (aead_underflow): Rewrite. -- Until we have integrated test into the test suite extensive tests can also be done with a script like this: --8<---------------cut here---------------start------------->8--- #!/bin/sh set -e GPG="../g10/gpg --rfc4880bis --pinentry-mode=loopback" GPG="$GPG --passphrase abc --batch" MKTDATA="$HOME/b/gnupg-2.0/tools/mk-tdata" for chunksize in 6 7 12 13 14 30; do for count in $(seq 1 200) $(seq 8100 8200) \ $(seq 16350 16400) $(seq 20000 20100); do if [ ! -f "testfile-$count" ]; then $MKTDATA $count >"testfile-$count" fi echo "testing chunk size 2^$chunksize with $count bytes" $GPG --force-aead --aead-algo ocb --s2k-mode 0 --cipher AES -v -z 0 \ -c --chunk-size $chunksize \ <"testfile-$count" >"testfile-$count.gpg" 2>/dev/null $GPG -vd <"testfile-$count.gpg" >"testfile-$count.out" 2>/dev/null if ! cmp "testfile-$count" "testfile-$count.out"; then echo "FAILED comparing count $count" >&2 exit 1 fi done done echo All good --8<---------------cut here---------------end--------------->8--- Signed-off-by: Werner Koch <wk@gnupg.org>
This commit is contained in:
parent
83a15fa88e
commit
ff1bdc23d9
@ -271,7 +271,7 @@ write_final_chunk (cipher_filter_context_t *cfx, iobuf_t a)
|
||||
static gpg_error_t
|
||||
do_flush (cipher_filter_context_t *cfx, iobuf_t a, byte *buf, size_t size)
|
||||
{
|
||||
gpg_error_t err;
|
||||
gpg_error_t err = 0;
|
||||
int newchunk = 0;
|
||||
size_t n;
|
||||
|
||||
@ -285,9 +285,9 @@ do_flush (cipher_filter_context_t *cfx, iobuf_t a, byte *buf, size_t size)
|
||||
else
|
||||
n = cfx->bufsize - cfx->buflen;
|
||||
|
||||
if (cfx->chunklen + n >= cfx->chunksize)
|
||||
if (cfx->chunklen + cfx->buflen + n >= cfx->chunksize)
|
||||
{
|
||||
size_t n1 = cfx->chunksize - cfx->chunklen;
|
||||
size_t n1 = cfx->chunksize - (cfx->chunklen + cfx->buflen);
|
||||
newchunk = 1;
|
||||
if (DBG_FILTER)
|
||||
log_debug ("chunksize %ju reached;"
|
||||
@ -305,8 +305,8 @@ do_flush (cipher_filter_context_t *cfx, iobuf_t a, byte *buf, size_t size)
|
||||
if (cfx->buflen == cfx->bufsize || newchunk)
|
||||
{
|
||||
if (DBG_FILTER)
|
||||
log_debug ("encrypting: buflen=%zu %s %p\n",
|
||||
cfx->buflen, newchunk?"(newchunk)":"", cfx->cipher_hd);
|
||||
log_debug ("encrypting: buflen=%zu %s n=%zu\n",
|
||||
cfx->buflen, newchunk?"(newchunk)":"", n);
|
||||
if (newchunk)
|
||||
gcry_cipher_final (cfx->cipher_hd);
|
||||
if (!DBG_FILTER)
|
||||
@ -372,6 +372,9 @@ do_free (cipher_filter_context_t *cfx, iobuf_t a)
|
||||
{
|
||||
gpg_error_t err = 0;
|
||||
|
||||
if (DBG_FILTER)
|
||||
log_debug ("do_free: buflen=%zu\n", cfx->buflen);
|
||||
|
||||
/* FIXME: Check what happens if we just wrote the last chunk and no
|
||||
* more bytes were to encrypt. We should then not call finalize and
|
||||
* write the auth tag again, right? May this at all happen? */
|
||||
@ -394,6 +397,8 @@ do_free (cipher_filter_context_t *cfx, iobuf_t a)
|
||||
cfx->chunklen += cfx->buflen;
|
||||
cfx->total += cfx->buflen;
|
||||
}
|
||||
else /* Dummy encryption. */
|
||||
gcry_cipher_encrypt (cfx->cipher_hd, cfx->buffer, 0, NULL, 0);
|
||||
|
||||
/* Get and write the authentication tag. */
|
||||
if (DBG_FILTER)
|
||||
@ -411,8 +416,8 @@ do_free (cipher_filter_context_t *cfx, iobuf_t a)
|
||||
leave:
|
||||
xfree (cfx->buffer);
|
||||
cfx->buffer = NULL;
|
||||
/* gcry_cipher_close (cfx->cipher_hd); */
|
||||
/* cfx->cipher_hd = NULL; */
|
||||
gcry_cipher_close (cfx->cipher_hd);
|
||||
cfx->cipher_hd = NULL;
|
||||
return err;
|
||||
}
|
||||
|
||||
|
@ -56,8 +56,8 @@ struct decode_filter_context_s
|
||||
/* The start IV for AEAD encryption. */
|
||||
byte startiv[16];
|
||||
|
||||
/* The holdback buffer and its used length. For AEAD we need at
|
||||
* least 32+1 byte for MDC 22 bytes are required. */
|
||||
/* The holdback buffer and its used length. For AEAD we need 32+1
|
||||
* bytes but we use 48 byte. For MDC we need 22 bytes. */
|
||||
char holdback[48];
|
||||
unsigned int holdbacklen;
|
||||
|
||||
@ -536,67 +536,49 @@ decrypt_data (ctrl_t ctrl, void *procctx, PKT_encrypted *ed, DEK *dek)
|
||||
static gpg_error_t
|
||||
aead_underflow (decode_filter_ctx_t dfx, iobuf_t a, byte *buf, size_t *ret_len)
|
||||
{
|
||||
const size_t size = *ret_len; /* The initial length of BUF. */
|
||||
const size_t size = *ret_len; /* The allocated size of BUF. */
|
||||
gpg_error_t err;
|
||||
size_t n; /* Finally the number of decrypted bytes in BUF. */
|
||||
size_t totallen = 0; /* The number of bytes to return on success or EOF. */
|
||||
size_t off = 0; /* The offset into the buffer. */
|
||||
size_t len; /* The current number of bytes in BUF+OFF. */
|
||||
int c;
|
||||
|
||||
log_assert (size > 64); /* Our code requires at least this size. */
|
||||
log_assert (size > 48); /* Our code requires at least this size. */
|
||||
|
||||
/* Get at least 32 bytes and put it ahead in the buffer. */
|
||||
/* Copy the rest from the last call of this function into BUF. */
|
||||
len = dfx->holdbacklen;
|
||||
dfx->holdbacklen = 0;
|
||||
memcpy (buf, dfx->holdback, len);
|
||||
|
||||
if (DBG_FILTER)
|
||||
log_debug ("aead_underflow: size=%zu len=%zu%s\n",
|
||||
size, len, dfx->eof_seen? " eof":"");
|
||||
|
||||
/* Read and fill up BUF. We need to watchout for an EOF so that we
|
||||
* can detect the last chunk which is commonly shorter than the
|
||||
* chunksize. After the last data byte from the last chunk 32 more
|
||||
* bytes are expected for the last chunk's tag and the following
|
||||
* final chunk's tag. To detect the EOF we need to read at least
|
||||
* one further byte; however we try to ready 16 extra bytes to avoid
|
||||
* singel byte reads in some lower layers. The outcome is that we
|
||||
* have up to 48 extra extra octets which we will later put into the
|
||||
* holdback buffer for the next invocation (which handles the EOF
|
||||
* case). */
|
||||
if (dfx->partial)
|
||||
{
|
||||
for (n=32; n < 64; n++)
|
||||
{
|
||||
if ((c = iobuf_get (a)) == -1)
|
||||
break;
|
||||
buf[n] = c;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (n=32; n < 64 && dfx->length; n++, dfx->length--)
|
||||
{
|
||||
if ((c = iobuf_get (a)) == -1)
|
||||
break; /* Premature EOF. */
|
||||
buf[n] = c;
|
||||
}
|
||||
}
|
||||
|
||||
if (n == 64)
|
||||
{
|
||||
/* We got 32 bytes from A which are good for the last chunk's
|
||||
* auth tag and the final chunk's auth tag. On the first time
|
||||
* we don't have anything in the holdback buffer and thus we move
|
||||
* those 32 bytes to the start of the buffer. All further calls
|
||||
* will copy the 32 bytes from the holdback buffer to the start of the
|
||||
* buffer. */
|
||||
if (!dfx->holdbacklen)
|
||||
{
|
||||
memcpy (buf, buf+32, 32);
|
||||
n = 32; /* Continue at this position. */
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy (buf, dfx->holdback, 32);
|
||||
}
|
||||
|
||||
/* Now fill up the provided buffer. */
|
||||
if (dfx->partial)
|
||||
{
|
||||
for (; n < size; n++ )
|
||||
for (; len < size; len++ )
|
||||
{
|
||||
if ((c = iobuf_get (a)) == -1)
|
||||
{
|
||||
dfx->eof_seen = 1; /* Normal EOF. */
|
||||
break;
|
||||
}
|
||||
buf[n] = c;
|
||||
buf[len] = c;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (; n < size && dfx->length; n++, dfx->length--)
|
||||
for (; len < size && dfx->length; len++, dfx->length--)
|
||||
{
|
||||
c = iobuf_get (a);
|
||||
if (c == -1)
|
||||
@ -604,94 +586,136 @@ aead_underflow (decode_filter_ctx_t dfx, iobuf_t a, byte *buf, size_t *ret_len)
|
||||
dfx->eof_seen = 3; /* Premature EOF. */
|
||||
break;
|
||||
}
|
||||
buf[n] = c;
|
||||
buf[len] = c;
|
||||
}
|
||||
if (!dfx->length)
|
||||
dfx->eof_seen = 1; /* Normal EOF. */
|
||||
}
|
||||
|
||||
/* Move the trailing 32 bytes back to the holdback buffer. We
|
||||
* got at least 64 bytes and thus a memmove is not needed. */
|
||||
n -= 32;
|
||||
memcpy (dfx->holdback, buf+n, 32);
|
||||
dfx->holdbacklen = 32;
|
||||
}
|
||||
else if (!dfx->holdbacklen)
|
||||
if (len < 32)
|
||||
{
|
||||
/* EOF seen but empty holdback buffer. This means that we did
|
||||
* not read enough for the two auth tags. */
|
||||
n -= 32;
|
||||
memcpy (buf, buf+32, n );
|
||||
dfx->eof_seen = 2; /* EOF with incomplete tag. */
|
||||
/* Not enough data for the last two tags. */
|
||||
err = gpg_error (GPG_ERR_TRUNCATED);
|
||||
goto leave;
|
||||
}
|
||||
if (dfx->eof_seen)
|
||||
{
|
||||
/* If have seen an EOF we copy only the last two auth tags into
|
||||
* the holdback buffer. */
|
||||
dfx->holdbacklen = 32;
|
||||
memcpy (dfx->holdback, buf+len-32, 32);
|
||||
len -= 32;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* EOF seen (i.e. read less than 32 bytes). */
|
||||
memcpy (buf, dfx->holdback, 32);
|
||||
n -= 32;
|
||||
memcpy (dfx->holdback, buf+n, 32);
|
||||
dfx->eof_seen = 1; /* Normal EOF. */
|
||||
/* If have not seen an EOF we copy the entire extra 48 bytes
|
||||
* into the holdback buffer for processing at the next call of
|
||||
* this function. */
|
||||
dfx->holdbacklen = len > 48? 48 : len;
|
||||
memcpy (dfx->holdback, buf+len-dfx->holdbacklen, dfx->holdbacklen);
|
||||
len -= dfx->holdbacklen;
|
||||
}
|
||||
/* log_printhex (dfx->holdback, dfx->holdbacklen, "holdback:"); */
|
||||
|
||||
/* Decrypt the buffer. This requires a loop because a chunk may end
|
||||
* within the buffer. */
|
||||
if (DBG_FILTER)
|
||||
log_debug ("decrypt: chunklen=%ju total=%ju size=%zu n=%zu%s\n",
|
||||
(uintmax_t)dfx->chunklen, (uintmax_t)dfx->total, size, n,
|
||||
log_debug ("decrypt loop: chunklen=%ju total=%ju size=%zu len=%zu%s\n",
|
||||
(uintmax_t)dfx->chunklen, (uintmax_t)dfx->total, size, len,
|
||||
dfx->eof_seen? " eof":"");
|
||||
|
||||
/* Now decrypt the buffer. */
|
||||
if (n && dfx->eof_seen > 1)
|
||||
while (len && dfx->chunklen + len >= dfx->chunksize)
|
||||
{
|
||||
err = gpg_error (GPG_ERR_TRUNCATED);
|
||||
}
|
||||
else if (!n)
|
||||
{
|
||||
log_assert (dfx->eof_seen);
|
||||
err = gpg_error (GPG_ERR_EOF);
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t off = 0;
|
||||
|
||||
if (dfx->chunklen + n >= dfx->chunksize)
|
||||
{
|
||||
size_t n0 = dfx->chunksize - dfx->chunklen;
|
||||
size_t n = dfx->chunksize - dfx->chunklen;
|
||||
byte tagbuf[16];
|
||||
|
||||
if (DBG_FILTER)
|
||||
log_debug ("chunksize will be reached: n0=%zu\n", n0);
|
||||
log_debug ("chunksize will be reached: n=%zu\n", n);
|
||||
/* log_printhex (buf, n, "ciph:"); */
|
||||
gcry_cipher_final (dfx->cipher_hd);
|
||||
err = gcry_cipher_decrypt (dfx->cipher_hd, buf, n0, NULL, 0);
|
||||
err = gcry_cipher_decrypt (dfx->cipher_hd, buf+off, n, NULL, 0);
|
||||
if (err)
|
||||
{
|
||||
log_error ("gcry_cipher_decrypt failed (1): %s\n",
|
||||
gpg_strerror (err));
|
||||
goto leave;
|
||||
}
|
||||
/*log_printhex (buf, n, "buf:");*/
|
||||
dfx->chunklen += n0;
|
||||
dfx->total += n0;
|
||||
off = n0;
|
||||
n -= n0;
|
||||
/* log_printhex (buf, n, "plai:"); */
|
||||
totallen += n;
|
||||
dfx->chunklen += n;
|
||||
dfx->total += n;
|
||||
off += n;
|
||||
len -= n;
|
||||
|
||||
if (DBG_FILTER)
|
||||
log_debug ("bytes left: %zu off=%zu\n", n, off);
|
||||
log_assert (n >= 16);
|
||||
log_assert (dfx->holdbacklen);
|
||||
log_debug ("bytes left: %zu at off=%zu\n", len, off);
|
||||
|
||||
/* Check the tag. */
|
||||
if (len < 16)
|
||||
{
|
||||
/* The tag is not entirely in the buffer. Read the rest of
|
||||
* the tag from the holdback buffer. The shift the holdback
|
||||
* buffer and fill it up again. */
|
||||
memcpy (tagbuf, buf+off, len);
|
||||
memcpy (tagbuf + len, dfx->holdback, 16 - len);
|
||||
dfx->holdbacklen -= 16-len;
|
||||
memmove (dfx->holdback, dfx->holdback + (16-len), dfx->holdbacklen);
|
||||
|
||||
len = dfx->holdbacklen;
|
||||
if (dfx->partial)
|
||||
{
|
||||
for (; len < 48; len++ )
|
||||
{
|
||||
if ((c = iobuf_get (a)) == -1)
|
||||
{
|
||||
dfx->eof_seen = 1; /* Normal EOF. */
|
||||
break;
|
||||
}
|
||||
dfx->holdback[len] = c;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (; len < 48 && dfx->length; len++, dfx->length--)
|
||||
{
|
||||
c = iobuf_get (a);
|
||||
if (c == -1)
|
||||
{
|
||||
dfx->eof_seen = 3; /* Premature EOF. */
|
||||
break;
|
||||
}
|
||||
dfx->holdback[len] = c;
|
||||
}
|
||||
if (!dfx->length)
|
||||
dfx->eof_seen = 1; /* Normal EOF. */
|
||||
}
|
||||
if (len < 32)
|
||||
{
|
||||
/* Not enough data for the last two tags. */
|
||||
err = gpg_error (GPG_ERR_TRUNCATED);
|
||||
goto leave;
|
||||
}
|
||||
dfx->holdbacklen = len;
|
||||
/* log_printhex (dfx->holdback, dfx->holdbacklen, "holdback:"); */
|
||||
len = 0;
|
||||
}
|
||||
else /* We already have the full tag. */
|
||||
{
|
||||
memcpy (tagbuf, buf+off, 16);
|
||||
/* Remove that tag from the output. */
|
||||
memmove (buf + off, buf + off + 16, len - 16);
|
||||
len -= 16;
|
||||
}
|
||||
if (DBG_CRYPTO)
|
||||
log_printhex (buf+off, 16, "tag:");
|
||||
err = gcry_cipher_checktag (dfx->cipher_hd, buf + off, 16);
|
||||
log_printhex (tagbuf, 16, "tag:");
|
||||
err = gcry_cipher_checktag (dfx->cipher_hd, tagbuf, 16);
|
||||
if (err)
|
||||
{
|
||||
if (DBG_FILTER)
|
||||
log_debug ("gcry_cipher_checktag failed (1): %s\n",
|
||||
gpg_strerror (err));
|
||||
/* Return Bad Signature like we do with MDC encryption. */
|
||||
if (gpg_err_code (err) == GPG_ERR_CHECKSUM)
|
||||
err = gpg_error (GPG_ERR_BAD_SIGNATURE);
|
||||
goto leave;
|
||||
}
|
||||
/* Remove that tag from the output. */
|
||||
memmove (buf + off, buf + off + 16, n - 16);
|
||||
n -= 16;
|
||||
|
||||
/* Prepare a new chunk. */
|
||||
dfx->chunklen = 0;
|
||||
@ -702,49 +726,39 @@ aead_underflow (decode_filter_ctx_t dfx, iobuf_t a, byte *buf, size_t *ret_len)
|
||||
err = aead_set_ad (dfx, 0);
|
||||
if (err)
|
||||
goto leave;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dfx->eof_seen)
|
||||
{
|
||||
/* This is the last block of the last chunk. Its length may
|
||||
* not be a multiple of the block length. We expect that it
|
||||
* is followed by two authtags. The first being the one
|
||||
* from the current chunk and the second form the final
|
||||
* chunk encrypting the empty string. Note that for the
|
||||
* other blocks we assume a multiple of the block length
|
||||
* which is only true because the filter is called with
|
||||
* large 2^n sized buffers. There is no assert because
|
||||
* gcry_cipher_decrypt would detect such an error. */
|
||||
* not be a multiple of the block length. */
|
||||
gcry_cipher_final (dfx->cipher_hd);
|
||||
/* log_printhex (buf+off, n, "buf+off:"); */
|
||||
}
|
||||
err = gcry_cipher_decrypt (dfx->cipher_hd, buf + off, n, NULL, 0);
|
||||
err = gcry_cipher_decrypt (dfx->cipher_hd, buf + off, len, NULL, 0);
|
||||
if (err)
|
||||
{
|
||||
log_error ("gcry_cipher_decrypt failed (2): %s\n",
|
||||
gpg_strerror (err));
|
||||
log_error ("gcry_cipher_decrypt failed (2): %s\n", gpg_strerror (err));
|
||||
goto leave;
|
||||
}
|
||||
dfx->chunklen += n;
|
||||
dfx->total += n;
|
||||
|
||||
totallen += len;
|
||||
dfx->chunklen += len;
|
||||
dfx->total += len;
|
||||
if (dfx->eof_seen)
|
||||
{
|
||||
log_printhex (buf+off, n, "buf+off:");
|
||||
if (DBG_FILTER)
|
||||
log_debug ("eof seen: chunklen=%ju total=%ju off=%zu n=%zu\n",
|
||||
(uintmax_t)dfx->chunklen, (uintmax_t)dfx->total, off, n);
|
||||
log_debug ("eof seen: holdback buffer has the tags.\n");
|
||||
|
||||
log_assert (dfx->holdbacklen);
|
||||
log_assert (dfx->holdbacklen >= 32);
|
||||
|
||||
if (DBG_FILTER)
|
||||
log_printhex (dfx->holdback, 16, "tag:");
|
||||
err = gcry_cipher_checktag (dfx->cipher_hd, dfx->holdback, 16);
|
||||
if (err)
|
||||
{
|
||||
log_printhex (dfx->holdback, 16, "tag:");
|
||||
log_error ("gcry_cipher_checktag failed (2): %s\n",
|
||||
gpg_strerror (err));
|
||||
/* Return Bad Signature like we do with MDC encryption. */
|
||||
if (gpg_err_code (err) == GPG_ERR_CHECKSUM)
|
||||
err = gpg_error (GPG_ERR_BAD_SIGNATURE);
|
||||
goto leave;
|
||||
}
|
||||
|
||||
@ -758,40 +772,41 @@ aead_underflow (decode_filter_ctx_t dfx, iobuf_t a, byte *buf, size_t *ret_len)
|
||||
goto leave;
|
||||
gcry_cipher_final (dfx->cipher_hd);
|
||||
/* decrypt an empty string. */
|
||||
err = gcry_cipher_decrypt (dfx->cipher_hd, buf, 0, NULL, 0);
|
||||
err = gcry_cipher_decrypt (dfx->cipher_hd, dfx->holdback, 0, NULL, 0);
|
||||
if (err)
|
||||
{
|
||||
log_error ("gcry_cipher_decrypt failed (final): %s\n",
|
||||
gpg_strerror (err));
|
||||
goto leave;
|
||||
}
|
||||
/* log_printhex (dfx->holdback+16, 16, "tag:"); */
|
||||
err = gcry_cipher_checktag (dfx->cipher_hd, dfx->holdback+16, 16);
|
||||
if (err)
|
||||
{
|
||||
if (DBG_FILTER)
|
||||
log_debug ("gcry_cipher_checktag failed (final): %s\n",
|
||||
gpg_strerror (err));
|
||||
/* Return Bad Signature like we do with MDC encryption. */
|
||||
if (gpg_err_code (err) == GPG_ERR_CHECKSUM)
|
||||
err = gpg_error (GPG_ERR_BAD_SIGNATURE);
|
||||
goto leave;
|
||||
}
|
||||
|
||||
n += off;
|
||||
if (DBG_FILTER)
|
||||
log_debug ("eof seen: returning %zu\n", n);
|
||||
/* log_printhex (buf, n, "buf:"); */
|
||||
}
|
||||
else
|
||||
n += off;
|
||||
err = gpg_error (GPG_ERR_EOF);
|
||||
}
|
||||
|
||||
leave:
|
||||
/* In case of a real error we better wipe out the buffer than to
|
||||
* keep partly encrypted data. */
|
||||
if (DBG_FILTER)
|
||||
log_debug ("aead_underflow: returning %zu (%s)\n",
|
||||
totallen, gpg_strerror (err));
|
||||
|
||||
/* In case of an auth error we map the error code to the same as
|
||||
* used by the MDC decryption. */
|
||||
if (gpg_err_code (err) == GPG_ERR_CHECKSUM)
|
||||
err = gpg_error (GPG_ERR_BAD_SIGNATURE);
|
||||
|
||||
/* In case of an error we better wipe out the buffer than to convey
|
||||
* partly decrypted data. */
|
||||
if (err && gpg_err_code (err) != GPG_ERR_EOF)
|
||||
memset (buf, 0, size);
|
||||
*ret_len = n;
|
||||
|
||||
*ret_len = totallen;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user