aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWerner Koch <[email protected]>2002-01-31 16:38:45 +0000
committerWerner Koch <[email protected]>2002-01-31 16:38:45 +0000
commit7d9ed16fe6bfbb80755b2dba983e58435088bc63 (patch)
tree76ff1323a1d60163dca3d2de431eaa3cbeac9e64
parentA few test certificates (diff)
downloadgnupg-7d9ed16fe6bfbb80755b2dba983e58435088bc63.tar.gz
gnupg-7d9ed16fe6bfbb80755b2dba983e58435088bc63.zip
* 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.
-rw-r--r--agent/ChangeLog13
-rw-r--r--agent/Makefile.am8
-rw-r--r--agent/agent.h12
-rw-r--r--agent/cache.c4
-rw-r--r--agent/findkey.c61
-rw-r--r--agent/genkey.c72
-rw-r--r--agent/keyformat.txt73
-rw-r--r--agent/protect-tool.c355
-rw-r--r--agent/protect.c861
-rw-r--r--agent/query.c14
10 files changed, 1409 insertions, 64 deletions
diff --git a/agent/ChangeLog b/agent/ChangeLog
index 9a42b4053..21f496628 100644
--- a/agent/ChangeLog
+++ b/agent/ChangeLog
@@ -1,3 +1,16 @@
+2002-01-31 Werner Koch <[email protected]>
+
+ * 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 <[email protected]>
+
+ * protect.c: New.
+ (hash_passphrase): Based on the GnuPG 1.0.6 version.
+ * protect-tool.c: New
+
2002-01-29 Werner Koch <[email protected]>
* findkey.c (agent_key_available): New.
diff --git a/agent/Makefile.am b/agent/Makefile.am
index 013862e48..eb5fa7d9d 100644
--- a/agent/Makefile.am
+++ b/agent/Makefile.am
@@ -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)
diff --git a/agent/agent.h b/agent/agent.h
index d999bdad8..955dd1ce2 100644
--- a/agent/agent.h
+++ b/agent/agent.h
@@ -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);
diff --git a/agent/cache.c b/agent/cache.c
index 96213712d..aa7a21b16 100644
--- a/agent/cache.c
+++ b/agent/cache.c
@@ -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;
diff --git a/agent/findkey.c b/agent/findkey.c
index 50f832be6..097903340 100644
--- a/agent/findkey.c
+++ b/agent/findkey.c
@@ -31,24 +31,39 @@
#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;
}
diff --git a/agent/genkey.c b/agent/genkey.c
index 166f4605a..2119bbb44 100644
--- a/agent/genkey.c
+++ b/agent/genkey.c
@@ -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)
{
diff --git a/agent/keyformat.txt b/agent/keyformat.txt
index 4f81f5b1d..ab2ad65fd 100644
--- a/agent/keyformat.txt
+++ b/agent/keyformat.txt
@@ -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
index 000000000..df58290ed
--- /dev/null
+++ b/agent/protect-tool.c
@@ -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);
+}
+
+
+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;
+}
+
+
+
+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
index 000000000..6b95dabfa
--- /dev/null
+++ b/agent/protect.c
@@ -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;
+}
+
+
+
+/* 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;
+}
+
+
+
+/* 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;
+}
+
+
+/* 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;
+}
+
+
+
+
+/* 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;
+}
+
+
diff --git a/agent/query.c b/agent/query.c
index fcee18c2a..3b8cd08df 100644
--- a/agent/query.c
+++ b/agent/query.c
@@ -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)