diff --git a/g10/cipher-aead.c b/g10/cipher-aead.c index cc306f900..1457baaa5 100644 --- a/g10/cipher-aead.c +++ b/g10/cipher-aead.c @@ -1,458 +1,443 @@ /* cipher-aead.c - Enciphering filter for AEAD modes * Copyright (C) 2018 Werner koch * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * SPDX-License-Identifier: GPL-3.0+ */ #include #include #include #include #include #include "gpg.h" #include "../common/status.h" #include "../common/iobuf.h" #include "../common/util.h" #include "filter.h" #include "packet.h" #include "options.h" #include "main.h" /* The size of the buffer we allocate to encrypt the data. This must * be a multiple of the OCB blocksize (16 byte). */ #define AEAD_ENC_BUFFER_SIZE (64*1024) /* Wrapper around iobuf_write to make sure that a proper error code is * always returned. */ static gpg_error_t my_iobuf_write (iobuf_t a, const void *buffer, size_t buflen) { if (iobuf_write (a, buffer, buflen)) { gpg_error_t err = iobuf_error (a); if (!err || !gpg_err_code (err)) /* (The latter should never happen) */ err = gpg_error (GPG_ERR_EIO); return err; } return 0; } -/* Set the additional data for the current chunk. If FINAL is set the - * final AEAD chunk is processed. */ +/* Set the nonce and the additional data for the current chunk. If + * FINAL is set the final AEAD chunk is processed. This also reset + * the encryption machinery so that the handle can be used for a new + * chunk. */ static gpg_error_t -set_additional_data (cipher_filter_context_t *cfx, int final) -{ - unsigned char ad[21]; - - ad[0] = (0xc0 | PKT_ENCRYPTED_AEAD); - ad[1] = 1; - ad[2] = cfx->dek->algo; - ad[3] = cfx->dek->use_aead; - ad[4] = cfx->chunkbyte; - ad[5] = cfx->chunkindex >> 56; - ad[6] = cfx->chunkindex >> 48; - ad[7] = cfx->chunkindex >> 40; - ad[8] = cfx->chunkindex >> 32; - ad[9] = cfx->chunkindex >> 24; - ad[10]= cfx->chunkindex >> 16; - ad[11]= cfx->chunkindex >> 8; - ad[12]= cfx->chunkindex; - if (final) - { - ad[13] = cfx->total >> 56; - ad[14] = cfx->total >> 48; - ad[15] = cfx->total >> 40; - ad[16] = cfx->total >> 32; - ad[17] = cfx->total >> 24; - ad[18] = cfx->total >> 16; - ad[19] = cfx->total >> 8; - ad[20] = cfx->total; - } - if (DBG_CRYPTO) - log_printhex (ad, final? 21 : 13, "authdata:"); - return gcry_cipher_authenticate (cfx->cipher_hd, ad, final? 21 : 13); -} - - -/* Set the nonce. This also reset the encryption machinery so that - * the handle can be used for a new chunk. */ -static gpg_error_t -set_nonce (cipher_filter_context_t *cfx) +set_nonce_and_ad (cipher_filter_context_t *cfx, int final) { + gpg_error_t err; unsigned char nonce[16]; + unsigned char ad[21]; int i; switch (cfx->dek->use_aead) { case AEAD_ALGO_OCB: memcpy (nonce, cfx->startiv, 15); i = 7; break; case AEAD_ALGO_EAX: memcpy (nonce, cfx->startiv, 16); i = 8; break; default: BUG (); } nonce[i++] ^= cfx->chunkindex >> 56; nonce[i++] ^= cfx->chunkindex >> 48; nonce[i++] ^= cfx->chunkindex >> 40; nonce[i++] ^= cfx->chunkindex >> 32; nonce[i++] ^= cfx->chunkindex >> 24; nonce[i++] ^= cfx->chunkindex >> 16; nonce[i++] ^= cfx->chunkindex >> 8; nonce[i++] ^= cfx->chunkindex; if (DBG_CRYPTO) log_printhex (nonce, 15, "nonce:"); - return gcry_cipher_setiv (cfx->cipher_hd, nonce, i); + err = gcry_cipher_setiv (cfx->cipher_hd, nonce, i); + if (err) + return err; + + ad[0] = (0xc0 | PKT_ENCRYPTED_AEAD); + ad[1] = 1; + ad[2] = cfx->dek->algo; + ad[3] = cfx->dek->use_aead; + ad[4] = cfx->chunkbyte; + ad[5] = cfx->chunkindex >> 56; + ad[6] = cfx->chunkindex >> 48; + ad[7] = cfx->chunkindex >> 40; + ad[8] = cfx->chunkindex >> 32; + ad[9] = cfx->chunkindex >> 24; + ad[10]= cfx->chunkindex >> 16; + ad[11]= cfx->chunkindex >> 8; + ad[12]= cfx->chunkindex; + if (final) + { + ad[13] = cfx->total >> 56; + ad[14] = cfx->total >> 48; + ad[15] = cfx->total >> 40; + ad[16] = cfx->total >> 32; + ad[17] = cfx->total >> 24; + ad[18] = cfx->total >> 16; + ad[19] = cfx->total >> 8; + ad[20] = cfx->total; + } + if (DBG_CRYPTO) + log_printhex (ad, final? 21 : 13, "authdata:"); + return gcry_cipher_authenticate (cfx->cipher_hd, ad, final? 21 : 13); } static gpg_error_t write_header (cipher_filter_context_t *cfx, iobuf_t a) { gpg_error_t err; PACKET pkt; PKT_encrypted ed; unsigned int blocksize; unsigned int startivlen; enum gcry_cipher_modes ciphermode; log_assert (cfx->dek->use_aead); blocksize = openpgp_cipher_get_algo_blklen (cfx->dek->algo); if (blocksize != 16 ) log_fatal ("unsupported blocksize %u for AEAD\n", blocksize); err = openpgp_aead_algo_info (cfx->dek->use_aead, &ciphermode, &startivlen); if (err) goto leave; log_assert (opt.chunk_size >= 6 && opt.chunk_size <= 62); cfx->chunkbyte = opt.chunk_size - 6; cfx->chunksize = (uint64_t)1 << (cfx->chunkbyte + 6); cfx->chunklen = 0; cfx->bufsize = AEAD_ENC_BUFFER_SIZE; cfx->buflen = 0; cfx->buffer = xtrymalloc (cfx->bufsize); if (!cfx->buffer) return gpg_error_from_syserror (); memset (&ed, 0, sizeof ed); ed.new_ctb = 1; /* (Is anyway required for the packet type). */ ed.len = 0; /* fixme: cfx->datalen */ ed.extralen = startivlen + 16; /* (16 is the taglen) */ ed.cipher_algo = cfx->dek->algo; ed.aead_algo = cfx->dek->use_aead; ed.chunkbyte = cfx->chunkbyte; init_packet (&pkt); pkt.pkttype = PKT_ENCRYPTED_AEAD; pkt.pkt.encrypted = &ed; if (DBG_FILTER) log_debug ("aead packet: len=%lu extralen=%d\n", (unsigned long)ed.len, ed.extralen); write_status_printf (STATUS_BEGIN_ENCRYPTION, "0 %d %d", cfx->dek->algo, ed.aead_algo); print_cipher_algo_note (cfx->dek->algo); if (build_packet( a, &pkt)) log_bug ("build_packet(ENCRYPTED_AEAD) failed\n"); log_assert (sizeof cfx->startiv >= startivlen); gcry_randomize (cfx->startiv, startivlen, GCRY_STRONG_RANDOM); err = my_iobuf_write (a, cfx->startiv, startivlen); if (err) goto leave; err = openpgp_cipher_open (&cfx->cipher_hd, cfx->dek->algo, ciphermode, GCRY_CIPHER_SECURE); if (err) goto leave; if (DBG_CRYPTO) log_printhex (cfx->dek->key, cfx->dek->keylen, "thekey:"); err = gcry_cipher_setkey (cfx->cipher_hd, cfx->dek->key, cfx->dek->keylen); if (err) return err; - err = set_nonce (cfx); + err = set_nonce_and_ad (cfx, 0); if (err) return err; - - err = set_additional_data (cfx, 0); - if (err) - return err; - cfx->wrote_header = 1; leave: return err; } /* Get and write the auth tag to stream A. */ static gpg_error_t write_auth_tag (cipher_filter_context_t *cfx, iobuf_t a) { gpg_error_t err; char tag[16]; err = gcry_cipher_gettag (cfx->cipher_hd, tag, 16); if (err) goto leave; err = my_iobuf_write (a, tag, 16); if (err) goto leave; leave: + if (err) + log_error ("write_auth_tag failed: %s\n", gpg_strerror (err)); return err; } /* Write the final chunk to stream A. */ static gpg_error_t write_final_chunk (cipher_filter_context_t *cfx, iobuf_t a) { gpg_error_t err; char dummy[1]; if (cfx->chunklen) cfx->chunkindex++; - err = set_nonce (cfx); - if (err) - goto leave; - err = set_additional_data (cfx, 1); + err = set_nonce_and_ad (cfx, 1); if (err) goto leave; gcry_cipher_final (cfx->cipher_hd); /* Encrypt an empty string. */ err = gcry_cipher_encrypt (cfx->cipher_hd, dummy, 0, NULL, 0); if (err) goto leave; err = write_auth_tag (cfx, a); leave: return err; } /* The core of the flush sub-function of cipher_filter_aead. */ static gpg_error_t do_flush (cipher_filter_context_t *cfx, iobuf_t a, byte *buf, size_t size) { gpg_error_t err = 0; - int newchunk = 0; + int finalize = 0; size_t n; /* Put the data into a buffer, flush and encrypt as needed. */ if (DBG_FILTER) log_debug ("flushing %zu bytes (cur buflen=%zu)\n", size, cfx->buflen); do { if (cfx->buflen + size < cfx->bufsize) n = size; else n = cfx->bufsize - cfx->buflen; if (cfx->chunklen + cfx->buflen + n >= cfx->chunksize) { size_t n1 = cfx->chunksize - (cfx->chunklen + cfx->buflen); - newchunk = 1; + finalize = 1; if (DBG_FILTER) log_debug ("chunksize %ju reached;" " cur buflen=%zu using %zu of %zu\n", (uintmax_t)cfx->chunksize, (uintmax_t)cfx->buflen, n1, n); n = n1; } memcpy (cfx->buffer + cfx->buflen, buf, n); cfx->buflen += n; buf += n; size -= n; - if (cfx->buflen == cfx->bufsize || newchunk) + if (cfx->buflen == cfx->bufsize || finalize) { if (DBG_FILTER) log_debug ("encrypting: buflen=%zu %s n=%zu\n", - cfx->buflen, newchunk?"(newchunk)":"", n); - if (newchunk) + cfx->buflen, finalize?"(finalize)":"", n); + if (finalize) gcry_cipher_final (cfx->cipher_hd); - if (!DBG_FILTER) - ; - else if (newchunk) - log_printhex (cfx->buffer, cfx->buflen, "plain(1):"); - else if (cfx->buflen > 32) - log_printhex (cfx->buffer + cfx->buflen - 32, 32, - "plain(last 32):"); + if (DBG_FILTER) + { + if (finalize) + log_printhex (cfx->buffer, cfx->buflen, "plain(1):"); + else if (cfx->buflen > 32) + log_printhex (cfx->buffer + cfx->buflen - 32, 32, + "plain(last32):"); + } /* Take care: even with a buflen of zero an encrypt needs to * be called after gcry_cipher_final and before * gcry_cipher_gettag - at least with libgcrypt 1.8 and OCB * mode. */ gcry_cipher_encrypt (cfx->cipher_hd, cfx->buffer, cfx->buflen, NULL, 0); - if (newchunk && DBG_FILTER) + if (finalize && DBG_FILTER) log_printhex (cfx->buffer, cfx->buflen, "ciphr(1):"); err = my_iobuf_write (a, cfx->buffer, cfx->buflen); if (err) goto leave; cfx->chunklen += cfx->buflen; cfx->total += cfx->buflen; cfx->buflen = 0; - if (newchunk) + if (finalize) { if (DBG_FILTER) log_debug ("chunklen=%ju total=%ju\n", (uintmax_t)cfx->chunklen, (uintmax_t)cfx->total); err = write_auth_tag (cfx, a); if (err) - { - log_error ("gcry_cipher_gettag failed: %s\n", - gpg_strerror (err)); - goto leave; - } + goto leave; if (DBG_FILTER) log_debug ("starting a new chunk (cur size=%zu)\n", size); cfx->chunkindex++; cfx->chunklen = 0; - err = set_nonce (cfx); - if (err) - goto leave; - err = set_additional_data (cfx, 0); + err = set_nonce_and_ad (cfx, 0); if (err) goto leave; - newchunk = 0; + finalize = 0; } } } while (size); leave: return err; } /* The core of the free sub-function of cipher_filter_aead. */ static gpg_error_t 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? */ /* Call finalize which will also allow us to flush out and encrypt * the last arbitrary length buffer. */ gcry_cipher_final (cfx->cipher_hd); /* Encrypt any remaining bytes. */ if (cfx->buflen) { if (DBG_FILTER) log_debug ("processing last %zu bytes of the last chunk\n", cfx->buflen); gcry_cipher_encrypt (cfx->cipher_hd, cfx->buffer, cfx->buflen, NULL, 0); err = my_iobuf_write (a, cfx->buffer, cfx->buflen); if (err) goto leave; /* log_printhex (cfx->buffer, cfx->buflen, "wrote:"); */ 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) log_debug ("chunklen=%ju total=%ju\n", (uintmax_t)cfx->chunklen, (uintmax_t)cfx->total); err = write_auth_tag (cfx, a); if (err) goto leave; /* Write the final chunk. */ if (DBG_FILTER) log_debug ("creating final chunk\n"); err = write_final_chunk (cfx, a); leave: xfree (cfx->buffer); cfx->buffer = NULL; gcry_cipher_close (cfx->cipher_hd); cfx->cipher_hd = NULL; return err; } /* * This filter is used to encrypt data with an AEAD algorithm */ int cipher_filter_aead (void *opaque, int control, iobuf_t a, byte *buf, size_t *ret_len) { cipher_filter_context_t *cfx = opaque; size_t size = *ret_len; int rc = 0; if (control == IOBUFCTRL_UNDERFLOW) /* decrypt */ { rc = -1; /* not used */ } else if (control == IOBUFCTRL_FLUSH) /* encrypt */ { if (!cfx->wrote_header && (rc=write_header (cfx, a))) ; else rc = do_flush (cfx, a, buf, size); } else if (control == IOBUFCTRL_FREE) { rc = do_free (cfx, a); } else if (control == IOBUFCTRL_DESC) { mem2str (buf, "cipher_filter_aead", *ret_len); } return rc; }