From 3f4ca85cb0cf58006417f4f7faafaa9a1f1bdf22 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Sun, 21 Jan 2018 16:24:43 +0100 Subject: [PATCH] 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 OUTFILE and gpg --rfc4880bis --pinentry-mode=loopback --passphrase=abc \ --status-fd 2 -v -d OUTFILE Signed-off-by: Werner Koch --- common/openpgpdefs.h | 2 + doc/DETAILS | 7 +- g10/build-packet.c | 31 ++ g10/cipher-aead.c | 393 ++++++++++++++++++++++- g10/decrypt-data.c | 723 +++++++++++++++++++++++++++++++++++++------ g10/dek.h | 4 +- g10/encrypt.c | 13 +- g10/filter.h | 55 +++- g10/gpg.c | 2 +- g10/mainproc.c | 23 +- g10/packet.h | 22 +- g10/parse-packet.c | 98 +++++- 12 files changed, 1235 insertions(+), 138 deletions(-) diff --git a/common/openpgpdefs.h b/common/openpgpdefs.h index aeb3389a7..8699a178d 100644 --- a/common/openpgpdefs.h +++ b/common/openpgpdefs.h @@ -51,6 +51,7 @@ typedef enum PKT_ATTRIBUTE = 17, /* PGP's attribute packet. */ PKT_ENCRYPTED_MDC = 18, /* Integrity protected encrypted data. */ PKT_MDC = 19, /* Manipulation detection code packet. */ + PKT_ENCRYPTED_AEAD= 20, /* AEAD encrypted data packet. */ PKT_COMMENT = 61, /* new comment packet (GnuPG specific). */ PKT_GPG_CONTROL = 63 /* internal control packet (GnuPG specific). */ } @@ -143,6 +144,7 @@ typedef enum cipher_algo_t; +/* Note that we encode the AEAD algo in a 3 bit field at some places. */ typedef enum { AEAD_ALGO_NONE = 0, diff --git a/doc/DETAILS b/doc/DETAILS index 3c089b278..a4063b4a6 100644 --- a/doc/DETAILS +++ b/doc/DETAILS @@ -518,9 +518,10 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB: actual key used for descryption. is the fingerprint of the primary key. is the letter with the ownertrust; this is in general a 'u' which stands for ultimately trusted. -*** DECRYPTION_INFO +*** DECRYPTION_INFO [] Print information about the symmetric encryption algorithm and the MDC method. This will be emitted even if the decryption fails. + For an AEAD algorithm AEAD_ALGO is not 0. *** DECRYPTION_FAILED The symmetric decryption failed - one reason could be a wrong @@ -540,8 +541,10 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB: --override-session-key. It is not an indication that the decryption will or has succeeded. -*** BEGIN_ENCRYPTION +*** BEGIN_ENCRYPTION [] Mark the start of the actual encryption process. + MDC_METHOD shall be 0 if an AEAD_ALGO is not 0. Users should + however ignore MDC_METHOD if AEAD_ALGO is not 0. *** END_ENCRYPTION Mark the end of the actual encryption process. diff --git a/g10/build-packet.c b/g10/build-packet.c index d4a1d6a53..fc64c9c9f 100644 --- a/g10/build-packet.c +++ b/g10/build-packet.c @@ -42,6 +42,7 @@ static u32 calc_plaintext( PKT_plaintext *pt ); static int do_plaintext( IOBUF out, int ctb, PKT_plaintext *pt ); static int do_encrypted( IOBUF out, int ctb, PKT_encrypted *ed ); static int do_encrypted_mdc( IOBUF out, int ctb, PKT_encrypted *ed ); +static int do_encrypted_aead (iobuf_t out, int ctb, PKT_encrypted *ed); static int do_compressed( IOBUF out, int ctb, PKT_compressed *cd ); static int do_signature( IOBUF out, int ctb, PKT_signature *sig ); static int do_onepass_sig( IOBUF out, int ctb, PKT_onepass_sig *ops ); @@ -106,6 +107,7 @@ build_packet (IOBUF out, PACKET *pkt) break; case PKT_ENCRYPTED: case PKT_ENCRYPTED_MDC: + case PKT_ENCRYPTED_AEAD: new_ctb = pkt->pkt.encrypted->new_ctb; break; case PKT_COMPRESSED: @@ -158,6 +160,9 @@ build_packet (IOBUF out, PACKET *pkt) case PKT_ENCRYPTED_MDC: rc = do_encrypted_mdc (out, ctb, pkt->pkt.encrypted); break; + case PKT_ENCRYPTED_AEAD: + rc = do_encrypted_aead (out, ctb, pkt->pkt.encrypted); + break; case PKT_COMPRESSED: rc = do_compressed (out, ctb, pkt->pkt.compressed); break; @@ -812,6 +817,32 @@ do_encrypted_mdc( IOBUF out, int ctb, PKT_encrypted *ed ) } +/* Serialize the symmetrically AEAD encrypted data packet + * (rfc4880bis-03, Section 5.16) described by ED and write it to OUT. + * + * Note: this only writes only packet's header. The caller must then + * follow up and write the actual encrypted data. This should be done + * by pushing the the cipher_filter_aead. */ +static int +do_encrypted_aead (iobuf_t out, int ctb, PKT_encrypted *ed) +{ + u32 n; + + log_assert (ctb_pkttype (ctb) == PKT_ENCRYPTED_AEAD); + + n = ed->len ? (ed->len + ed->extralen + 4) : 0; + write_header (out, ctb, n ); + iobuf_writebyte (out, 1); /* Version. */ + iobuf_writebyte (out, ed->cipher_algo); + iobuf_writebyte (out, ed->aead_algo); + iobuf_writebyte (out, ed->chunkbyte); + + /* This is all. The caller has to write the encrypted data */ + + return 0; +} + + /* Serialize the compressed packet (RFC 4880, Section 5.6) described by CD and write it to OUT. 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) { diff --git a/g10/decrypt-data.c b/g10/decrypt-data.c index 736534d75..80e16ecb7 100644 --- a/g10/decrypt-data.c +++ b/g10/decrypt-data.c @@ -1,6 +1,6 @@ /* decrypt-data.c - Decrypt an encrypted data packet - * Copyright (C) 1998, 1999, 2000, 2001, 2005, - * 2006, 2009 Free Software Foundation, Inc. + * Copyright (C) 1998-2001, 2005-2006, 2009 Free Software Foundation, Inc. + * Copyright (C) 1998-2001, 2005-2006, 2009, 2018 Werner Koch * * This file is part of GnuPG. * @@ -32,22 +32,71 @@ #include "../common/compliance.h" +static int aead_decode_filter (void *opaque, int control, iobuf_t a, + byte *buf, size_t *ret_len); static int mdc_decode_filter ( void *opaque, int control, IOBUF a, byte *buf, size_t *ret_len); static int decode_filter ( void *opaque, int control, IOBUF a, byte *buf, size_t *ret_len); -typedef struct decode_filter_context_s +/* Our context object. */ +struct decode_filter_context_s { - gcry_cipher_hd_t cipher_hd; - gcry_md_hd_t mdc_hash; - char defer[22]; - int defer_filled; - int eof_seen; + /* Recounter (max value is 2). We need it becuase we do not know + * whether the iobuf or the outer control code frees this object + * first. */ int refcount; - int partial; /* Working on a partial length packet. */ - size_t length; /* If !partial: Remaining bytes in the packet. */ -} *decode_filter_ctx_t; + + /* The cipher handle. */ + gcry_cipher_hd_t cipher_hd; + + /* The hash handle for use in MDC mode. */ + gcry_md_hd_t mdc_hash; + + /* The start IV for AEAD encryption. */ + byte startiv[16]; + + /* The holdback buffer. For AEAD we need 32 bytes for MDC 22 bytes + * are enough. The flag indicates whether the holdback buffer is + * filled. */ + char defer[32]; + unsigned int defer_filled : 1; + + /* Working on a partial length packet. */ + unsigned int partial : 1; + + /* EOF indicator with these true values: + * 1 = normal EOF + * 2 = premature EOF (tag incomplete) + * 3 = premature EOF (general) */ + unsigned int eof_seen : 2; + + /* The actually used cipher algo for AEAD. */ + byte cipher_algo; + + /* The AEAD algo. */ + byte aead_algo; + + /* The encoded chunk byte for AEAD. */ + byte chunkbyte; + + /* The decoded CHUNKBYTE. */ + uint64_t chunksize; + + /* The chunk index for AEAD. */ + uint64_t chunkindex; + + /* The number of bytes in the current chunk. */ + uint64_t chunklen; + + /* The total count of decrypted plaintext octets. */ + uint64_t total; + + /* Remaining bytes in the packet according to the packet header. + * Not used if PARTIAL is true. */ + size_t length; +}; +typedef struct decode_filter_context_s *decode_filter_ctx_t; /* Helper to release the decode context. */ @@ -69,6 +118,78 @@ release_dfx_context (decode_filter_ctx_t dfx) } +/* Set the nonce for AEAD. This also reset the decryption machinery + * so that the handle can be used for a new chunk. */ +static gpg_error_t +aead_set_nonce (decode_filter_ctx_t dfx) +{ + unsigned char nonce[16]; + int i; + + switch (dfx->aead_algo) + { + case AEAD_ALGO_OCB: + memcpy (nonce, dfx->startiv, 15); + i = 7; + break; + + case AEAD_ALGO_EAX: + memcpy (nonce, dfx->startiv, 16); + i = 8; + break; + + default: + BUG (); + } + nonce[i++] ^= dfx->chunkindex >> 56; + nonce[i++] ^= dfx->chunkindex >> 48; + nonce[i++] ^= dfx->chunkindex >> 40; + nonce[i++] ^= dfx->chunkindex >> 32; + nonce[i++] ^= dfx->chunkindex >> 24; + nonce[i++] ^= dfx->chunkindex >> 16; + nonce[i++] ^= dfx->chunkindex >> 8; + nonce[i++] ^= dfx->chunkindex; + + log_printhex (nonce, i, "nonce:"); + return gcry_cipher_setiv (dfx->cipher_hd, nonce, i); +} + + +/* Set the additional data for the current chunk. If FINAL is set the + * final AEAD chunk is processed. */ +static gpg_error_t +aead_set_ad (decode_filter_ctx_t dfx, int final) +{ + unsigned char ad[21]; + + ad[0] = (0xc0 | PKT_ENCRYPTED_AEAD); + ad[1] = 1; + ad[2] = dfx->cipher_algo; + ad[3] = dfx->aead_algo; + ad[4] = dfx->chunkbyte; + ad[5] = dfx->chunkindex >> 56; + ad[6] = dfx->chunkindex >> 48; + ad[7] = dfx->chunkindex >> 40; + ad[8] = dfx->chunkindex >> 32; + ad[9] = dfx->chunkindex >> 24; + ad[10]= dfx->chunkindex >> 16; + ad[11]= dfx->chunkindex >> 8; + ad[12]= dfx->chunkindex; + if (final) + { + ad[13] = dfx->total >> 56; + ad[14] = dfx->total >> 48; + ad[15] = dfx->total >> 40; + ad[16] = dfx->total >> 32; + ad[17] = dfx->total >> 24; + ad[18] = dfx->total >> 16; + ad[19] = dfx->total >> 8; + ad[20] = dfx->total; + } + log_printhex (ad, final? 21 : 13, "authdata:"); + return gcry_cipher_authenticate (dfx->cipher_hd, ad, final? 21 : 13); +} + /**************** * Decrypt the data, specified by ED with the key DEK. @@ -80,8 +201,8 @@ decrypt_data (ctrl_t ctrl, void *procctx, PKT_encrypted *ed, DEK *dek) byte *p; int rc=0, c, i; byte temp[32]; - unsigned blocksize; - unsigned nprefix; + unsigned int blocksize; + unsigned int nprefix; dfx = xtrycalloc (1, sizeof *dfx); if (!dfx) @@ -109,19 +230,18 @@ decrypt_data (ctrl_t ctrl, void *procctx, PKT_encrypted *ed, DEK *dek) goto leave; } - { - char buf[20]; - - snprintf (buf, sizeof buf, "%d %d", ed->mdc_method, dek->algo); - write_status_text (STATUS_DECRYPTION_INFO, buf); - } + write_status_printf (STATUS_DECRYPTION_INFO, "%d %d %d", + ed->mdc_method, dek->algo, ed->aead_algo); if (opt.show_session_key) { - char numbuf[25]; + char numbuf[30]; char *hexbuf; - snprintf (numbuf, sizeof numbuf, "%d:", dek->algo); + if (ed->aead_algo) + snprintf (numbuf, sizeof numbuf, "%d.%u:", dek->algo, ed->aead_algo); + else + snprintf (numbuf, sizeof numbuf, "%d:", dek->algo); hexbuf = bin2hex (dek->key, dek->keylen, NULL); if (!hexbuf) { @@ -139,95 +259,209 @@ decrypt_data (ctrl_t ctrl, void *procctx, PKT_encrypted *ed, DEK *dek) blocksize = openpgp_cipher_get_algo_blklen (dek->algo); if ( !blocksize || blocksize > 16 ) log_fatal ("unsupported blocksize %u\n", blocksize ); - nprefix = blocksize; - if ( ed->len && ed->len < (nprefix+2) ) + if (ed->aead_algo) { - /* An invalid message. We can't check that during parsing - because we may not know the used cipher then. */ - rc = gpg_error (GPG_ERR_INV_PACKET); - goto leave; - } + enum gcry_cipher_modes ciphermode; + unsigned int startivlen; - if ( ed->mdc_method ) - { - if (gcry_md_open (&dfx->mdc_hash, ed->mdc_method, 0 )) - BUG (); - if ( DBG_HASHING ) - gcry_md_debug (dfx->mdc_hash, "checkmdc"); - } - - rc = openpgp_cipher_open (&dfx->cipher_hd, dek->algo, - GCRY_CIPHER_MODE_CFB, - (GCRY_CIPHER_SECURE - | ((ed->mdc_method || dek->algo >= 100)? - 0 : GCRY_CIPHER_ENABLE_SYNC))); - if (rc) - { - /* We should never get an error here cause we already checked - * that the algorithm is available. */ - BUG(); - } - - - /* log_hexdump( "thekey", dek->key, dek->keylen );*/ - rc = gcry_cipher_setkey (dfx->cipher_hd, dek->key, dek->keylen); - if ( gpg_err_code (rc) == GPG_ERR_WEAK_KEY ) - { - log_info(_("WARNING: message was encrypted with" - " a weak key in the symmetric cipher.\n")); - rc=0; - } - else if( rc ) - { - log_error("key setup failed: %s\n", gpg_strerror (rc) ); - goto leave; - } - - if (!ed->buf) - { - log_error(_("problem handling encrypted packet\n")); - goto leave; - } - - gcry_cipher_setiv (dfx->cipher_hd, NULL, 0); - - if ( ed->len ) - { - for (i=0; i < (nprefix+2) && ed->len; i++, ed->len-- ) + if (blocksize != 16) { - if ( (c=iobuf_get(ed->buf)) == -1 ) - break; - else - temp[i] = c; + rc = gpg_error (GPG_ERR_CIPHER_ALGO); + goto leave; } - } - else - { - for (i=0; i < (nprefix+2); i++ ) - if ( (c=iobuf_get(ed->buf)) == -1 ) + + switch (ed->aead_algo) + { + case AEAD_ALGO_OCB: + startivlen = 15; + ciphermode = GCRY_CIPHER_MODE_OCB; break; - else - temp[i] = c; - } + case AEAD_ALGO_EAX: + startivlen = 16; + log_error ("unsupported AEAD algo %d\n", ed->aead_algo); + rc = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + goto leave; + default: + log_error ("unknown AEAD algo %d\n", ed->aead_algo); + rc = gpg_error (GPG_ERR_INV_CIPHER_MODE); + goto leave; + } + log_assert (startivlen <= sizeof dfx->startiv); - gcry_cipher_decrypt (dfx->cipher_hd, temp, nprefix+2, NULL, 0); - gcry_cipher_sync (dfx->cipher_hd); - p = temp; - /* log_hexdump( "prefix", temp, nprefix+2 ); */ - if (dek->symmetric - && (p[nprefix-2] != p[nprefix] || p[nprefix-1] != p[nprefix+1]) ) + if (ed->chunkbyte != 10) + { + /* FIXME */ + log_error ("unsupported chunkbyte %u\n", ed->chunkbyte); + rc = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + goto leave; + } + + /* Read the Start-IV. */ + if (ed->len) + { + for (i=0; i < startivlen && ed->len; i++, ed->len--) + { + if ((c=iobuf_get (ed->buf)) == -1) + break; + dfx->startiv[i] = c; + } + } + else + { + for (i=0; i < startivlen; i++ ) + if ( (c=iobuf_get (ed->buf)) == -1 ) + break; + else + dfx->startiv[i] = c; + } + if (i != startivlen) + { + log_error ("Start-IV in AEAD packet too short (%d/%u)\n", + i, startivlen); + rc = gpg_error (GPG_ERR_TOO_SHORT); + goto leave; + } + + dfx->cipher_algo = ed->cipher_algo; + dfx->aead_algo = ed->aead_algo; + dfx->chunkbyte = ed->chunkbyte; + dfx->chunksize = (uint64_t)1 << (dfx->chunkbyte + 6); + + if (dek->algo != dfx->cipher_algo) + log_info ("Note: different cipher algorithms used (%s/%s)\n", + openpgp_cipher_algo_name (dek->algo), + openpgp_cipher_algo_name (dfx->cipher_algo)); + + rc = openpgp_cipher_open (&dfx->cipher_hd, + dfx->cipher_algo, + ciphermode, + GCRY_CIPHER_SECURE); + if (rc) + goto leave; /* Should never happen. */ + + log_printhex (dek->key, dek->keylen, "thekey:"); + rc = gcry_cipher_setkey (dfx->cipher_hd, dek->key, dek->keylen); + if (gpg_err_code (rc) == GPG_ERR_WEAK_KEY) + { + log_info (_("WARNING: message was encrypted with" + " a weak key in the symmetric cipher.\n")); + rc = 0; + } + else if (rc) + { + log_error("key setup failed: %s\n", gpg_strerror (rc)); + goto leave; + } + + if (!ed->buf) + { + log_error(_("problem handling encrypted packet\n")); + goto leave; + } + + rc = aead_set_nonce (dfx); + if (rc) + goto leave; + + rc = aead_set_ad (dfx, 0); + if (rc) + goto leave; + + } + else /* CFB encryption. */ { - rc = gpg_error (GPG_ERR_BAD_KEY); - goto leave; - } + nprefix = blocksize; + if ( ed->len && ed->len < (nprefix+2) ) + { + /* An invalid message. We can't check that during parsing + because we may not know the used cipher then. */ + rc = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } - if ( dfx->mdc_hash ) - gcry_md_write (dfx->mdc_hash, temp, nprefix+2); + if ( ed->mdc_method ) + { + if (gcry_md_open (&dfx->mdc_hash, ed->mdc_method, 0 )) + BUG (); + if ( DBG_HASHING ) + gcry_md_debug (dfx->mdc_hash, "checkmdc"); + } + + rc = openpgp_cipher_open (&dfx->cipher_hd, dek->algo, + GCRY_CIPHER_MODE_CFB, + (GCRY_CIPHER_SECURE + | ((ed->mdc_method || dek->algo >= 100)? + 0 : GCRY_CIPHER_ENABLE_SYNC))); + if (rc) + { + /* We should never get an error here cause we already checked + * that the algorithm is available. */ + BUG(); + } + + + /* log_hexdump( "thekey", dek->key, dek->keylen );*/ + rc = gcry_cipher_setkey (dfx->cipher_hd, dek->key, dek->keylen); + if ( gpg_err_code (rc) == GPG_ERR_WEAK_KEY ) + { + log_info(_("WARNING: message was encrypted with" + " a weak key in the symmetric cipher.\n")); + rc=0; + } + else if( rc ) + { + log_error("key setup failed: %s\n", gpg_strerror (rc) ); + goto leave; + } + + if (!ed->buf) + { + log_error(_("problem handling encrypted packet\n")); + goto leave; + } + + gcry_cipher_setiv (dfx->cipher_hd, NULL, 0); + + if ( ed->len ) + { + for (i=0; i < (nprefix+2) && ed->len; i++, ed->len-- ) + { + if ( (c=iobuf_get(ed->buf)) == -1 ) + break; + else + temp[i] = c; + } + } + else + { + for (i=0; i < (nprefix+2); i++ ) + if ( (c=iobuf_get(ed->buf)) == -1 ) + break; + else + temp[i] = c; + } + + gcry_cipher_decrypt (dfx->cipher_hd, temp, nprefix+2, NULL, 0); + gcry_cipher_sync (dfx->cipher_hd); + p = temp; + /* log_hexdump( "prefix", temp, nprefix+2 ); */ + if (dek->symmetric + && (p[nprefix-2] != p[nprefix] || p[nprefix-1] != p[nprefix+1]) ) + { + rc = gpg_error (GPG_ERR_BAD_KEY); + goto leave; + } + + if ( dfx->mdc_hash ) + gcry_md_write (dfx->mdc_hash, temp, nprefix+2); + } dfx->refcount++; - dfx->partial = ed->is_partial; + dfx->partial = !!ed->is_partial; dfx->length = ed->len; - if ( ed->mdc_method ) + if (ed->aead_algo) + iobuf_push_filter ( ed->buf, aead_decode_filter, dfx ); + else if (ed->mdc_method) iobuf_push_filter ( ed->buf, mdc_decode_filter, dfx ); else iobuf_push_filter ( ed->buf, decode_filter, dfx ); @@ -307,6 +541,296 @@ decrypt_data (ctrl_t ctrl, void *procctx, PKT_encrypted *ed, DEK *dek) } +/* The core of the AEAD decryption. This is the underflow function of + * the aead_decode_filter. */ +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. */ + gpg_error_t err; + size_t n; /* Finally the number of decrypted bytes in BUF. */ + int c; + + log_assert (size > 64); /* Our code requires at least this size. */ + + /* Get at least 32 bytes and put it ahead in the buffer. */ + 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 defer buffer and thus we move + * those 32 bytes to the start of the buffer. All further calls + * will copy the deferred 32 bytes to the start of the + * buffer. */ + if (!dfx->defer_filled) + { + memcpy (buf, buf+32, 32); + n = 32; /* Continue at this position. */ + } + else + { + memcpy (buf, dfx->defer, 32); + } + + /* Now fill up the provided buffer. */ + if (dfx->partial) + { + for (; n < size; n++ ) + { + if ((c = iobuf_get (a)) == -1) + { + dfx->eof_seen = 1; /* Normal EOF. */ + break; + } + buf[n] = c; + } + } + else + { + for (; n < size && dfx->length; n++, dfx->length--) + { + c = iobuf_get (a); + if (c == -1) + { + dfx->eof_seen = 3; /* Premature EOF. */ + break; + } + buf[n] = c; + } + if (!dfx->length) + dfx->eof_seen = 1; /* Normal EOF. */ + } + + /* Move the trailing 32 bytes back to the defer buffer. We + * got at least 64 bytes and thus a memmove is not needed. */ + n -= 32; + memcpy (dfx->defer, buf+n, 32); + dfx->defer_filled = 1; + } + else if (!dfx->defer_filled) + { + /* EOF seen but empty defer 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. */ + } + else + { + /* EOF seen (i.e. read less than 32 bytes). */ + memcpy (buf, dfx->defer, 32); + n -= 32; + memcpy (dfx->defer, buf+n, 32); + dfx->eof_seen = 1; /* Normal EOF. */ + } + + log_debug ("decrypt: chunklen=%ju total=%ju size=%zu n=%zu%s\n", + (uintmax_t)dfx->chunklen, (uintmax_t)dfx->total, size, n, + dfx->eof_seen? " eof":""); + + /* Now decrypt the buffer. */ + if (n && dfx->eof_seen > 1) + { + 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; + + log_debug ("chunksize will be reached: n0=%zu\n", n0); + gcry_cipher_final (dfx->cipher_hd); + err = gcry_cipher_decrypt (dfx->cipher_hd, buf, n0, NULL, 0); + if (err) + { + log_debug ("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_debug ("bytes left: %zu off=%zu\n", n, off); + log_assert (n >= 16); + log_assert (dfx->defer_filled); + log_printhex (buf+off, 16, "tag:"); + err = gcry_cipher_checktag (dfx->cipher_hd, buf + off, 16); + if (err) + { + 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; + dfx->chunkindex++; + err = aead_set_nonce (dfx); + if (err) + goto leave; + err = aead_set_ad (dfx, 0); + if (err) + goto leave; + } + + 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. */ + 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); + if (err) + { + log_debug ("gcry_cipher_decrypt failed (2): %s\n",gpg_strerror (err)); + goto leave; + } + dfx->chunklen += n; + dfx->total += n; + + if (dfx->eof_seen) + { + /* log_printhex (buf+off, n, "buf+off:"); */ + log_debug ("eof seen: chunklen=%ju total=%ju off=%zu n=%zu\n", + (uintmax_t)dfx->chunklen, (uintmax_t)dfx->total, off, n); + + log_assert (dfx->defer_filled); + err = gcry_cipher_checktag (dfx->cipher_hd, dfx->defer, 16); + if (err) + { + log_debug ("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; + } + + /* Check the final chunk. */ + dfx->chunkindex++; + err = aead_set_nonce (dfx); + if (err) + goto leave; + err = aead_set_ad (dfx, 1); + if (err) + goto leave; + gcry_cipher_final (dfx->cipher_hd); + /* decrypt an empty string. */ + err = gcry_cipher_decrypt (dfx->cipher_hd, buf, 0, NULL, 0); + if (err) + { + log_debug ("gcry_cipher_decrypt failed (final): %s\n", + gpg_strerror (err)); + goto leave; + } + err = gcry_cipher_checktag (dfx->cipher_hd, dfx->defer+16, 16); + if (err) + { + 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; + log_debug ("eof seen: returning %zu\n", n); + /* log_printhex (buf, n, "buf:"); */ + } + else + n += off; + } + + leave: + /* In case of a real error we better wipe out the buffer than to + * keep partly encrypted data. */ + if (err && gpg_err_code (err) != GPG_ERR_EOF) + memset (buf, 0, size); + *ret_len = n; + + return err; +} + + +/* The IOBUF filter used to decrypt AEAD encrypted data. */ +static int +aead_decode_filter (void *opaque, int control, IOBUF a, + byte *buf, size_t *ret_len) +{ + decode_filter_ctx_t dfx = opaque; + int rc = 0; + + if ( control == IOBUFCTRL_UNDERFLOW && dfx->eof_seen ) + { + *ret_len = 0; + rc = -1; + } + else if ( control == IOBUFCTRL_UNDERFLOW ) + { + log_assert (a); + + rc = aead_underflow (dfx, a, buf, ret_len); + if (gpg_err_code (rc) == GPG_ERR_EOF) + rc = -1; /* We need to use the old convention in the filter. */ + + } + else if ( control == IOBUFCTRL_FREE ) + { + release_dfx_context (dfx); + } + else if ( control == IOBUFCTRL_DESC ) + { + mem2str (buf, "aead_decode_filter", *ret_len); + } + + return rc; +} + static int mdc_decode_filter (void *opaque, int control, IOBUF a, @@ -365,6 +889,7 @@ mdc_decode_filter (void *opaque, int control, IOBUF a, } else { + memcpy (buf, dfx->defer, 22); } /* Fill up the buffer. */ diff --git a/g10/dek.h b/g10/dek.h index 64e98fca7..1e861f565 100644 --- a/g10/dek.h +++ b/g10/dek.h @@ -32,8 +32,8 @@ typedef struct * verbose mode. */ int algo_info_printed : 1; - /* AEAD shall be used. */ - int use_aead : 1; + /* AEAD shall be used. The value is the AEAD algo. */ + int use_aead : 4; /* MDC shall be used. */ int use_mdc : 1; diff --git a/g10/encrypt.c b/g10/encrypt.c index 01feb4a7d..856849799 100644 --- a/g10/encrypt.c +++ b/g10/encrypt.c @@ -123,7 +123,7 @@ use_aead (pk_list_t pk_list, int algo) return 0; } - can_use = openpgp_cipher_get_algo_blklen (algo) != 16; + can_use = openpgp_cipher_get_algo_blklen (algo) == 16; /* With --force-mdc we clearly do not want AEAD. */ if (opt.force_mdc) @@ -133,12 +133,15 @@ use_aead (pk_list_t pk_list, int algo) if (opt.force_aead) { if (!can_use) - log_info ("Warning: request to use AEAD ignored for cipher '%s'\n", - openpgp_cipher_algo_name (algo)); + { + log_info ("Warning: request to use AEAD ignored for cipher '%s'\n", + openpgp_cipher_algo_name (algo)); + return 0; + } return 1; } - /* AEAD does noly work with 128 bit cipher blocklength. */ + /* AEAD does only work with 128 bit cipher blocklength. */ if (!can_use) return 0; @@ -307,7 +310,7 @@ encrypt_simple (const char *filename, int mode, int use_seskey) openpgp_cipher_algo_name (cfx.dek->algo)); if (use_aead (NULL, cfx.dek->algo)) - cfx.dek->use_aead = 1; + cfx.dek->use_aead = default_aead_algo (); else cfx.dek->use_mdc = !!use_mdc (NULL, cfx.dek->algo); } diff --git a/g10/filter.h b/g10/filter.h index 29243556e..cd177f4a4 100644 --- a/g10/filter.h +++ b/g10/filter.h @@ -88,15 +88,52 @@ struct compress_filter_context_s { typedef struct compress_filter_context_s compress_filter_context_t; -typedef struct { - DEK *dek; - u32 datalen; - gcry_cipher_hd_t cipher_hd; - unsigned int wrote_header : 1; - unsigned int short_blklen_warn : 1; - unsigned long short_blklen_count; - gcry_md_hd_t mdc_hash; - byte enchash[20]; +typedef struct +{ + /* Object with the key and algo */ + DEK *dek; + + /* Length of the data to encrypt if known - 32 bit because OpenPGP + * requires partial encoding for a larger data size. */ + u32 datalen; + + /* The current cipher handle. */ + gcry_cipher_hd_t cipher_hd; + + /* Various processing flags. */ + unsigned int wrote_header : 1; + unsigned int short_blklen_warn : 1; + unsigned long short_blklen_count; + + /* The encoded chunk byte for AEAD. */ + byte chunkbyte; + + /* The decoded CHUNKBYTE. */ + uint64_t chunksize; + + /* The chunk index for AEAD. */ + uint64_t chunkindex; + + /* The number of bytes in the current chunk. */ + uint64_t chunklen; + + /* The total count of encrypted plaintext octets. Note that we + * don't care about encrypting more than 16 Exabyte. */ + uint64_t total; + + /* The hash context and a buffer used for MDC. */ + gcry_md_hd_t mdc_hash; + byte enchash[20]; + + /* The start IV for AEAD encryption. */ + byte startiv[16]; + + /* Using a large buffer for encryption makes processing easier and + * also makes sure the data is well aligned. */ + char *buffer; + size_t bufsize; /* Allocated length. */ + size_t buflen; /* Used length. */ + } cipher_filter_context_t; diff --git a/g10/gpg.c b/g10/gpg.c index a6433889e..2ae3e8aa0 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -3882,7 +3882,7 @@ main (int argc, char **argv) /* Check our chosen algorithms against the list of legal algorithms. */ - if(!GNUPG) + if(!GNUPG && !opt.flags.rfc4880bis) { const char *badalg=NULL; preftype_t badtype=PREFTYPE_NONE; diff --git a/g10/mainproc.c b/g10/mainproc.c index b712e6048..92da98242 100644 --- a/g10/mainproc.c +++ b/g10/mainproc.c @@ -648,6 +648,7 @@ proc_encrypted (CTX c, PACKET *pkt) else if (!result && !opt.ignore_mdc_error && !pkt->pkt.encrypted->mdc_method + && !pkt->pkt.encrypted->aead_algo && openpgp_cipher_get_algo_blklen (c->dek->algo) != 8 && c->dek->algo != CIPHER_ALGO_TWOFISH) { @@ -662,17 +663,25 @@ proc_encrypted (CTX c, PACKET *pkt) write_status (STATUS_DECRYPTION_FAILED); } else if (!result || (gpg_err_code (result) == GPG_ERR_BAD_SIGNATURE + && !pkt->pkt.encrypted->aead_algo && opt.ignore_mdc_error)) { + /* All is fine or for an MDC message the MDC failed but the + * --ignore-mdc-error option is active. For compatibility + * reasons we issue GOODMDC also for AEAD messages. */ write_status (STATUS_DECRYPTION_OKAY); if (opt.verbose > 1) log_info(_("decryption okay\n")); - if (pkt->pkt.encrypted->mdc_method && !result) + + if (pkt->pkt.encrypted->aead_algo) + write_status (STATUS_GOODMDC); + else if (pkt->pkt.encrypted->mdc_method && !result) write_status (STATUS_GOODMDC); else if (!opt.no_mdc_warn) log_info (_("WARNING: message was not integrity protected\n")); } - else if (gpg_err_code (result) == GPG_ERR_BAD_SIGNATURE) + else if (gpg_err_code (result) == GPG_ERR_BAD_SIGNATURE + || gpg_err_code (result) == GPG_ERR_TRUNCATED) { glo_ctrl.lasterr = result; log_error (_("WARNING: encrypted message has been manipulated!\n")); @@ -1391,7 +1400,8 @@ do_proc_packets (ctrl_t ctrl, CTX c, iobuf_t a) case PKT_PUBKEY_ENC: proc_pubkey_enc (ctrl, c, pkt); break; case PKT_SYMKEY_ENC: proc_symkey_enc (c, pkt); break; case PKT_ENCRYPTED: - case PKT_ENCRYPTED_MDC: proc_encrypted (c, pkt); break; + case PKT_ENCRYPTED_MDC: + case PKT_ENCRYPTED_AEAD:proc_encrypted (c, pkt); break; case PKT_COMPRESSED: rc = proc_compressed (c, pkt); break; default: newpkt = 0; break; } @@ -1407,6 +1417,7 @@ do_proc_packets (ctrl_t ctrl, CTX c, iobuf_t a) case PKT_PUBKEY_ENC: case PKT_ENCRYPTED: case PKT_ENCRYPTED_MDC: + case PKT_ENCRYPTED_AEAD: write_status_text( STATUS_UNEXPECTED, "0" ); rc = GPG_ERR_UNEXPECTED; goto leave; @@ -1434,7 +1445,8 @@ do_proc_packets (ctrl_t ctrl, CTX c, iobuf_t a) case PKT_SYMKEY_ENC: proc_symkey_enc (c, pkt); break; case PKT_PUBKEY_ENC: proc_pubkey_enc (ctrl, c, pkt); break; case PKT_ENCRYPTED: - case PKT_ENCRYPTED_MDC: proc_encrypted (c, pkt); break; + case PKT_ENCRYPTED_MDC: + case PKT_ENCRYPTED_AEAD: proc_encrypted (c, pkt); break; case PKT_PLAINTEXT: proc_plaintext (c, pkt); break; case PKT_COMPRESSED: rc = proc_compressed (c, pkt); break; case PKT_ONEPASS_SIG: newpkt = add_onepass_sig (c, pkt); break; @@ -1461,7 +1473,8 @@ do_proc_packets (ctrl_t ctrl, CTX c, iobuf_t a) case PKT_PUBKEY_ENC: proc_pubkey_enc (ctrl, c, pkt); break; case PKT_SYMKEY_ENC: proc_symkey_enc (c, pkt); break; case PKT_ENCRYPTED: - case PKT_ENCRYPTED_MDC: proc_encrypted (c, pkt); break; + case PKT_ENCRYPTED_MDC: + case PKT_ENCRYPTED_AEAD: proc_encrypted (c, pkt); break; case PKT_PLAINTEXT: proc_plaintext (c, pkt); break; case PKT_COMPRESSED: rc = proc_compressed (c, pkt); break; case PKT_ONEPASS_SIG: newpkt = add_onepass_sig (c, pkt); break; diff --git a/g10/packet.h b/g10/packet.h index 894b38946..4d155746e 100644 --- a/g10/packet.h +++ b/g10/packet.h @@ -459,12 +459,13 @@ typedef struct { typedef struct { /* Remaining length of encrypted data. */ u32 len; - /* When encrypting, the first block size bytes of data are random - data and the following 2 bytes are copies of the last two bytes - of the random data (RFC 4880, Section 5.7). This provides a - simple check that the key is correct. extralen is the size of - this extra data. This is used by build_packet when writing out - the packet's header. */ + /* When encrypting in CFB mode, the first block size bytes of data + * are random data and the following 2 bytes are copies of the last + * two bytes of the random data (RFC 4880, Section 5.7). This + * provides a simple check that the key is correct. EXTRALEN is the + * size of this extra data or, in AEAD mode, the length of the + * headers and the tags. This is used by build_packet when writing + * out the packet's header. */ int extralen; /* Whether the serialized version of the packet used / should use the new format. */ @@ -476,6 +477,15 @@ typedef struct { /* If 0, MDC is disabled. Otherwise, the MDC method that was used (only DIGEST_ALGO_SHA1 has ever been defined). */ byte mdc_method; + /* If 0, AEAD is not used. Otherwise, the used AEAD algorithm. + * MDC_METHOD (above) shall be zero if AEAD is used. */ + byte aead_algo; + /* The cipher algo for/from the AEAD packet. 0 for other encryption + * packets. */ + byte cipher_algo; + /* The chunk byte from the AEAD packet. */ + byte chunkbyte; + /* An iobuf holding the data to be decrypted. (This is not used for encryption!) */ iobuf_t buf; diff --git a/g10/parse-packet.c b/g10/parse-packet.c index eee14f64e..de51770af 100644 --- a/g10/parse-packet.c +++ b/g10/parse-packet.c @@ -1,7 +1,6 @@ /* parse-packet.c - read packets - * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, - * 2007, 2009, 2010 Free Software Foundation, Inc. - * Copyright (C) 2014 Werner Koch + * Copyright (C) 1998-2007, 2009-2010 Free Software Foundation, Inc. + * Copyright (C) 2014, 2018 Werner Koch * Copyright (C) 2015 g10 Code GmbH * * This file is part of GnuPG. @@ -18,6 +17,7 @@ * * 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 @@ -82,6 +82,9 @@ static int parse_compressed (IOBUF inp, int pkttype, unsigned long pktlen, PACKET * packet, int new_ctb); static int parse_encrypted (IOBUF inp, int pkttype, unsigned long pktlen, PACKET * packet, int new_ctb, int partial); +static gpg_error_t parse_encrypted_aead (IOBUF inp, int pkttype, + unsigned long pktlen, PACKET *packet, + int partial); static int parse_mdc (IOBUF inp, int pkttype, unsigned long pktlen, PACKET * packet, int new_ctb); static int parse_gpg_control (IOBUF inp, int pkttype, unsigned long pktlen, @@ -636,6 +639,7 @@ parse (parse_packet_ctx_t ctx, PACKET *pkt, int onlykeypkts, off_t * retpos, case PKT_PLAINTEXT: case PKT_ENCRYPTED: case PKT_ENCRYPTED_MDC: + case PKT_ENCRYPTED_AEAD: case PKT_COMPRESSED: iobuf_set_partial_body_length_mode (inp, c & 0xff); pktlen = 0; /* To indicate partial length. */ @@ -823,6 +827,9 @@ parse (parse_packet_ctx_t ctx, PACKET *pkt, int onlykeypkts, off_t * retpos, case PKT_MDC: rc = parse_mdc (inp, pkttype, pktlen, pkt, new_ctb); break; + case PKT_ENCRYPTED_AEAD: + rc = parse_encrypted_aead (inp, pkttype, pktlen, pkt, partial); + break; case PKT_GPG_CONTROL: rc = parse_gpg_control (inp, pkttype, pktlen, pkt, partial); break; @@ -1392,6 +1399,11 @@ dump_sig_subpkt (int hashed, int type, int critical, for (i = 0; i < length; i++) es_fprintf (listfp, " %d", buffer[i]); break; + case SIGSUBPKT_PREF_AEAD: + es_fputs ("pref-aead-algos:", listfp); + for (i = 0; i < length; i++) + es_fprintf (listfp, " %d", buffer[i]); + break; case SIGSUBPKT_REV_KEY: es_fputs ("revocation key: ", listfp); if (length < 22) @@ -1554,6 +1566,7 @@ parse_one_sig_subpkt (const byte * buffer, size_t n, int type) case SIGSUBPKT_KEY_FLAGS: case SIGSUBPKT_KS_FLAGS: case SIGSUBPKT_PREF_SYM: + case SIGSUBPKT_PREF_AEAD: case SIGSUBPKT_PREF_HASH: case SIGSUBPKT_PREF_COMPR: case SIGSUBPKT_POLICY: @@ -1636,6 +1649,7 @@ can_handle_critical (const byte * buffer, size_t n, int type) case SIGSUBPKT_ISSUER: /* issuer key ID */ case SIGSUBPKT_ISSUER_FPR: /* issuer fingerprint */ case SIGSUBPKT_PREF_SYM: + case SIGSUBPKT_PREF_AEAD: case SIGSUBPKT_PREF_HASH: case SIGSUBPKT_PREF_COMPR: case SIGSUBPKT_KEY_FLAGS: @@ -3161,6 +3175,9 @@ parse_encrypted (IOBUF inp, int pkttype, unsigned long pktlen, ed->buf = NULL; ed->new_ctb = new_ctb; ed->is_partial = partial; + ed->aead_algo = 0; + ed->cipher_algo = 0; /* Only used with AEAD. */ + ed->chunkbyte = 0; /* Only used with AEAD. */ if (pkttype == PKT_ENCRYPTED_MDC) { /* Fixme: add some pktlen sanity checks. */ @@ -3252,6 +3269,81 @@ parse_mdc (IOBUF inp, int pkttype, unsigned long pktlen, } +static gpg_error_t +parse_encrypted_aead (iobuf_t inp, int pkttype, unsigned long pktlen, + PACKET *pkt, int partial) +{ + int rc = 0; + PKT_encrypted *ed; + unsigned long orig_pktlen = pktlen; + int version; + + ed = pkt->pkt.encrypted = xtrymalloc (sizeof *pkt->pkt.encrypted); + if (!ed) + return gpg_error_from_syserror (); + ed->len = 0; + ed->extralen = 0; /* (only used in build_packet.) */ + ed->buf = NULL; + ed->new_ctb = 1; /* (packet number requires a new CTB anyway.) */ + ed->is_partial = partial; + ed->mdc_method = 0; + /* A basic sanity check. We need one version byte, one algo byte, + * one aead algo byte, one chunkbyte, at least 15 byte IV. */ + if (orig_pktlen && pktlen < 19) + { + log_error ("packet(%d) too short\n", pkttype); + if (list_mode) + es_fputs (":aead encrypted packet: [too short]\n", listfp); + rc = gpg_error (GPG_ERR_INV_PACKET); + iobuf_skip_rest (inp, pktlen, partial); + goto leave; + } + + version = iobuf_get_noeof (inp); + if (orig_pktlen) + pktlen--; + if (version != 1) + { + log_error ("aead encrypted packet with unknown version %d\n", + version); + if (list_mode) + es_fputs (":aead encrypted packet: [unknown version]\n", listfp); + /*skip_rest(inp, pktlen); should we really do this? */ + rc = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + + ed->cipher_algo = iobuf_get_noeof (inp); + if (orig_pktlen) + pktlen--; + ed->aead_algo = iobuf_get_noeof (inp); + if (orig_pktlen) + pktlen--; + ed->chunkbyte = iobuf_get_noeof (inp); + if (orig_pktlen) + pktlen--; + + /* Store the remaining length of the encrypted data. We read the + * rest during decryption. */ + ed->len = pktlen; + + if (list_mode) + { + es_fprintf (listfp, ":aead encrypted packet: cipher=%u aead=%u cb=%u\n", + ed->cipher_algo, ed->aead_algo, ed->chunkbyte); + if (orig_pktlen) + es_fprintf (listfp, "\tlength: %lu\n", orig_pktlen); + else + es_fprintf (listfp, "\tlength: unknown\n"); + } + + ed->buf = inp; + + leave: + return rc; +} + + /* * This packet is internally generated by us (in armor.c) to transfer * some information to the lower layer. To make sure that this packet