* gpg.sgml: Note that --throw-keyid is --throw-keyids. Note changes in
[gnupg.git] / sm / decrypt.c
index 6748b2a..17483aa 100644 (file)
@@ -1,5 +1,5 @@
 /* decrypt.c - Decrypt a message
- *     Copyright (C) 2001 Free Software Foundation, Inc.
+ *     Copyright (C) 2001, 2003 Free Software Foundation, Inc.
  *
  * This file is part of GnuPG.
  *
 #include <time.h>
 #include <assert.h>
 
+#include "gpgsm.h"
 #include <gcrypt.h>
 #include <ksba.h>
 
-#include "gpgsm.h"
 #include "keydb.h"
 #include "i18n.h"
 
-static void
-print_integer (unsigned char *p)
+struct decrypt_filter_parm_s {
+  int algo;
+  int mode;
+  int blklen;
+  gcry_cipher_hd_t hd;
+  char iv[16];
+  size_t ivlen;
+  int any_data;  /* dod we push anything through the filter at all? */
+  unsigned char lastblock[16];  /* to strip the padding we have to
+                                   keep this one */
+  char helpblock[16];  /* needed because there is no block buffering in
+                          libgcrypt (yet) */
+  int  helpblocklen;
+};
+
+
+
+/* decrypt the session key and fill in the parm structure.  The
+   algo and the IV is expected to be already in PARM. */
+static int 
+prepare_decryption (const char *hexkeygrip, KsbaConstSexp enc_val,
+                    struct decrypt_filter_parm_s *parm)
 {
-  unsigned long len;
+  char *seskey = NULL;
+  size_t n, seskeylen;
+  int rc;
+
+  rc = gpgsm_agent_pkdecrypt (hexkeygrip, enc_val,
+                              &seskey, &seskeylen);
+  if (rc)
+    {
+      log_error ("error decrypting session key: %s\n", gpg_strerror (rc));
+      goto leave;
+    }
+
+  if (DBG_CRYPTO)
+    log_printhex ("pkcs1 encoded session key:", seskey, seskeylen);
 
-  if (!p)
-    log_printf ("none");
+  n=0;
+  if (seskeylen == 24)
+    {
+      /* Smells like a 3-des key.  This might happen because a SC has
+         already done the unpacking. fixme! */
+    }
   else
     {
-      len = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
-      for (p+=4; len; len--, p++)
-        log_printf ("%02X", *p);
+      if (n + 7 > seskeylen )
+        {
+          rc = gpg_error (GPG_ERR_INV_SESSION_KEY);
+          goto leave; 
+        }
+      
+      /* FIXME: Actually the leading zero is required but due to the way
+         we encode the output in libgcrypt as an MPI we are not able to
+         encode that leading zero.  However, when using a Smartcard we are
+         doing it the rightway and therefore we have to skip the zero.  This
+         should be fixed in gpg-agent of course. */
+      if (!seskey[n])
+        n++;
+      
+      if (seskey[n] != 2 )  /* wrong block type version */
+        { 
+          rc = gpg_error (GPG_ERR_INV_SESSION_KEY);
+          goto leave; 
+        }
+      
+      for (n++; n < seskeylen && seskey[n]; n++) /* skip the random bytes */
+        ;
+      n++; /* and the zero byte */
+      if (n >= seskeylen )
+        { 
+          rc = gpg_error (GPG_ERR_INV_SESSION_KEY);
+          goto leave; 
+        }
+    }
+
+  if (DBG_CRYPTO)
+    log_printhex ("session key:", seskey+n, seskeylen-n);
+
+  rc = gcry_cipher_open (&parm->hd, parm->algo, parm->mode, 0);
+  if (rc)
+    {
+      log_error ("error creating decryptor: %s\n", gpg_strerror (rc));
+      goto leave;
+    }
+                        
+  rc = gcry_cipher_setkey (parm->hd, seskey+n, seskeylen-n);
+  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;
     }
+  if (rc)
+    {
+      log_error("key setup failed: %s\n", gpg_strerror(rc) );
+      goto leave;
+    }
+
+  gcry_cipher_setiv (parm->hd, parm->iv, parm->ivlen);
+
+ leave:
+  xfree (seskey);
+  return rc;
 }
 
 
