aboutsummaryrefslogtreecommitdiffstats
path: root/agent
diff options
context:
space:
mode:
authorWerner Koch <[email protected]>2010-08-31 15:58:39 +0000
committerWerner Koch <[email protected]>2010-08-31 15:58:39 +0000
commit87fac9911241310a4b601e126fa2e26b10bd370f (patch)
tree49e09cc881b44a1dba0e9474040cda1d5f9ce581 /agent
parentFix for W32. (diff)
downloadgnupg-87fac9911241310a4b601e126fa2e26b10bd370f.tar.gz
gnupg-87fac9911241310a4b601e126fa2e26b10bd370f.zip
Import OpenPGP keys into the agent.
Diffstat (limited to 'agent')
-rw-r--r--agent/ChangeLog15
-rw-r--r--agent/Makefile.am1
-rw-r--r--agent/agent.h9
-rw-r--r--agent/command.c80
-rw-r--r--agent/cvt-openpgp.c807
-rw-r--r--agent/cvt-openpgp.h27
-rw-r--r--agent/findkey.c10
-rw-r--r--agent/keyformat.txt29
-rw-r--r--agent/pksign.c26
-rw-r--r--agent/protect.c15
10 files changed, 992 insertions, 27 deletions
diff --git a/agent/ChangeLog b/agent/ChangeLog
index 0c31fc5b7..e584005ff 100644
--- a/agent/ChangeLog
+++ b/agent/ChangeLog
@@ -1,3 +1,18 @@
+2010-08-31 Werner Koch <[email protected]>
+
+ * pksign.c (do_encode_dsa): Fix sign problem.
+ * findkey.c (agent_is_dsa_key): Adjust to actual usage.
+
+2010-08-30 Werner Koch <[email protected]>
+
+ * protect.c (s2k_hash_passphrase): New public function.
+
+2010-08-27 Werner Koch <[email protected]>
+
+ * command.c (cmd_import_key): Support OpenPGP keys.
+ * cvt-openpgp.h, cvt-openpgp.c: New. Some of the code is based on
+ code taken from g10/seckey-cert.c.
+
2010-08-26 Werner Koch <[email protected]>
* command-ssh.c (open_control_file): Use estream to create the file.
diff --git a/agent/Makefile.am b/agent/Makefile.am
index abd39bed8..9c58627e6 100644
--- a/agent/Makefile.am
+++ b/agent/Makefile.am
@@ -46,6 +46,7 @@ gpg_agent_SOURCES = \
protect.c \
trustlist.c \
divert-scd.c \
+ cvt-openpgp.c cvt-openpgp.h \
call-scd.c \
learncard.c
diff --git a/agent/agent.h b/agent/agent.h
index b39f2325c..57078ab40 100644
--- a/agent/agent.h
+++ b/agent/agent.h
@@ -171,8 +171,8 @@ struct pin_entry_info_s
int with_qualitybar; /* Set if the quality bar should be displayed. */
int (*check_cb)(struct pin_entry_info_s *); /* CB used to check the PIN */
void *check_cb_arg; /* optional argument which might be of use in the CB */
- const char *cb_errtext; /* used by the cb to displaye a specific error */
- size_t max_length; /* allocated length of the buffer */
+ const char *cb_errtext; /* used by the cb to display a specific error */
+ size_t max_length; /* allocated length of the buffer */
char pin[1];
};
@@ -306,6 +306,11 @@ int agent_get_shadow_info (const unsigned char *shadowkey,
unsigned char const **shadow_info);
gpg_error_t parse_shadow_info (const unsigned char *shadow_info,
char **r_hexsn, char **r_idstr);
+gpg_error_t s2k_hash_passphrase (const char *passphrase, int hashalgo,
+ int s2kmode,
+ const unsigned char *s2ksalt,
+ unsigned int s2kcount,
+ unsigned char *key, size_t keylen);
/*-- trustlist.c --*/
diff --git a/agent/command.c b/agent/command.c
index 3a3b61ba8..4b847e503 100644
--- a/agent/command.c
+++ b/agent/command.c
@@ -37,7 +37,7 @@
#include "agent.h"
#include <assuan.h>
#include "i18n.h"
-
+#include "cvt-openpgp.h"
/* Maximum allowed size of the inquired ciphertext. */
@@ -357,6 +357,12 @@ leave_cmd (assuan_context_t ctx, gpg_error_t err)
const char *name = assuan_get_command_name (ctx);
if (!name)
name = "?";
+
+ /* Most code from common/ does not know the error source, thus
+ we fix this here. */
+ if (gpg_err_source (err) == GPG_ERR_SOURCE_UNKNOWN)
+ err = gpg_err_make (GPG_ERR_SOURCE_DEFAULT, gpg_err_code (err));
+
if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT)
log_error ("command '%s' failed: %s\n", name,
gpg_strerror (err));
@@ -566,8 +572,8 @@ cmd_sigkey (assuan_context_t ctx, char *line)
static const char hlp_setkeydesc[] =
"SETKEYDESC plus_percent_escaped_string\n"
"\n"
- "Set a description to be used for the next PKSIGN, PKDECRYPT or EXPORT_KEY\n"
- "operation if this operation requires the entry of a passphrase. If\n"
+ "Set a description to be used for the next PKSIGN, PKDECRYPT, IMPORT_KEY\n"
+ "or EXPORT_KEY operation if this operation requires a passphrase. If\n"
"this command is not used a default text will be used. Note, that\n"
"this description implictly selects the label used for the entry\n"
"box; if the string contains the string PIN (which in general will\n"
@@ -575,8 +581,8 @@ static const char hlp_setkeydesc[] =
"\"passphrase\" is used. The description string should not contain\n"
"blanks unless they are percent or '+' escaped.\n"
"\n"
- "The description is only valid for the next PKSIGN, PKDECRYPT or\n"
- "EXPORT_KEY operation.";
+ "The description is only valid for the next PKSIGN, PKDECRYPT,\n"
+ "IMPORT_KEY or EXPORT_KEY operation.";
static gpg_error_t
cmd_setkeydesc (assuan_context_t ctx, char *line)
{
@@ -1478,6 +1484,7 @@ cmd_import_key (assuan_context_t ctx, char *line)
unsigned char *finalkey = NULL;
size_t finalkeylen;
unsigned char grip[20];
+ gcry_sexp_t openpgp_sexp = NULL;
(void)line;
@@ -1528,20 +1535,58 @@ cmd_import_key (assuan_context_t ctx, char *line)
err = keygrip_from_canon_sexp (key, realkeylen, grip);
if (err)
- goto leave;
-
- if (!agent_key_available (grip))
{
- err = gpg_error (GPG_ERR_EEXIST);
- goto leave;
+ /* This might be due to an unsupported S-expression format.
+ Check whether this is openpgp-private-key and trigger that
+ import code. */
+ if (!gcry_sexp_sscan (&openpgp_sexp, NULL, key, realkeylen))
+ {
+ const char *tag;
+ size_t taglen;
+
+ tag = gcry_sexp_nth_data (openpgp_sexp, 0, &taglen);
+ if (tag && taglen == 19 && !memcmp (tag, "openpgp-private-key", 19))
+ ;
+ else
+ {
+ gcry_sexp_release (openpgp_sexp);
+ openpgp_sexp = NULL;
+ }
+ }
+ if (!openpgp_sexp)
+ goto leave; /* Note that ERR is still set. */
}
- err = agent_ask_new_passphrase
- (ctrl, _("Please enter the passphrase to protect the "
- "imported object within the GnuPG system."),
- &passphrase);
- if (err)
- goto leave;
+
+ if (openpgp_sexp)
+ {
+ /* In most cases the key is encrypted and thus the conversion
+ function from the OpenPGP format to our internal format will
+ ask for a passphrase. That passphrase will be returned and
+ used to protect the key using the same code as for regular
+ key import. */
+
+ err = convert_openpgp (ctrl, openpgp_sexp, grip,
+ ctrl->server_local->keydesc,
+ &key, &passphrase);
+ if (err)
+ goto leave;
+ realkeylen = gcry_sexp_canon_len (key, keylen, NULL, &err);
+ if (!realkeylen)
+ goto leave; /* Invalid canonical encoded S-expression. */
+ }
+ else
+ {
+ if (!agent_key_available (grip))
+ err = gpg_error (GPG_ERR_EEXIST);
+ else
+ err = agent_ask_new_passphrase
+ (ctrl, _("Please enter the passphrase to protect the "
+ "imported object within the GnuPG system."),
+ &passphrase);
+ if (err)
+ goto leave;
+ }
if (passphrase)
{
@@ -1553,11 +1598,14 @@ cmd_import_key (assuan_context_t ctx, char *line)
err = agent_write_private_key (grip, key, realkeylen, 0);
leave:
+ gcry_sexp_release (openpgp_sexp);
xfree (finalkey);
xfree (passphrase);
xfree (key);
gcry_cipher_close (cipherhd);
xfree (wrappedkey);
+ xfree (ctrl->server_local->keydesc);
+ ctrl->server_local->keydesc = NULL;
return leave_cmd (ctx, err);
}
diff --git a/agent/cvt-openpgp.c b/agent/cvt-openpgp.c
new file mode 100644
index 000000000..a392518ba
--- /dev/null
+++ b/agent/cvt-openpgp.c
@@ -0,0 +1,807 @@
+/* cvt-openpgp.c - Convert an OpenPGP key to our internal format.
+ * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2006, 2009,
+ * 2010 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "agent.h"
+#include "i18n.h"
+#include "cvt-openpgp.h"
+
+
+/* Helper to pass data via the callback to do_unprotect. */
+struct try_do_unprotect_arg_s
+{
+ int is_v4;
+ int is_protected;
+ int pubkey_algo;
+ int protect_algo;
+ char *iv;
+ int ivlen;
+ int s2k_mode;
+ int s2k_algo;
+ byte *s2k_salt;
+ u32 s2k_count;
+ u16 desired_csum;
+ gcry_mpi_t *skey;
+ size_t skeysize;
+ int skeyidx;
+ gcry_sexp_t *r_key;
+};
+
+
+
+/* Compute the keygrip from the public key and store it at GRIP. */
+static gpg_error_t
+get_keygrip (int pubkey_algo, gcry_mpi_t *pkey, unsigned char *grip)
+{
+ gpg_error_t err;
+ gcry_sexp_t s_pkey = NULL;
+
+ switch (pubkey_algo)
+ {
+ case GCRY_PK_DSA:
+ err = gcry_sexp_build (&s_pkey, NULL,
+ "(public-key(dsa(p%m)(q%m)(g%m)(y%m)))",
+ pkey[0], pkey[1], pkey[2], pkey[3]);
+ break;
+
+ case GCRY_PK_ELG:
+ case GCRY_PK_ELG_E:
+ err = gcry_sexp_build (&s_pkey, NULL,
+ "(public-key(elg(p%m)(g%m)(y%m)))",
+ pkey[0], pkey[1], pkey[2]);
+ break;
+
+ case GCRY_PK_RSA:
+ case GCRY_PK_RSA_E:
+ case GCRY_PK_RSA_S:
+ err = gcry_sexp_build (&s_pkey, NULL,
+ "(public-key(rsa(n%m)(e%m)))", pkey[0], pkey[1]);
+ break;
+
+ default:
+ err = gpg_error (GPG_ERR_PUBKEY_ALGO);
+ break;
+ }
+
+ if (!err && !gcry_pk_get_keygrip (s_pkey, grip))
+ err = gpg_error (GPG_ERR_INTERNAL);
+
+ gcry_sexp_release (s_pkey);
+ return err;
+}
+
+
+/* Convert a secret key given as algorithm id and an array of key
+ parameters into our s-expression based format. */
+static gpg_error_t
+convert_secret_key (gcry_sexp_t *r_key, int pubkey_algo, gcry_mpi_t *skey)
+{
+ gpg_error_t err;
+ gcry_sexp_t s_skey = NULL;
+
+ *r_key = NULL;
+
+ switch (pubkey_algo)
+ {
+ case GCRY_PK_DSA:
+ err = gcry_sexp_build (&s_skey, NULL,
+ "(private-key(dsa(p%m)(q%m)(g%m)(y%m)(x%m)))",
+ skey[0], skey[1], skey[2], skey[3], skey[4]);
+ break;
+
+ case GCRY_PK_ELG:
+ case GCRY_PK_ELG_E:
+ err = gcry_sexp_build (&s_skey, NULL,
+ "(private-key(elg(p%m)(g%m)(y%m)(x%m)))",
+ skey[0], skey[1], skey[2], skey[3]);
+ break;
+
+
+ case GCRY_PK_RSA:
+ case GCRY_PK_RSA_E:
+ case GCRY_PK_RSA_S:
+ err = gcry_sexp_build (&s_skey, NULL,
+ "(private-key(rsa(n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))",
+ skey[0], skey[1], skey[2], skey[3], skey[4],
+ skey[5]);
+
+ default:
+ err = gpg_error (GPG_ERR_PUBKEY_ALGO);
+ break;
+ }
+
+ if (!err)
+ *r_key = s_skey;
+ return err;
+}
+
+
+
+/* Hash the passphrase and set the key. */
+static gpg_error_t
+hash_passphrase_and_set_key (const char *passphrase,
+ gcry_cipher_hd_t hd, int protect_algo,
+ int s2k_mode, int s2k_algo,
+ byte *s2k_salt, u32 s2k_count)
+{
+ gpg_error_t err;
+ unsigned char *key;
+ size_t keylen;
+
+ keylen = gcry_cipher_get_algo_keylen (protect_algo);
+ if (!keylen)
+ return gpg_error (GPG_ERR_INTERNAL);
+
+ key = xtrymalloc_secure (keylen);
+ if (!key)
+ return gpg_error_from_syserror ();
+
+ err = s2k_hash_passphrase (passphrase,
+ s2k_algo, s2k_mode, s2k_salt, s2k_count,
+ key, keylen);
+ if (!err)
+ err = gcry_cipher_setkey (hd, key, keylen);
+
+ xfree (key);
+ return err;
+}
+
+
+static u16
+checksum (const unsigned char *p, unsigned int n)
+{
+ u16 a;
+
+ for (a=0; n; n-- )
+ a += *p++;
+ return a;
+}
+
+
+/* Note that this function modified SKEY. SKEYSIZE is the allocated
+ size of the array including the NULL item; this is used for a
+ bounds check. On success a converted key is stored at R_KEY. */
+static int
+do_unprotect (const char *passphrase,
+ int pkt_version, int pubkey_algo, int is_protected,
+ gcry_mpi_t *skey, size_t skeysize,
+ int protect_algo, void *protect_iv, size_t protect_ivlen,
+ int s2k_mode, int s2k_algo, byte *s2k_salt, u32 s2k_count,
+ u16 desired_csum, gcry_sexp_t *r_key)
+{
+ gpg_error_t err;
+ size_t npkey, nskey, skeylen;
+ gcry_cipher_hd_t cipher_hd = NULL;
+ u16 actual_csum;
+ size_t nbytes;
+ int i;
+ gcry_mpi_t tmpmpi;
+
+ *r_key = NULL;
+
+ /* Count the actual number of MPIs is in the array and set the
+ remainder to NULL for easier processing later on. */
+ for (skeylen = 0; skey[skeylen]; skeylen++)
+ ;
+ for (i=skeylen; i < skeysize; i++)
+ skey[i] = NULL;
+
+ /* Check some args. */
+ if (s2k_mode == 1001)
+ {
+ /* Stub key. */
+ log_info (_("secret key parts are not available\n"));
+ return gpg_error (GPG_ERR_UNUSABLE_SECKEY);
+ }
+
+ if (gcry_pk_test_algo (pubkey_algo))
+ {
+ /* The algorithm numbers are Libgcrypt numbers but fortunately
+ the OpenPGP algorithm numbers map one-to-one to the Libgcrypt
+ numbers. */
+ log_info (_("public key algorithm %d (%s) is not supported\n"),
+ pubkey_algo, gcry_pk_algo_name (pubkey_algo));
+ return gpg_error (GPG_ERR_PUBKEY_ALGO);
+ }
+
+ /* Get properties of the public key algorithm and do some
+ consistency checks. Note that we need at least NPKEY+1 elements
+ in the SKEY array. */
+ if ( (err = gcry_pk_algo_info (pubkey_algo, GCRYCTL_GET_ALGO_NPKEY,
+ NULL, &npkey))
+ || (err = gcry_pk_algo_info (pubkey_algo, GCRYCTL_GET_ALGO_NSKEY,
+ NULL, &nskey)))
+ return err;
+ if (!npkey || npkey >= nskey)
+ return gpg_error (GPG_ERR_INTERNAL);
+ if (skeylen <= npkey)
+ return gpg_error (GPG_ERR_MISSING_VALUE);
+ if (nskey+1 >= skeysize)
+ return gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
+
+ /* Check whether SKEY is at all protected. If it is not protected
+ merely verify the checksum. */
+ if (!is_protected)
+ {
+ unsigned char *buffer;
+
+ actual_csum = 0;
+ for (i=npkey; i < nskey; i++)
+ {
+ if (!skey[i] || gcry_mpi_get_flag (skey[i], GCRYMPI_FLAG_OPAQUE))
+ return gpg_error (GPG_ERR_BAD_SECKEY);
+
+ err = gcry_mpi_print (GCRYMPI_FMT_PGP, NULL, 0, &nbytes, skey[i]);
+ if (!err)
+ {
+ buffer = (gcry_is_secure (skey[i])?
+ xtrymalloc_secure (nbytes) : xtrymalloc (nbytes));
+ if (!buffer)
+ return gpg_error_from_syserror ();
+ err = gcry_mpi_print (GCRYMPI_FMT_PGP, buffer, nbytes,
+ NULL, skey[i]);
+ if (!err)
+ actual_csum += checksum (buffer, nbytes);
+ xfree (buffer);
+ }
+ if (err)
+ return err;
+ }
+
+ if (actual_csum != desired_csum)
+ return gpg_error (GPG_ERR_CHECKSUM);
+ return 0;
+ }
+
+
+ if (gcry_cipher_test_algo (protect_algo))
+ {
+ /* The algorithm numbers are Libgcrypt numbers but fortunately
+ the OpenPGP algorithm numbers map one-to-one to the Libgcrypt
+ numbers. */
+ log_info (_("protection algorithm %d (%s) is not supported\n"),
+ protect_algo, gcry_cipher_algo_name (protect_algo));
+ return gpg_error (GPG_ERR_CIPHER_ALGO);
+ }
+
+ if (gcry_md_test_algo (s2k_algo))
+ {
+ log_info (_("protection hash algorithm %d (%s) is not supported\n"),
+ s2k_algo, gcry_md_algo_name (s2k_algo));
+ return gpg_error (GPG_ERR_DIGEST_ALGO);
+ }
+
+ err = gcry_cipher_open (&cipher_hd, protect_algo,
+ GCRY_CIPHER_MODE_CFB,
+ (GCRY_CIPHER_SECURE
+ | (protect_algo >= 100 ?
+ 0 : GCRY_CIPHER_ENABLE_SYNC)));
+ if (err)
+ {
+ log_error ("failed to open cipher_algo %d: %s\n",
+ protect_algo, gpg_strerror (err));
+ return err;
+ }
+
+ err = hash_passphrase_and_set_key (passphrase, cipher_hd, protect_algo,
+ s2k_mode, s2k_algo, s2k_salt, s2k_count);
+ if (err)
+ {
+ gcry_cipher_close (cipher_hd);
+ return err;
+ }
+
+ gcry_cipher_setiv (cipher_hd, protect_iv, protect_ivlen);
+
+ actual_csum = 0;
+ if (pkt_version >= 4)
+ {
+ int ndata;
+ unsigned int ndatabits;
+ unsigned char *p, *data;
+ u16 csum_pgp7 = 0;
+
+ if (!gcry_mpi_get_flag (skey[npkey], GCRYMPI_FLAG_OPAQUE ))
+ {
+ gcry_cipher_close (cipher_hd);
+ return gpg_error (GPG_ERR_BAD_SECKEY);
+ }
+ p = gcry_mpi_get_opaque (skey[npkey], &ndatabits);
+ ndata = (ndatabits+7)/8;
+
+ if (ndata > 1)
+ csum_pgp7 = p[ndata-2] << 8 | p[ndata-1];
+ data = xtrymalloc_secure (ndata);
+ if (!data)
+ {
+ err = gpg_error_from_syserror ();
+ gcry_cipher_close (cipher_hd);
+ return err;
+ }
+ gcry_cipher_decrypt (cipher_hd, data, ndata, p, ndata);
+
+ p = data;
+ if (is_protected == 2)
+ {
+ /* This is the new SHA1 checksum method to detect tampering
+ with the key as used by the Klima/Rosa attack. */
+ desired_csum = 0;
+ actual_csum = 1; /* Default to bad checksum. */
+
+ if (ndata < 20)
+ log_error ("not enough bytes for SHA-1 checksum\n");
+ else
+ {
+ gcry_md_hd_t h;
+
+ if (gcry_md_open (&h, GCRY_MD_SHA1, 1))
+ BUG(); /* Algo not available. */
+ gcry_md_write (h, data, ndata - 20);
+ gcry_md_final (h);
+ if (!memcmp (gcry_md_read (h, GCRY_MD_SHA1), data+ndata-20, 20))
+ actual_csum = 0; /* Digest does match. */
+ gcry_md_close (h);
+ }
+ }
+ else
+ {
+ /* Old 16 bit checksum method. */
+ if (ndata < 2)
+ {
+ log_error ("not enough bytes for checksum\n");
+ desired_csum = 0;
+ actual_csum = 1; /* Mark checksum bad. */
+ }
+ else
+ {
+ desired_csum = (data[ndata-2] << 8 | data[ndata-1]);
+ actual_csum = checksum (data, ndata-2);
+ if (desired_csum != actual_csum)
+ {
+ /* This is a PGP 7.0.0 workaround */
+ desired_csum = csum_pgp7; /* Take the encrypted one. */
+ }
+ }
+ }
+
+ /* Better check it here. Otherwise the gcry_mpi_scan would fail
+ because the length may have an arbitrary value. */
+ if (desired_csum == actual_csum)
+ {
+ for (i=npkey; i < nskey; i++ )
+ {
+ if (gcry_mpi_scan (&tmpmpi, GCRYMPI_FMT_PGP, p, ndata, &nbytes))
+ {
+ /* Checksum was okay, but not correctly decrypted. */
+ desired_csum = 0;
+ actual_csum = 1; /* Mark checksum bad. */
+ break;
+ }
+ gcry_mpi_release (skey[i]);
+ skey[i] = tmpmpi;
+ ndata -= nbytes;
+ p += nbytes;
+ }
+ skey[i] = NULL;
+ skeylen = i;
+ assert (skeylen <= skeysize);
+
+ /* Note: at this point NDATA should be 2 for a simple
+ checksum or 20 for the sha1 digest. */
+ }
+ xfree(data);
+ }
+ else /* Packet version <= 3. */
+ {
+ unsigned char *buffer;
+
+ for (i = npkey; i < nskey; i++)
+ {
+ unsigned char *p;
+ size_t ndata;
+ unsigned int ndatabits;
+
+ if (!skey[i] || !gcry_mpi_get_flag (skey[i], GCRYMPI_FLAG_OPAQUE))
+ {
+ gcry_cipher_close (cipher_hd);
+ return gpg_error (GPG_ERR_BAD_SECKEY);
+ }
+ p = gcry_mpi_get_opaque (skey[i], &ndatabits);
+ ndata = (ndatabits+7)/8;
+
+ if (!(ndata >= 2) || !(ndata == ((p[0] << 8 | p[1]) + 7)/8 + 2))
+ {
+ gcry_cipher_close (cipher_hd);
+ return gpg_error (GPG_ERR_BAD_SECKEY);
+ }
+
+ buffer = xtrymalloc_secure (ndata);
+ if (!buffer)
+ {
+ err = gpg_error_from_syserror ();
+ gcry_cipher_close (cipher_hd);
+ return err;
+ }
+
+ gcry_cipher_sync (cipher_hd);
+ buffer[0] = p[0];
+ buffer[1] = p[1];
+ gcry_cipher_decrypt (cipher_hd, buffer+2, ndata-2, p+2, ndata-2);
+ actual_csum += checksum (buffer, ndata);
+ err = gcry_mpi_scan (&tmpmpi, GCRYMPI_FMT_PGP, buffer, ndata, &ndata);
+ xfree (buffer);
+ if (err)
+ {
+ /* Checksum was okay, but not correctly decrypted. */
+ desired_csum = 0;
+ actual_csum = 1; /* Mark checksum bad. */
+ break;
+ }
+ gcry_mpi_release (skey[i]);
+ skey[i] = tmpmpi;
+ }
+ }
+ gcry_cipher_close (cipher_hd);
+
+ /* Now let's see whether we have used the correct passphrase. */
+ if (actual_csum != desired_csum)
+ return gpg_error (GPG_ERR_BAD_PASSPHRASE);
+
+ if (nskey != skeylen)
+ err = gpg_error (GPG_ERR_BAD_SECKEY);
+ else
+ err = convert_secret_key (r_key, pubkey_algo, skey);
+ if (err)
+ return err;
+
+ /* The checksum may fail, thus we also check the key itself. */
+ err = gcry_pk_testkey (*r_key);
+ if (err)
+ {
+ gcry_sexp_release (*r_key);
+ *r_key = NULL;
+ return gpg_error (GPG_ERR_BAD_PASSPHRASE);
+ }
+
+ return 0;
+}
+
+
+/* Callback function to try the unprotection from the passpharse query
+ code. */
+static int
+try_do_unprotect_cb (struct pin_entry_info_s *pi)
+{
+ gpg_error_t err;
+ struct try_do_unprotect_arg_s *arg = pi->check_cb_arg;
+
+ err = do_unprotect (pi->pin, arg->is_v4? 4:3,
+ arg->pubkey_algo, arg->is_protected,
+ arg->skey, arg->skeysize,
+ arg->protect_algo, arg->iv, arg->ivlen,
+ arg->s2k_mode, arg->s2k_algo,
+ arg->s2k_salt, arg->s2k_count,
+ arg->desired_csum, arg->r_key);
+ /* SKEY may be modified now, thus we need to re-compute SKEYIDX. */
+ for (arg->skeyidx = 0; (arg->skeyidx < arg->skeysize
+ && arg->skey[arg->skeyidx]); arg->skeyidx++)
+ ;
+ return err;
+}
+
+
+/* Convert an OpenPGP transfer key into our internal format. Before
+ asking for a passphrase we check whether the key already exists in
+ our key storage. S_PGP is the OpenPGP key in transfer format. On
+ success R_KEY will receive a canonical encoded S-expression with
+ the unprotected key in our internal format; the caller needs to
+ release that memory. The passphrase used to decrypt the OpenPGP
+ key will be returned at R_PASSPHRASE; the caller must release this
+ passphrase. The keygrip will be stored at the 20 byte buffer
+ pointed to by GRIP. On error NULL is stored at all return
+ arguments. */
+gpg_error_t
+convert_openpgp (ctrl_t ctrl, gcry_sexp_t s_pgp,
+ unsigned char *grip, const char *prompt,
+ unsigned char **r_key, char **r_passphrase)
+{
+ gpg_error_t err;
+ gcry_sexp_t top_list;
+ gcry_sexp_t list = NULL;
+ const char *value;
+ size_t valuelen;
+ char *string;
+ int idx;
+ int is_v4, is_protected;
+ int pubkey_algo;
+ int protect_algo = 0;
+ char iv[16];
+ int ivlen = 0;
+ int s2k_mode = 0;
+ int s2k_algo = 0;
+ byte s2k_salt[8];
+ u32 s2k_count = 0;
+ size_t npkey, nskey;
+ gcry_mpi_t skey[10]; /* We support up to 9 parameters. */
+ u16 desired_csum;
+ int skeyidx = 0;
+ gcry_sexp_t s_skey;
+ struct pin_entry_info_s *pi;
+ struct try_do_unprotect_arg_s pi_arg;
+
+ *r_key = NULL;
+ *r_passphrase = NULL;
+
+ top_list = gcry_sexp_find_token (s_pgp, "openpgp-private-key", 0);
+ if (!top_list)
+ goto bad_seckey;
+
+ list = gcry_sexp_find_token (top_list, "version", 0);
+ if (!list)
+ goto bad_seckey;
+ value = gcry_sexp_nth_data (list, 1, &valuelen);
+ if (!value || valuelen != 1 || !(value[0] == '3' || value[0] == '4'))
+ goto bad_seckey;
+ is_v4 = (value[0] == '4');
+
+ gcry_sexp_release (list);
+ list = gcry_sexp_find_token (top_list, "protection", 0);
+ if (!list)
+ goto bad_seckey;
+ value = gcry_sexp_nth_data (list, 1, &valuelen);
+ if (!value)
+ goto bad_seckey;
+ if (valuelen == 4 && !memcmp (value, "sha1", 4))
+ is_protected = 2;
+ else if (valuelen == 3 && !memcmp (value, "sum", 3))
+ is_protected = 1;
+ else if (valuelen == 4 && !memcmp (value, "none", 4))
+ is_protected = 0;
+ else
+ goto bad_seckey;
+ if (is_protected)
+ {
+ string = gcry_sexp_nth_string (list, 2);
+ if (!string)
+ goto bad_seckey;
+ protect_algo = gcry_cipher_map_name (string);
+ if (!protect_algo && !!strcmp (string, "IDEA"))
+ protect_algo = GCRY_CIPHER_IDEA;
+ xfree (string);
+
+ value = gcry_sexp_nth_data (list, 3, &valuelen);
+ if (!value || !valuelen || valuelen > sizeof iv)
+ goto bad_seckey;
+ memcpy (iv, value, valuelen);
+ ivlen = valuelen;
+
+ string = gcry_sexp_nth_string (list, 4);
+ if (!string)
+ goto bad_seckey;
+ s2k_mode = strtol (string, NULL, 10);
+ xfree (string);
+
+ string = gcry_sexp_nth_string (list, 5);
+ if (!string)
+ goto bad_seckey;
+ s2k_algo = gcry_md_map_name (string);
+ xfree (string);
+
+ value = gcry_sexp_nth_data (list, 6, &valuelen);
+ if (!value || !valuelen || valuelen > sizeof s2k_salt)
+ goto bad_seckey;
+ memcpy (s2k_salt, value, valuelen);
+
+ string = gcry_sexp_nth_string (list, 7);
+ if (!string)
+ goto bad_seckey;
+ s2k_count = strtoul (string, NULL, 10);
+ xfree (string);
+ }
+
+ gcry_sexp_release (list);
+ list = gcry_sexp_find_token (top_list, "algo", 0);
+ if (!list)
+ goto bad_seckey;
+ string = gcry_sexp_nth_string (list, 1);
+ if (!string)
+ goto bad_seckey;
+ pubkey_algo = gcry_pk_map_name (string);
+ xfree (string);
+
+ if (gcry_pk_algo_info (pubkey_algo, GCRYCTL_GET_ALGO_NPKEY, NULL, &npkey)
+ || gcry_pk_algo_info (pubkey_algo, GCRYCTL_GET_ALGO_NSKEY, NULL, &nskey)
+ || !npkey || npkey >= nskey)
+ goto bad_seckey;
+
+ gcry_sexp_release (list);
+ list = gcry_sexp_find_token (top_list, "skey", 0);
+ if (!list)
+ goto bad_seckey;
+ for (idx=0;;)
+ {
+ int is_enc;
+
+ value = gcry_sexp_nth_data (list, ++idx, &valuelen);
+ if (!value && skeyidx >= npkey)
+ break; /* Ready. */
+
+ /* Check for too many parameters. Note that depending on the
+ protection mode and version number we may see less than NSKEY
+ (but at least NPKEY+1) parameters. */
+ if (idx >= 2*nskey)
+ goto bad_seckey;
+ if (skeyidx >= DIM (skey)-1)
+ goto bad_seckey;
+
+ if (!value || valuelen != 1 || !(value[0] == '_' || value[0] == 'e'))
+ goto bad_seckey;
+ is_enc = (value[0] == 'e');
+ value = gcry_sexp_nth_data (list, ++idx, &valuelen);
+ if (!value || !valuelen)
+ goto bad_seckey;
+ if (is_enc)
+ {
+ void *p = xtrymalloc (valuelen);
+ if (!p)
+ goto outofmem;
+ memcpy (p, value, valuelen);
+ skey[skeyidx] = gcry_mpi_set_opaque (NULL, p, valuelen*8);
+ if (!skey[skeyidx])
+ goto outofmem;
+ }
+ else
+ {
+ if (gcry_mpi_scan (skey + skeyidx, GCRYMPI_FMT_STD,
+ value, valuelen, NULL))
+ goto bad_seckey;
+ }
+ skeyidx++;
+ }
+ skey[skeyidx++] = NULL;
+
+ gcry_sexp_release (list);
+ list = gcry_sexp_find_token (top_list, "csum", 0);
+ if (list)
+ {
+ string = gcry_sexp_nth_string (list, 1);
+ if (!string)
+ goto bad_seckey;
+ desired_csum = strtoul (string, NULL, 10);
+ xfree (string);
+ }
+ else
+ desired_csum = 0;
+
+
+ gcry_sexp_release (list); list = NULL;
+ gcry_sexp_release (top_list); top_list = NULL;
+
+ /* log_debug ("XXX is_v4=%d\n", is_v4); */
+ /* log_debug ("XXX pubkey_algo=%d\n", pubkey_algo); */
+ /* log_debug ("XXX is_protected=%d\n", is_protected); */
+ /* log_debug ("XXX protect_algo=%d\n", protect_algo); */
+ /* log_printhex ("XXX iv", iv, ivlen); */
+ /* log_debug ("XXX ivlen=%d\n", ivlen); */
+ /* log_debug ("XXX s2k_mode=%d\n", s2k_mode); */
+ /* log_debug ("XXX s2k_algo=%d\n", s2k_algo); */
+ /* log_printhex ("XXX s2k_salt", s2k_salt, sizeof s2k_salt); */
+ /* log_debug ("XXX s2k_count=%lu\n", (unsigned long)s2k_count); */
+ /* for (idx=0; skey[idx]; idx++) */
+ /* { */
+ /* int is_enc = gcry_mpi_get_flag (skey[idx], GCRYMPI_FLAG_OPAQUE); */
+ /* log_info ("XXX skey[%d]%s:", idx, is_enc? " (enc)":""); */
+ /* if (is_enc) */
+ /* { */
+ /* void *p; */
+ /* unsigned int nbits; */
+ /* p = gcry_mpi_get_opaque (skey[idx], &nbits); */
+ /* log_printhex (NULL, p, (nbits+7)/8); */
+ /* } */
+ /* else */
+ /* gcry_mpi_dump (skey[idx]); */
+ /* log_printf ("\n"); */
+ /* } */
+
+ err = get_keygrip (pubkey_algo, skey, grip);
+ if (err)
+ goto leave;
+
+ if (!agent_key_available (grip))
+ {
+ err = gpg_error (GPG_ERR_EEXIST);
+ goto leave;
+ }
+
+ pi = xtrycalloc_secure (1, sizeof (*pi) + 100);
+ if (!pi)
+ return gpg_error_from_syserror ();
+ pi->max_length = 100;
+ pi->min_digits = 0; /* We want a real passphrase. */
+ pi->max_digits = 16;
+ pi->max_tries = 3;
+ pi->check_cb = try_do_unprotect_cb;
+ pi->check_cb_arg = &pi_arg;
+ pi_arg.is_v4 = is_v4;
+ pi_arg.is_protected = is_protected;
+ pi_arg.pubkey_algo = pubkey_algo;
+ pi_arg.protect_algo = protect_algo;
+ pi_arg.iv = iv;
+ pi_arg.ivlen = ivlen;
+ pi_arg.s2k_mode = s2k_mode;
+ pi_arg.s2k_algo = s2k_algo;
+ pi_arg.s2k_salt = s2k_salt;
+ pi_arg.s2k_count = s2k_count;
+ pi_arg.desired_csum = desired_csum;
+ pi_arg.skey = skey;
+ pi_arg.skeysize = DIM (skey);
+ pi_arg.skeyidx = skeyidx;
+ pi_arg.r_key = &s_skey;
+ err = agent_askpin (ctrl, prompt, NULL, NULL, pi);
+ skeyidx = pi_arg.skeyidx;
+ if (!err)
+ {
+ *r_passphrase = xtrystrdup (pi->pin);
+ if (!*r_passphrase)
+ err = gpg_error_from_syserror ();
+ }
+ xfree (pi);
+ if (err)
+ goto leave;
+
+ /* Save some memory and get rid of the SKEY array now. */
+ for (idx=0; idx < skeyidx; idx++)
+ gcry_mpi_release (skey[idx]);
+ skeyidx = 0;
+
+ /* Note that the padding is not required - we use it only because
+ that function allows us to created the result in secure memory. */
+ err = make_canon_sexp_pad (s_skey, 1, r_key, NULL);
+ gcry_sexp_release (s_skey);
+
+ leave:
+ gcry_sexp_release (list);
+ gcry_sexp_release (top_list);
+ for (idx=0; idx < skeyidx; idx++)
+ gcry_mpi_release (skey[idx]);
+ if (err)
+ {
+ xfree (*r_passphrase);
+ *r_passphrase = NULL;
+ }
+ return err;
+
+ bad_seckey:
+ err = gpg_error (GPG_ERR_BAD_SECKEY);
+ goto leave;
+
+ outofmem:
+ err = gpg_error (GPG_ERR_ENOMEM);
+ goto leave;
+
+}
+
+
+
diff --git a/agent/cvt-openpgp.h b/agent/cvt-openpgp.h
new file mode 100644
index 000000000..17b1a6ead
--- /dev/null
+++ b/agent/cvt-openpgp.h
@@ -0,0 +1,27 @@
+/* cvt-openpgp.h - Convert an OpenPGP key to our internal format.
+ * Copyright (C) 2010 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef GNUPG_AGENT_CVT_OPENPGP_H
+#define GNUPG_AGENT_CVT_OPENPGP_H
+
+gpg_error_t convert_openpgp (ctrl_t ctrl, gcry_sexp_t s_pgp,
+ unsigned char *grip, const char *prompt,
+ unsigned char **r_key, char **r_passphrase);
+
+
+#endif /*GNUPG_AGENT_CVT_OPENPGP_H*/
diff --git a/agent/findkey.c b/agent/findkey.c
index 5668aafbc..76221119e 100644
--- a/agent/findkey.c
+++ b/agent/findkey.c
@@ -702,7 +702,8 @@ key_parms_from_sexp (gcry_sexp_t s_key, gcry_sexp_t *r_list,
}
-/* Return true if S_KEY is a DSA style key. */
+/* Return the public key algorithm number if S_KEY is a DSA style key.
+ If it is not a DSA style key, return 0. */
int
agent_is_dsa_key (gcry_sexp_t s_key)
{
@@ -714,7 +715,12 @@ agent_is_dsa_key (gcry_sexp_t s_key)
if (key_parms_from_sexp (s_key, NULL, algoname, sizeof algoname, NULL, 0))
return 0; /* Error - assume it is not an DSA key. */
- return (!strcmp (algoname, "dsa") || !strcmp (algoname, "ecdsa"));
+ if (!strcmp (algoname, "dsa"))
+ return GCRY_PK_DSA;
+ else if (!strcmp (algoname, "ecdsa"))
+ return GCRY_PK_ECDSA;
+ else
+ return 0;
}
diff --git a/agent/keyformat.txt b/agent/keyformat.txt
index e246e888c..841e5840f 100644
--- a/agent/keyformat.txt
+++ b/agent/keyformat.txt
@@ -159,7 +159,34 @@ second list with the information has this layout:
More items may be added to the list.
-
+OpenPGP Private Key Transfer Format
+===================================
+
+This format is used to transfer keys between gpg and gpg-agent.
+
+(openpgp-private-key
+ (version V)
+ (protection PROTTYPE PROTALGO IV S2KMODE S2KHASH S2KSALT S2KCOUNT)
+ (algo PUBKEYALGO)
+ (skey CSUM c P1 c P2 c P3 ... e PN))
+
+
+* V is the packet version number (3 or 4).
+* PUBKEYALGO is a Libgcrypt algo name
+* CSUM is the 16 bit checksum as defined by OpenPGP.
+* P1 .. PN are the parameters; the public parameters are never encrypted
+ the secrect key parameters are encrypted if the "protection" list is
+ given. To make this more explicit each parameter is preceded by a
+ flag "_" for cleartext or "e" for encrypted text.
+* If PROTTYPE is "sha1" the new style SHA1 checksum is used if it is "sum"
+ the old 16 bit checksum is used and if it is "none" no protection at
+ all is used.
+* PROTALGO is a Libgcrypt style cipher algorithm name
+* IV is the initialization verctor.
+* S2KMODE is the value from RFC-4880.
+* S2KHASH is a a libgcrypt style hash algorithm identifier.
+* S2KSALT is the 8 byte salt
+* S2KCOUNT is the count value from RFC-4880.
diff --git a/agent/pksign.c b/agent/pksign.c
index 7ae50a931..28e208e55 100644
--- a/agent/pksign.c
+++ b/agent/pksign.c
@@ -164,10 +164,22 @@ do_encode_dsa (const byte * md, size_t mdlen, int dsaalgo, gcry_sexp_t pkey,
if (mdlen > qbits/8)
mdlen = qbits/8;
- /* Create the S-expression. */
- err = gcry_sexp_build (&hash, NULL,
- "(data (flags raw) (value %b))",
- (int)mdlen, md);
+ /* Create the S-expression. We need to convert to an MPI first
+ because we want an unsigned integer. Using %b directly is not
+ possible because libgcrypt assumes an mpi and uses
+ GCRYMPI_FMT_STD for parsing and thus possible yielding a negative
+ value. */
+ {
+ gcry_mpi_t mpi;
+
+ err = gcry_mpi_scan (&mpi, GCRYMPI_FMT_USG, md, mdlen, NULL);
+ if (!err)
+ {
+ err = gcry_sexp_build (&hash, NULL,
+ "(data (flags raw) (value %m))", mpi);
+ gcry_mpi_release (mpi);
+ }
+ }
if (!err)
*r_hash = hash;
return err;
@@ -304,8 +316,10 @@ agent_pksign_do (ctrl_t ctrl, const char *desc_text,
if (DBG_CRYPTO)
{
- log_debug ("skey: ");
+ log_debug ("skey:\n");
gcry_sexp_dump (s_skey);
+ log_debug ("hash:\n");
+ gcry_sexp_dump (s_hash);
}
/* sign */
@@ -319,7 +333,7 @@ agent_pksign_do (ctrl_t ctrl, const char *desc_text,
if (DBG_CRYPTO)
{
- log_debug ("result: ");
+ log_debug ("result:\n");
gcry_sexp_dump (s_sig);
}
}
diff --git a/agent/protect.c b/agent/protect.c
index db6caa48c..3a983e2bd 100644
--- a/agent/protect.c
+++ b/agent/protect.c
@@ -73,6 +73,8 @@ hash_passphrase (const char *passphrase, int hashalgo,
const unsigned char *s2ksalt, unsigned long s2kcount,
unsigned char *key, size_t keylen);
+
+
/* Get the process time and store it in DATA. */
static void
calibrate_get_time (struct calibrate_time_s *data)
@@ -1076,6 +1078,19 @@ hash_passphrase (const char *passphrase, int hashalgo,
}
+gpg_error_t
+s2k_hash_passphrase (const char *passphrase, int hashalgo,
+ int s2kmode,
+ const unsigned char *s2ksalt,
+ unsigned int s2kcount,
+ unsigned char *key, size_t keylen)
+{
+ return hash_passphrase (passphrase, hashalgo, s2kmode, s2ksalt,
+ (16ul + (s2kcount & 15)) << ((s2kcount >> 4) + 6),
+ key, keylen);
+}
+
+
/* Create an canonical encoded S-expression with the shadow info from