gpg: Limit keysize for unattended key generation to useful values.
[gnupg.git] / g10 / encode.c
index 6195b53..88d0a69 100644 (file)
@@ -1,11 +1,12 @@
 /* encode.c - encode data
- *     Copyright (C) 1998, 1999, 2000 Free Software Foundation, Inc.
+ * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
+ *               2006, 2009 Free Software Foundation, Inc.
  *
  * 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 2 of the License, or
+ * 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,
@@ -14,8 +15,7 @@
  * 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, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
 #include <config.h>
 #include <errno.h>
 #include <assert.h>
 
+#include "gpg.h"
 #include "options.h"
 #include "packet.h"
-#include "errors.h"
+#include "status.h"
 #include "iobuf.h"
 #include "keydb.h"
-#include <gcrypt.h>
 #include "util.h"
 #include "main.h"
 #include "filter.h"
 #include "trustdb.h"
 #include "i18n.h"
+#include "status.h"
+#include "pkglue.h"
 
 
-static int encode_simple( const char *filename, int mode );
+static int encode_simple( const char *filename, int mode, int use_seskey );
 static int write_pubkey_enc_from_list( PK_LIST pk_list, DEK *dek, IOBUF out );
 
-
-/****************
- * Emulate our old PK interface here - sometime in the future we might
- * change the internal design to directly fit to libgcrypt.
- */
-static int
-pk_encrypt( int algo, MPI *resarr, MPI data, MPI *pkey )
-{
-    GCRY_SEXP s_ciph, s_data, s_pkey;
-    int rc;
-
-    /* make a sexp from pkey */
-    if( algo == GCRY_PK_ELG || algo == GCRY_PK_ELG_E ) {
-       s_pkey = SEXP_CONS( SEXP_NEW( "public-key", 0 ),
-                         gcry_sexp_vlist( SEXP_NEW( "elg", 3 ),
-                         gcry_sexp_new_name_mpi( "p", pkey[0] ),
-                         gcry_sexp_new_name_mpi( "g", pkey[1] ),
-                         gcry_sexp_new_name_mpi( "y", pkey[2] ),
-                         NULL ));
-    }
-    else
-       return GPGERR_PUBKEY_ALGO;
-
-    /* put the data into a simple list */
-    s_data = gcry_sexp_new_mpi( data );
-
-    /* pass it to libgcrypt */
-    rc = gcry_pk_encrypt( &s_ciph, s_data, s_pkey );
-    gcry_sexp_release( s_data );
-    gcry_sexp_release( s_pkey );
-
-    if( rc )
-       ;
-    else { /* add better error handling or make gnupg use S-Exp directly */
-       GCRY_SEXP list = gcry_sexp_find_token( s_ciph, "a" , 0 );
-       assert( list );
-       resarr[0] = gcry_sexp_cdr_mpi( list, 0 );
-       assert( resarr[0] );
-       list = gcry_sexp_find_token( s_ciph, "b" , 0 );
-       assert( list );
-       resarr[1] = gcry_sexp_cdr_mpi( list, 0 );
-       assert( resarr[1] );
-    }
-
-    gcry_sexp_release( s_ciph );
-    return rc;
-}
-
-
 /****************
  * Encode FILENAME with only the symmetric cipher.  Take input from
  * stdin if FILENAME is NULL.
@@ -97,7 +50,7 @@ pk_encrypt( int algo, MPI *resarr, MPI data, MPI *pkey )
 int
 encode_symmetric( const char *filename )
 {
-    return encode_simple( filename, 1 );
+    return encode_simple( filename, 1, 0 );
 }
 
 /****************
@@ -107,143 +60,299 @@ encode_symmetric( const char *filename )
 int
 encode_store( const char *filename )
 {
-    return encode_simple( filename, 0 );
+    return encode_simple( filename, 0, 0 );
 }
 
 
+static void
+encode_seskey( DEK *dek, DEK **seskey, byte *enckey )
+{
+    gcry_cipher_hd_t hd;
+    byte buf[33];
+
+    assert ( dek->keylen <= 32 );
+    if(!*seskey)
+      {
+       *seskey=xmalloc_clear(sizeof(DEK));
+       (*seskey)->keylen=dek->keylen;
+       (*seskey)->algo=dek->algo;
+       make_session_key(*seskey);
+       /*log_hexdump( "thekey", c->key, c->keylen );*/
+      }
+
+    /* The encrypted session key is prefixed with a one-octet algorithm id.  */
+    buf[0] = (*seskey)->algo;
+    memcpy( buf + 1, (*seskey)->key, (*seskey)->keylen );
+
+    /* We only pass already checked values to the following fucntion,
+       thus we consider any failure as fatal.  */
+    if (openpgp_cipher_open (&hd, dek->algo, GCRY_CIPHER_MODE_CFB, 1))
+      BUG ();
+    if (gcry_cipher_setkey (hd, dek->key, dek->keylen))
+      BUG ();
+    gcry_cipher_setiv (hd, NULL, 0);
+    gcry_cipher_encrypt (hd, buf, (*seskey)->keylen + 1, NULL, 0);
+    gcry_cipher_close (hd);
+
+    memcpy( enckey, buf, (*seskey)->keylen + 1 );
+    wipememory( buf, sizeof buf ); /* burn key */
+}
 
+/* We try very hard to use a MDC */
 static int