+/* This function is called by the KSBA writer just before the actual
+   write is done.  The function must take INLEN bytes from INBUF,
+   decrypt it and store it inoutbuf which has a maximum size of
+   maxoutlen.  The valid bytes in outbuf should be return in outlen.
+   Due to different buffer sizes or different length of input and
+   output, it may happen that fewer bytes are process or fewer bytes
+   are written. */
+static KsbaError  
+decrypt_filter (void *arg,
+                const void *inbuf, size_t inlen, size_t *inused,
+                void *outbuf, size_t maxoutlen, size_t *outlen)
+{
+  struct decrypt_filter_parm_s *parm = arg;
+  int blklen = parm->blklen;
+  size_t orig_inlen = inlen;
+
+  /* fixme: Should we issue an error when we have not seen one full block? */
+  if (!inlen)
+    return KSBA_Bug;
+
+  if (maxoutlen < 2*parm->blklen)
+    return KSBA_Bug;
+  /* make some space becuase we will later need an extra block at the end */
+  maxoutlen -= blklen;
+
+  if (parm->helpblocklen)
+    {
+      int i, j;
+
+      for (i=parm->helpblocklen,j=0; i < blklen && j < inlen; i++, j++)
+        parm->helpblock[i] = ((const char*)inbuf)[j];
+      inlen -= j;
+      if (blklen > maxoutlen)
+        return KSBA_Bug;
+      if (i < blklen)
+        {
+          parm->helpblocklen = i;
+          *outlen = 0;
+        }
+      else
+        {
+          parm->helpblocklen = 0;
+          if (parm->any_data)
+            {
+              memcpy (outbuf, parm->lastblock, blklen);
+              *outlen =blklen;
+            }
+          else
+            *outlen = 0;
+          gcry_cipher_decrypt (parm->hd, parm->lastblock, blklen,
+                               parm->helpblock, blklen);
+          parm->any_data = 1;
+        }
+      *inused = orig_inlen - inlen;
+      return 0;
+    }
+
+
+  if (inlen > maxoutlen)
+    inlen = maxoutlen;
+  if (inlen % blklen)
+    { /* store the remainder away */
+      parm->helpblocklen = inlen%blklen;
+      inlen = inlen/blklen*blklen;
+      memcpy (parm->helpblock, (const char*)inbuf+inlen, parm->helpblocklen);
+    }
+
+  *inused = inlen + parm->helpblocklen;
+  if (inlen)
+    {
+      assert (inlen >= blklen);
+      if (parm->any_data)
+        {
+          gcry_cipher_decrypt (parm->hd, (char*)outbuf+blklen, inlen,
+                               inbuf, inlen);
+          memcpy (outbuf, parm->lastblock, blklen);
+          memcpy (parm->lastblock,(char*)outbuf+inlen, blklen);
+          *outlen = inlen;
+        }
+      else
+        {
+          gcry_cipher_decrypt (parm->hd, outbuf, inlen, inbuf, inlen);
+          memcpy (parm->lastblock, (char*)outbuf+inlen-blklen, blklen);
+          *outlen = inlen - blklen;
+          parm->any_data = 1;
+        }
+    }
+  else
+    *outlen = 0;
+  return 0;
+}
+
 
 \f
 /* Perform a decrypt operation.  */
@@ -67,12 +250,15 @@ gpgsm_decrypt (CTRL ctrl, int in_fd, FILE *out_fp)
   KEYDB_HANDLE kh;
   int recp;
   FILE *in_fp = NULL;
+  struct decrypt_filter_parm_s dfparm;
+
+  memset (&dfparm, 0, sizeof dfparm);
 
   kh = keydb_new (0);
   if (!kh)
     {
       log_error (_("failed to allocated keyDB handle\n"));
-      rc = GNUPG_General_Error;
+      rc = gpg_error (GPG_ERR_GENERAL);
       goto leave;
     }
 
@@ -80,29 +266,29 @@ gpgsm_decrypt (CTRL ctrl, int in_fd, FILE *out_fp)
   in_fp = fdopen ( dup (in_fd), "rb");
   if (!in_fp)
     {
+      rc = gpg_error (gpg_err_code_from_errno (errno));
       log_error ("fdopen() failed: %s\n", strerror (errno));
-      rc = seterr (IO_Error);
       goto leave;
     }
 
   rc = gpgsm_create_reader (&b64reader, ctrl, in_fp, &reader);
   if (rc)
     {
-      log_error ("can't create reader: %s\n", gnupg_strerror (rc));
+      log_error ("can't create reader: %s\n", gpg_strerror (rc));
       goto leave;
     }
 
