* genkey.c (store_key): Protect the key.
authorWerner Koch <wk@gnupg.org>
Thu, 31 Jan 2002 16:38:45 +0000 (16:38 +0000)
committerWerner Koch <wk@gnupg.org>
Thu, 31 Jan 2002 16:38:45 +0000 (16:38 +0000)
(agent_genkey): Ask for the passphrase.
* findkey.c (unprotect): Actually unprotect the key.
* query.c (agent_askpin): Add an optional start_err_text.

agent/ChangeLog
agent/Makefile.am
agent/agent.h
agent/cache.c
agent/findkey.c
agent/genkey.c
agent/keyformat.txt
agent/protect-tool.c [new file with mode: 0644]
agent/protect.c [new file with mode: 0644]
agent/query.c

index 9a42b40..21f4966 100644 (file)
@@ -1,3 +1,16 @@
+2002-01-31  Werner Koch  <wk@gnupg.org>
+
+       * genkey.c (store_key): Protect the key.
+       (agent_genkey): Ask for the passphrase.
+       * findkey.c (unprotect): Actually unprotect the key.
+       * query.c (agent_askpin): Add an optional start_err_text. 
+
+2002-01-30  Werner Koch  <wk@gnupg.org>
+
+       * protect.c: New.  
+       (hash_passphrase): Based on the GnuPG 1.0.6 version.
+       * protect-tool.c: New
+
 2002-01-29  Werner Koch  <wk@gnupg.org>
 
        * findkey.c (agent_key_available): New.
index 013862e..eb5fa7d 100644 (file)
@@ -19,6 +19,7 @@
 ## Process this file with automake to produce Makefile.in
 
 bin_PROGRAMS = gpg-agent
+noinst_PROGRAMS = protect-tool
 
 AM_CPPFLAGS = -I$(top_srcdir)/common $(LIBGCRYPT_CFLAGS)
 LDFLAGS = @LDFLAGS@ 
@@ -33,11 +34,16 @@ gpg_agent_SOURCES = \
        pksign.c \
        pkdecrypt.c \
        genkey.c \
+       protect.c \
        trustlist.c 
 
 gpg_agent_LDADD = ../jnlib/libjnlib.a ../assuan/libassuan.a  \
                ../common/libcommon.a $(LIBGCRYPT_LIBS)
 
+protect_tool_SOURCES = \
+       protect-tool.c \
+       protect.c
 
-
+protect_tool_LDADD = ../jnlib/libjnlib.a \
+                    ../common/libcommon.a $(LIBGCRYPT_LIBS)
 
index d999bda..955dd1c 100644 (file)
@@ -84,7 +84,7 @@ struct pin_entry_info_s {
 
 
 /*-- gpg-agent.c --*/
-void agent_exit (int rc);
+void agent_exit (int rc); /* also implemented in other tools */
 
 /*-- trans.c --*/
 const char *trans (const char *text);
@@ -97,7 +97,8 @@ GCRY_SEXP agent_key_from_file (const unsigned char *grip);
 int agent_key_available (const unsigned char *grip);
 
 /*-- query.c --*/
-int agent_askpin (const char *desc_text, struct pin_entry_info_s *pininfo);
+int agent_askpin (const char *desc_text, const char *err_text,
+                  struct pin_entry_info_s *pininfo);
 int agent_get_passphrase (char **retpass,
                           const char *desc, const char *prompt,
                           const char *errtext);
@@ -119,6 +120,13 @@ int agent_pkdecrypt (CTRL ctrl, const char *ciphertext, size_t ciphertextlen,
 int agent_genkey (CTRL ctrl,
                   const char *keyparam, size_t keyparmlen, FILE *outfp);
 
+/*-- protect.c --*/
+int agent_protect (const unsigned char *plainkey, const char *passphrase,
+                   unsigned char **result, size_t *resultlen);
+int agent_unprotect (const unsigned char *protectedkey, const char *passphrase,
+                     unsigned char **result, size_t *resultlen);
+
+
 /*-- trustlist.c --*/
 int agent_istrusted (const char *fpr);
 int agent_listtrusted (void *assuan_context);
index 9621371..aa7a21b 100644 (file)
@@ -132,7 +132,7 @@ housekeeping (void)
 
 
 /* Store DATA of length DATALEN in the cache under KEY and mark it
-   with a maxiumum lifetime of TTL seconds.  If tehre is already data
+   with a maximum lifetime of TTL seconds.  If tehre is already data
    under this key, it will be replaced.  Using a DATA of NULL deletes
    the entry */
 int
@@ -206,7 +206,7 @@ agent_get_cache (const char *key)
     {
       if (r->pw && !strcmp (r->key, key))
         {
-          /* put_cache does onlu put strings into the cache, so we
+          /* put_cache does only put strings into the cache, so we
              don't need the lengths */
           r->accessed = time (NULL);
           return r->pw->data;
index 50f832b..0979033 100644 (file)
 #include "agent.h"
 
 static int
-unprotect (GCRY_SEXP s_skey)
+unprotect (unsigned char **keybuf)
 {
   struct pin_entry_info_s *pi;
   int rc;
+  unsigned char *result;
+  size_t resultlen;
+  int tries = 0;
 
   /* fixme: check whether the key needs unprotection */
 
-  /* fixme: allocate the pin in secure memory */
-  pi = xtrycalloc (1, sizeof (*pi) + 100);
+  pi = gcry_calloc_secure (1, sizeof (*pi) + 100);
   pi->max_length = 100;
-  pi->min_digits = 4;
+  pi->min_digits = 0;  /* we want a real passphrase */
   pi->max_digits = 8;
   pi->max_tries = 3;
 
-  rc = agent_askpin (NULL, pi);
-  /* fixme: actually unprotect the key and ask again until we get a valid
-     PIN - agent_askpin takes care of counting failed tries */
-
+  do
+    {
+      rc = agent_askpin (NULL, NULL, pi);
+      if (!rc)
+        {
+          rc = agent_unprotect (*keybuf, pi->pin, &result, &resultlen);
+          if (!rc)
+            {
+              xfree (*keybuf);
+              *keybuf = result;
+              xfree (pi);
+              return 0;
+            }
+        }
+    }
+  while ((rc == GNUPG_Bad_Passphrase || rc == GNUPG_Bad_PIN)
+         && tries++ < 3);
   xfree (pi);
   return rc;
 }
@@ -64,8 +79,8 @@ agent_key_from_file (const unsigned char *grip)
   char *fname;
   FILE *fp;
   struct stat st;
-  char *buf;
-  size_t buflen, erroff;
+  unsigned char *buf;
+  size_t len, buflen, erroff;
   GCRY_SEXP s_skey;
   char hexgrip[41];
   
@@ -111,13 +126,35 @@ agent_key_from_file (const unsigned char *grip)
                  (unsigned int)erroff, gcry_strerror (rc));
       return NULL;
     }
+  len = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0);
+  assert (len);
+  buf = xtrymalloc (len);
+  if (!buf)
+    {
+      gcry_sexp_release (s_skey);
+      return NULL;
+    }
+  len = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, buf, len);
+  assert (len);
+  gcry_sexp_release (s_skey);
 
