diff options
author | Werner Koch <wk@gnupg.org> | 2018-01-21 16:24:43 +0100 |
---|---|---|
committer | Werner Koch <wk@gnupg.org> | 2018-01-21 16:30:53 +0100 |
commit | 3f4ca85cb0cf58006417f4f7faafaa9a1f1bdf22 (patch) | |
tree | d77273b89c38dda139c8e204c6d60673e46a5e36 /g10/cipher-aead.c | |
parent | gpg: Add stub function for encrypting AEAD. (diff) | |
download | gnupg2-3f4ca85cb0cf58006417f4f7faafaa9a1f1bdf22.tar.xz gnupg2-3f4ca85cb0cf58006417f4f7faafaa9a1f1bdf22.zip |
gpg: First take on PKT_ENCRYPTED_AEAD.
* common/openpgpdefs.h (PKT_ENCRYPTED_AEAD): New const.
* g10/dek.h (DEK): Increase size of use_aead to 4 bits.
* g10/filter.h (cipher_filter_context_t): Add new fields for AEAD.
* g10/packet.h (PKT_encrypted): Add fields aead_algo, cipher_algo, and
chunkbyte.
* g10/build-packet.c (do_encrypted_aead): New.
(build_packet): Call it.
* g10/parse-packet.c (dump_sig_subpkt): Handle SIGSUBPKT_PREF_AEAD.
(parse_one_sig_subpkt, can_handle_critical): Ditto.
(parse_encrypted): Clear new PKT_ENCRYPTED fields.
(parse_encrypted_aead): New.
(parse): Call it.
* g10/gpg.c (main): Take care of --rfc4880bis option when checking
compliance.
* g10/cipher-aead.c: Replace the stub by real code.
* g10/decrypt-data.c (decode_filter_ctx_t): Add fields for use with
AEAD.
(aead_set_nonce): New.
(aead_set_ad): New.
(decrypt_data): Support AEAD.
(aead_underflow): New.
(aead_decode_filter): New.
* g10/encrypt.c (use_aead): Make that new fucntion work.
(encrypt_simple): Use default_aead_algo() instead of EAX.
* g10/mainproc.c (proc_encrypted): Support AEAD.
(do_proc_packets): Support PKT_ENCRYPTED_AEAD.
--
This code has seen only a very few manual tests. Encrypting always
uses a 64k chunks and decryption has not been tested with larger
chunks. Those small chunks make debugging much faster.
Tests can be done using:
gpg --rfc4880bis --pinentry-mode=loopback --passphrase abc \
--force-aead --aead-algo ocb --s2k-mode 0 --cipher AES \
-v -z 0 --status-fd 2 -c <INFILE >OUTFILE
and
gpg --rfc4880bis --pinentry-mode=loopback --passphrase=abc \
--status-fd 2 -v -d <INFILE >OUTFILE
Signed-off-by: Werner Koch <wk@gnupg.org>
Diffstat (limited to 'g10/cipher-aead.c')
-rw-r--r-- | g10/cipher-aead.c | 393 |
1 files changed, 387 insertions, 6 deletions
diff --git a/g10/cipher-aead.c b/g10/cipher-aead.c index bf0afcfcb..f247a83de 100644 --- a/g10/cipher-aead.c +++ b/g10/cipher-aead.c @@ -33,13 +33,392 @@ #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. */ +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; + } + 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) +{ + unsigned char nonce[16]; + 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; + + log_printhex (nonce, 15, "nonce:"); + return gcry_cipher_setiv (cfx->cipher_hd, nonce, i); +} + + +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); + + switch (cfx->dek->use_aead) + { + case AEAD_ALGO_OCB: + ciphermode = GCRY_CIPHER_MODE_OCB; + startivlen = 15; + break; + + default: + log_error ("unsupported AEAD algo %d\n", cfx->dek->use_aead); + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + goto leave; + } + + cfx->chunkbyte = 10; + 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; + + 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; + + 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); + 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; + log_printhex (tag, 16, "wrote tag:"); + + leave: + 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]; + + cfx->chunkindex++; + + err = set_nonce (cfx); + if (err) + goto leave; + err = set_additional_data (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; + int newchunk = 0; + size_t n; + + /* Put the data into a buffer, flush and encrypt as needed. */ + 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 + n >= cfx->chunksize) + { + size_t n1 = cfx->chunksize - cfx->chunklen; + newchunk = 1; + 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) + { + log_debug ("encrypting: buflen=%zu %s %p\n", + cfx->buflen, newchunk?"(newchunk)":"", cfx->cipher_hd); + if (newchunk) + gcry_cipher_final (cfx->cipher_hd); + 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):"); + + /* 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) + 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) + { + log_debug ("chunklen=%ju total=%ju\n", + (uintmax_t)cfx->chunklen, (uintmax_t)cfx->total); + err = write_auth_tag (cfx, a); + if (err) + { + log_debug ("gcry_cipher_gettag failed: %s\n", + gpg_strerror (err)); + goto leave; + } + + log_debug ("starting a new chunk (cur size=%zu)\n", size); + log_printhex (buf, size, "cur buf:"); + cfx->chunkindex++; + cfx->chunklen = 0; + err = set_nonce (cfx); + if (err) + goto leave; + err = set_additional_data (cfx, 0); + if (err) + goto leave; + newchunk = 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; + + /* 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) + { + log_debug ("processing last %zu bytes of the last chunk\n", cfx->buflen); + log_printhex (cfx->buffer, cfx->buflen, "plain(2):"); + gcry_cipher_encrypt (cfx->cipher_hd, cfx->buffer, cfx->buflen, NULL, 0); + log_printhex (cfx->buffer, cfx->buflen, "ciphr(2):"); + 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; + } + + /* Get and write the authentication tag. */ + 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. */ + 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 encipher data with an AEAD algorithm + * 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) + iobuf_t a, byte *buf, size_t *ret_len) { cipher_filter_context_t *cfx = opaque; size_t size = *ret_len; @@ -47,16 +426,18 @@ cipher_filter_aead (void *opaque, int control, if (control == IOBUFCTRL_UNDERFLOW) /* decrypt */ { - rc = -1; /* not yet used */ + rc = -1; /* not used */ } else if (control == IOBUFCTRL_FLUSH) /* encrypt */ { - log_assert (a); - rc = GPG_ERR_NOT_IMPLEMENTED; + if (!cfx->wrote_header && (rc=write_header (cfx, a))) + ; + else + rc = do_flush (cfx, a, buf, size); } else if (control == IOBUFCTRL_FREE) { - gcry_cipher_close (cfx->cipher_hd); + rc = do_free (cfx, a); } else if (control == IOBUFCTRL_DESC) { |