-encode_simple( const char *filename, int mode )
+use_mdc(PK_LIST pk_list,int algo)
+{
+  /* RFC-1991 and 2440 don't have MDC */
+  if(RFC1991 || RFC2440)
+    return 0;
+
+  /* --force-mdc overrides --disable-mdc */
+  if(opt.force_mdc)
+    return 1;
+
+  if(opt.disable_mdc)
+    return 0;
+
+  /* Do the keys really support MDC? */
+
+  if(select_mdc_from_pklist(pk_list))
+    return 1;
+
+  /* The keys don't support MDC, so now we do a bit of a hack - if any
+     of the AESes or TWOFISH are in the prefs, we assume that the user
+     can handle a MDC.  This is valid for PGP 7, which can handle MDCs
+     though it will not generate them.  2440bis allows this, by the
+     way. */
+
+  if(select_algo_from_prefs(pk_list,PREFTYPE_SYM,
+                           CIPHER_ALGO_AES,NULL)==CIPHER_ALGO_AES)
+    return 1;
+
+  if(select_algo_from_prefs(pk_list,PREFTYPE_SYM,
+                           CIPHER_ALGO_AES192,NULL)==CIPHER_ALGO_AES192)
+    return 1;
+
+  if(select_algo_from_prefs(pk_list,PREFTYPE_SYM,
+                           CIPHER_ALGO_AES256,NULL)==CIPHER_ALGO_AES256)
+    return 1;
+
+  if(select_algo_from_prefs(pk_list,PREFTYPE_SYM,
+                           CIPHER_ALGO_TWOFISH,NULL)==CIPHER_ALGO_TWOFISH)
+    return 1;
+
+  /* Last try.  Use MDC for the modern ciphers. */
+
+  if (openpgp_cipher_get_algo_blklen (algo) != 8)
+    return 1;
+
+  if (opt.verbose)
+    warn_missing_mdc_from_pklist (pk_list);
+
+  return 0; /* No MDC */
+}
+
+/* We don't want to use use_seskey yet because older gnupg versions
+   can't handle it, and there isn't really any point unless we're
+   making a message that can be decrypted by a public key or
+   passphrase. */
+static int
+encode_simple( const char *filename, int mode, int use_seskey )
 {
     IOBUF inp, out;
     PACKET pkt;
     PKT_plaintext *pt = NULL;
     STRING2KEY *s2k = NULL;
+    byte enckey[33];
     int rc = 0;
+    int seskeylen = 0;
     u32 filesize;
     cipher_filter_context_t cfx;
-    armor_filter_context_t afx;
+    armor_filter_context_t  *afx = NULL;
     compress_filter_context_t zfx;
     text_filter_context_t tfx;
-    int do_compress = opt.compress && !opt.rfc1991;
+    progress_filter_context_t *pfx;
+    int do_compress = !RFC1991 && default_compress_algo();
 
+    pfx = new_progress_context ();
     memset( &cfx, 0, sizeof cfx);
-    memset( &afx, 0, sizeof afx);
     memset( &zfx, 0, sizeof zfx);
     memset( &tfx, 0, sizeof tfx);
     init_packet(&pkt);
 
     /* prepare iobufs */
-    if( !(inp = iobuf_open(filename)) ) {
-       log_error(_("%s: can't open: %s\n"), filename? filename: "[stdin]",
-                                       strerror(errno) );
-       return GPGERR_OPEN_FILE;
+    inp = iobuf_open(filename);
+    if (inp)
+      iobuf_ioctl (inp,3,1,NULL); /* disable fd caching */
+    if (inp && is_secured_file (iobuf_get_fd (inp)))
+      {
+        iobuf_close (inp);
+        inp = NULL;
+        errno = EPERM;
+      }
+    if( !inp ) {
+        rc = gpg_error_from_syserror ();
+       log_error(_("can't open `%s': %s\n"), filename? filename: "[stdin]",
+                  strerror(errno) );
+        release_progress_context (pfx);
+       return rc;
     }
 
+    handle_progress (pfx, inp, filename);
+
     if( opt.textmode )
        iobuf_push_filter( inp, text_filter, &tfx );
 
+    /* Due the the fact that we use don't use an IV to encrypt the
+       session key we can't use the new mode with RFC1991 because
+       it has no S2K salt. RFC1991 always uses simple S2K. */
+    if ( RFC1991 && use_seskey )
+        use_seskey = 0;
+
     cfx.dek = NULL;
     if( mode ) {
-       s2k = gcry_xcalloc( 1, sizeof *s2k );
-       s2k->mode = opt.rfc1991? 0:opt.s2k_mode;
-       s2k->hash_algo = opt.def_digest_algo ? opt.def_digest_algo
-                                            : opt.s2k_digest_algo;
+        int canceled;
+
+       s2k = xmalloc_clear( sizeof *s2k );
+       s2k->mode = RFC1991? 0:opt.s2k_mode;
+       s2k->hash_algo=S2K_DIGEST_ALGO;
        cfx.dek = passphrase_to_dek( NULL, 0,
-                      opt.def_cipher_algo ? opt.def_cipher_algo
-                                          : opt.s2k_cipher_algo , s2k, 2 );
+                                    default_cipher_algo(), s2k, 4,
+                                     NULL, &canceled);
        if( !cfx.dek || !cfx.dek->keylen ) {
-           rc = GPGERR_PASSPHRASE;
-           gcry_free(cfx.dek);
-           gcry_free(s2k);
+           rc = gpg_error (canceled? GPG_ERR_CANCELED:GPG_ERR_INV_PASSPHRASE);
+           xfree(cfx.dek);
+           xfree(s2k);
            iobuf_close(inp);
-           log_error(_("error creating passphrase: %s\n"), gpg_errstr(rc) );
+           log_error(_("error creating passphrase: %s\n"), gpg_strerror (rc));
+            release_progress_context (pfx);
            return rc;
        }
+        if (use_seskey && s2k->mode != 1 && s2k->mode != 3) {
+            use_seskey = 0;
+            log_info (_("can't use a symmetric ESK packet "
+                        "due to the S2K mode\n"));
+        }
+
+        if ( use_seskey )
+         {
+           DEK *dek = NULL;
+
+            seskeylen = openpgp_cipher_get_algo_keylen (default_cipher_algo ());
+            encode_seskey( cfx.dek, &dek, enckey );
+            xfree( cfx.dek ); cfx.dek = dek;
+         }
+
+       if(opt.verbose)
+         log_info(_("using cipher %s\n"),
+                  openpgp_cipher_algo_name (cfx.dek->algo));
+
+       cfx.dek->use_mdc=use_mdc(NULL,cfx.dek->algo);
     }
 
-    if( (rc = open_outfile( filename, opt.armor? 1:0, &out )) ) {
+    if (do_compress && cfx.dek && cfx.dek->use_mdc
+       && is_file_compressed(filename, &rc))
+      {
+        if (opt.verbose)
+          log_info(_("`%s' already compressed\n"), filename);
+        do_compress = 0;
+      }
+
+    if( rc || (rc = open_outfile( filename, opt.armor? 1:0, &out )) ) {
        iobuf_cancel(inp);
-       gcry_free(cfx.dek);
-       gcry_free(s2k);
+       xfree(cfx.dek);
+       xfree(s2k);
+        release_progress_context (pfx);
        return rc;
     }
 
-    if( opt.armor )
-       iobuf_push_filter( out, armor_filter, &afx );
-  #ifdef ENABLE_COMMENT_PACKETS
-    else {
-       write_comment( out, "#created by GNUPG v" VERSION " ("
-                                           PRINTABLE_OS_NAME ")");
-       if( opt.comment_string )
-           write_comment( out, opt.comment_string );
-    }
-  #endif
-    if( s2k && !opt.rfc1991 ) {
-       PKT_symkey_enc *enc = gcry_xcalloc( 1, sizeof *enc );
+    if ( opt.armor )
+      {
+        afx = new_armor_context ();
+       push_armor_filter (afx, out);
+      }
+
+    if( s2k && !RFC1991 ) {
+       PKT_symkey_enc *enc = xmalloc_clear( sizeof *enc + seskeylen + 1 );
        enc->version = 4;
        enc->cipher_algo = cfx.dek->algo;
        enc->s2k = *s2k;
+        if ( use_seskey && seskeylen ) {
+            enc->seskeylen = seskeylen + 1; /* algo id */
+            memcpy( enc->seskey, enckey, seskeylen + 1 );
+        }
        pkt.pkttype = PKT_SYMKEY_ENC;
        pkt.pkt.symkey_enc = enc;
        if( (rc = build_packet( out, &pkt )) )
-           log_error("build symkey packet failed: %s\n", gpg_errstr(rc) );
-       gcry_free(enc);
-    }
-
-    if (!opt.no_literal) {
-       /* setup the inner packet */
-       if( filename || opt.set_filename ) {
-           char *s = make_basename( opt.set_filename ? opt.set_filename
-                                                     : filename );
-           pt = gcry_xmalloc( sizeof *pt + strlen(s) - 1 );
-           pt->namelen = strlen(s);
-           memcpy(pt->name, s, pt->namelen );
-           gcry_free(s);
-       }
-       else { /* no filename */
-           pt = gcry_xmalloc( sizeof *pt - 1 );
-           pt->namelen = 0;
-       }
+           log_error("build symkey packet failed: %s\n", g10_errstr(rc) );
+       xfree(enc);
     }
 
-    /* pgp5 has problems to decrypt symmetrically encrypted data from
-     * GnuPG if the filelength is in the inner packet. It works
-     * when only partial length headers are use.  Until we have
-     * tracked this problem down. We use this temporary fix
-     * (fixme: remove the && !mode )
-     */
-    if( filename && !opt.textmode && !mode ) {
-       if( !(filesize = iobuf_get_filelength(inp)) )
-           log_info(_("%s: WARNING: empty file\n"), filename );
-    }
+    if (!opt.no_literal)
+      pt=setup_plaintext_name(filename,inp);
+
+    /* Note that PGP 5 has problems decrypting symmetrically encrypted
+       data if the file length is in the inner packet. It works when
+       only partial length headers are use.  In the past, we always
+       used partial body length here, but since PGP 2, PGP 6, and PGP
+       7 need the file length, and nobody should be using PGP 5
+       nowadays anyway, this is now set to the file length.  Note also
+       that this only applies to the RFC-1991 style symmetric
+       messages, and not the RFC-2440 style.  PGP 6 and 7 work with
+       either partial length or fixed length with the new style
+       messages. */
+
+    if ( !iobuf_is_pipe_filename (filename) && *filename && !opt.textmode )
+      {
+        off_t tmpsize;
+        int overflow;
+
+       if ( !(tmpsize = iobuf_get_filelength(inp, &overflow))
+             && !overflow && opt.verbose)
+          log_info(_("WARNING: `%s' is an empty file\n"), filename );
+        /* We can't encode the length of very large files because
+           OpenPGP uses only 32 bit for file sizes.  So if the the
+           size of a file is larger than 2^32 minus some bytes for
+           packet headers, we switch to partial length encoding. */
+        if ( tmpsize < (IOBUF_FILELENGTH_LIMIT - 65536) )
+          filesize = tmpsize;
+        else
+          filesize = 0;
+      }
     else
-       filesize = opt.set_filesize ? opt.set_filesize : 0; /* stdin */
+      filesize = opt.set_filesize ? opt.set_filesize : 0; /* stdin */
 
     if (!opt.no_literal) {
        pt->timestamp = make_timestamp();
        pt->mode = opt.textmode? 't' : 'b';
        pt->len = filesize;
-       pt->new_ctb = !pt->len && !opt.rfc1991;
+       pt->new_ctb = !pt->len && !RFC1991;
        pt->buf = inp;
        pkt.pkttype = PKT_PLAINTEXT;
        pkt.pkt.plaintext = pt;
        cfx.datalen = filesize && !do_compress ? calc_packet_length( &pkt ) : 0;
     }
     else
-       cfx.datalen = filesize && !do_compress ? filesize : 0;
+      {
+        cfx.datalen = filesize && !do_compress ? filesize : 0;
+        pkt.pkttype = 0;
+        pkt.pkt.generic = NULL;
+      }
 
     /* register the cipher filter */
     if( mode )
        iobuf_push_filter( out, cipher_filter, &cfx );
     /* register the compress filter */
     if( do_compress )
-       iobuf_push_filter( out, compress_filter, &zfx );
+      {
+        if (cfx.dek && cfx.dek->use_mdc)
+          zfx.new_ctb = 1;
+       push_compress_filter(out,&zfx,default_compress_algo());
+      }
 
     /* do the work */
     if (!opt.no_literal) {
        if( (rc = build_packet( out, &pkt )) )
-           log_error("build_packet failed: %s\n", gpg_errstr(rc) );
+           log_error("build_packet failed: %s\n", g10_errstr(rc) );
     }
     else {
        /* user requested not to create a literal packet,
@@ -251,128 +360,272 @@ encode_simple( const char *filename, int mode )
        byte copy_buffer[4096];
        int  bytes_copied;
        while ((bytes_copied = iobuf_read(inp, copy_buffer, 4096)) != -1)
-           if (iobuf_write(out, copy_buffer, bytes_copied) == -1) {
-               rc = GPGERR_WRITE_FILE;
-               log_error("copying input to output failed: %s\n", gpg_errstr(rc) );
+           if ( (rc=iobuf_write(out, copy_buffer, bytes_copied)) ) {
+               log_error ("copying input to output failed: %s\n",
+                           gpg_strerror (rc) );
                break;
            }
-       memset(copy_buffer, 0, 4096); /* burn buffer */
+       wipememory(copy_buffer, 4096); /* burn buffer */
     }
 
     /* finish the stuff */
     iobuf_close(inp);
     if (rc)
        iobuf_cancel(out);
-    else
+    else {
        iobuf_close(out); /* fixme: check returncode */
+        if (mode)
+            write_status( STATUS_END_ENCRYPTION );
+    }
     if (pt)
        pt->buf = NULL;
     free_packet(&pkt);
-    gcry_free(cfx.dek);
-    gcry_free(s2k);
+    xfree(cfx.dek);
+    xfree(s2k);
+    release_armor_context (afx);
+    release_progress_context (pfx);
     return rc;
 }
 
+int
+setup_symkey(STRING2KEY **symkey_s2k,DEK **symkey_dek)
+{
+  int canceled;
+
+  *symkey_s2k=xmalloc_clear(sizeof(STRING2KEY));
+  (*symkey_s2k)->mode = opt.s2k_mode;
+  (*symkey_s2k)->hash_algo = S2K_DIGEST_ALGO;
+
+  *symkey_dek=passphrase_to_dek(NULL,0,opt.s2k_cipher_algo,
+                               *symkey_s2k, 4, NULL, &canceled);
+  if(!*symkey_dek || !(*symkey_dek)->keylen)
+    {
+      xfree(*symkey_dek);
+      xfree(*symkey_s2k);
+      return gpg_error (canceled?GPG_ERR_CANCELED:GPG_ERR_BAD_PASSPHRASE);
+    }
+
+  return 0;
+}
+
+static int
+write_symkey_enc(STRING2KEY *symkey_s2k,DEK *symkey_dek,DEK *dek,IOBUF out)
+{
+  int rc, seskeylen = openpgp_cipher_get_algo_keylen (dek->algo);
+
+  PKT_symkey_enc *enc;
+  byte enckey[33];
+  PACKET pkt;
+
+  enc=xmalloc_clear(sizeof(PKT_symkey_enc)+seskeylen+1);
+  encode_seskey(symkey_dek,&dek,enckey);
+
+  enc->version = 4;
+  enc->cipher_algo = opt.s2k_cipher_algo;
+  enc->s2k = *symkey_s2k;
+  enc->seskeylen = seskeylen + 1; /* algo id */
+  memcpy( enc->seskey, enckey, seskeylen + 1 );
+
+  pkt.pkttype = PKT_SYMKEY_ENC;
+  pkt.pkt.symkey_enc = enc;
+
+  if((rc=build_packet(out,&pkt)))
+    log_error("build symkey_enc packet failed: %s\n",g10_errstr(rc));
+
+  xfree(enc);
+  return rc;
+}
+
 /****************
  * Encrypt the file with the given userids (or ask if none
  * is supplied).
  */
 int
-encode_crypt( const char *filename, STRLIST remusr )
+encode_crypt( const char *filename, strlist_t remusr, int use_symkey )
 {
     IOBUF inp = NULL, out = NULL;
     PACKET pkt;
     PKT_plaintext *pt = NULL;
-    int rc = 0;
+    DEK *symkey_dek = NULL;
+    STRING2KEY *symkey_s2k = NULL;
+    int rc = 0, rc2 = 0;
     u32 filesize;
     cipher_filter_context_t cfx;
-    armor_filter_context_t afx;
+    armor_filter_context_t *afx = NULL;
     compress_filter_context_t zfx;
     text_filter_context_t tfx;
-    PK_LIST pk_list;
-    int do_compress = opt.compress && !opt.rfc1991;
-
+    progress_filter_context_t *pfx;
+    PK_LIST pk_list,work_list;
+    int do_compress = opt.compress_algo && !RFC1991;
 
+    pfx = new_progress_context ();
     memset( &cfx, 0, sizeof cfx);
-    memset( &afx, 0, sizeof afx);
     memset( &zfx, 0, sizeof zfx);
     memset( &tfx, 0, sizeof tfx);
     init_packet(&pkt);
 
-    if( (rc=build_pk_list( remusr, &pk_list, GCRY_PK_USAGE_ENCR)) )
+    if(use_symkey
+       && (rc=setup_symkey(&symkey_s2k,&symkey_dek)))
+      {
+        release_progress_context (pfx);
+        return rc;
+      }
+
+    if( (rc=build_pk_list( remusr, &pk_list, PUBKEY_USAGE_ENC)) )
+      {
+        release_progress_context (pfx);
        return rc;
+      }
+
+    if(PGP2) {
+      for(work_list=pk_list; work_list; work_list=work_list->next)
+       if(!(is_RSA(work_list->pk->pubkey_algo) &&
+            nbits_from_pk(work_list->pk)<=2048))
+         {
+           log_info(_("you can only encrypt to RSA keys of 2048 bits or "
+                      "less in --pgp2 mode\n"));
+           compliance_failure();
+           break;
+         }
+    }
 
     /* prepare iobufs */
-    if( !(inp = iobuf_open(filename)) ) {
-       log_error(_("can't open %s: %s\n"), filename? filename: "[stdin]",
-                                       strerror(errno) );
-       rc = GPGERR_OPEN_FILE;
+    inp = iobuf_open(filename);
+    if (inp)
+      iobuf_ioctl (inp,3,1,NULL); /* disable fd caching */
+    if (inp && is_secured_file (iobuf_get_fd (inp)))
+      {
+        iobuf_close (inp);
+        inp = NULL;
+        errno = EPERM;
+      }
+    if( !inp ) {
+        rc = gpg_error_from_syserror ();
+       log_error(_("can't open `%s': %s\n"),
+                  filename? filename: "[stdin]",
+                  gpg_strerror (rc) );
        goto leave;
     }
     else if( opt.verbose )
        log_info(_("reading from `%s'\n"), filename? filename: "[stdin]");
 
+    handle_progress (pfx, inp, filename);
+
     if( opt.textmode )
        iobuf_push_filter( inp, text_filter, &tfx );
 
     if( (rc = open_outfile( filename, opt.armor? 1:0, &out )) )
        goto leave;
 
+    if ( opt.armor )
+      {
+        afx = new_armor_context ();
+       push_armor_filter (afx, out);
+      }
 
-    if( opt.armor )
-       iobuf_push_filter( out, armor_filter, &afx );
-  #ifdef ENABLE_COMMENT_PACKETS
-    else {
-       write_comment( out, "#created by GNUPG v" VERSION " ("
-                                           PRINTABLE_OS_NAME ")");
-       if( opt.comment_string )
-           write_comment( out, opt.comment_string );
-    }
-  #endif
     /* create a session key */
-    cfx.dek = gcry_xmalloc_secure( sizeof *cfx.dek );
+    cfx.dek = xmalloc_secure_clear (sizeof *cfx.dek);
     if( !opt.def_cipher_algo ) { /* try to get it from the prefs */
-       cfx.dek->algo = select_algo_from_prefs( pk_list, PREFTYPE_SYM );
-       if( cfx.dek->algo == -1 )
-           cfx.dek->algo = DEFAULT_CIPHER_ALGO;
+       cfx.dek->algo = select_algo_from_prefs(pk_list,PREFTYPE_SYM,-1,NULL);
+       /* The only way select_algo_from_prefs can fail here is when
+           mixing v3 and v4 keys, as v4 keys have an implicit
+           preference entry for 3DES, and the pk_list cannot be empty.
+           In this case, use 3DES anyway as it's the safest choice -
+           perhaps the v3 key is being used in an OpenPGP
+           implementation and we know that the implementation behind
+           any v4 key can handle 3DES. */
+       if( cfx.dek->algo == -1 ) {
+           cfx.dek->algo = CIPHER_ALGO_3DES;
+
+           if( PGP2 ) {
+             log_info(_("unable to use the IDEA cipher for all of the keys "
+                        "you are encrypting to.\n"));
+             compliance_failure();
+           }
+       }
+
+        /* In case 3DES has been selected, print a warning if
+           any key does not have a preference for AES.  This
+           should help to indentify why encrypting to several
+           recipients falls back to 3DES. */
+        if (opt.verbose
+            && cfx.dek->algo == CIPHER_ALGO_3DES)
+          warn_missing_aes_from_pklist (pk_list);
     }
-    else
-       cfx.dek->algo = opt.def_cipher_algo;
+    else {
+      if(!opt.expert &&
+        select_algo_from_prefs(pk_list,PREFTYPE_SYM,
+                               opt.def_cipher_algo,NULL)!=opt.def_cipher_algo)
+       log_info(_("WARNING: forcing symmetric cipher %s (%d)"
+                  " violates recipient preferences\n"),
+                openpgp_cipher_algo_name (opt.def_cipher_algo),
+                opt.def_cipher_algo);
+
+      cfx.dek->algo = opt.def_cipher_algo;
+    }
+
+    cfx.dek->use_mdc=use_mdc(pk_list,cfx.dek->algo);
+
+    /* Only do the is-file-already-compressed check if we are using a
+       MDC.  This forces compressed files to be re-compressed if we do
+       not have a MDC to give some protection against chosen
+       ciphertext attacks. */
+
+    if (do_compress && cfx.dek->use_mdc && is_file_compressed(filename, &rc2) )
+      {
+        if (opt.verbose)
+          log_info(_("`%s' already compressed\n"), filename);
+        do_compress = 0;
+      }
+    if (rc2)
+      {
+        rc = rc2;
+        goto leave;
+      }
+
     make_session_key( cfx.dek );
     if( DBG_CIPHER )
-       log_hexdump("DEK is: ", cfx.dek->key, cfx.dek->keylen );
+       log_printhex ("DEK is: ", cfx.dek->key, cfx.dek->keylen );
 
     rc = write_pubkey_enc_from_list( pk_list, cfx.dek, out );
     if( rc  )
        goto leave;
 
-    if (!opt.no_literal) {
-       /* setup the inner packet */
-       if( filename || opt.set_filename ) {
-           char *s = make_basename( opt.set_filename ? opt.set_filename : filename );
-           pt = gcry_xmalloc( sizeof *pt + strlen(s) - 1 );
-           pt->namelen = strlen(s);
-           memcpy(pt->name, s, pt->namelen );
-           gcry_free(s);
-       }
-       else { /* no filename */
-           pt = gcry_xmalloc( sizeof *pt - 1 );
-           pt->namelen = 0;
-       }
-    }
-
-    if( filename && !opt.textmode ) {
-       if( !(filesize = iobuf_get_filelength(inp)) )
-           log_info(_("%s: WARNING: empty file\n"), filename );
-    }
+    /* We put the passphrase (if any) after any public keys as this
+       seems to be the most useful on the recipient side - there is no
+       point in prompting a user for a passphrase if they have the
+       secret key needed to decrypt. */
+    if(use_symkey && (rc=write_symkey_enc(symkey_s2k,symkey_dek,cfx.dek,out)))
+      goto leave;
+
+    if (!opt.no_literal)
+      pt=setup_plaintext_name(filename,inp);
+
+    if (!iobuf_is_pipe_filename (filename) && *filename && !opt.textmode )
+      {
+        off_t tmpsize;
+        int overflow;
+
+       if ( !(tmpsize = iobuf_get_filelength(inp, &overflow))
+             && !overflow && opt.verbose)
+          log_info(_("WARNING: `%s' is an empty file\n"), filename );
+        /* We can't encode the length of very large files because
+           OpenPGP uses only 32 bit for file sizes.  So if the the
+           size of a file is larger than 2^32 minus some bytes for
+           packet headers, we switch to partial length encoding. */
+        if (tmpsize < (IOBUF_FILELENGTH_LIMIT - 65536) )
+          filesize = tmpsize;
+        else
+          filesize = 0;
+      }
     else
-       filesize = opt.set_filesize ? opt.set_filesize : 0; /* stdin */
+      filesize = opt.set_filesize ? opt.set_filesize : 0; /* stdin */
 
     if (!opt.no_literal) {
        pt->timestamp = make_timestamp();
        pt->mode = opt.textmode ? 't' : 'b';
        pt->len = filesize;
-       pt->new_ctb = !pt->len && !opt.rfc1991;
+       pt->new_ctb = !pt->len && !RFC1991;
        pt->buf = inp;
        pkt.pkttype = PKT_PLAINTEXT;
        pkt.pkt.plaintext = pt;
@@ -386,32 +639,49 @@ encode_crypt( const char *filename, STRLIST remusr )
 
     /* register the compress filter */
     if( do_compress ) {
-       int compr_algo = select_algo_from_prefs( pk_list, PREFTYPE_COMPR );
-       if( !compr_algo )
-           ; /* don't use compression */
-       else {
-           if( compr_algo == 1 )
-               zfx.algo = 1; /* default is 2 */
-           iobuf_push_filter( out, compress_filter, &zfx );
-       }
+       int compr_algo = opt.compress_algo;
+
+       if(compr_algo==-1)
+         {
+           if((compr_algo=
+               select_algo_from_prefs(pk_list,PREFTYPE_ZIP,-1,NULL))==-1)
+             compr_algo=DEFAULT_COMPRESS_ALGO;
+           /* Theoretically impossible to get here since uncompressed
+              is implicit. */
+         }
+       else if(!opt.expert &&
+               select_algo_from_prefs(pk_list,PREFTYPE_ZIP,
+                                      compr_algo,NULL)!=compr_algo)
+         log_info(_("WARNING: forcing compression algorithm %s (%d)"
+                    " violates recipient preferences\n"),
+                  compress_algo_to_string(compr_algo),compr_algo);
+
+       /* algo 0 means no compression */
+       if( compr_algo )
+         {
+            if (cfx.dek && cfx.dek->use_mdc)
+              zfx.new_ctb = 1;
+           push_compress_filter(out,&zfx,compr_algo);
+         }
     }
 
     /* do the work */
     if (!opt.no_literal) {
        if( (rc = build_packet( out, &pkt )) )
-           log_error("build_packet failed: %s\n", gpg_errstr(rc) );
+           log_error("build_packet failed: %s\n", g10_errstr(rc) );
     }
     else {
-       /* user requested not to create a literal packet, so we copy the plain data */
+       /* user requested not to create a literal packet, so we copy
+           the plain data */
        byte copy_buffer[4096];
        int  bytes_copied;
        while ((bytes_copied = iobuf_read(inp, copy_buffer, 4096)) != -1)
-           if (iobuf_write(out, copy_buffer, bytes_copied) == -1) {
-               rc = GPGERR_WRITE_FILE;
-               log_error("copying input to output failed: %s\n", gpg_errstr(rc) );
+           if ( (rc=iobuf_write(out, copy_buffer, bytes_copied)) ) {
+               log_error ("copying input to output failed: %s\n",
+                           gpg_strerror (rc));
                break;
            }
-       memset(copy_buffer, 0, 4096); /* burn buffer */
+       wipememory(copy_buffer, 4096); /* burn buffer */
     }
 
     /* finish the stuff */
@@ -419,13 +689,19 @@ encode_crypt( const char *filename, STRLIST remusr )
     iobuf_close(inp);
     if( rc )
        iobuf_cancel(out);
-    else
+    else {
        iobuf_close(out); /* fixme: check returncode */
+        write_status( STATUS_END_ENCRYPTION );
+    }
     if( pt )
        pt->buf = NULL;
     free_packet(&pkt);
-    gcry_free(cfx.dek);
+    xfree(cfx.dek);
+    xfree(symkey_dek);
+    xfree(symkey_s2k);
     release_pk_list( pk_list );
+    release_armor_context (afx);
+    release_progress_context (pfx);
     return rc;
 }
 
@@ -448,25 +724,57 @@ encrypt_filter( void *opaque, int control,
     }
     else if( control == IOBUFCTRL_FLUSH ) { /* encrypt */
        if( !efx->header_okay ) {
-           efx->cfx.dek = gcry_xmalloc_secure( sizeof *efx->cfx.dek );
+           efx->cfx.dek = xmalloc_secure_clear( sizeof *efx->cfx.dek );
 
            if( !opt.def_cipher_algo  ) { /* try to get it from the prefs */
                efx->cfx.dek->algo =
-                         select_algo_from_prefs( efx->pk_list, PREFTYPE_SYM );
-               if( efx->cfx.dek->algo == -1 )
+                 select_algo_from_prefs(efx->pk_list,PREFTYPE_SYM,-1,NULL);
+               if( efx->cfx.dek->algo == -1 ) {
+                    /* because 3DES is implicitly in the prefs, this can only
+                     * happen if we do not have any public keys in the list */
                    efx->cfx.dek->algo = DEFAULT_CIPHER_ALGO;
+                }
+
+                /* In case 3DES has been selected, print a warning if
+                   any key does not have a preference for AES.  This
+                   should help to indentify why encrypting to several
+                   recipients falls back to 3DES. */
+                if (opt.verbose
+                    && efx->cfx.dek->algo == CIPHER_ALGO_3DES)
+                  warn_missing_aes_from_pklist (efx->pk_list);
+           }
+           else {
+             if(!opt.expert &&
+                select_algo_from_prefs(efx->pk_list,PREFTYPE_SYM,
+                                       opt.def_cipher_algo,
+                                       NULL)!=opt.def_cipher_algo)
+               log_info(_("forcing symmetric cipher %s (%d) "
+                          "violates recipient preferences\n"),
+                        openpgp_cipher_algo_name (opt.def_cipher_algo),
+                        opt.def_cipher_algo);
+
+             efx->cfx.dek->algo = opt.def_cipher_algo;
            }
-           else
-               efx->cfx.dek->algo = opt.def_cipher_algo;
+
+            efx->cfx.dek->use_mdc = use_mdc(efx->pk_list,efx->cfx.dek->algo);
+
            make_session_key( efx->cfx.dek );
            if( DBG_CIPHER )
-               log_hexdump("DEK is: ",
-                            efx->cfx.dek->key, efx->cfx.dek->keylen );
+               log_printhex ("DEK is: ",
+                              efx->cfx.dek->key, efx->cfx.dek->keylen );
 
            rc = write_pubkey_enc_from_list( efx->pk_list, efx->cfx.dek, a );
            if( rc )
                return rc;
 
+           if(efx->symkey_s2k && efx->symkey_dek)
+             {
+               rc=write_symkey_enc(efx->symkey_s2k,efx->symkey_dek,
+                                   efx->cfx.dek,a);
+               if(rc)
+                 return rc;
+             }
+
            iobuf_push_filter( a, cipher_filter, &efx->cfx );
 
            efx->header_okay = 1;
@@ -474,8 +782,11 @@ encrypt_filter( void *opaque, int control,
        rc = iobuf_write( a, buf, size );
 
     }
-    else if( control == IOBUFCTRL_FREE ) {
-    }
+    else if( control == IOBUFCTRL_FREE )
+      {
+       xfree(efx->symkey_dek);
+       xfree(efx->symkey_s2k);
+      }
     else if( control == IOBUFCTRL_DESC ) {
        *(char**)buf = "encrypt_filter";
     }
@@ -495,15 +806,22 @@ write_pubkey_enc_from_list( PK_LIST pk_list, DEK *dek, IOBUF out )
     int rc;
 
     for( ; pk_list; pk_list = pk_list->next ) {
-       MPI frame;
+       gcry_mpi_t frame;
 
        pk = pk_list->pk;
 
        print_pubkey_algo_note( pk->pubkey_algo );
-       enc = gcry_xcalloc( 1, sizeof *enc );
+       enc = xmalloc_clear( sizeof *enc );
        enc->pubkey_algo = pk->pubkey_algo;
        keyid_from_pk( pk, enc->keyid );
-       enc->throw_keyid = opt.throw_keyid;
+       enc->throw_keyid = (opt.throw_keyid || (pk_list->flags&1));
+
+       if(opt.throw_keyid && (PGP2 || PGP6 || PGP7 || PGP8))
+         {
+           log_info(_("you may not use %s while in %s mode\n"),
+                    "--throw-keyid",compliance_option_string());
+           compliance_failure();
+         }
 
        /* Okay, what's going on: We have the session key somewhere in
         * the structure DEK and want to encode this session key in
@@ -514,23 +832,24 @@ write_pubkey_enc_from_list( PK_LIST pk_list, DEK *dek, IOBUF out )
         * algorithm number PK->PUBKEY_ALGO and pass it to pubkey_encrypt
         * which returns the encrypted value in the array ENC->DATA.
         * This array has a size which depends on the used algorithm
-        * (e.g. 2 for ElGamal).  We don't need frame anymore because we
+        * (e.g. 2 for Elgamal).  We don't need frame anymore because we
         * have everything now in enc->data which is the passed to
         * build_packet()
         */
-       frame = encode_session_key( dek, pubkey_nbits( pk->pubkey_algo,
-                                                         pk->pkey ) );
-       rc = pk_encrypt( pk->pubkey_algo, enc->data, frame, pk->pkey );
-       mpi_release( frame );
+       frame = encode_session_key (dek, pubkey_nbits (pk->pubkey_algo,
+                                                       pk->pkey) );
+       rc = pk_encrypt (pk->pubkey_algo, enc->data, frame, pk->pkey);
+       gcry_mpi_release (frame);
        if( rc )
-           log_error("pubkey_encrypt failed: %s\n", gpg_errstr(rc) );
+           log_error ("pubkey_encrypt failed: %s\n", gpg_strerror (rc) );
        else {
            if( opt.verbose ) {
-               char *ustr = get_user_id_string_native( enc->keyid );
-               log_info(_("%s/%s encrypted for: %s\n"),
-                   gcry_pk_algo_name(enc->pubkey_algo),
-                   gcry_cipher_algo_name(dek->algo), ustr );
-               gcry_free(ustr);
+               char *ustr = get_user_id_string_native (enc->keyid);
+               log_info(_("%s/%s encrypted for: \"%s\"\n"),
+                         openpgp_pk_algo_name (enc->pubkey_algo),
+                         openpgp_cipher_algo_name (dek->algo),
+                         ustr );
+               xfree(ustr);
            }
            /* and write it */
            init_packet(&pkt);
@@ -538,7 +857,7 @@ write_pubkey_enc_from_list( PK_LIST pk_list, DEK *dek, IOBUF out )
            pkt.pkt.pubkey_enc = enc;
            rc = build_packet( out, &pkt );
            if( rc )
-              log_error("build_packet(pubkey_enc) failed: %s\n", gpg_errstr(rc));
+              log_error("build_packet(pubkey_enc) failed: %s\n", g10_errstr(rc));
        }
        free_pubkey_enc(enc);
        if( rc )
@@ -547,3 +866,47 @@ write_pubkey_enc_from_list( PK_LIST pk_list, DEK *dek, IOBUF out )
     return 0;
 }
 
+void
+encode_crypt_files(int nfiles, char **files, strlist_t remusr)
+{
+  int rc = 0;
+
+  if (opt.outfile)
+    {
+      log_error(_("--output doesn't work for this command\n"));
+      return;
+    }
+
+  if (!nfiles)
+    {
+      char line[2048];
+      unsigned int lno = 0;
+      while ( fgets(line, DIM(line), stdin) )
+        {
+          lno++;
+          if (!*line || line[strlen(line)-1] != '\n')
+            {
+              log_error("input line %u too long or missing LF\n", lno);
+              return;
+            }
+          line[strlen(line)-1] = '\0';
+          print_file_status(STATUS_FILE_START, line, 2);
+          if ( (rc = encode_crypt(line, remusr, 0)) )
+            log_error("encryption of `%s' failed: %s\n",
+                      print_fname_stdin(line), g10_errstr(rc) );
+          write_status( STATUS_FILE_DONE );
+        }
+    }
+  else
+    {
+      while (nfiles--)
+        {
+          print_file_status(STATUS_FILE_START, *files, 2);
+          if ( (rc = encode_crypt(*files, remusr, 0)) )
+            log_error("encryption of `%s' failed: %s\n",
+                      print_fname_stdin(*files), g10_errstr(rc) );
+          write_status( STATUS_FILE_DONE );
+          files++;
+        }
+    }
+}