-  rc = unprotect (s_skey);
+  rc = unprotect (&buf);
   if (rc)
     {
-      gcry_sexp_release (s_skey);
       log_error ("failed to unprotect the secret key: %s\n",
                  gcry_strerror (rc));
+      xfree (buf);
+      return NULL;
+    }
+
+  /* arggg FIXME: does scna support secure memory? */
+  rc = gcry_sexp_sscan (&s_skey, &erroff,
+                        buf, gcry_sexp_canon_len (buf, 0, NULL, NULL));
+  xfree (buf);
+  if (rc)
+    {
+      log_error ("failed to build S-Exp (off=%u): %s\n",
+                 (unsigned int)erroff, gcry_strerror (rc));
       return NULL;
     }
 
index 166f460..2119bbb 100644 (file)
@@ -30,8 +30,9 @@
 
 #include "agent.h"
 
+
 static int
-store_key (GCRY_SEXP private)
+store_key (GCRY_SEXP private, const char *passphrase)
 {
   int i;
   char *fname;
@@ -58,9 +59,11 @@ store_key (GCRY_SEXP private)
       xfree (fname);
       return seterr (General_Error);
     }
-  fp = fopen (fname, "wbx");
-  if (!fp)
-    {
+  fp = fopen (fname, "wbx");  /* FIXME: the x is a GNU extension - let
+                                 configure check whether this actually
+                                 works */
+  if (!fp) 
+    { 
       log_error ("can't create `%s': %s\n", fname, strerror (errno));
       xfree (fname);
       return seterr (File_Create_Error);
@@ -79,6 +82,24 @@ store_key (GCRY_SEXP private)
   len = gcry_sexp_sprint (private, GCRYSEXP_FMT_CANON, buf, len);
   assert (len);
 
+  if (passphrase)
+    {
+      unsigned char *p;
+      int rc;
+
+      rc = agent_protect (buf, passphrase, &p, &len);
+      if (rc)
+        {
+          fclose (fp);
+          remove (fname);
+          xfree (fname);
+          xfree (buf);
+          return rc;
+        }
+      xfree (buf);
+      buf = p;
+    }
+
   if (fwrite (buf, len, 1, fp) != 1)
     {
       log_error ("error writing `%s': %s\n", fname, strerror (errno));
@@ -111,6 +132,7 @@ agent_genkey (CTRL ctrl, const char *keyparam, size_t keyparamlen,
               FILE *outfp) 
 {
   GCRY_SEXP s_keyparam, s_key, s_private, s_public;
+  struct pin_entry_info_s *pi, *pi2;
   int rc;
   size_t len;
   char *buf;
@@ -122,13 +144,48 @@ agent_genkey (CTRL ctrl, const char *keyparam, size_t keyparamlen,
       return seterr (Invalid_Data);
     }
 
-  /* fixme: Get the passphrase now, cause key generation may take a while */
+  /* Get the passphrase now, cause key generation may take a while */
+  {
+    const char *text1 = trans ("Please enter the passphrase to%0A"
+                               "to protect your new key");
+    const char *text2 = trans ("Please re-enter this passphrase");
+    const char *nomatch = trans ("does not match - try again");
+    int tries = 0;
+
+    pi = gcry_calloc_secure (2, sizeof (*pi) + 100);
+    pi2 = pi + sizeof *pi;
+    pi->max_length = 100;
+    pi->max_tries = 3;
+    pi2->max_length = 100;
+    pi2->max_tries = 3;
+
+    rc = agent_askpin (text1, NULL, pi);
+    if (!rc)
+      {
+        do 
+          {
+            rc = agent_askpin (text2, tries? nomatch:NULL, pi2);
+            tries++;
+          }
+        while (!rc && tries < 3 && strcmp (pi->pin, pi2->pin));
+        if (!rc && strcmp (pi->pin, pi2->pin))
+          rc = GNUPG_Canceled;
+      }
+    if (rc)
+      return rc;
+    if (!*pi->pin)
+      {
+        xfree (pi);
+        pi = NULL; /* use does not want a passphrase */
+      }
+  }
 
   rc = gcry_pk_genkey (&s_key, s_keyparam );
   gcry_sexp_release (s_keyparam);
   if (rc)
     {
       log_error ("key generation failed: %s\n", gcry_strerror (rc));
+      xfree (pi);
       return map_gcry_err (rc);
     }
 
@@ -138,6 +195,7 @@ agent_genkey (CTRL ctrl, const char *keyparam, size_t keyparamlen,
     {
       log_error ("key generation failed: invalid return value\n");
       gcry_sexp_release (s_key);
+      xfree (pi);
       return seterr (Invalid_Data);
     }
   s_public = gcry_sexp_find_token (s_key, "public-key", 0);
@@ -146,13 +204,15 @@ agent_genkey (CTRL ctrl, const char *keyparam, size_t keyparamlen,
       log_error ("key generation failed: invalid return value\n");
       gcry_sexp_release (s_private);
       gcry_sexp_release (s_key);
+      xfree (pi);
       return seterr (Invalid_Data);
     }
   gcry_sexp_release (s_key); s_key = NULL;
   
   /* store the secret key */
   log_debug ("storing private key\n");
-  rc = store_key (s_private);
+  rc = store_key (s_private, pi->pin);
+  xfree (pi); pi = NULL;
   gcry_sexp_release (s_private);
   if (rc)
     {
index 4f81f5b..ab2ad65 100644 (file)
@@ -6,7 +6,7 @@ Some notes on the format of the secret keys used with gpg-agent.
 
 
 The secret keys[1] are stored on a per file basis in a directory below
-the .gnupg home directory.  This directory is named
+the ~/.gnupg home directory.  This directory is named
 
    private-keys-v1.d
 
@@ -26,19 +26,15 @@ example of an unprotected file:
   (q #00f7a7c..[some bytes not shown]..61#)
   (u #304559a..[some bytes not shown]..9b#)
  )
+ (uri http://foo.bar x-foo:whatever_you_want)
 )
 
 Actually this form should not be used for regular purposes and only
 accepted by gpg-agent with the configuration option:
---allow-non-canonical-key-format.  
+--allow-non-canonical-key-format.  The regular way to represent the
+keys is in canonical representation[3]:
 
-The regular way to represent the keys is in canonical representation
-with the additional requirement of an extra object container around
-it[3]:
-
-(oid.1.3.6.1.4.1.11591.2.2.2
- (keyinfo human_readable_information_to_decribe_this_key)
- (private-key
+(private-key
    (rsa
     (n #00e0ce9..[some bytes not shown]..51#)
     (e #010001#)
@@ -47,76 +43,79 @@ it[3]:
     (q #00f7a7c..[some bytes not shown]..61#)
     (u #304559a..[some bytes not shown]..9b#)
    )
- )  
-)
+   (uri http://foo.bar x-foo:whatever_you_want)
+)  
+
 
 
 This describes an unprotected key; a protected key is like this:
 
-(oid.1.3.6.1.4.1.11591.2.2.3
- (keyinfo human_readable_information_to_decribe_this_key)
- (private-key
+(protected-private-key
    (rsa
     (n #00e0ce9..[some bytes not shown]..51#)
     (e #010001#)
-    (oid.1.3.6.1.4.1.11591.2.1.1.1 (parms) encrypted_octet_string)
+    (protected mode (parms) encrypted_octet_string)
    )
- )  
-)
+   (uri http://foo.bar x-foo:whatever_you_want)
+)  
+
 
 In this scheme the encrypted_octet_string is encrypted according to
-the scheme identifier by the OID,  most protection algorithms need
-some parameters, which are given in a list before the
+the algorithm described after the keyword protected; most protection
+algorithms need some parameters, which are given in a list before the
 encrypted_octet_string.  The result of the decryption process is a
 list of the secret key parameters.
 
-Defined protection methods are:
+The only available protection mode for now is
 
-1.3.6.1.4.1.gnu(11591).aegypten(2)
-.algorithms(1).keyprotection(1).s2k3-sha1-aes-cbc(1)
+  openpgp-s2k3-sha1-aes-cbc
 
-This uses AES in CBC mode for encryption, SHA-1 for integrity
-protection and the String to Key algorithm 3 from OpenPGP (rfc2440).
+which describesan algorithm using using AES in CBC mode for
+encryption, SHA-1 for integrity protection and the String to Key
+algorithm 3 from OpenPGP (rfc2440).
 
 Example:
 
-(oid.1.3.6.1.4.1.11591.2.1.1.1 
-  ((salt iterations) iv)
+(protected openpgp-s2k3-sha1-aes-cbc
+  ((sha1 16byte_salt no_of_iterations) 16byte_iv)
   encrypted_octet_string
 )
 
 The encrypted_octet string should yield this S-Exp (in canonical
 representation) after decryption:
 
-(sha1_hash
- (d #046129F..[some bytes not shown]..81#)
- (p #00e861b..[some bytes not shown]..f1#)
- (q #00f7a7c..[some bytes not shown]..61#)
- (u #304559a..[some bytes not shown]..9b#)
+(
+ (
+  (d #046129F..[some bytes not shown]..81#)
+  (p #00e861b..[some bytes not shown]..f1#)
+  (q #00f7a7c..[some bytes not shown]..61#)
+  (u #304559a..[some bytes not shown]..9b#) 
+ ) 
+ (hash sha1 #...[hashvalue]...#)
 )
 
 For padding reasons, random bytes are appended to this list - they can
 easily be stripped by looking for the end of the list.
 
-The first element is the SHA-1 hash calculated on the concatenation of the
-public key and secret key parameter lists: i.e one has to hash the
-concatenatiohn of these 6 canonical encoded lists for RSA, including
-the parenthesis.
+The hash is calculated on the concatenation of the public key and
+secret key parameter lists: i.e it is required to hash the
+concatenation of these 6 canonical encoded lists for RSA, including
+the parenthesis and the algorithm keyword.
 
+(rsa
  (n #00e0ce9..[some bytes not shown]..51#)
  (e #010001#)
  (d #046129F..[some bytes not shown]..81#)
  (p #00e861b..[some bytes not shown]..f1#)
  (q #00f7a7c..[some bytes not shown]..61#)
  (u #304559a..[some bytes not shown]..9b#)
-
+)
 
 After decryption the hash must be recalculated and compared against
 the stored one - If they don't match the integrity of the key is not
 given.
 
 
-TODO: write a more elaborated version.
 
 
 
diff --git a/agent/protect-tool.c b/agent/protect-tool.c
new file mode 100644 (file)
index 0000000..df58290
--- /dev/null
@@ -0,0 +1,355 @@
+/* protect-tool.c - A tool to text the secret key protection
+ *     Copyright (C) 2002 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
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <gcrypt.h>
+
+#define JNLIB_NEED_LOG_LOGV
+#include "agent.h"
+
+#define N_(a) a
+#define _(a) a
+
+
+enum cmd_and_opt_values 
+{ aNull = 0,
+  oVerbose       = 'v',
+  oArmor          = 'a',
+  oPassphrase     = 'P',
+
+  oProtect        = 'p',
+  oUnprotect      = 'u',
+  
+  oNoVerbose = 500,
+
+aTest };
+
+
+static int opt_armor;
+static const char *passphrase = "abc";
+
+static ARGPARSE_OPTS opts[] = {
+  
+  { 301, NULL, 0, N_("@Options:\n ") },
+
+  { oVerbose, "verbose",   0, "verbose" },
+  { oArmor,   "armor",     0, "write output in advanced format" },
+  { oPassphrase, "passphrase", 2, "|STRING| Use passphrase STRING" },
+  { oProtect, "protect",     256, "protect a private key"},
+  { oUnprotect, "unprotect", 256, "unprotect a private key"},
+
+  {0}
+};
+
+static const char *
+my_strusage (int level)
+{
+  const char *p;
+  switch (level)
+    {
+    case 11: p = "protect-tool (GnuPG)";
+      break;
+    case 13: p = VERSION; break;
+    case 17: p = PRINTABLE_OS_NAME; break;
+    case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\n");
+      break;
+    case 1:
+    case 40: p =  _("Usage: protect-tool [options] (-h for help)\n");
+      break;
+    case 41: p =  _("Syntax: protect-tool [options] [args]]\n"
+                    "INTERNAL USE ONLY!\n");
+    break;
+    
+    default: p = NULL;
+    }
+  return p;
+}
+
+
+
+static void
+i18n_init (void)
+{
+#ifdef USE_SIMPLE_GETTEXT
+    set_gettext_file( PACKAGE );
+#else
+#ifdef ENABLE_NLS
+    /* gtk_set_locale (); HMMM: We have not yet called gtk_init */
+    bindtextdomain( PACKAGE, GNUPG_LOCALEDIR );
+    textdomain( PACKAGE );
+#endif
+#endif
+}
+
+
+
+/* Used by gcry for logging */
+static void
+my_gcry_logger (void *dummy, int level, const char *fmt, va_list arg_ptr)
+{
+  /* translate the log levels */
+  switch (level)
+    {
+    case GCRY_LOG_CONT: level = JNLIB_LOG_CONT; break;
+    case GCRY_LOG_INFO: level = JNLIB_LOG_INFO; break;
+    case GCRY_LOG_WARN: level = JNLIB_LOG_WARN; break;
+    case GCRY_LOG_ERROR:level = JNLIB_LOG_ERROR; break;
+    case GCRY_LOG_FATAL:level = JNLIB_LOG_FATAL; break;
+    case GCRY_LOG_BUG:  level = JNLIB_LOG_BUG; break;
+    case GCRY_LOG_DEBUG:level = JNLIB_LOG_DEBUG; break;
+    default:            level = JNLIB_LOG_ERROR; break;  
+    }
+  log_logv (level, fmt, arg_ptr);
+}
+
+\f
+static unsigned char *
+make_canonical (const char *fname, const char *buf, size_t buflen)
+{
+  int rc;
+  size_t erroff, len;
+  GCRY_SEXP sexp;
+  unsigned char *result;
+
+  rc = gcry_sexp_sscan (&sexp, &erroff, buf, buflen);
+  if (rc)
+    {
+      log_error ("invalid S-Expression in `%s' (off=%u): %s\n",
+                 fname, (unsigned int)erroff, gcry_strerror (rc));
+      return NULL;
+    }
+  len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, NULL, 0);
+  assert (len);
+  result = xmalloc (len);
+  len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, result, len);
+  assert (len);
+  gcry_sexp_release (sexp);
+  return result;
+}
+
+static char *
+make_advanced (const unsigned char *buf, size_t buflen)
+{
+  int rc;
+  size_t erroff, len;
+  GCRY_SEXP sexp;
+  unsigned char *result;
+
+  rc = gcry_sexp_sscan (&sexp, &erroff, buf, buflen);
+  if (rc)
+    {
+      log_error ("invalid canonical S-Expression (off=%u): %s\n",
+                 (unsigned int)erroff, gcry_strerror (rc));
+      return NULL;
+    }
+  len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, NULL, 0);
+  assert (len);
+  result = xmalloc (len);
+  len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, result, len);
+  assert (len);
+  gcry_sexp_release (sexp);
+  return result;
+}
+
+
+static unsigned char *
+read_key (const char *fname)
+{
+  FILE *fp;
+  struct stat st;
+  char *buf;
+  size_t buflen;
+  unsigned char *key;
+  
+  fp = fopen (fname, "rb");
+  if (!fp)
+    {
+      log_error ("can't open `%s': %s\n", fname, strerror (errno));
+      return NULL;
+    }
+  
+  if (fstat (fileno(fp), &st))
+    {
+      log_error ("can't stat `%s': %s\n", fname, strerror (errno));
+      fclose (fp);
+      return NULL;
+    }
+
+  buflen = st.st_size;
+  buf = xmalloc (buflen+1);
+  if (fread (buf, buflen, 1, fp) != 1)
+    {
+      log_error ("error reading `%s': %s\n", fname, strerror (errno));
+      fclose (fp);
+      xfree (buf);
+      return NULL;
+    }
+  fclose (fp);
+
+  key = make_canonical (fname, buf, buflen);
+  xfree (buf);
+  return key;
+}
+
+
+\f
+static void
+read_and_protect (const char *fname)
+{
+  int  rc;
+  unsigned char *key;
+  unsigned char *result;
+  size_t resultlen;
+  
+  key = read_key (fname);
+  if (!key)
+    return;
+
+  rc = agent_protect (key, passphrase, &result, &resultlen);
+  xfree (key);
+  if (rc)
+    {
+      log_error ("protecting the key failed: %s\n", gnupg_strerror (rc));
+      return;
+    }
+  
+  if (opt_armor)
+    {
+      char *p = make_advanced (result, resultlen);
+      xfree (result);
+      if (!p)
+        return;
+      result = p;
+      resultlen = strlen (p);
+    }
+
+  fwrite (result, resultlen, 1, stdout);
+  xfree (result);
+}
+
+
+static void
+read_and_unprotect (const char *fname)
+{
+  int  rc;
+  unsigned char *key;
+  unsigned char *result;
+  size_t resultlen;
+  
+  key = read_key (fname);
+  if (!key)
+    return;
+
+  rc = agent_unprotect (key, passphrase, &result, &resultlen);
+  xfree (key);
+  if (rc)
+    {
+      log_error ("unprotecting the key failed: %s\n", gnupg_strerror (rc));
+      return;
+    }
+  
+  if (opt_armor)
+    {
+      char *p = make_advanced (result, resultlen);
+      xfree (result);
+      if (!p)
+        return;
+      result = p;
+      resultlen = strlen (p);
+    }
+
+  fwrite (result, resultlen, 1, stdout);
+  xfree (result);
+}
+
+
+
+int
+main (int argc, char **argv )
+{
+  ARGPARSE_ARGS pargs;
+  int cmd = 0;
+
+  set_strusage (my_strusage);
+  gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
+  log_set_prefix ("protect-tool", 1); 
+  i18n_init ();
+
+  if (!gcry_check_version ( "1.1.5" ) )
+    {
+      log_fatal( _("libgcrypt is too old (need %s, have %s)\n"),
+                 "1.1.5", gcry_check_version (NULL) );
+    }
+
+  gcry_set_log_handler (my_gcry_logger, NULL);
+  
+  gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
+
+  pargs.argc = &argc;
+  pargs.argv = &argv;
+  pargs.flags=  1;  /* do not remove the args */
+  while (arg_parse (&pargs, opts) )
+    {
+      switch (pargs.r_opt)
+        {
+        case oVerbose: opt.verbose++; break;
+        case oArmor:   opt_armor=1; break;
+
+        case oProtect: cmd = oProtect; break;
+        case oUnprotect: cmd = oUnprotect; break;
+
+        case oPassphrase: passphrase = pargs.r.ret_str; break;
+
+        default : pargs.err = 2; break;
+       }
+    }
+  if (log_get_errorcount(0))
+    exit(2);
+
+  if (argc != 1)
+    usage (1);
+
+  if (cmd == oProtect)
+    read_and_protect (*argv);
+  else if (cmd == oUnprotect)
+    read_and_unprotect (*argv);
+  else
+    log_info ("no action requested\n");
+
+  return 0;
+}
+
+void
+agent_exit (int rc)
+{
+  rc = rc? rc : log_get_errorcount(0)? 2 : 0;
+  exit (rc);
+}
diff --git a/agent/protect.c b/agent/protect.c
new file mode 100644 (file)
index 0000000..6b95dab
--- /dev/null
@@ -0,0 +1,861 @@
+/* protect.c - Un/Protect a secret key
+ * Copyright (C) 1998, 1999, 2000, 2001, 2002 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
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "agent.h"
+
+#define PROT_CIPHER        GCRY_CIPHER_AES
+#define PROT_CIPHER_STRING "aes"
+#define PROT_CIPHER_KEYLEN (128/8)
+
+
+/* A table containing the information needed to create a protected
+   private key */
+static struct {
+  const char *algo;
+  const char *parmlist;
+  int prot_from, prot_to;
+} protect_info[] = {
+  { "rsa",  "nedpqu", 2, 5 },
+  { NULL }
+};
+
+
+static int
+hash_passphrase (const char *passphrase, int hashalgo,
+                 int s2kmode,
+                 const unsigned char *s2ksalt, unsigned long s2kcount,
+                 unsigned char *key, size_t keylen);
+
+
+
+/* Return the length of the next S-Exp part and update the pointer to
+   the first data byte.  0 is return on error */
+static size_t
+snext (unsigned char const **buf)
+{
+  const unsigned char *s;
+  int n;
+
+  s = *buf;
+  for (n=0; *s && *s != ':' && digitp (s); s++)
+    n = n*10 + atoi_1 (s);
+  if (!n || *s != ':')
+    return 0; /* we don't allow empty lengths */
+  *buf = s+1;
+  return n;
+}
+
+/* Skip over the S-Expression BUF points to and update BUF to point to
+   the chacter right behind.  DEPTH gives the initial number of open
+   lists and may be passed as a positive number to skip over the
+   remainder of an S-Expression if the current position is somewhere
+   in an S-Expression.  The function may return an error code if it
+   encounters an impossible conditions */
+static int
+sskip (unsigned char const **buf, int *depth)
+{
+  const unsigned char *s = *buf;
+  size_t n;
+  int d = *depth;
+  
+  while (d > 0)
+    {
+      if (*s == '(')
+        {
+          d++;
+          s++;
+        }
+      else if (*s == ')')
+        {
+          d--;
+          s++;
+        }
+      else
+        {
+          if (!d)
+            return GNUPG_Invalid_Sexp;
+          n = snext (&s);
+          if (!n)
+            return GNUPG_Invalid_Sexp; 
+          s += n;
+        }
+    }
+  *buf = s;
+  *depth = d;
+  return 0;
+}
+
+
+/* Check whether the the string at the address BUF points to matches
+   the token.  Return true on match and update BUF to point behind the
+   token. */
+static int
+smatch (unsigned char const **buf, size_t buflen, const char *token)
+{
+  size_t toklen = strlen (token);
+
+  if (buflen != toklen || memcmp (*buf, token, toklen))
+    return 0;
+  *buf += toklen;
+  return 1;
+}
+
+
+\f
+/* Calculate the MIC for a private key S-Exp. SHA1HASH should pint to
+   a 20 byte buffer.  This function is suitable for any algorithms. */
+static int 
+calculate_mic (const unsigned char *plainkey, unsigned char *sha1hash)
+{
+  const unsigned char *hash_begin, *hash_end;
+  const unsigned char *s;
+  size_t n;
+
+  s = plainkey;
+  if (*s != '(')
+    return GNUPG_Invalid_Sexp;
+  s++;
+  n = snext (&s);
+  if (!n)
+    return GNUPG_Invalid_Sexp; 
+  if (!smatch (&s, n, "private-key"))
+    return GNUPG_Unknown_Sexp; 
+  if (*s != '(')
+    return GNUPG_Unknown_Sexp;
+  hash_begin = s;
+  s++;
+  n = snext (&s);
+  if (!n)
+    return GNUPG_Invalid_Sexp; 
+  s += n; /* skip over the algorithm name */
+
+  while (*s == '(')
+    {
+      s++;
+      n = snext (&s);
+      if (!n)
+        return GNUPG_Invalid_Sexp; 
+      s += n;
+      n = snext (&s);
+      if (!n)
+        return GNUPG_Invalid_Sexp; 
+      s += n;
+      if ( *s != ')' )
+        return GNUPG_Invalid_Sexp; 
+      s++;
+    }
+  if (*s != ')')
+    return GNUPG_Invalid_Sexp; 
+  s++;
+  hash_end = s;
+
+  gcry_md_hash_buffer (GCRY_MD_SHA1, sha1hash,
+                       hash_begin, hash_end - hash_begin);
+
+  return 0;
+}
+
+
+\f
+/* Encrypt the parameter block starting at PROTBEGIN with length
+   PROTLEN using the utf8 encoded key PASSPHRASE and return the entire
+   encrypted block in RESULT or ereturn with an error code.  SHA1HASH
+   is the 20 byte SHA-1 hash required for the integrity code.
+
+   The parameter block is expected to be an incomplete S-Expression of
+   the form (example in advanced format):
+
+     (d #046129F..[some bytes not shown]..81#)
+     (p #00e861b..[some bytes not shown]..f1#)
+     (q #00f7a7c..[some bytes not shown]..61#)
+     (u #304559a..[some bytes not shown]..9b#) 
+
+   the returned block is the S-Expression:
+
+    (protected mode (parms) encrypted_octet_string)
+
+*/
+static int
+do_encryption (const char *protbegin, size_t protlen, 
+               const char *passphrase,  const unsigned char *sha1hash,
+               unsigned char **result, size_t *resultlen)
+{
+  GCRY_CIPHER_HD hd;
+  const char *modestr = "openpgp-s2k3-sha1-" PROT_CIPHER_STRING "-cbc";
+  int blklen, enclen, outlen;
+  char *iv = NULL;
+  int rc = 0;
+  char *outbuf = NULL;
+  char *p;
+  int saltpos, ivpos, encpos;
+
+  hd = gcry_cipher_open (PROT_CIPHER, GCRY_CIPHER_MODE_CBC,
+                         GCRY_CIPHER_SECURE);
+  if (!hd)
+    return map_gcry_err (gcry_errno());
+
+
+  /* We need to work on a copy of the data because this makes it
+     easier to add the trailer and the padding and more important we
+     have to prefix the text with 2 parenthesis, so we have to
+     allocate enough space for:
+
+     ((<parameter_list>)(4:hash4:sha120:<hashvalue>)) + padding
+
+     We always append a full block of random bytes as padding but
+     encrypt only what is needed for a full blocksize */
+  blklen = gcry_cipher_get_algo_blklen (PROT_CIPHER);
+  outlen = 2 + protlen + 2 + 6 + 6 + 23 + 2 + blklen;
+  enclen = outlen/blklen * blklen;
+  outbuf = gcry_malloc_secure (outlen);
+  if (!outbuf)
+    rc = GNUPG_Out_Of_Core;
+  if (!rc)
+    {
+      /* allocate random bytes to be used as IV, padding and s2k salt*/
+      iv = gcry_random_bytes (blklen*2+8, GCRY_WEAK_RANDOM);
+      if (!iv)
+        rc = GNUPG_Out_Of_Core;
+      else
+        rc = gcry_cipher_setiv (hd, iv, blklen);
+    }
+  if (!rc)
+    {
+      unsigned char *key;
+      size_t keylen = PROT_CIPHER_KEYLEN;
+      
+      key = gcry_malloc_secure (keylen);
+      if (!key)
+        rc = GNUPG_Out_Of_Core;
+      else
+        {
+          rc = hash_passphrase (passphrase, GCRY_MD_SHA1,
+                                3, iv+2*blklen, 96, key, keylen);
+          if (!rc)
+            rc = gcry_cipher_setkey (hd, key, keylen);
+          xfree (key);
+        }
+    }
+  if (!rc)
+    {
+      p = outbuf;
+      *p++ = '(';
+      *p++ = '(';
+      memcpy (p, protbegin, protlen);
+      p += protlen;
+      memcpy (p, ")(4:hash4:sha120:", 17);
+      p += 17;
+      memcpy (p, sha1hash, 20);
+      p += 20;
+      *p++ = ')';
+      *p++ = ')';
+      memcpy (p, iv+blklen, blklen); 
+      p += blklen;
+      assert ( p - outbuf == outlen);
+      rc = gcry_cipher_encrypt (hd, outbuf, enclen, NULL, 0);
+    }
+  gcry_cipher_close (hd);
+  if (rc)
+    {
+      xfree (iv);
+      xfree (outbuf);
+      return rc;
+    }
+
+  /* Now allocate the buffer we want to return.  This is
+
+     (protected openpgp-s2k3-sha1-aes-cbc
+       ((sha1 salt no_of_iterations) 16byte_iv)
+       encrypted_octet_string)
+       
+     in canoncical format of course.  We use asprintf and %n modifier
+     and spaces as palceholders.  */
+  asprintf (&p,
+            "(9:protected%d:%s((4:sha18:%n_8bytes_2:96)%d:%n%*s)%d:%n%*s)",
+            (int)strlen (modestr), modestr,
+            &saltpos, 
+            blklen, &ivpos, blklen, "",
+            enclen, &encpos, enclen, "");
+  if (p)
+    { /* asprintf does not use out malloc system */
+      char *psave = p;
+      p = xtrymalloc (strlen (psave)+1);
+      if (p)
+        strcpy (p, psave);
+      free (psave);
+    }
+  if (!p)
+    {
+      xfree (iv);
+      xfree (outbuf);
+      return GNUPG_Out_Of_Core;
+    }
+  *resultlen = strlen (p);
+  *result = p;
+  memcpy (p+saltpos, iv+2*blklen, 8);
+  memcpy (p+ivpos, iv, blklen);
+  memcpy (p+encpos, outbuf, enclen);
+  xfree (iv);
+  xfree (outbuf);
+  return 0;
+}
+
+
+
+/* Protect the key encoded in canonical format in plainkey.  We assume
+   a valid S-Exp here. */
+int 
+agent_protect (const unsigned char *plainkey, const char *passphrase,
+               unsigned char **result, size_t *resultlen)
+{
+  int rc;
+  const unsigned char *s;
+  const unsigned char *hash_begin, *hash_end;
+  const unsigned char *prot_begin, *prot_end, *real_end;
+  size_t n;
+  int c, infidx, i;
+  unsigned char hashvalue[20];
+  unsigned char *protected;
+  size_t protectedlen;
+  int depth = 0;
+  unsigned char *p;
+
+  s = plainkey;
+  if (*s != '(')
+    return GNUPG_Invalid_Sexp;
+  depth++;
+  s++;
+  n = snext (&s);
+  if (!n)
+    return GNUPG_Invalid_Sexp; 
+  if (!smatch (&s, n, "private-key"))
+    return GNUPG_Unknown_Sexp; 
+  if (*s != '(')
+    return GNUPG_Unknown_Sexp;
+  depth++;
+  hash_begin = s;
+  s++;
+  n = snext (&s);
+  if (!n)
+    return GNUPG_Invalid_Sexp; 
+
+  for (infidx=0; protect_info[infidx].algo
+              && !smatch (&s, n, protect_info[infidx].algo); infidx++)
+    ;
+  if (!protect_info[infidx].algo)
+    return GNUPG_Unsupported_Algorithm; 
+
+  prot_begin = prot_end = NULL;
+  for (i=0; (c=protect_info[infidx].parmlist[i]); i++)
+    {
+      if (i == protect_info[infidx].prot_from)
+        prot_begin = s;
+      if (*s != '(')
+        return GNUPG_Invalid_Sexp;
+      depth++;
+      s++;
+      n = snext (&s);
+      if (!n)
+        return GNUPG_Invalid_Sexp; 
+      if (n != 1 || c != *s)
+        return GNUPG_Invalid_Sexp; 
+      s += n;
+      n = snext (&s);
+      if (!n)
+        return GNUPG_Invalid_Sexp; 
+      s +=n; /* skip value */
+      if (*s != ')')
+        return GNUPG_Invalid_Sexp; 
+      depth--;
+      if (i == protect_info[infidx].prot_to)
+        prot_end = s;
+      s++;
+    }
+  if (*s != ')' || !prot_begin || !prot_end )
+    return GNUPG_Invalid_Sexp; 
+  depth--;
+  hash_end = s;
+  s++;
+  /* skip to the end of the S-exp */
+  assert (depth == 1);
+  rc = sskip (&s, &depth);
+  if (rc)
+    return rc;
+  assert (!depth);
+  real_end = s-1;
+
+  gcry_md_hash_buffer (GCRY_MD_SHA1, hashvalue,
+                       hash_begin, hash_end - hash_begin + 1);
+
+  rc = do_encryption (prot_begin, prot_end - prot_begin + 1,
+                      passphrase,  hashvalue,
+                      &protected, &protectedlen);
+  if (rc)
+    return rc;
+
+  /* Now create the protected version of the key.  Note that the 10
+     extra bytes are for for the inserted "protected-" string (the
+     beginning of the plaintext reads: "((11:private-key(" ). */
+  *resultlen = (10
+                + (prot_begin-plainkey)
+                + protectedlen
+                + (real_end-prot_end));
+  *result = p = xtrymalloc (*resultlen);
+  if (!p)
+    {
+      xfree (protected);
+      return GNUPG_Out_Of_Core;
+    }
+  memcpy (p, "(21:protected-", 14);
+  p += 14;
+  memcpy (p, plainkey+4, prot_begin - plainkey - 4);
+  p += prot_begin - plainkey - 4;
+  memcpy (p, protected, protectedlen);
+  p += protectedlen;
+  memcpy (p, prot_end+1, real_end - prot_end);
+  p += real_end - prot_end;
+  assert ( p - *result == *resultlen);
+  xfree (protected);
+  return 0;
+}
+
+\f
+/* Do the actual decryption and check the return list for consistency.  */
+static int
+do_decryption (const unsigned char *protected, size_t protectedlen, 
+               const char *passphrase, 
+               const unsigned char *s2ksalt, unsigned long s2kcount,
+               const unsigned char *iv, size_t ivlen,
+               unsigned char **result)
+{
+  int rc = 0;
+  int blklen;
+  GCRY_CIPHER_HD hd;
+  unsigned char *outbuf;
+  size_t reallen;
+
+  blklen = gcry_cipher_get_algo_blklen (PROT_CIPHER);
+  if (protectedlen < 4 || (protectedlen%blklen))
+    return GNUPG_Corrupted_Protection;
+
+  hd = gcry_cipher_open (PROT_CIPHER, GCRY_CIPHER_MODE_CBC,
+                         GCRY_CIPHER_SECURE);
+  if (!hd)
+    return map_gcry_err (gcry_errno());
+
+  outbuf = gcry_malloc_secure (protectedlen);
+  if (!outbuf)
+    rc = GNUPG_Out_Of_Core;
+  if (!rc)
+    rc = gcry_cipher_setiv (hd, iv, ivlen);
+  if (!rc)
+    {
+      unsigned char *key;
+      size_t keylen = PROT_CIPHER_KEYLEN;
+      
+      key = gcry_malloc_secure (keylen);
+      if (!key)
+        rc = GNUPG_Out_Of_Core;
+      else
+        {
+          rc = hash_passphrase (passphrase, GCRY_MD_SHA1,
+                                3, s2ksalt, s2kcount, key, keylen);
+          if (!rc)
+            rc = gcry_cipher_setkey (hd, key, keylen);
+          xfree (key);
+        }
+    }
+  if (!rc)
+    rc = gcry_cipher_decrypt (hd, outbuf, protectedlen,
+                              protected, protectedlen);
+  gcry_cipher_close (hd);
+  if (rc)
+    {
+      xfree (outbuf);
+      return rc;
+    }
+  /* do a quick check first */
+  if (*outbuf != '(' && outbuf[1] != '(')
+    {
+      xfree (outbuf);
+      return GNUPG_Bad_Passphrase;
+    }
+  /* check that we have a consistent S-Exp */
+  reallen = gcry_sexp_canon_len (outbuf, protectedlen, NULL, NULL);
+  if (!reallen || (reallen + blklen < protectedlen) )
+    {
+      xfree (outbuf);
+      return GNUPG_Bad_Passphrase;
+    }
+  *result = outbuf;
+  return 0;
+}
+
+
+/* Merge the parameter list contained in CLEARTEXT with the original
+   protect lists PROTECTEDKEY by replacing the list at REPLACEPOS.
+   Return the new list in RESULT and the MIC value in the 20 byte
+   buffer SHA1HASH. */
+static int
+merge_lists (const unsigned char *protectedkey,
+             size_t replacepos, 
+             const unsigned char *cleartext,
+             unsigned char *sha1hash, unsigned char **result)
+{
+  size_t n, newlistlen;
+  unsigned char *newlist, *p;
+  const unsigned char *s;
+  const unsigned char *startpos, *endpos;
+  int i, rc;
+  
+  if (replacepos < 26)
+    return GNUPG_Bug;
+
+  /* Estimate the required size of the resulting list.  We have a large
+     safety margin of >20 bytes (MIC hash from CLEARTEXT and the
+     removed "protected-" */
+  newlistlen = gcry_sexp_canon_len (protectedkey, 0, NULL, NULL);
+  if (!newlistlen)
+    return GNUPG_Bug;
+  n = gcry_sexp_canon_len (cleartext, 0, NULL, NULL);
+  if (!n)
+    return GNUPG_Bug;
+  newlistlen += n;
+  newlist = gcry_malloc_secure (newlistlen);
+  if (!newlist)
+    return GNUPG_Out_Of_Core;
+
+  /* Copy the initial segment */
+  strcpy (newlist, "(11:private-key");
+  p = newlist + 15;
+  memcpy (p, protectedkey+15+10, replacepos-15-10);
+  p += replacepos-15-10;
+
+  /* copy the cleartext */
+  s = cleartext;
+  if (*s != '(' && s[1] != '(')
+    return GNUPG_Bug;  /*we already checked this */
+  s += 2;
+  startpos = s;
+  while ( *s == '(' )
+    {
+      s++;
+      n = snext (&s);
+      if (!n)
+        goto invalid_sexp;
+      s += n;
+      n = snext (&s);
+      if (!n)
+        goto invalid_sexp;
+      s += n;
+      if ( *s != ')' )
+        goto invalid_sexp;
+      s++;
+    }
+  if ( *s != ')' )
+    goto invalid_sexp;
+  endpos = s;
+  s++;
+  /* short intermezzo: Get the MIC */
+  if (*s != '(')
+    goto invalid_sexp;
+  s++;
+  n = snext (&s);
+  if (!smatch (&s, n, "hash"))
+    goto invalid_sexp;
+  n = snext (&s);
+  if (!smatch (&s, n, "sha1"))
+    goto invalid_sexp; 
+  n = snext (&s);
+  if (n != 20)
+    goto invalid_sexp;
+  memcpy (sha1hash, s, 20);
+  s += n;
+  if (*s != ')')
+    goto invalid_sexp;
+  /* end intermezzo */
+
+  /* append the parameter list */
+  memcpy (p, startpos, endpos - startpos);
+  p += endpos - startpos;
+  
+  /* skip overt the protected list element in the original list */
+  s = protectedkey + replacepos;
+  assert (*s == '(');
+  s++;
+  i = 1;
+  rc = sskip (&s, &i);
+  if (rc)
+    goto failure;
+  startpos = s;
+  i = 2; /* we are inside this level */
+  rc = sskip (&s, &i);
+  if (rc)
+    goto failure;
+  assert (s[-1] == ')');
+  endpos = s; /* one behind the end of the list */
+
+  /* append the rest */
+  memcpy (p, startpos, endpos - startpos);
+  p += endpos - startpos;
+
+  /* ready */
+  *result = newlist;
+  return 0;
+
+ failure:
+  xfree (newlist);
+  return rc;
+
+ invalid_sexp:
+  xfree (newlist);
+  return GNUPG_Invalid_Sexp;
+}
+
+
+
+/* Unprotect the key encoded in canonical format.  We assume a valid
+   S-Exp here. */
+int 
+agent_unprotect (const unsigned char *protectedkey, const char *passphrase,
+                 unsigned char **result, size_t *resultlen)
+{
+  int rc;
+  const unsigned char *s;
+  size_t n;
+  int infidx, i;
+  unsigned char sha1hash[20], sha1hash2[20];
+  const unsigned char *s2ksalt;
+  unsigned long s2kcount;
+  const unsigned char *iv;
+  const unsigned char *prot_begin;
+  unsigned char *cleartext;
+  unsigned char *final;
+
+  s = protectedkey;
+  if (*s != '(')
+    return GNUPG_Invalid_Sexp;
+  s++;
+  n = snext (&s);
+  if (!n)
+    return GNUPG_Invalid_Sexp; 
+  if (!smatch (&s, n, "protected-private-key"))
+    return GNUPG_Unknown_Sexp; 
+  if (*s != '(')
+    return GNUPG_Unknown_Sexp;
+  s++;
+  n = snext (&s);
+  if (!n)
+    return GNUPG_Invalid_Sexp; 
+
+  for (infidx=0; protect_info[infidx].algo
+              && !smatch (&s, n, protect_info[infidx].algo); infidx++)
+    ;
+  if (!protect_info[infidx].algo)
+    return GNUPG_Unsupported_Algorithm; 
+
+  /* now find the list with the protected information.  Here is an
+     example for such a list:
+     (protected openpgp-s2k3-sha1-aes-cbc 
+        ((sha1 <salt> <count>) <Initialization_Vector>)
+        <encrypted_data>)
+   */
+  for (;;)
+    {
+      if (*s != '(')
+        return GNUPG_Invalid_Sexp;
+      prot_begin = s;
+      s++;
+      n = snext (&s);
+      if (!n)
+        return GNUPG_Invalid_Sexp; 
+      if (smatch (&s, n, "protected"))
+        break;
+      s += n;
+      i = 1;
+      rc = sskip (&s, &i);
+      if (rc)
+        return rc;
+    }
+  /* found */
+  n = snext (&s);
+  if (!n)
+    return GNUPG_Invalid_Sexp; 
+  if (!smatch (&s, n, "openpgp-s2k3-sha1-" PROT_CIPHER_STRING "-cbc"))
+    return GNUPG_Unsupported_Protection;
+  if (*s != '(' || s[1] != '(')
+    return GNUPG_Invalid_Sexp;
+  s += 2;
+  n = snext (&s);
+  if (!n)
+    return GNUPG_Invalid_Sexp; 
+  if (!smatch (&s, n, "sha1"))
+    return GNUPG_Unsupported_Protection;
+  n = snext (&s);
+  if (n != 8)
+    return GNUPG_Corrupted_Protection;
+  s2ksalt = s;
+  s += n;
+  n = snext (&s);
+  if (!n)
+    return GNUPG_Corrupted_Protection;
+  /* We expect a list close as next, so we can simply use strtoul()
+     here.  We might want to check that we only have digits - but this
+     is nothing we should worry about */
+  if (s[n] != ')' )
+    return GNUPG_Invalid_Sexp;
+  s2kcount = strtoul (s, NULL, 10);
+  if (!s2kcount)
+    return GNUPG_Corrupted_Protection;
+  s += n;
+  s++; /* skip list end */
+
+  n = snext (&s);
+  if (n != 16) /* Wrong blocksize for IV (we support ony aes-128) */
+    return GNUPG_Corrupted_Protection;
+  iv = s;
+  s += n;
+  if (*s != ')' )
+    return GNUPG_Invalid_Sexp;
+  s++;
+  n = snext (&s);
+  if (!n)
+    return GNUPG_Invalid_Sexp; 
+  
+  rc = do_decryption (s, n,
+                      passphrase, s2ksalt, s2kcount,
+                      iv, 16,
+                      &cleartext);
+  if (rc)
+    return rc;
+
+  rc = merge_lists (protectedkey, prot_begin-protectedkey, cleartext,
+                    sha1hash, &final);
+  xfree (cleartext);
+  if (rc)
+    return rc;
+
+  rc = calculate_mic (final, sha1hash2);
+  if (!rc && memcmp (sha1hash, sha1hash2, 20))
+    rc = GNUPG_Corrupted_Protection;
+  if (rc)
+    {
+      xfree (final);
+      return rc;
+    }
+
+  *result = final;
+  *resultlen = gcry_sexp_canon_len (final, 0, NULL, NULL);
+  return 0;
+}
+
+
+
+\f
+/* Transform a passphrase into a suitable key of length KEYLEN and
+   store this key in the caller provided buffer KEY.  The caller must
+   provide an HASHALGO, a valid S2KMODE (see rfc-2440) and depending on
+   that mode an S2KSALT of 8 random bytes and an S2KCOUNT (a suitable
+   value is 96).
+  
+   Returns an error code on failure.  */
+static int
+hash_passphrase (const char *passphrase, int hashalgo,
+                 int s2kmode,
+                 const unsigned char *s2ksalt,
+                 unsigned long s2kcount,
+                 unsigned char *key, size_t keylen)
+{
+  GCRY_MD_HD md;
+  int pass, i;
+  int used = 0;
+  int pwlen = strlen (passphrase);
+
+  if ( (s2kmode != 0 && s2kmode != 1 && s2kmode != 3)
+      || !hashalgo || !keylen || !key || !passphrase)
+    return GNUPG_Invalid_Value;
+  if ((s2kmode == 1 ||s2kmode == 3) && !s2ksalt)
+    return GNUPG_Invalid_Value;
+  
+  md = gcry_md_open (hashalgo, GCRY_MD_FLAG_SECURE);
+  if (!md)
+    return map_gcry_err (gcry_errno());
+
+  for (pass=0; used < keylen; pass++)
+    {
+      if (pass)
+        {
+          gcry_md_reset (md);
+          for (i=0; i < pass; i++) /* preset the hash context */
+            gcry_md_putc (md, 0);
+       }
+
+      if (s2kmode == 1 || s2kmode == 3)
+        {
+          int len2 = pwlen + 8;
+          unsigned long count = len2;
+
+          if (s2kmode == 3)
+            {
+              count = (16ul + (s2kcount & 15)) << ((s2kcount >> 4) + 6);
+              if (count < len2)
+                count = len2;
+            }
+
+          while (count > len2)
+            {
+              gcry_md_write (md, s2ksalt, 8);
+              gcry_md_write (md, passphrase, pwlen);
+              count -= len2;
+            }
+          if (count < 8)
+            gcry_md_write (md, s2ksalt, count);
+          else 
+            {
+              gcry_md_write (md, s2ksalt, 8);
+              count -= 8;
+              gcry_md_write (md, passphrase, count);
+            }
+        }
+      else
+        gcry_md_write (md, passphrase, pwlen);
+      
+      gcry_md_final (md);
+      i = gcry_md_get_algo_dlen (hashalgo);
+      if (i > keylen - used)
+        i = keylen - used;
+      memcpy  (key+used, gcry_md_read (md, hashalgo), i);
+      used += i;
+    }
+  gcry_md_close(md);
+  return 0;
+}
+
+
index fcee18c..3b8cd08 100644 (file)
@@ -137,13 +137,13 @@ all_digitsp( const char *s)
    number here and repeat it as long as we have invalid formed
    numbers. */
 int
-agent_askpin (const char *desc_text,
+agent_askpin (const char *desc_text, const char *start_err_text,
               struct pin_entry_info_s *pininfo)
 {
   int rc;
   char line[ASSUAN_LINELENGTH];
   struct entry_parm_s parm;
-  const char *errtext = NULL;
+  const char *errtext = start_err_text;
 
   if (opt.batch)
     return 0; /* fixme: we should return BAD PIN */
@@ -180,8 +180,14 @@ agent_askpin (const char *desc_text,
       if (errtext)
         { 
           /* fixme: should we show the try count? It must be translated */
-          snprintf (line, DIM(line)-1, "SETERROR %s (try %d of %d)",
-                    errtext, pininfo->failed_tries+1, pininfo->max_tries);
+          if (start_err_text)
+            {
+              snprintf (line, DIM(line)-1, "SETERROR %s", errtext);
+              start_err_text = NULL;
+            }
+          else
+            snprintf (line, DIM(line)-1, "SETERROR %s (try %d of %d)",
+                      errtext, pininfo->failed_tries+1, pininfo->max_tries);
           line[DIM(line)-1] = 0;
           rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL);
           if (rc)