-  rc = gpgsm_create_writer (&b64reader, ctrl, out_fp, &writer);
+  rc = gpgsm_create_writer (&b64writer, ctrl, out_fp, &writer);
   if (rc)
     {
-      log_error ("can't create writer: %s\n", gnupg_strerror (rc));
+      log_error ("can't create writer: %s\n", gpg_strerror (rc));
       goto leave;
     }
 
   cms = ksba_cms_new ();
   if (!cms)
     {
-      rc = seterr (Out_Of_Core);
+      rc = gpg_error (GPG_ERR_ENOMEM);
       goto leave;
     }
 
@@ -125,19 +311,62 @@ gpgsm_decrypt (CTRL ctrl, int in_fd, FILE *out_fp)
           rc = map_ksba_err (err);
           goto leave;
         }
-      log_debug ("ksba_cms_parse - stop reason %d\n", stopreason);
 
       if (stopreason == KSBA_SR_BEGIN_DATA
           || stopreason == KSBA_SR_DETACHED_DATA)
         {
-          for (recp=0; recp < 1; recp++)
+          int algo, mode;
+          const char *algoid;
+          int any_key = 0;
+          
+          algoid = ksba_cms_get_content_oid (cms, 2/* encryption algo*/);
+          algo = gcry_cipher_map_name (algoid);
+          mode = gcry_cipher_mode_from_oid (algoid);
+          if (!algo || !mode)
+            {
+              rc = gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
+              log_error ("unsupported algorithm `%s'\n", algoid? algoid:"?");
+              if (algoid && !strcmp (algoid, "1.2.840.113549.3.2"))
+                log_info (_("(this is the RC2 algorithm)\n"));
+              else if (!algoid)
+                log_info (_("(this does not seem to be an encrypted"
+                            " message)\n"));
+              {
+                char numbuf[50];
+                sprintf (numbuf, "%d", rc);
+                gpgsm_status2 (ctrl, STATUS_ERROR, "decrypt.algorithm",
+                               numbuf, algoid?algoid:"?", NULL);
+              }
+
+              goto leave;
+            }
+          dfparm.algo = algo;
+          dfparm.mode = mode;
+          dfparm.blklen = gcry_cipher_get_algo_blklen (algo);
+          if (dfparm.blklen > sizeof (dfparm.helpblock))
+            return gpg_error (GPG_ERR_BUG);
+
+          rc = ksba_cms_get_content_enc_iv (cms,
+                                            dfparm.iv,
+                                            sizeof (dfparm.iv),
+                                            &dfparm.ivlen);
+          if (rc)
+            {
+              log_error ("error getting IV: %s\n", ksba_strerror (err));
+              rc = map_ksba_err (err);
+              goto leave;
+            }
+          
+          for (recp=0; !any_key; recp++)
             {
               char *issuer;
-              unsigned char *serial;
-              char *enc_val;
+              KsbaSexp serial;
+              KsbaSexp enc_val;
               char *hexkeygrip = NULL;
 
               err = ksba_cms_get_issuer_serial (cms, recp, &issuer, &serial);
+              if (err == -1 && recp)
+                break; /* no more recipients */
               if (err)
                 log_error ("recp %d - error getting info: %s\n",
                            recp, ksba_strerror (err));
@@ -148,23 +377,35 @@ gpgsm_decrypt (CTRL ctrl, int in_fd, FILE *out_fp)
                   log_debug ("recp %d - issuer: `%s'\n",
                              recp, issuer? issuer:"[NONE]");
                   log_debug ("recp %d - serial: ", recp);
-                  print_integer (serial);
+                  gpgsm_dump_serial (serial);
                   log_printf ("\n");
 
                   keydb_search_reset (kh);
                   rc = keydb_search_issuer_sn (kh, issuer, serial);
                   if (rc)
                     {
-                      log_debug ("failed to find the certificate: %s\n",
-                                 gnupg_strerror(rc));
+                      log_error ("failed to find the certificate: %s\n",
+                                 gpg_strerror(rc));
                       goto oops;
                     }
 
                   rc = keydb_get_cert (kh, &cert);
                   if (rc)
                     {
-                      log_debug ("failed to get cert: %s\n", gnupg_strerror (rc));
-                      goto oops;
+                      log_error ("failed to get cert: %s\n", gpg_strerror (rc));
+                      goto oops;     
+                    }
+                  /* Just in case there is a problem with the own
+                     certificate we print this message - should never
+                     happen of course */
+                  rc = gpgsm_cert_use_decrypt_p (cert);
+                  if (rc)
+                    {
+                      char numbuf[50];
+                      sprintf (numbuf, "%d", rc);
+                      gpgsm_status2 (ctrl, STATUS_ERROR, "decrypt.keyusage",
+                                     numbuf, NULL);
+                      rc = 0;
                     }
 
                   hexkeygrip = gpgsm_get_keygrip_hexstring (cert);
@@ -175,48 +416,90 @@ gpgsm_decrypt (CTRL ctrl, int in_fd, FILE *out_fp)
                   ksba_cert_release (cert);
                 }
 
-              enc_val = ksba_cms_get_enc_val (cms, recp);
-              if (!enc_val)
+              if (!hexkeygrip)
+                ;
+              else if (!(enc_val = ksba_cms_get_enc_val (cms, recp)))
                 log_error ("recp %d - error getting encrypted session key\n",
                            recp);
               else
                 {
-                  char *seskey;
-                  size_t seskeylen;
-
-                  log_debug ("recp %d - enc-val: `%s'\n",
-                             recp, enc_val);
-
-                  rc = gpgsm_agent_pkdecrypt (hexkeygrip,
-                                              enc_val, strlen (enc_val),
-                                              &seskey, &seskeylen);
+                  rc = prepare_decryption (hexkeygrip, enc_val, &dfparm);
+                  xfree (enc_val);
                   if (rc)
-                    log_debug ("problem: %s\n", gnupg_strerror (rc));
+                    {
+                      log_debug ("decrypting session key failed: %s\n",
+                                 gpg_strerror (rc));
+                    }
                   else
+                    { /* setup the bulk decrypter */
+                      any_key = 1;
+                      ksba_writer_set_filter (writer,
+                                              decrypt_filter,
+                                              &dfparm);
+                    }
+                }
+            }
+          if (!any_key)
+            {
+              rc = gpg_error (GPG_ERR_NO_SECKEY);
+              goto leave;
+            }
+        }
+      else if (stopreason == KSBA_SR_END_DATA)
+        {
+          ksba_writer_set_filter (writer, NULL, NULL);
+          if (dfparm.any_data)
+            { /* write the last block with padding removed */
+              int i, npadding = dfparm.lastblock[dfparm.blklen-1];
+              if (!npadding || npadding > dfparm.blklen)
+                {
+                  log_error ("invalid padding with value %d\n", npadding);
+                  rc = gpg_error (GPG_ERR_INV_DATA);
+                  goto leave;
+                }
+              rc = ksba_writer_write (writer,
+                                      dfparm.lastblock, 
+                                      dfparm.blklen - npadding);
+              if (rc)
+                {
+                  rc = map_ksba_err (rc);
+                  goto leave;
+                }
+              for (i=dfparm.blklen - npadding; i < dfparm.blklen; i++)
+                {
+                  if (dfparm.lastblock[i] != npadding)
                     {
-                      unsigned char *p;
-                      log_debug ("plaintext=");
-                      for (p=seskey; seskeylen; seskeylen--, p++)
-                        log_printf (" %02X", *p);
-                      log_printf ("\n");
+                      log_error ("inconsistent padding\n");
+                      rc = gpg_error (GPG_ERR_INV_DATA);
+                      goto leave;
                     }
-                  xfree (enc_val);
                 }
             }
         }
 
-
-
     }
   while (stopreason != KSBA_SR_READY);   
 
+  rc = gpgsm_finish_writer (b64writer);
+  if (rc) 
+    {
+      log_error ("write failed: %s\n", gpg_strerror (rc));
+      goto leave;
+    }
+  gpgsm_status (ctrl, STATUS_DECRYPTION_OKAY, NULL);
+
+
  leave:
+  if (rc)
+    gpgsm_status (ctrl, STATUS_DECRYPTION_FAILED, NULL);
   ksba_cms_release (cms);
   gpgsm_destroy_reader (b64reader);
   gpgsm_destroy_writer (b64writer);
   keydb_release (kh); 
   if (in_fp)
     fclose (in_fp);
+  if (dfparm.hd)
+    gcry_cipher_close (dfparm.hd); 
   return rc;
 }