summaryrefslogtreecommitdiffstats
path: root/sm
diff options
context:
space:
mode:
authorWerner Koch <wk@gnupg.org>2010-06-17 17:44:44 +0200
committerWerner Koch <wk@gnupg.org>2010-06-17 17:44:44 +0200
commit006fd75aea5cc766bc223e435e5a07b543d658d3 (patch)
tree2f4365e73bf0a8b5272426fcf1fde9b75be4002e /sm
parentAdd makefiles to build a w32 development package. (diff)
downloadgnupg2-006fd75aea5cc766bc223e435e5a07b543d658d3.tar.xz
gnupg2-006fd75aea5cc766bc223e435e5a07b543d658d3.zip
Avoid using the protect-tool to import pkcs#12.
Diffstat (limited to 'sm')
-rw-r--r--sm/ChangeLog16
-rw-r--r--sm/Makefile.am1
-rw-r--r--sm/call-agent.c137
-rw-r--r--sm/gpgsm.h6
-rw-r--r--sm/import.c542
-rw-r--r--sm/minip12.c2363
-rw-r--r--sm/minip12.h36
7 files changed, 2892 insertions, 209 deletions
diff --git a/sm/ChangeLog b/sm/ChangeLog
index ebcd30589..686baca3a 100644
--- a/sm/ChangeLog
+++ b/sm/ChangeLog
@@ -1,3 +1,19 @@
+2010-06-17 Werner Koch <wk@g10code.com>
+
+ * import.c (parse_p12): Remove arg retfp. Use the agent's new
+ import command.
+ (import_one): Adjust call to pkcs12.
+ (store_cert_cb, rsa_key_check): New.
+ (popen_protect_tool): Remove.
+ * minip12.c (parse_bag_encrypted_data, p12_parse): Add arg
+ R_BADPASS.
+ * call-agent.c (gpgsm_agent_ask_passphrase): New.
+ (gpgsm_agent_keywrap_key): New.
+ (struct import_key_parm_s): New.
+ (gpgsm_agent_import_key): New.
+ * minip12.c, minip12.h: Move from ../agent/.
+ * Makefile.am (gpgsm_SOURCES): Add them.
+
2010-06-11 Marcus Brinkmann <marcus@g10code.de>
* server.c (cmd_message) [HAVE_W32CE_SYSTEM]: Finish pipe.
diff --git a/sm/Makefile.am b/sm/Makefile.am
index 2754b8535..7386debcb 100644
--- a/sm/Makefile.am
+++ b/sm/Makefile.am
@@ -49,6 +49,7 @@ gpgsm_SOURCES = \
delete.c \
certreqgen.c \
certreqgen-ui.c \
+ minip12.c minip12.h \
qualified.c
diff --git a/sm/call-agent.c b/sm/call-agent.c
index 402cb7dd0..e77f03847 100644
--- a/sm/call-agent.c
+++ b/sm/call-agent.c
@@ -1,6 +1,6 @@
/* call-agent.c - Divert GPGSM operations to the agent
* Copyright (C) 2001, 2002, 2003, 2005, 2007,
- * 2008, 2009 Free Software Foundation, Inc.
+ * 2008, 2009, 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
@@ -66,6 +66,14 @@ struct learn_parm_s
membuf_t *data;
};
+struct import_key_parm_s
+{
+ ctrl_t ctrl;
+ assuan_context_t ctx;
+ const void *key;
+ size_t keylen;
+};
+
/* Try to connect to the agent via socket or fork it off and work by
@@ -1066,3 +1074,130 @@ gpgsm_agent_keyinfo (ctrl_t ctrl, const char *hexkeygrip, char **r_serialno)
return err;
}
+
+
+/* Ask for the passphrase (this is used for pkcs#12 import/export. On
+ success the caller needs to free the string stored at R_PASSPHRASE.
+ On error NULL will be stored at R_PASSPHRASE and an appropriate
+ error code returned. */
+gpg_error_t
+gpgsm_agent_ask_passphrase (ctrl_t ctrl, const char *desc_msg,
+ char **r_passphrase)
+{
+ gpg_error_t err;
+ char line[ASSUAN_LINELENGTH];
+ char *arg4 = NULL;
+ membuf_t data;
+
+ *r_passphrase = NULL;
+
+ err = start_agent (ctrl);
+ if (err)
+ return err;
+
+ if (desc_msg && *desc_msg && !(arg4 = percent_plus_escape (desc_msg)))
+ return gpg_error_from_syserror ();
+
+ snprintf (line, DIM(line)-1, "GET_PASSPHRASE --data -- X X X %s", arg4);
+ xfree (arg4);
+
+ init_membuf_secure (&data, 64);
+ err = assuan_transact (agent_ctx, line,
+ membuf_data_cb, &data,
+ default_inq_cb, NULL, NULL, NULL);
+
+ if (err)
+ xfree (get_membuf (&data, NULL));
+ else
+ {
+ put_membuf (&data, "", 1);
+ *r_passphrase = get_membuf (&data, NULL);
+ if (!*r_passphrase)
+ err = gpg_error_from_syserror ();
+ }
+ return err;
+}
+
+
+
+/* Retrieve a key encryption key from the agent. With FOREXPORT true
+ the key shall be use for export, with false for import. On success
+ the new key is stored at R_KEY and its length at R_KEKLEN. */
+gpg_error_t
+gpgsm_agent_keywrap_key (ctrl_t ctrl, int forexport,
+ void **r_kek, size_t *r_keklen)
+{
+ gpg_error_t err;
+ membuf_t data;
+ size_t len;
+ unsigned char *buf;
+ char line[ASSUAN_LINELENGTH];
+
+ *r_kek = NULL;
+ err = start_agent (ctrl);
+ if (err)
+ return err;
+
+ snprintf (line, DIM(line)-1, "KEYWRAP_KEY %s",
+ forexport? "--export":"--import");
+
+ init_membuf_secure (&data, 64);
+ err = assuan_transact (agent_ctx, line,
+ membuf_data_cb, &data,
+ default_inq_cb, ctrl, NULL, NULL);
+ if (err)
+ {
+ xfree (get_membuf (&data, &len));
+ return err;
+ }
+ buf = get_membuf (&data, &len);
+ if (!buf)
+ return gpg_error_from_syserror ();
+ *r_kek = buf;
+ *r_keklen = len;
+ return 0;
+}
+
+
+
+
+/* Handle the inquiry for an IMPORT_KEY command. */
+static gpg_error_t
+inq_import_key_parms (void *opaque, const char *line)
+{
+ struct import_key_parm_s *parm = opaque;
+ gpg_error_t err;
+
+ if (!strncmp (line, "KEYDATA", 7) && (line[7]==' '||!line[7]))
+ {
+ assuan_begin_confidential (parm->ctx);
+ err = assuan_send_data (parm->ctx, parm->key, parm->keylen);
+ assuan_end_confidential (parm->ctx);
+ }
+ else
+ err = default_inq_cb (parm->ctrl, line);
+
+ return err;
+}
+
+
+/* Call the agent to import a key into the agent. */
+gpg_error_t
+gpgsm_agent_import_key (ctrl_t ctrl, const void *key, size_t keylen)
+{
+ gpg_error_t err;
+ struct import_key_parm_s parm;
+
+ err = start_agent (ctrl);
+ if (err)
+ return err;
+
+ parm.ctrl = ctrl;
+ parm.ctx = agent_ctx;
+ parm.key = key;
+ parm.keylen = keylen;
+
+ err = assuan_transact (agent_ctx, "IMPORT_KEY",
+ NULL, NULL, inq_import_key_parms, &parm, NULL, NULL);
+ return err;
+}
diff --git a/sm/gpgsm.h b/sm/gpgsm.h
index 542c292ac..f065bfa11 100644
--- a/sm/gpgsm.h
+++ b/sm/gpgsm.h
@@ -405,6 +405,12 @@ gpg_error_t gpgsm_agent_get_confirmation (ctrl_t ctrl, const char *desc);
gpg_error_t gpgsm_agent_send_nop (ctrl_t ctrl);
gpg_error_t gpgsm_agent_keyinfo (ctrl_t ctrl, const char *hexkeygrip,
char **r_serialno);
+gpg_error_t gpgsm_agent_ask_passphrase (ctrl_t ctrl, const char *desc_msg,
+ char **r_passphrase);
+gpg_error_t gpgsm_agent_keywrap_key (ctrl_t ctrl, int forexport,
+ void **r_kek, size_t *r_keklen);
+gpg_error_t gpgsm_agent_import_key (ctrl_t ctrl,
+ const void *key, size_t keylen);
/*-- call-dirmngr.c --*/
int gpgsm_dirmngr_isvalid (ctrl_t ctrl,
diff --git a/sm/import.c b/sm/import.c
index 6a012ca66..c920ac51a 100644
--- a/sm/import.c
+++ b/sm/import.c
@@ -35,6 +35,11 @@
#include "i18n.h"
#include "sysutils.h"
#include "../kbx/keybox.h" /* for KEYBOX_FLAG_* */
+#include "../common/membuf.h"
+#include "minip12.h"
+
+/* The arbitrary limit of one PKCS#12 object. */
+#define MAX_P12OBJ_SIZE 128 /*kb*/
struct stats_s {
@@ -48,8 +53,19 @@ struct stats_s {
};
+struct rsa_secret_key_s
+{
+ gcry_mpi_t n; /* public modulus */
+ gcry_mpi_t e; /* public exponent */
+ gcry_mpi_t d; /* exponent */
+ gcry_mpi_t p; /* prime p. */
+ gcry_mpi_t q; /* prime q. */
+ gcry_mpi_t u; /* inverse of p mod q. */
+};
+
+
static gpg_error_t parse_p12 (ctrl_t ctrl, ksba_reader_t reader,
- estream_t *retfp, struct stats_s *stats);
+ struct stats_s *stats);
@@ -325,51 +341,11 @@ import_one (ctrl_t ctrl, struct stats_s *stats, int in_fd)
any = 1;
}
else if (ct == KSBA_CT_PKCS12)
- { /* This seems to be a pkcs12 message. We use an external
- tool to parse the message and to store the private keys.
- We need to use a another reader here to parse the
- certificate we included in the p12 file; then we continue
- to look for other pkcs12 files (works only if they are in
- PEM format. */
- estream_t certfp;
- Base64Context b64p12rdr;
- ksba_reader_t p12rdr;
-
- rc = parse_p12 (ctrl, reader, &certfp, stats);
+ {
+ /* This seems to be a pkcs12 message. */
+ rc = parse_p12 (ctrl, reader, stats);
if (!rc)
- {
- any = 1;
-
- es_rewind (certfp);
- rc = gpgsm_create_reader (&b64p12rdr, ctrl, certfp, 1, &p12rdr);
- if (rc)
- {
- log_error ("can't create reader: %s\n", gpg_strerror (rc));
- es_fclose (certfp);
- goto leave;
- }
-
- do
- {
- ksba_cert_release (cert); cert = NULL;
- rc = ksba_cert_new (&cert);
- if (!rc)
- {
- rc = ksba_cert_read_der (cert, p12rdr);
- if (!rc)
- check_and_store (ctrl, stats, cert, 0);
- }
- ksba_reader_clear (p12rdr, NULL, NULL);
- }
- while (!rc && !gpgsm_reader_eof_seen (b64p12rdr));
-
- if (gpg_err_code (rc) == GPG_ERR_EOF)
- rc = 0;
- gpgsm_destroy_reader (b64p12rdr);
- es_fclose (certfp);
- if (rc)
- goto leave;
- }
+ any = 1;
}
else if (ct == KSBA_CT_NONE)
{ /* Failed to identify this message - assume a certificate */
@@ -578,213 +554,363 @@ gpgsm_import_files (ctrl_t ctrl, int nfiles, char **files,
}
-/* Fork and exec the protect tool, connect the file descriptor of
- INFILE to stdin, return a new estream in STATUSFILE, write the
- output to OUTFILE and the pid of the process in PID. Returns 0 on
- success or an error code. */
+/* Check that the RSA secret key SKEY is valid. Swap parameters to
+ the libgcrypt standard. */
static gpg_error_t
-popen_protect_tool (ctrl_t ctrl, const char *pgmname,
- estream_t infile, estream_t outfile,
- estream_t *statusfile, pid_t *pid)
+rsa_key_check (struct rsa_secret_key_s *skey)
{
- const char *argv[22];
- int i=0;
-
- /* Make sure that the agent is running so that the protect tool is
- able to ask for a passphrase. This has only an effect under W32
- where the agent is started on demand; sending a NOP does not harm
- on other platforms. This is not really necessary anymore because
- the protect tool does this now by itself; it does not harm either. */
- gpgsm_agent_send_nop (ctrl);
-
- argv[i++] = "--homedir";
- argv[i++] = opt.homedir;
- argv[i++] = "--p12-import";
- argv[i++] = "--store";
- argv[i++] = "--no-fail-on-exist";
- argv[i++] = "--enable-status-msg";
- if (opt.fixed_passphrase)
- {
- argv[i++] = "--passphrase";
- argv[i++] = opt.fixed_passphrase;
- }
- if (opt.agent_program)
- {
- argv[i++] = "--agent-program";
- argv[i++] = opt.agent_program;
- }
- argv[i++] = "--",
- argv[i] = NULL;
- assert (i < sizeof argv);
-
- return gnupg_spawn_process (pgmname, argv, infile, outfile,
- setup_pinentry_env, (128 | 64),
- statusfile, pid);
+ int err = 0;
+ gcry_mpi_t t = gcry_mpi_snew (0);
+ gcry_mpi_t t1 = gcry_mpi_snew (0);
+ gcry_mpi_t t2 = gcry_mpi_snew (0);
+ gcry_mpi_t phi = gcry_mpi_snew (0);
+
+ /* Check that n == p * q. */
+ gcry_mpi_mul (t, skey->p, skey->q);
+ if (gcry_mpi_cmp( t, skey->n) )
+ {
+ log_error ("RSA oops: n != p * q\n");
+ err++;
+ }
+
+ /* Check that p is less than q. */
+ if (gcry_mpi_cmp (skey->p, skey->q) > 0)
+ {
+ gcry_mpi_t tmp;
+
+ log_info ("swapping secret primes\n");
+ tmp = gcry_mpi_copy (skey->p);
+ gcry_mpi_set (skey->p, skey->q);
+ gcry_mpi_set (skey->q, tmp);
+ gcry_mpi_release (tmp);
+ /* Recompute u. */
+ gcry_mpi_invm (skey->u, skey->p, skey->q);
+ }
+
+ /* Check that e divides neither p-1 nor q-1. */
+ gcry_mpi_sub_ui (t, skey->p, 1 );
+ gcry_mpi_div (NULL, t, t, skey->e, 0);
+ if (!gcry_mpi_cmp_ui( t, 0) )
+ {
+ log_error ("RSA oops: e divides p-1\n");
+ err++;
+ }
+ gcry_mpi_sub_ui (t, skey->q, 1);
+ gcry_mpi_div (NULL, t, t, skey->e, 0);
+ if (!gcry_mpi_cmp_ui( t, 0))
+ {
+ log_info ("RSA oops: e divides q-1\n" );
+ err++;
+ }
+
+ /* Check that d is correct. */
+ gcry_mpi_sub_ui (t1, skey->p, 1);
+ gcry_mpi_sub_ui (t2, skey->q, 1);
+ gcry_mpi_mul (phi, t1, t2);
+ gcry_mpi_invm (t, skey->e, phi);
+ if (gcry_mpi_cmp (t, skey->d))
+ {
+ /* No: try universal exponent. */
+ gcry_mpi_gcd (t, t1, t2);
+ gcry_mpi_div (t, NULL, phi, t, 0);
+ gcry_mpi_invm (t, skey->e, t);
+ if (gcry_mpi_cmp (t, skey->d))
+ {
+ log_error ("RSA oops: bad secret exponent\n");
+ err++;
+ }
+ }
+
+ /* Check for correctness of u. */
+ gcry_mpi_invm (t, skey->p, skey->q);
+ if (gcry_mpi_cmp (t, skey->u))
+ {
+ log_info ("RSA oops: bad u parameter\n");
+ err++;
+ }
+
+ if (err)
+ log_info ("RSA secret key check failed\n");
+
+ gcry_mpi_release (t);
+ gcry_mpi_release (t1);
+ gcry_mpi_release (t2);
+ gcry_mpi_release (phi);
+
+ return err? gpg_error (GPG_ERR_BAD_SECKEY):0;
+}
+
+
+/* Object passed to store_cert_cb. */
+struct store_cert_parm_s
+{
+ gpg_error_t err; /* First error seen. */
+ struct stats_s *stats; /* The stats object. */
+ ctrl_t ctrl; /* The control object. */
+};
+
+/* Helper to store the DER encoded certificate CERTDATA of length
+ CERTDATALEN. */
+static void
+store_cert_cb (void *opaque,
+ const unsigned char *certdata, size_t certdatalen)
+{
+ struct store_cert_parm_s *parm = opaque;
+ gpg_error_t err;
+ ksba_cert_t cert;
+
+ err = ksba_cert_new (&cert);
+ if (err)
+ {
+ if (!parm->err)
+ parm->err = err;
+ return;
+ }
+
+ err = ksba_cert_init_from_mem (cert, certdata, certdatalen);
+ if (err)
+ {
+ log_error ("failed to parse a certificate: %s\n", gpg_strerror (err));
+ if (!parm->err)
+ parm->err = err;
+ }
+ else
+ check_and_store (parm->ctrl, parm->stats, cert, 0);
+ ksba_cert_release (cert);
}
/* Assume that the reader is at a pkcs#12 message and try to import
- certificates from that stupid format. We will also store secret
- keys. All of the pkcs#12 parsing and key storing is handled by the
- gpg-protect-tool, we merely have to take care of receiving the
- certificates. On success RETFP returns a stream to a temporary
- file with certificates. */
+ certificates from that stupid format. We will transfer secret
+ keys to the agent. */
static gpg_error_t
-parse_p12 (ctrl_t ctrl, ksba_reader_t reader,
- estream_t *retfp, struct stats_s *stats)
+parse_p12 (ctrl_t ctrl, ksba_reader_t reader, struct stats_s *stats)
{
- const char *pgmname;
- gpg_error_t err = 0, child_err = 0;
- int c, cont_line;
- unsigned int pos;
- estream_t tmpfp;
- estream_t fp = NULL;
- estream_t certfp = NULL;
+ gpg_error_t err = 0;
char buffer[1024];
- size_t nread;
- pid_t pid = -1;
+ size_t ntotal, nread;
+ membuf_t p12mbuf;
+ char *p12buffer = NULL;
+ size_t p12buflen;
+ size_t p12bufoff;
+ gcry_mpi_t *kparms = NULL;
+ struct rsa_secret_key_s sk;
+ char *passphrase = NULL;
+ unsigned char *key = NULL;
+ size_t keylen;
+ void *kek = NULL;
+ size_t keklen;
+ unsigned char *wrappedkey = NULL;
+ size_t wrappedkeylen;
+ gcry_cipher_hd_t cipherhd = NULL;
+ gcry_sexp_t s_key = NULL;
+ unsigned char grip[20];
int bad_pass = 0;
+ int i;
+ struct store_cert_parm_s store_cert_parm;
- if (!opt.protect_tool_program || !*opt.protect_tool_program)
- pgmname = gnupg_module_name (GNUPG_MODULE_NAME_PROTECT_TOOL);
- else
- pgmname = opt.protect_tool_program;
-
- *retfp = NULL;
+ memset (&store_cert_parm, 0, sizeof store_cert_parm);
+ store_cert_parm.ctrl = ctrl;
+ store_cert_parm.stats = stats;
- /* To avoid an extra feeder process or doing selects and because
- gpg-protect-tool will anyway parse the entire pkcs#12 message in
- memory, we simply use tempfiles here and pass them to
- the gpg-protect-tool. */
- tmpfp = es_tmpfile ();
- if (!tmpfp)
- {
- err = gpg_error_from_syserror ();
- log_error (_("error creating temporary file: %s\n"), strerror (errno));
- goto cleanup;
- }
+ init_membuf (&p12mbuf, 4096);
+ ntotal = 0;
while (!(err = ksba_reader_read (reader, buffer, sizeof buffer, &nread)))
{
- if (nread && es_fwrite (buffer, nread, 1, tmpfp) != 1)
+ if (ntotal >= MAX_P12OBJ_SIZE*1024)
{
- err = gpg_error_from_syserror ();
- log_error (_("error writing to temporary file: %s\n"),
- strerror (errno));
- goto cleanup;
+ /* Arbitrary limit to avoid DoS attacks. */
+ err = gpg_error (GPG_ERR_TOO_LARGE);
+ log_error ("pkcs#12 object is larger than %dk\n", MAX_P12OBJ_SIZE);
+ break;
}
+ put_membuf (&p12mbuf, buffer, nread);
+ ntotal += nread;
}
if (gpg_err_code (err) == GPG_ERR_EOF)
err = 0;
+ if (!err)
+ {
+ p12buffer = get_membuf (&p12mbuf, &p12buflen);
+ if (!p12buffer)
+ err = gpg_error_from_syserror ();
+ }
if (err)
{
log_error (_("error reading input: %s\n"), gpg_strerror (err));
- goto cleanup;
+ goto leave;
}
- certfp = es_tmpfile ();
- if (!certfp)
+ /* GnuPG 2.0.4 accidently created binary P12 files with the string
+ "The passphrase is %s encoded.\n\n" prepended to the ASN.1 data.
+ We fix that here. */
+ if (p12buflen > 29 && !memcmp (p12buffer, "The passphrase is ", 18))
{
- err = gpg_error_from_syserror ();
- log_error (_("error creating temporary file: %s\n"), strerror (errno));
- goto cleanup;
+ for (p12bufoff=18;
+ p12bufoff < p12buflen && p12buffer[p12bufoff] != '\n';
+ p12bufoff++)
+ ;
+ p12bufoff++;
+ if (p12bufoff < p12buflen && p12buffer[p12bufoff] == '\n')
+ p12bufoff++;
}
+ else
+ p12bufoff = 0;
- err = popen_protect_tool (ctrl, pgmname, tmpfp, certfp, &fp, &pid);
+
+ err = gpgsm_agent_ask_passphrase
+ (ctrl, _("Please enter the passphrase to unprotect the PKCS#12 object."),
+ &passphrase);
if (err)
+ goto leave;
+
+ kparms = p12_parse (p12buffer + p12bufoff, p12buflen - p12bufoff,
+ passphrase, store_cert_cb, &store_cert_parm, &bad_pass);
+
+ xfree (passphrase);
+ passphrase = NULL;
+
+ if (!kparms)
{
- pid = -1;
- goto cleanup;
+ log_error ("error parsing or decrypting the PKCS#12 file\n");
+ err = gpg_error (GPG_ERR_INV_OBJ);
+ goto leave;
}
- es_fclose (tmpfp);
- tmpfp = NULL;
- /* Read stderr of the protect tool. */
- pos = 0;
- cont_line = 0;
- while ((c=es_getc (fp)) != EOF)
+/* print_mpi (" n", kparms[0]); */
+/* print_mpi (" e", kparms[1]); */
+/* print_mpi (" d", kparms[2]); */
+/* print_mpi (" p", kparms[3]); */
+/* print_mpi (" q", kparms[4]); */
+/* print_mpi ("dmp1", kparms[5]); */
+/* print_mpi ("dmq1", kparms[6]); */
+/* print_mpi (" u", kparms[7]); */
+
+ sk.n = kparms[0];
+ sk.e = kparms[1];
+ sk.d = kparms[2];
+ sk.q = kparms[3];
+ sk.p = kparms[4];
+ sk.u = kparms[7];
+ err = rsa_key_check (&sk);
+ if (err)
+ goto leave;
+/* print_mpi (" n", sk.n); */
+/* print_mpi (" e", sk.e); */
+/* print_mpi (" d", sk.d); */
+/* print_mpi (" p", sk.p); */
+/* print_mpi (" q", sk.q); */
+/* print_mpi (" u", sk.u); */
+
+ /* Create an S-expresion from the parameters. */
+ err = gcry_sexp_build (&s_key, NULL,
+ "(private-key(rsa(n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))",
+ sk.n, sk.e, sk.d, sk.p, sk.q, sk.u, NULL);
+ for (i=0; i < 8; i++)
+ gcry_mpi_release (kparms[i]);
+ gcry_free (kparms);
+ kparms = NULL;
+ if (err)
{
- /* fixme: We could here grep for status information of the
- protect tool to figure out better error codes for
- CHILD_ERR. */
- buffer[pos++] = c;
- if (pos >= sizeof buffer - 5 || c == '\n')
- {
- buffer[pos - (c == '\n')] = 0;
- if (cont_line)
- log_printf ("%s", buffer);
- else
- {
- if (!strncmp (buffer, "gpg-protect-tool: [PROTECT-TOOL:] ",34))
- {
- char *p, *pend;
-
- p = buffer + 34;
- pend = strchr (p, ' ');
- if (pend)
- *pend = 0;
- if ( !strcmp (p, "secretkey-stored"))
- {
- stats->count++;
- stats->secret_read++;
- stats->secret_imported++;
- }
- else if ( !strcmp (p, "secretkey-exists"))
- {
- stats->count++;
- stats->secret_read++;
- stats->secret_dups++;
- }
- else if ( !strcmp (p, "bad-passphrase"))
- {
-
- }
- }
- else
- {
- log_info ("%s", buffer);
- if (!strncmp (buffer, "gpg-protect-tool: "
- "possibly bad passphrase given",46))
- bad_pass++;
- }
- }
- pos = 0;
- cont_line = (c != '\n');
- }
+ log_error ("failed to created S-expression from key: %s\n",
+ gpg_strerror (err));
+ goto leave;
}
- if (pos)
+ /* Compute the keygrip. */
+ if (!gcry_pk_get_keygrip (s_key, grip))
{
- buffer[pos] = 0;
- if (cont_line)
- log_printf ("%s\n", buffer);
- else
- log_info ("%s\n", buffer);
+ err = gpg_error (GPG_ERR_GENERAL);
+ log_error ("can't calculate keygrip\n");
+ goto leave;
}
+ log_printhex ("keygrip=", grip, 20);
+ /* Convert to canonical encoding using a function which pads it to a
+ multiple of 64 bits. We need this padding for AESWRAP. */
+ err = make_canon_sexp_pad (s_key, &key, &keylen);
+ if (err)
+ {
+ log_error ("error creating canonical S-expression\n");
+ goto leave;
+ }
+ gcry_sexp_release (s_key);
+ s_key = NULL;
- /* If we found no error in the output of the child, setup a suitable
- error code, which will later be reset if the exit status of the
- child is 0. */
- if (!child_err)
- child_err = gpg_error (GPG_ERR_DECRYPT_FAILED);
+ /* Get the current KEK. */
+ err = gpgsm_agent_keywrap_key (ctrl, 0, &kek, &keklen);
+ if (err)
+ {
+ log_error ("error getting the KEK: %s\n", gpg_strerror (err));
+ goto leave;
+ }
- cleanup:
- es_fclose (tmpfp);
- es_fclose (fp);
- if (pid != -1)
+ /* Wrap the key. */
+ err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
+ GCRY_CIPHER_MODE_AESWRAP, 0);
+ if (err)
+ goto leave;
+ err = gcry_cipher_setkey (cipherhd, kek, keklen);
+ if (err)
+ goto leave;
+ xfree (kek);
+ kek = NULL;
+
+ wrappedkeylen = keylen + 8;
+ wrappedkey = xtrymalloc (wrappedkeylen);
+ if (!wrappedkey)
{
- if (!gnupg_wait_process (pgmname, pid, 0, NULL))
- child_err = 0;
- gnupg_release_process (pid);
+ err = gpg_error_from_syserror ();
+ goto leave;
}
- if (!err)
- err = child_err;
+
+ err = gcry_cipher_encrypt (cipherhd, wrappedkey, wrappedkeylen, key, keylen);
if (err)
+ goto leave;
+ xfree (key);
+ key = NULL;
+ gcry_cipher_close (cipherhd);
+ cipherhd = NULL;
+
+ /* Send the wrapped key to the agent. */
+ err = gpgsm_agent_import_key (ctrl, wrappedkey, wrappedkeylen);
+ if (!err)
{
- es_fclose (certfp);
+ stats->count++;
+ stats->secret_read++;
+ stats->secret_imported++;
}
- else
- *retfp = certfp;
+ else if ( gpg_err_code (err) == GPG_ERR_EEXIST )
+ {
+ err = 0;
+ stats->count++;
+ stats->secret_read++;
+ stats->secret_dups++;
+ }
+
+ /* If we did not get an error from storing the secret key we return
+ a possible error from parsing the certificates. We do this after
+ storing the secret keys so that a bad certificate does not
+ inhibit our chance to store the secret key. */
+ if (!err && store_cert_parm.err)
+ err = store_cert_parm.err;
+
+ leave:
+ if (kparms)
+ {
+ for (i=0; i < 8; i++)
+ gcry_mpi_release (kparms[i]);
+ gcry_free (kparms);
+ kparms = NULL;
+ }
+ xfree (key);
+ gcry_sexp_release (s_key);
+ xfree (passphrase);
+ gcry_cipher_close (cipherhd);
+ xfree (wrappedkey);
+ xfree (kek);
+ xfree (get_membuf (&p12mbuf, NULL));
+ xfree (p12buffer);
if (bad_pass)
{
diff --git a/sm/minip12.c b/sm/minip12.c
new file mode 100644
index 000000000..f50fbd419
--- /dev/null
+++ b/sm/minip12.c
@@ -0,0 +1,2363 @@
+/* minip12.c - A minimal pkcs-12 implementation.
+ * Copyright (C) 2002, 2003, 2004, 2006 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/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <gcrypt.h>
+#include <errno.h>
+
+#ifdef TEST
+#include <sys/stat.h>
+#include <unistd.h>
+#endif
+
+#include "../common/logging.h"
+#include "../common/utf8conv.h"
+#include "minip12.h"
+
+#ifndef DIM
+#define DIM(v) (sizeof(v)/sizeof((v)[0]))
+#endif
+
+
+enum
+{
+ UNIVERSAL = 0,
+ APPLICATION = 1,
+ ASNCONTEXT = 2,
+ PRIVATE = 3
+};
+
+
+enum
+{
+ TAG_NONE = 0,
+ TAG_BOOLEAN = 1,
+ TAG_INTEGER = 2,
+ TAG_BIT_STRING = 3,
+ TAG_OCTET_STRING = 4,
+ TAG_NULL = 5,
+ TAG_OBJECT_ID = 6,
+ TAG_OBJECT_DESCRIPTOR = 7,
+ TAG_EXTERNAL = 8,
+ TAG_REAL = 9,
+ TAG_ENUMERATED = 10,
+ TAG_EMBEDDED_PDV = 11,
+ TAG_UTF8_STRING = 12,
+ TAG_REALTIVE_OID = 13,
+ TAG_SEQUENCE = 16,
+ TAG_SET = 17,
+ TAG_NUMERIC_STRING = 18,
+ TAG_PRINTABLE_STRING = 19,
+ TAG_TELETEX_STRING = 20,
+ TAG_VIDEOTEX_STRING = 21,
+ TAG_IA5_STRING = 22,
+ TAG_UTC_TIME = 23,
+ TAG_GENERALIZED_TIME = 24,
+ TAG_GRAPHIC_STRING = 25,
+ TAG_VISIBLE_STRING = 26,
+ TAG_GENERAL_STRING = 27,
+ TAG_UNIVERSAL_STRING = 28,
+ TAG_CHARACTER_STRING = 29,
+ TAG_BMP_STRING = 30
+};
+
+
+static unsigned char const oid_data[9] = {
+ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x01 };
+static unsigned char const oid_encryptedData[9] = {
+ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x06 };
+static unsigned char const oid_pkcs_12_keyBag[11] = {
+ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01, 0x01 };
+static unsigned char const oid_pkcs_12_pkcs_8ShroudedKeyBag[11] = {
+ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01, 0x02 };
+static unsigned char const oid_pkcs_12_CertBag[11] = {
+ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01, 0x03 };
+static unsigned char const oid_pkcs_12_CrlBag[11] = {
+ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01, 0x04 };
+
+static unsigned char const oid_pbeWithSHAAnd3_KeyTripleDES_CBC[10] = {
+ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x03 };
+static unsigned char const oid_pbeWithSHAAnd40BitRC2_CBC[10] = {
+ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x06 };
+static unsigned char const oid_x509Certificate_for_pkcs_12[10] = {
+ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x16, 0x01 };
+
+
+static unsigned char const oid_rsaEncryption[9] = {
+ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 };
+
+
+static unsigned char const data_3desiter2048[30] = {
+ 0x30, 0x1C, 0x06, 0x0A, 0x2A, 0x86, 0x48, 0x86,
+ 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x03, 0x30, 0x0E,
+ 0x04, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0x02, 0x02, 0x08, 0x00 };
+#define DATA_3DESITER2048_SALT_OFF 18
+
+static unsigned char const data_rc2iter2048[30] = {
+ 0x30, 0x1C, 0x06, 0x0A, 0x2A, 0x86, 0x48, 0x86,
+ 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x06, 0x30, 0x0E,
+ 0x04, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0x02, 0x02, 0x08, 0x00 };
+#define DATA_RC2ITER2048_SALT_OFF 18
+
+static unsigned char const data_mactemplate[51] = {
+ 0x30, 0x31, 0x30, 0x21, 0x30, 0x09, 0x06, 0x05,
+ 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04,
+ 0x14, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0x04, 0x08, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x02,
+ 0x02, 0x08, 0x00 };
+#define DATA_MACTEMPLATE_MAC_OFF 17
+#define DATA_MACTEMPLATE_SALT_OFF 39
+
+static unsigned char const data_attrtemplate[106] = {
+ 0x31, 0x7c, 0x30, 0x55, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x14, 0x31,
+ 0x48, 0x1e, 0x46, 0x00, 0x47, 0x00, 0x6e, 0x00,
+ 0x75, 0x00, 0x50, 0x00, 0x47, 0x00, 0x20, 0x00,
+ 0x65, 0x00, 0x78, 0x00, 0x70, 0x00, 0x6f, 0x00,
+ 0x72, 0x00, 0x74, 0x00, 0x65, 0x00, 0x64, 0x00,
+ 0x20, 0x00, 0x63, 0x00, 0x65, 0x00, 0x72, 0x00,
+ 0x74, 0x00, 0x69, 0x00, 0x66, 0x00, 0x69, 0x00,
+ 0x63, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00,
+ 0x20, 0x00, 0x66, 0x00, 0x66, 0x00, 0x66, 0x00,
+ 0x66, 0x00, 0x66, 0x00, 0x66, 0x00, 0x66, 0x00,
+ 0x66, 0x30, 0x23, 0x06, 0x09, 0x2a, 0x86, 0x48,
+ 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x15, 0x31, 0x16,
+ 0x04, 0x14 }; /* Need to append SHA-1 digest. */
+#define DATA_ATTRTEMPLATE_KEYID_OFF 73
+
+struct buffer_s
+{
+ unsigned char *buffer;
+ size_t length;
+};
+
+
+struct tag_info
+{
+ int class;
+ int is_constructed;
+ unsigned long tag;
+ unsigned long length; /* length part of the TLV */
+ int nhdr;
+ int ndef; /* It is an indefinite length */
+};
+
+
+/* Parse the buffer at the address BUFFER which is of SIZE and return
+ the tag and the length part from the TLV triplet. Update BUFFER
+ and SIZE on success. Checks that the encoded length does not
+ exhaust the length of the provided buffer. */
+static int
+parse_tag (unsigned char const **buffer, size_t *size, struct tag_info *ti)
+{
+ int c;
+ unsigned long tag;
+ const unsigned char *buf = *buffer;
+ size_t length = *size;
+
+ ti->length = 0;
+ ti->ndef = 0;
+ ti->nhdr = 0;
+
+ /* Get the tag */
+ if (!length)
+ return -1; /* premature eof */
+ c = *buf++; length--;
+ ti->nhdr++;
+
+ ti->class = (c & 0xc0) >> 6;
+ ti->is_constructed = !!(c & 0x20);
+ tag = c & 0x1f;
+
+ if (tag == 0x1f)
+ {
+ tag = 0;
+ do
+ {
+ tag <<= 7;
+ if (!length)
+ return -1; /* premature eof */
+ c = *buf++; length--;
+ ti->nhdr++;
+ tag |= c & 0x7f;
+ }
+ while (c & 0x80);
+ }
+ ti->tag = tag;
+
+ /* Get the length */
+ if (!length)
+ return -1; /* prematureeof */
+ c = *buf++; length--;
+ ti->nhdr++;
+
+ if ( !(c & 0x80) )
+ ti->length = c;
+ else if (c == 0x80)
+ ti->ndef = 1;
+ else if (c == 0xff)
+ return -1; /* forbidden length value */
+ else
+ {
+ unsigned long len = 0;
+ int count = c & 0x7f;
+
+ for (; count; count--)
+ {
+ len <<= 8;
+ if (!length)
+ return -1; /* premature_eof */
+ c = *buf++; length--;
+ ti->nhdr++;
+ len |= c & 0xff;
+ }
+ ti->length = len;
+ }
+
+ if (ti->class == UNIVERSAL && !ti->tag)
+ ti->length = 0;
+
+ if (ti->length > length)
+ return -1; /* data larger than buffer. */
+
+ *buffer = buf;
+ *size = length;
+ return 0;
+}
+
+
+/* Given an ASN.1 chunk of a structure like:
+
+ 24 NDEF: OCTET STRING -- This is not passed to us
+ 04 1: OCTET STRING -- INPUT point s to here
+ : 30
+ 04 1: OCTET STRING
+ : 80
+ [...]
+ 04 2: OCTET STRING
+ : 00 00
+ : } -- This denotes a Null tag and are the last
+ -- two bytes in INPUT.
+
+ Create a new buffer with the content of that octet string. INPUT
+ is the orginal buffer with a length as stored at LENGTH. Returns
+ NULL on error or a new malloced buffer with the length of this new
+ buffer stored at LENGTH and the number of bytes parsed from input
+ are added to the value stored at INPUT_CONSUMED. INPUT_CONSUMED is
+ allowed to be passed as NULL if the caller is not interested in
+ this value. */
+static unsigned char *
+cram_octet_string (const unsigned char *input, size_t *length,
+ size_t *input_consumed)
+{
+ const unsigned char *s = input;
+ size_t n = *length;
+ unsigned char *output, *d;
+ struct tag_info ti;
+
+ /* Allocate output buf. We know that it won't be longer than the
+ input buffer. */
+ d = output = gcry_malloc (n);
+ if (!output)
+ goto bailout;
+
+ for (;;)
+ {
+ if (parse_tag (&s, &n, &ti))
+ goto bailout;
+ if (ti.class == UNIVERSAL && ti.tag == TAG_OCTET_STRING
+ && !ti.ndef && !ti.is_constructed)
+ {
+ memcpy (d, s, ti.length);
+ s += ti.length;
+ d += ti.length;
+ n -= ti.length;
+ }
+ else if (ti.class == UNIVERSAL && !ti.tag && !ti.is_constructed)
+ break; /* Ready */
+ else
+ goto bailout;
+ }
+
+
+ *length = d - output;
+ if (input_consumed)
+ *input_consumed += s - input;
+ return output;
+
+ bailout:
+ if (input_consumed)
+ *input_consumed += s - input;
+ gcry_free (output);
+ return NULL;
+}
+
+
+
+static int
+string_to_key (int id, char *salt, size_t saltlen, int iter, const char *pw,
+ int req_keylen, unsigned char *keybuf)
+{
+ int rc, i, j;
+ gcry_md_hd_t md;
+ gcry_mpi_t num_b1 = NULL;
+ int pwlen;
+ unsigned char hash[20], buf_b[64], buf_i[128], *p;
+ size_t cur_keylen;
+ size_t n;
+
+ cur_keylen = 0;
+ pwlen = strlen (pw);
+ if (pwlen > 63/2)
+ {
+ log_error ("password too long\n");
+ return -1;
+ }
+
+ if (saltlen < 8)
+ {
+ log_error ("salt too short\n");
+ return -1;
+ }
+
+ /* Store salt and password in BUF_I */
+ p = buf_i;
+ for(i=0; i < 64; i++)
+ *p++ = salt [i%saltlen];
+ for(i=j=0; i < 64; i += 2)
+ {
+ *p++ = 0;
+ *p++ = pw[j];
+ if (++j > pwlen) /* Note, that we include the trailing zero */
+ j = 0;
+ }
+
+ for (;;)
+ {
+ rc = gcry_md_open (&md, GCRY_MD_SHA1, 0);
+ if (rc)
+ {
+ log_error ( "gcry_md_open failed: %s\n", gpg_strerror (rc));
+ return rc;
+ }
+ for(i=0; i < 64; i++)
+ gcry_md_putc (md, id);
+ gcry_md_write (md, buf_i, 128);
+ memcpy (hash, gcry_md_read (md, 0), 20);
+ gcry_md_close (md);
+ for (i=1; i < iter; i++)
+ gcry_md_hash_buffer (GCRY_MD_SHA1, hash, hash, 20);
+
+ for (i=0; i < 20 && cur_keylen < req_keylen; i++)
+ keybuf[cur_keylen++] = hash[i];
+ if (cur_keylen == req_keylen)
+ {
+ gcry_mpi_release (num_b1);
+ return 0; /* ready */
+ }
+
+ /* need more bytes. */
+ for(i=0; i < 64; i++)
+ buf_b[i] = hash[i % 20];
+ rc = gcry_mpi_scan (&num_b1, GCRYMPI_FMT_USG, buf_b, 64, &n);
+ if (rc)
+ {
+ log_error ( "gcry_mpi_scan failed: %s\n", gpg_strerror (rc));
+ return -1;
+ }
+ gcry_mpi_add_ui (num_b1, num_b1, 1);
+ for (i=0; i < 128; i += 64)
+ {
+ gcry_mpi_t num_ij;
+
+ rc = gcry_mpi_scan (&num_ij, GCRYMPI_FMT_USG, buf_i + i, 64, &n);
+ if (rc)
+ {
+ log_error ( "gcry_mpi_scan failed: %s\n",
+ gpg_strerror (rc));
+ return -1;
+ }
+ gcry_mpi_add (num_ij, num_ij, num_b1);
+ gcry_mpi_clear_highbit (num_ij, 64*8);
+ rc = gcry_mpi_print (GCRYMPI_FMT_USG, buf_i + i, 64, &n, num_ij);
+ if (rc)
+ {
+ log_error ( "gcry_mpi_print failed: %s\n",
+ gpg_strerror (rc));
+ return -1;
+ }
+ gcry_mpi_release (num_ij);
+ }
+ }
+}
+
+
+static int
+set_key_iv (gcry_cipher_hd_t chd, char *salt, size_t saltlen, int iter,
+ const char *pw, int keybytes)
+{
+ unsigned char keybuf[24];
+ int rc;
+
+ assert (keybytes == 5 || keybytes == 24);
+ if (string_to_key (1, salt, saltlen, iter, pw, keybytes, keybuf))
+ return -1;
+ rc = gcry_cipher_setkey (chd, keybuf, keybytes);
+ if (rc)
+ {
+ log_error ( "gcry_cipher_setkey failed: %s\n", gpg_strerror (rc));
+ return -1;
+ }
+
+ if (string_to_key (2, salt, saltlen, iter, pw, 8, keybuf))
+ return -1;
+ rc = gcry_cipher_setiv (chd, keybuf, 8);
+ if (rc)
+ {
+ log_error ("gcry_cipher_setiv failed: %s\n", gpg_strerror (rc));
+ return -1;
+ }
+ return 0;
+}
+
+
+static void
+crypt_block (unsigned char *buffer, size_t length, char *salt, size_t saltlen,
+ int iter, const char *pw, int cipher_algo, int encrypt)
+{
+ gcry_cipher_hd_t chd;
+ int rc;
+
+ rc = gcry_cipher_open (&chd, cipher_algo, GCRY_CIPHER_MODE_CBC, 0);
+ if (rc)
+ {
+ log_error ( "gcry_cipher_open failed: %s\n", gpg_strerror(rc));
+ wipememory (buffer, length);
+ return;
+ }
+ if (set_key_iv (chd, salt, saltlen, iter, pw,
+ cipher_algo == GCRY_CIPHER_RFC2268_40? 5:24))
+ {
+ wipememory (buffer, length);
+ goto leave;
+ }
+
+ rc = encrypt? gcry_cipher_encrypt (chd, buffer, length, NULL, 0)
+ : gcry_cipher_decrypt (chd, buffer, length, NULL, 0);
+
+ if (rc)
+ {
+ wipememory (buffer, length);
+ log_error ( "en/de-crytion failed: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+
+ leave:
+ gcry_cipher_close (chd);
+}
+
+
+/* Decrypt a block of data and try several encodings of the key.
+ CIPHERTEXT is the encrypted data of size LENGTH bytes; PLAINTEXT is
+ a buffer of the same size to receive the decryption result. SALT,
+ SALTLEN, ITER and PW are the information required for decryption
+ and CIPHER_ALGO is the algorithm id to use. CHECK_FNC is a
+ function called with the plaintext and used to check whether the
+ decryption succeeded; i.e. that a correct passphrase has been
+ given. That function shall return true if the decryption has likely
+ succeeded. */
+static void
+decrypt_block (const void *ciphertext, unsigned char *plaintext, size_t length,
+ char *salt, size_t saltlen,
+ int iter, const char *pw, int cipher_algo,
+ int (*check_fnc) (const void *, size_t))
+{
+ static const char * const charsets[] = {
+ "", /* No conversion - use the UTF-8 passphrase direct. */
+ "ISO-8859-1",
+ "ISO-8859-15",
+ "ISO-8859-2",
+ "ISO-8859-3",
+ "ISO-8859-4",
+ "ISO-8859-5",
+ "ISO-8859-6",
+ "ISO-8859-7",
+ "ISO-8859-8",
+ "ISO-8859-9",
+ "KOI8-R",
+ "IBM437",
+ "IBM850",
+ "EUC-JP",
+ "BIG5",
+ NULL
+ };
+ int charsetidx = 0;
+ char *convertedpw = NULL; /* Malloced and converted password or NULL. */
+ size_t convertedpwsize = 0; /* Allocated length. */
+
+ for (charsetidx=0; charsets[charsetidx]; charsetidx++)
+ {
+ if (*charsets[charsetidx])
+ {
+ jnlib_iconv_t cd;
+ const char *inptr;
+ char *outptr;
+ size_t inbytes, outbytes;
+
+ if (!convertedpw)
+ {
+ /* We assume one byte encodings. Thus we can allocate
+ the buffer of the same size as the original
+ passphrase; the result will actually be shorter
+ then. */
+ convertedpwsize = strlen (pw) + 1;
+ convertedpw = gcry_malloc_secure (convertedpwsize);
+ if (!convertedpw)
+ {
+ log_info ("out of secure memory while"
+ " converting passphrase\n");
+ break; /* Give up. */
+ }
+ }
+
+ cd = jnlib_iconv_open (charsets[charsetidx], "utf-8");
+ if (cd == (jnlib_iconv_t)(-1))
+ continue;
+
+ inptr = pw;
+ inbytes = strlen (pw);
+ outptr = convertedpw;
+ outbytes = convertedpwsize - 1;
+ if ( jnlib_iconv (cd, (const char **)&inptr, &inbytes,
+ &outptr, &outbytes) == (size_t)-1)
+ {
+ jnlib_iconv_close (cd);
+ continue;
+ }
+ *outptr = 0;
+ jnlib_iconv_close (cd);
+ log_info ("decryption failed; trying charset `%s'\n",
+ charsets[charsetidx]);
+ }
+ memcpy (plaintext, ciphertext, length);
+ crypt_block (plaintext, length, salt, saltlen, iter,
+ convertedpw? convertedpw:pw, cipher_algo, 0);
+ if (check_fnc (plaintext, length))
+ break; /* Decryption succeeded. */
+ }
+ gcry_free (convertedpw);
+}
+
+
+/* Return true if the decryption of an bag_encrypted_data object has
+ likely succeeded. */
+static int
+bag_decrypted_data_p (const void *plaintext, size_t length)
+{
+ struct tag_info ti;
+ const unsigned char *p = plaintext;
+ size_t n = length;
+
+ /* { */
+ /* # warning debug code is enabled */
+ /* FILE *fp = fopen ("tmp-rc2-plain.der", "wb"); */
+ /* if (!fp || fwrite (p, n, 1, fp) != 1) */
+ /* exit (2); */
+ /* fclose (fp); */
+ /* } */
+
+ if (parse_tag (&p, &n, &ti))
+ return 0;
+ if (ti.class || ti.tag != TAG_SEQUENCE)
+ return 0;
+ if (parse_tag (&p, &n, &ti))
+ return 0;
+
+ return 1;
+}
+
+/* Note: If R_RESULT is passed as NULL, a key object as already be
+ processed and thus we need to skip it here. */
+static int
+parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
+ int startoffset, size_t *r_consumed, const char *pw,
+ void (*certcb)(void*, const unsigned char*, size_t),
+ void *certcbarg, gcry_mpi_t **r_result,
+ int *r_badpass)
+{
+ struct tag_info ti;
+ const unsigned char *p = buffer;
+ const unsigned char *p_start = buffer;
+ size_t n = length;
+ const char *where;
+ char salt[20];
+ size_t saltlen;
+ unsigned int iter;
+ unsigned char *plain = NULL;
+ int bad_pass = 0;
+ unsigned char *cram_buffer = NULL;
+ size_t consumed = 0; /* Number of bytes consumed from the orginal buffer. */
+ int is_3des = 0;
+ gcry_mpi_t *result = NULL;
+ int result_count;
+
+ if (r_result)
+ *r_result = NULL;
+ where = "start";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class != ASNCONTEXT || ti.tag)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.tag != TAG_SEQUENCE)
+ goto bailout;
+
+ where = "bag.encryptedData.version";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.tag != TAG_INTEGER || ti.length != 1 || *p != 0)
+ goto bailout;
+ p++; n--;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.tag != TAG_SEQUENCE)
+ goto bailout;
+
+ where = "bag.encryptedData.data";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.tag != TAG_OBJECT_ID || ti.length != DIM(oid_data)
+ || memcmp (p, oid_data, DIM(oid_data)))
+ goto bailout;
+ p += DIM(oid_data);
+ n -= DIM(oid_data);
+
+ where = "bag.encryptedData.keyinfo";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (!ti.class && ti.tag == TAG_OBJECT_ID
+ && ti.length == DIM(oid_pbeWithSHAAnd40BitRC2_CBC)
+ && !memcmp (p, oid_pbeWithSHAAnd40BitRC2_CBC,
+ DIM(oid_pbeWithSHAAnd40BitRC2_CBC)))
+ {
+ p += DIM(oid_pbeWithSHAAnd40BitRC2_CBC);
+ n -= DIM(oid_pbeWithSHAAnd40BitRC2_CBC);
+ }
+ else if (!ti.class && ti.tag == TAG_OBJECT_ID
+ && ti.length == DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC)
+ && !memcmp (p, oid_pbeWithSHAAnd3_KeyTripleDES_CBC,
+ DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC)))
+ {
+ p += DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC);
+ n -= DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC);
+ is_3des = 1;
+ }
+ else
+ goto bailout;
+
+ where = "rc2or3des-params";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_OCTET_STRING
+ || ti.length < 8 || ti.length > 20 )
+ goto bailout;
+ saltlen = ti.length;
+ memcpy (salt, p, saltlen);
+ p += saltlen;
+ n -= saltlen;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_INTEGER || !ti.length )
+ goto bailout;
+ for (iter=0; ti.length; ti.length--)
+ {
+ iter <<= 8;
+ iter |= (*p++) & 0xff;
+ n--;
+ }
+
+ where = "rc2or3des-ciphertext";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+
+ consumed = p - p_start;
+ if (ti.class == ASNCONTEXT && ti.tag == 0 && ti.is_constructed && ti.ndef)
+ {
+ /* Mozilla exported certs now come with single byte chunks of
+ octect strings. (Mozilla Firefox 1.0.4). Arghh. */
+ where = "cram-rc2or3des-ciphertext";
+ cram_buffer = cram_octet_string ( p, &n, &consumed);
+ if (!cram_buffer)
+ goto bailout;
+ p = p_start = cram_buffer;
+ if (r_consumed)
+ *r_consumed = consumed;
+ r_consumed = NULL; /* Ugly hack to not update that value any further. */
+ ti.length = n;
+ }
+ else if (ti.class == ASNCONTEXT && ti.tag == 0 && ti.length )
+ ;
+ else
+ goto bailout;
+
+ log_info ("%lu bytes of %s encrypted text\n",ti.length,is_3des?"3DES":"RC2");
+
+ plain = gcry_malloc_secure (ti.length);
+ if (!plain)
+ {
+ log_error ("error allocating decryption buffer\n");
+ goto bailout;
+ }
+ decrypt_block (p, plain, ti.length, salt, saltlen, iter, pw,
+ is_3des? GCRY_CIPHER_3DES : GCRY_CIPHER_RFC2268_40,
+ bag_decrypted_data_p);
+ n = ti.length;
+ startoffset = 0;
+ p_start = p = plain;
+
+ where = "outer.outer.seq";
+ if (parse_tag (&p, &n, &ti))
+ {
+ bad_pass = 1;
+ goto bailout;
+ }
+ if (ti.class || ti.tag != TAG_SEQUENCE)
+ {
+ bad_pass = 1;
+ goto bailout;
+ }
+
+ if (parse_tag (&p, &n, &ti))
+ {
+ bad_pass = 1;
+ goto bailout;
+ }
+
+ /* Loop over all certificates inside the bag. */
+ while (n)
+ {
+ int iscrlbag = 0;
+ int iskeybag = 0;
+
+ where = "certbag.nextcert";
+ if (ti.class || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+
+ where = "certbag.objectidentifier";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_OBJECT_ID)
+ goto bailout;
+ if ( ti.length == DIM(oid_pkcs_12_CertBag)
+ && !memcmp (p, oid_pkcs_12_CertBag, DIM(oid_pkcs_12_CertBag)))
+ {
+ p += DIM(oid_pkcs_12_CertBag);
+ n -= DIM(oid_pkcs_12_CertBag);
+ }
+ else if ( ti.length == DIM(oid_pkcs_12_CrlBag)
+ && !memcmp (p, oid_pkcs_12_CrlBag, DIM(oid_pkcs_12_CrlBag)))
+ {
+ p += DIM(oid_pkcs_12_CrlBag);
+ n -= DIM(oid_pkcs_12_CrlBag);
+ iscrlbag = 1;
+ }
+ else if ( ti.length == DIM(oid_pkcs_12_keyBag)
+ && !memcmp (p, oid_pkcs_12_keyBag, DIM(oid_pkcs_12_keyBag)))
+ {
+ /* The TrustedMIME plugin for MS Outlook started to create
+ files with just one outer 3DES encrypted container and
+ inside the certificates as well as the key. */
+ p += DIM(oid_pkcs_12_keyBag);
+ n -= DIM(oid_pkcs_12_keyBag);
+ iskeybag = 1;
+ }
+ else
+ goto bailout;
+
+ where = "certbag.before.certheader";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class != ASNCONTEXT || ti.tag)
+ goto bailout;
+ if (iscrlbag)
+ {
+ log_info ("skipping unsupported crlBag\n");
+ p += ti.length;
+ n -= ti.length;
+ }
+ else if (iskeybag && (result || !r_result))
+ {
+ log_info ("one keyBag already processed; skipping this one\n");
+ p += ti.length;
+ n -= ti.length;
+ }
+ else if (iskeybag)
+ {
+ int len;
+
+ log_info ("processing simple keyBag\n");
+
+ /* Fixme: This code is duplicated from parse_bag_data. */
+ if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER
+ || ti.length != 1 || *p)
+ goto bailout;
+ p++; n--;
+ if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+ len = ti.length;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (len < ti.nhdr)
+ goto bailout;
+ len -= ti.nhdr;
+ if (ti.class || ti.tag != TAG_OBJECT_ID
+ || ti.length != DIM(oid_rsaEncryption)
+ || memcmp (p, oid_rsaEncryption,
+ DIM(oid_rsaEncryption)))
+ goto bailout;
+ p += DIM (oid_rsaEncryption);
+ n -= DIM (oid_rsaEncryption);
+ if (len < ti.length)
+ goto bailout;
+ len -= ti.length;
+ if (n < len)
+ goto bailout;
+ p += len;
+ n -= len;
+ if ( parse_tag (&p, &n, &ti)
+ || ti.class || ti.tag != TAG_OCTET_STRING)
+ goto bailout;
+ if ( parse_tag (&p, &n, &ti)
+ || ti.class || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+ len = ti.length;
+
+ result = gcry_calloc (10, sizeof *result);
+ if (!result)
+ {
+ log_error ( "error allocating result array\n");
+ goto bailout;
+ }
+ result_count = 0;
+
+ where = "reading.keybag.key-parameters";
+ for (result_count = 0; len && result_count < 9;)
+ {
+ if ( parse_tag (&p, &n, &ti)
+ || ti.class || ti.tag != TAG_INTEGER)
+ goto bailout;
+ if (len < ti.nhdr)
+ goto bailout;
+ len -= ti.nhdr;
+ if (len < ti.length)
+ goto bailout;
+ len -= ti.length;
+ if (!result_count && ti.length == 1 && !*p)
+ ; /* ignore the very first one if it is a 0 */
+ else
+ {
+ int rc;
+
+ rc = gcry_mpi_scan (result+result_count, GCRYMPI_FMT_USG, p,
+ ti.length, NULL);
+ if (rc)
+ {
+ log_error ("error parsing key parameter: %s\n",
+ gpg_strerror (rc));
+ goto bailout;
+ }
+ result_count++;
+ }
+ p += ti.length;
+ n -= ti.length;
+ }
+ if (len)
+ goto bailout;
+ }
+ else
+ {
+ log_info ("processing certBag\n");
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_OBJECT_ID
+ || ti.length != DIM(oid_x509Certificate_for_pkcs_12)
+ || memcmp (p, oid_x509Certificate_for_pkcs_12,
+ DIM(oid_x509Certificate_for_pkcs_12)))
+ goto bailout;
+ p += DIM(oid_x509Certificate_for_pkcs_12);
+ n -= DIM(oid_x509Certificate_for_pkcs_12);
+
+ where = "certbag.before.octetstring";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class != ASNCONTEXT || ti.tag)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_OCTET_STRING || ti.ndef)
+ goto bailout;
+
+ /* Return the certificate. */
+ if (certcb)
+ certcb (certcbarg, p, ti.length);
+
+ p += ti.length;
+ n -= ti.length;
+ }
+
+ /* Ugly hack to cope with the padding: Forget about the rest if
+ that is less or equal to the cipher's block length. We can
+ reasonable assume that all valid data will be longer than
+ just one block. */
+ if (n <= 8)
+ n = 0;
+
+ /* Skip the optional SET with the pkcs12 cert attributes. */
+ if (n)
+ {
+ where = "bag.attributes";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (!ti.class && ti.tag == TAG_SEQUENCE)
+ ; /* No attributes. */
+ else if (!ti.class && ti.tag == TAG_SET && !ti.ndef)
+ { /* The optional SET. */
+ p += ti.length;
+ n -= ti.length;
+ if (n <= 8)
+ n = 0;
+ if (n && parse_tag (&p, &n, &ti))
+ goto bailout;
+ }
+ else
+ goto bailout;
+ }
+ }
+
+ if (r_consumed)
+ *r_consumed = consumed;
+ gcry_free (plain);
+ gcry_free (cram_buffer);
+ if (r_result)
+ *r_result = result;
+ return 0;
+
+ bailout:
+ if (result)
+ {
+ int i;
+
+ for (i=0; result[i]; i++)
+ gcry_mpi_release (result[i]);
+ gcry_free (result);
+ }
+ if (r_consumed)
+ *r_consumed = consumed;
+ gcry_free (plain);
+ gcry_free (cram_buffer);
+ log_error ("encryptedData error at \"%s\", offset %u\n",
+ where, (unsigned int)((p - p_start)+startoffset));
+ if (bad_pass)
+ {
+ /* Note, that the following string might be used by other programs
+ to check for a bad passphrase; it should therefore not be
+ translated or changed. */
+ log_error ("possibly bad passphrase given\n");
+ *r_badpass = 1;
+ }
+ return -1;
+}
+
+
+/* Return true if the decryption of a bag_data object has likely
+ succeeded. */
+static int
+bag_data_p (const void *plaintext, size_t length)
+{
+ struct tag_info ti;
+ const unsigned char *p = plaintext;
+ size_t n = length;
+
+/* { */
+/* # warning debug code is enabled */
+/* FILE *fp = fopen ("tmp-3des-plain-key.der", "wb"); */
+/* if (!fp || fwrite (p, n, 1, fp) != 1) */
+/* exit (2); */
+/* fclose (fp); */
+/* } */
+
+ if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE)
+ return 0;
+ if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER
+ || ti.length != 1 || *p)
+ return 0;
+
+ return 1;
+}
+
+
+static gcry_mpi_t *
+parse_bag_data (const unsigned char *buffer, size_t length, int startoffset,
+ size_t *r_consumed, const char *pw)
+{
+ int rc;
+ struct tag_info ti;
+ const unsigned char *p = buffer;
+ const unsigned char *p_start = buffer;
+ size_t n = length;
+ const char *where;
+ char salt[20];
+ size_t saltlen;
+ unsigned int iter;
+ int len;
+ unsigned char *plain = NULL;
+ gcry_mpi_t *result = NULL;
+ int result_count, i;
+ unsigned char *cram_buffer = NULL;
+ size_t consumed = 0; /* Number of bytes consumed from the orginal buffer. */
+
+ where = "start";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class != ASNCONTEXT || ti.tag)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_OCTET_STRING)
+ goto bailout;
+
+ consumed = p - p_start;
+ if (ti.is_constructed && ti.ndef)
+ {
+ /* Mozilla exported certs now come with single byte chunks of
+ octect strings. (Mozilla Firefox 1.0.4). Arghh. */
+ where = "cram-data.outersegs";
+ cram_buffer = cram_octet_string ( p, &n, &consumed);
+ if (!cram_buffer)
+ goto bailout;
+ p = p_start = cram_buffer;
+ if (r_consumed)
+ *r_consumed = consumed;
+ r_consumed = NULL; /* Ugly hack to not update that value any further. */
+ }
+
+
+ where = "data.outerseqs";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+
+ where = "data.objectidentifier";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_OBJECT_ID
+ || ti.length != DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag)
+ || memcmp (p, oid_pkcs_12_pkcs_8ShroudedKeyBag,
+ DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag)))
+ goto bailout;
+ p += DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag);
+ n -= DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag);
+
+ where = "shrouded,outerseqs";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class != ASNCONTEXT || ti.tag)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_OBJECT_ID
+ || ti.length != DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC)
+ || memcmp (p, oid_pbeWithSHAAnd3_KeyTripleDES_CBC,
+ DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC)))
+ goto bailout;
+ p += DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC);
+ n -= DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC);
+
+ where = "3des-params";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_OCTET_STRING
+ || ti.length < 8 || ti.length > 20)
+ goto bailout;
+ saltlen = ti.length;
+ memcpy (salt, p, saltlen);
+ p += saltlen;
+ n -= saltlen;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_INTEGER || !ti.length )
+ goto bailout;
+ for (iter=0; ti.length; ti.length--)
+ {
+ iter <<= 8;
+ iter |= (*p++) & 0xff;
+ n--;
+ }
+
+ where = "3des-ciphertext";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_OCTET_STRING || !ti.length )
+ goto bailout;
+
+ log_info ("%lu bytes of 3DES encrypted text\n", ti.length);
+
+ plain = gcry_malloc_secure (ti.length);
+ if (!plain)
+ {
+ log_error ("error allocating decryption buffer\n");
+ goto bailout;
+ }
+ consumed += p - p_start + ti.length;
+ decrypt_block (p, plain, ti.length, salt, saltlen, iter, pw,
+ GCRY_CIPHER_3DES,
+ bag_data_p);
+ n = ti.length;
+ startoffset = 0;
+ p_start = p = plain;
+
+ where = "decrypted-text";
+ if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER
+ || ti.length != 1 || *p)
+ goto bailout;
+ p++; n--;
+ if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+ len = ti.length;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (len < ti.nhdr)
+ goto bailout;
+ len -= ti.nhdr;
+ if (ti.class || ti.tag != TAG_OBJECT_ID
+ || ti.length != DIM(oid_rsaEncryption)
+ || memcmp (p, oid_rsaEncryption,
+ DIM(oid_rsaEncryption)))
+ goto bailout;
+ p += DIM (oid_rsaEncryption);
+ n -= DIM (oid_rsaEncryption);
+ if (len < ti.length)
+ goto bailout;
+ len -= ti.length;
+ if (n < len)
+ goto bailout;
+ p += len;
+ n -= len;
+ if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_OCTET_STRING)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+ len = ti.length;
+
+ result = gcry_calloc (10, sizeof *result);
+ if (!result)
+ {
+ log_error ( "error allocating result array\n");
+ goto bailout;
+ }
+ result_count = 0;
+
+ where = "reading.key-parameters";
+ for (result_count=0; len && result_count < 9;)
+ {
+ if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER)
+ goto bailout;
+ if (len < ti.nhdr)
+ goto bailout;
+ len -= ti.nhdr;
+ if (len < ti.length)
+ goto bailout;
+ len -= ti.length;
+ if (!result_count && ti.length == 1 && !*p)
+ ; /* ignore the very first one if it is a 0 */
+ else
+ {
+ rc = gcry_mpi_scan (result+result_count, GCRYMPI_FMT_USG, p,
+ ti.length, NULL);
+ if (rc)
+ {
+ log_error ("error parsing key parameter: %s\n",
+ gpg_strerror (rc));
+ goto bailout;
+ }
+ result_count++;
+ }
+ p += ti.length;
+ n -= ti.length;
+ }
+ if (len)
+ goto bailout;
+
+ gcry_free (cram_buffer);
+ if (r_consumed)
+ *r_consumed = consumed;
+ return result;
+
+ bailout:
+ gcry_free (plain);
+ if (result)
+ {
+ for (i=0; result[i]; i++)
+ gcry_mpi_release (result[i]);
+ gcry_free (result);
+ }
+ gcry_free (cram_buffer);
+ log_error ( "data error at \"%s\", offset %u\n",
+ where, (unsigned int)((p - buffer) + startoffset));
+ if (r_consumed)
+ *r_consumed = consumed;
+ return NULL;
+}
+
+
+/* Parse a PKCS12 object and return an array of MPI representing the
+ secret key parameters. This is a very limited implementation in
+ that it is only able to look for 3DES encoded encryptedData and
+ tries to extract the first private key object it finds. In case of
+ an error NULL is returned. CERTCB and CERRTCBARG are used to pass
+ X.509 certificates back to the caller. */
+gcry_mpi_t *
+p12_parse (const unsigned char *buffer, size_t length, const char *pw,
+ void (*certcb)(void*, const unsigned char*, size_t),
+ void *certcbarg, int *r_badpass)
+{
+ struct tag_info ti;
+ const unsigned char *p = buffer;
+ const unsigned char *p_start = buffer;
+ size_t n = length;
+ const char *where;
+ int bagseqlength, len;
+ int bagseqndef, lenndef;
+ gcry_mpi_t *result = NULL;
+ unsigned char *cram_buffer = NULL;
+
+ *r_badpass = 0;
+ where = "pfx";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.tag != TAG_SEQUENCE)
+ goto bailout;
+
+ where = "pfxVersion";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.tag != TAG_INTEGER || ti.length != 1 || *p != 3)
+ goto bailout;
+ p++; n--;
+
+ where = "authSave";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.tag != TAG_SEQUENCE)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.tag != TAG_OBJECT_ID || ti.length != DIM(oid_data)
+ || memcmp (p, oid_data, DIM(oid_data)))
+ goto bailout;
+ p += DIM(oid_data);
+ n -= DIM(oid_data);
+
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class != ASNCONTEXT || ti.tag)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class != UNIVERSAL || ti.tag != TAG_OCTET_STRING)
+ goto bailout;
+
+ if (ti.is_constructed && ti.ndef)
+ {
+ /* Mozilla exported certs now come with single byte chunks of
+ octect strings. (Mozilla Firefox 1.0.4). Arghh. */
+ where = "cram-bags";
+ cram_buffer = cram_octet_string ( p, &n, NULL);
+ if (!cram_buffer)
+ goto bailout;
+ p = p_start = cram_buffer;
+ }
+
+ where = "bags";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class != UNIVERSAL || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+ bagseqndef = ti.ndef;
+ bagseqlength = ti.length;
+ while (bagseqlength || bagseqndef)
+ {
+/* log_debug ( "at offset %u\n", (p - p_start)); */
+ where = "bag-sequence";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (bagseqndef && ti.class == UNIVERSAL && !ti.tag && !ti.is_constructed)
+ break; /* Ready */
+ if (ti.class != UNIVERSAL || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+
+ if (!bagseqndef)
+ {
+ if (bagseqlength < ti.nhdr)
+ goto bailout;
+ bagseqlength -= ti.nhdr;
+ if (bagseqlength < ti.length)
+ goto bailout;
+ bagseqlength -= ti.length;
+ }
+ lenndef = ti.ndef;
+ len = ti.length;
+
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (lenndef)
+ len = ti.nhdr;
+ else
+ len -= ti.nhdr;
+
+ if (ti.tag == TAG_OBJECT_ID && ti.length == DIM(oid_encryptedData)
+ && !memcmp (p, oid_encryptedData, DIM(oid_encryptedData)))
+ {
+ size_t consumed = 0;
+
+ p += DIM(oid_encryptedData);
+ n -= DIM(oid_encryptedData);
+ if (!lenndef)
+ len -= DIM(oid_encryptedData);
+ where = "bag.encryptedData";
+ if (parse_bag_encrypted_data (p, n, (p - p_start), &consumed, pw,
+ certcb, certcbarg,
+ result? NULL : &result, r_badpass))
+ goto bailout;
+ if (lenndef)
+ len += consumed;
+ }
+ else if (ti.tag == TAG_OBJECT_ID && ti.length == DIM(oid_data)
+ && !memcmp (p, oid_data, DIM(oid_data)))
+ {
+ if (result)
+ {
+ log_info ("already got an key object, skipping this one\n");
+ p += ti.length;
+ n -= ti.length;
+ }
+ else
+ {
+ size_t consumed = 0;
+
+ p += DIM(oid_data);
+ n -= DIM(oid_data);
+ if (!lenndef)
+ len -= DIM(oid_data);
+ result = parse_bag_data (p, n, (p - p_start), &consumed, pw);
+ if (!result)
+ goto bailout;
+ if (lenndef)
+ len += consumed;
+ }
+ }
+ else
+ {
+ log_info ("unknown bag type - skipped\n");
+ p += ti.length;
+ n -= ti.length;
+ }
+
+ if (len < 0 || len > n)
+ goto bailout;
+ p += len;
+ n -= len;
+ if (lenndef)
+ {
+ /* Need to skip the Null Tag. */
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (!(ti.class == UNIVERSAL && !ti.tag && !ti.is_constructed))
+ goto bailout;
+ }
+ }
+
+ gcry_free (cram_buffer);
+ return result;
+ bailout:
+ log_error ("error at \"%s\", offset %u\n",
+ where, (unsigned int)(p - p_start));
+ if (result)
+ {
+ int i;
+
+ for (i=0; result[i]; i++)
+ gcry_mpi_release (result[i]);
+ gcry_free (result);
+ }
+ gcry_free (cram_buffer);
+ return NULL;
+}
+
+
+
+static size_t
+compute_tag_length (size_t n)
+{
+ int needed = 0;
+
+ if (n < 128)
+ needed += 2; /* tag and one length byte */
+ else if (n < 256)
+ needed += 3; /* tag, number of length bytes, 1 length byte */
+ else if (n < 65536)
+ needed += 4; /* tag, number of length bytes, 2 length bytes */
+ else
+ {
+ log_error ("object too larger to encode\n");
+ return 0;
+ }
+ return needed;
+}
+
+static unsigned char *
+store_tag_length (unsigned char *p, int tag, size_t n)
+{
+ if (tag == TAG_SEQUENCE)
+ tag |= 0x20; /* constructed */
+
+ *p++ = tag;
+ if (n < 128)
+ *p++ = n;
+ else if (n < 256)
+ {
+ *p++ = 0x81;
+ *p++ = n;
+ }
+ else if (n < 65536)
+ {
+ *p++ = 0x82;
+ *p++ = n >> 8;
+ *p++ = n;
+ }
+
+ return p;
+}
+
+
+/* Create the final PKCS-12 object from the sequences contained in
+ SEQLIST. PW is the password. That array is terminated with an NULL
+ object. */
+static unsigned char *
+create_final (struct buffer_s *sequences, const char *pw, size_t *r_length)
+{
+ int i;
+ size_t needed = 0;
+ size_t len[8], n;
+ unsigned char *macstart;
+ size_t maclen;
+ unsigned char *result, *p;
+ size_t resultlen;
+ char salt[8];
+ unsigned char keybuf[20];
+ gcry_md_hd_t md;
+ int rc;
+ int with_mac = 1;
+
+
+ /* 9 steps to create the pkcs#12 Krampf. */
+
+ /* 8. The MAC. */
+ /* We add this at step 0. */
+
+ /* 7. All the buffers. */
+ for (i=0; sequences[i].buffer; i++)
+ needed += sequences[i].length;
+
+ /* 6. This goes into a sequences. */
+ len[6] = needed;
+ n = compute_tag_length (needed);
+ needed += n;
+
+ /* 5. Encapsulate all in an octet string. */
+ len[5] = needed;
+ n = compute_tag_length (needed);
+ needed += n;
+
+ /* 4. And tag it with [0]. */
+ len[4] = needed;
+ n = compute_tag_length (needed);
+ needed += n;
+
+ /* 3. Prepend an data OID. */
+ needed += 2 + DIM (oid_data);
+
+ /* 2. Put all into a sequences. */
+ len[2] = needed;
+ n = compute_tag_length (needed);
+ needed += n;
+
+ /* 1. Prepend the version integer 3. */
+ needed += 3;
+
+ /* 0. And the final outer sequence. */
+ if (with_mac)
+ needed += DIM (data_mactemplate);
+ len[0] = needed;
+ n = compute_tag_length (needed);
+ needed += n;
+
+ /* Allocate a buffer. */
+ result = gcry_malloc (needed);
+ if (!result)
+ {
+ log_error ("error allocating buffer\n");
+ return NULL;
+ }
+ p = result;
+
+ /* 0. Store the very outer sequence. */
+ p = store_tag_length (p, TAG_SEQUENCE, len[0]);
+
+ /* 1. Store the version integer 3. */
+ *p++ = TAG_INTEGER;
+ *p++ = 1;
+ *p++ = 3;
+
+ /* 2. Store another sequence. */
+ p = store_tag_length (p, TAG_SEQUENCE, len[2]);
+
+ /* 3. Store the data OID. */
+ p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_data));
+ memcpy (p, oid_data, DIM (oid_data));
+ p += DIM (oid_data);
+
+ /* 4. Next comes a context tag. */
+ p = store_tag_length (p, 0xa0, len[4]);
+
+ /* 5. And an octet string. */
+ p = store_tag_length (p, TAG_OCTET_STRING, len[5]);
+
+ /* 6. And the inner sequence. */
+ macstart = p;
+ p = store_tag_length (p, TAG_SEQUENCE, len[6]);
+
+ /* 7. Append all the buffers. */
+ for (i=0; sequences[i].buffer; i++)
+ {
+ memcpy (p, sequences[i].buffer, sequences[i].length);
+ p += sequences[i].length;
+ }
+
+ if (with_mac)
+ {
+ /* Intermezzo to compute the MAC. */
+ maclen = p - macstart;
+ gcry_randomize (salt, 8, GCRY_STRONG_RANDOM);
+ if (string_to_key (3, salt, 8, 2048, pw, 20, keybuf))
+ {
+ gcry_free (result);
+ return NULL;
+ }
+ rc = gcry_md_open (&md, GCRY_MD_SHA1, GCRY_MD_FLAG_HMAC);
+ if (rc)
+ {
+ log_error ("gcry_md_open failed: %s\n", gpg_strerror (rc));
+ gcry_free (result);
+ return NULL;
+ }
+ rc = gcry_md_setkey (md, keybuf, 20);
+ if (rc)
+ {
+ log_error ("gcry_md_setkey failed: %s\n", gpg_strerror (rc));
+ gcry_md_close (md);
+ gcry_free (result);
+ return NULL;
+ }
+ gcry_md_write (md, macstart, maclen);
+
+ /* 8. Append the MAC template and fix it up. */
+ memcpy (p, data_mactemplate, DIM (data_mactemplate));
+ memcpy (p + DATA_MACTEMPLATE_SALT_OFF, salt, 8);
+ memcpy (p + DATA_MACTEMPLATE_MAC_OFF, gcry_md_read (md, 0), 20);
+ p += DIM (data_mactemplate);
+ gcry_md_close (md);
+ }
+
+ /* Ready. */
+ resultlen = p - result;
+ if (needed != resultlen)
+ log_debug ("length mismatch: %lu, %lu\n",
+ (unsigned long)needed, (unsigned long)resultlen);
+
+ *r_length = resultlen;
+ return result;
+}
+
+
+/* Build a DER encoded SEQUENCE with the key:
+
+ SEQUENCE {
+ INTEGER 0
+ SEQUENCE {
+ OBJECT IDENTIFIER rsaEncryption (1 2 840 113549 1 1 1)
+ NULL
+ }
+ OCTET STRING, encapsulates {
+ SEQUENCE {
+ INTEGER 0
+ INTEGER
+ INTEGER
+ INTEGER
+ INTEGER
+ INTEGER
+ INTEGER
+ INTEGER
+ INTEGER
+ }
+ }
+ }
+*/
+
+static unsigned char *
+build_key_sequence (gcry_mpi_t *kparms, size_t *r_length)
+{
+ int rc, i;
+ size_t needed, n;
+ unsigned char *plain, *p;
+ size_t plainlen;
+ size_t outseqlen, oidseqlen, octstrlen, inseqlen;
+
+ needed = 3; /* The version(?) integer of value 0. */
+ for (i=0; kparms[i]; i++)
+ {
+ n = 0;
+ rc = gcry_mpi_print (GCRYMPI_FMT_STD, NULL, 0, &n, kparms[i]);
+ if (rc)
+ {
+ log_error ("error formatting parameter: %s\n", gpg_strerror (rc));
+ return NULL;
+ }
+ needed += n;
+ n = compute_tag_length (n);
+ if (!n)
+ return NULL;
+ needed += n;
+ }
+ if (i != 8)
+ {
+ log_error ("invalid parameters for p12_build\n");
+ return NULL;
+ }
+ /* Now this all goes into a sequence. */
+ inseqlen = needed;
+ n = compute_tag_length (needed);
+ if (!n)
+ return NULL;
+ needed += n;
+ /* Encapsulate all into an octet string. */
+ octstrlen = needed;
+ n = compute_tag_length (needed);
+ if (!n)
+ return NULL;
+ needed += n;
+ /* Prepend the object identifier sequence. */
+ oidseqlen = 2 + DIM (oid_rsaEncryption) + 2;
+ needed += 2 + oidseqlen;
+ /* The version number. */
+ needed += 3;
+ /* And finally put the whole thing into a sequence. */
+ outseqlen = needed;
+ n = compute_tag_length (needed);
+ if (!n)
+ return NULL;
+ needed += n;
+
+ /* allocate 8 extra bytes for padding */
+ plain = gcry_malloc_secure (needed+8);
+ if (!plain)
+ {
+ log_error ("error allocating encryption buffer\n");
+ return NULL;
+ }
+
+ /* And now fill the plaintext buffer. */
+ p = plain;
+ p = store_tag_length (p, TAG_SEQUENCE, outseqlen);
+ /* Store version. */
+ *p++ = TAG_INTEGER;
+ *p++ = 1;
+ *p++ = 0;
+ /* Store object identifier sequence. */
+ p = store_tag_length (p, TAG_SEQUENCE, oidseqlen);
+ p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_rsaEncryption));
+ memcpy (p, oid_rsaEncryption, DIM (oid_rsaEncryption));
+ p += DIM (oid_rsaEncryption);
+ *p++ = TAG_NULL;
+ *p++ = 0;
+ /* Start with the octet string. */
+ p = store_tag_length (p, TAG_OCTET_STRING, octstrlen);
+ p = store_tag_length (p, TAG_SEQUENCE, inseqlen);
+ /* Store the key parameters. */
+ *p++ = TAG_INTEGER;
+ *p++ = 1;
+ *p++ = 0;
+ for (i=0; kparms[i]; i++)
+ {
+ n = 0;
+ rc = gcry_mpi_print (GCRYMPI_FMT_STD, NULL, 0, &n, kparms[i]);
+ if (rc)
+ {
+ log_error ("oops: error formatting parameter: %s\n",
+ gpg_strerror (rc));
+ gcry_free (plain);
+ return NULL;
+ }
+ p = store_tag_length (p, TAG_INTEGER, n);
+
+ n = plain + needed - p;
+ rc = gcry_mpi_print (GCRYMPI_FMT_STD, p, n, &n, kparms[i]);
+ if (rc)
+ {
+ log_error ("oops: error storing parameter: %s\n",
+ gpg_strerror (rc));
+ gcry_free (plain);
+ return NULL;
+ }
+ p += n;
+ }
+
+ plainlen = p - plain;
+ assert (needed == plainlen);
+ /* Append some pad characters; we already allocated extra space. */
+ n = 8 - plainlen % 8;
+ for (i=0; i < n; i++, plainlen++)
+ *p++ = n;
+
+ *r_length = plainlen;
+ return plain;
+}
+
+
+
+static unsigned char *
+build_key_bag (unsigned char *buffer, size_t buflen, char *salt,
+ const unsigned char *sha1hash, const char *keyidstr,
+ size_t *r_length)
+{
+ size_t len[11], needed;
+ unsigned char *p, *keybag;
+ size_t keybaglen;
+
+ /* Walk 11 steps down to collect the info: */
+
+ /* 10. The data goes into an octet string. */
+ needed = compute_tag_length (buflen);
+ needed += buflen;
+
+ /* 9. Prepend the algorithm identifier. */
+ needed += DIM (data_3desiter2048);
+
+ /* 8. Put a sequence around. */
+ len[8] = needed;
+ needed += compute_tag_length (needed);
+
+ /* 7. Prepend a [0] tag. */
+ len[7] = needed;
+ needed += compute_tag_length (needed);
+
+ /* 6b. The attributes which are appended at the end. */
+ if (sha1hash)
+ needed += DIM (data_attrtemplate) + 20;
+
+ /* 6. Prepend the shroudedKeyBag OID. */
+ needed += 2 + DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag);
+
+ /* 5+4. Put all into two sequences. */
+ len[5] = needed;
+ needed += compute_tag_length ( needed);
+ len[4] = needed;
+ needed += compute_tag_length (needed);
+
+ /* 3. This all goes into an octet string. */
+ len[3] = needed;
+ needed += compute_tag_length (needed);
+
+ /* 2. Prepend another [0] tag. */
+ len[2] = needed;
+ needed += compute_tag_length (needed);
+
+ /* 1. Prepend the data OID. */
+ needed += 2 + DIM (oid_data);
+
+ /* 0. Prepend another sequence. */
+ len[0] = needed;
+ needed += compute_tag_length (needed);
+
+ /* Now that we have all length information, allocate a buffer. */
+ p = keybag = gcry_malloc (needed);
+ if (!keybag)
+ {
+ log_error ("error allocating buffer\n");
+ return NULL;
+ }
+
+ /* Walk 11 steps up to store the data. */
+
+ /* 0. Store the first sequence. */
+ p = store_tag_length (p, TAG_SEQUENCE, len[0]);
+
+ /* 1. Store the data OID. */
+ p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_data));
+ memcpy (p, oid_data, DIM (oid_data));
+ p += DIM (oid_data);
+
+ /* 2. Store a [0] tag. */
+ p = store_tag_length (p, 0xa0, len[2]);
+
+ /* 3. And an octet string. */
+ p = store_tag_length (p, TAG_OCTET_STRING, len[3]);
+
+ /* 4+5. Two sequences. */
+ p = store_tag_length (p, TAG_SEQUENCE, len[4]);
+ p = store_tag_length (p, TAG_SEQUENCE, len[5]);
+
+ /* 6. Store the shroudedKeyBag OID. */
+ p = store_tag_length (p, TAG_OBJECT_ID,
+ DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag));
+ memcpy (p, oid_pkcs_12_pkcs_8ShroudedKeyBag,
+ DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag));
+ p += DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag);
+
+ /* 7. Store a [0] tag. */
+ p = store_tag_length (p, 0xa0, len[7]);
+
+ /* 8. Store a sequence. */
+ p = store_tag_length (p, TAG_SEQUENCE, len[8]);
+
+ /* 9. Now for the pre-encoded algorithm identifier and the salt. */
+ memcpy (p, data_3desiter2048, DIM (data_3desiter2048));
+ memcpy (p + DATA_3DESITER2048_SALT_OFF, salt, 8);
+ p += DIM (data_3desiter2048);
+
+ /* 10. And the octet string with the encrypted data. */
+ p = store_tag_length (p, TAG_OCTET_STRING, buflen);
+ memcpy (p, buffer, buflen);
+ p += buflen;
+
+ /* Append the attributes whose length we calculated at step 2b. */
+ if (sha1hash)
+ {
+ int i;
+
+ memcpy (p, data_attrtemplate, DIM (data_attrtemplate));
+ for (i=0; i < 8; i++)
+ p[DATA_ATTRTEMPLATE_KEYID_OFF+2*i+1] = keyidstr[i];
+ p += DIM (data_attrtemplate);
+ memcpy (p, sha1hash, 20);
+ p += 20;
+ }
+
+
+ keybaglen = p - keybag;
+ if (needed != keybaglen)
+ log_debug ("length mismatch: %lu, %lu\n",
+ (unsigned long)needed, (unsigned long)keybaglen);
+
+ *r_length = keybaglen;
+ return keybag;
+}
+
+
+static unsigned char *
+build_cert_bag (unsigned char *buffer, size_t buflen, char *salt,
+ size_t *r_length)
+{
+ size_t len[9], needed;
+ unsigned char *p, *certbag;
+ size_t certbaglen;
+
+ /* Walk 9 steps down to collect the info: */
+
+ /* 8. The data goes into an octet string. */
+ needed = compute_tag_length (buflen);
+ needed += buflen;
+
+ /* 7. The algorithm identifier. */
+ needed += DIM (data_rc2iter2048);
+
+ /* 6. The data OID. */
+ needed += 2 + DIM (oid_data);
+
+ /* 5. A sequence. */
+ len[5] = needed;
+ needed += compute_tag_length ( needed);
+
+ /* 4. An integer. */
+ needed += 3;
+
+ /* 3. A sequence. */
+ len[3] = needed;
+ needed += compute_tag_length (needed);
+
+ /* 2. A [0] tag. */
+ len[2] = needed;
+ needed += compute_tag_length (needed);
+
+ /* 1. The encryptedData OID. */
+ needed += 2 + DIM (oid_encryptedData);
+
+ /* 0. The first sequence. */
+ len[0] = needed;
+ needed += compute_tag_length (needed);
+
+ /* Now that we have all length information, allocate a buffer. */
+ p = certbag = gcry_malloc (needed);
+ if (!certbag)
+ {
+ log_error ("error allocating buffer\n");
+ return NULL;
+ }
+
+ /* Walk 9 steps up to store the data. */
+
+ /* 0. Store the first sequence. */
+ p = store_tag_length (p, TAG_SEQUENCE, len[0]);
+
+ /* 1. Store the encryptedData OID. */
+ p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_encryptedData));
+ memcpy (p, oid_encryptedData, DIM (oid_encryptedData));
+ p += DIM (oid_encryptedData);
+
+ /* 2. Store a [0] tag. */
+ p = store_tag_length (p, 0xa0, len[2]);
+
+ /* 3. Store a sequence. */
+ p = store_tag_length (p, TAG_SEQUENCE, len[3]);
+
+ /* 4. Store the integer 0. */
+ *p++ = TAG_INTEGER;
+ *p++ = 1;
+ *p++ = 0;
+
+ /* 5. Store a sequence. */
+ p = store_tag_length (p, TAG_SEQUENCE, len[5]);
+
+ /* 6. Store the data OID. */
+ p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_data));
+ memcpy (p, oid_data, DIM (oid_data));
+ p += DIM (oid_data);
+
+ /* 7. Now for the pre-encoded algorithm identifier and the salt. */
+ memcpy (p, data_rc2iter2048, DIM (data_rc2iter2048));
+ memcpy (p + DATA_RC2ITER2048_SALT_OFF, salt, 8);
+ p += DIM (data_rc2iter2048);
+
+ /* 8. And finally the [0] tag with the encrypted data. */
+ p = store_tag_length (p, 0x80, buflen);
+ memcpy (p, buffer, buflen);
+ p += buflen;
+ certbaglen = p - certbag;
+
+ if (needed != certbaglen)
+ log_debug ("length mismatch: %lu, %lu\n",
+ (unsigned long)needed, (unsigned long)certbaglen);
+
+ *r_length = certbaglen;
+ return certbag;
+}
+
+
+static unsigned char *
+build_cert_sequence (unsigned char *buffer, size_t buflen,
+ const unsigned char *sha1hash, const char *keyidstr,
+ size_t *r_length)
+{
+ size_t len[8], needed, n;
+ unsigned char *p, *certseq;
+ size_t certseqlen;
+ int i;
+
+ assert (strlen (keyidstr) == 8);
+
+ /* Walk 8 steps down to collect the info: */
+
+ /* 7. The data goes into an octet string. */
+ needed = compute_tag_length (buflen);
+ needed += buflen;
+
+ /* 6. A [0] tag. */
+ len[6] = needed;
+ needed += compute_tag_length (needed);
+
+ /* 5. An OID. */
+ needed += 2 + DIM (oid_x509Certificate_for_pkcs_12);
+
+ /* 4. A sequence. */
+ len[4] = needed;
+ needed += compute_tag_length (needed);
+
+ /* 3. A [0] tag. */
+ len[3] = needed;
+ needed += compute_tag_length (needed);
+
+ /* 2b. The attributes which are appended at the end. */
+ if (sha1hash)
+ needed += DIM (data_attrtemplate) + 20;
+
+ /* 2. An OID. */
+ needed += 2 + DIM (oid_pkcs_12_CertBag);
+
+ /* 1. A sequence. */
+ len[1] = needed;
+ needed += compute_tag_length (needed);
+
+ /* 0. The first sequence. */
+ len[0] = needed;
+ needed += compute_tag_length (needed);
+
+ /* Now that we have all length information, allocate a buffer. */
+ p = certseq = gcry_malloc (needed + 8 /*(for padding)*/);
+ if (!certseq)
+ {
+ log_error ("error allocating buffer\n");
+ return NULL;
+ }
+
+ /* Walk 8 steps up to store the data. */
+
+ /* 0. Store the first sequence. */
+ p = store_tag_length (p, TAG_SEQUENCE, len[0]);
+
+ /* 1. Store the second sequence. */
+ p = store_tag_length (p, TAG_SEQUENCE, len[1]);
+
+ /* 2. Store the pkcs12-cert-bag OID. */
+ p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_pkcs_12_CertBag));
+ memcpy (p, oid_pkcs_12_CertBag, DIM (oid_pkcs_12_CertBag));
+ p += DIM (oid_pkcs_12_CertBag);
+
+ /* 3. Store a [0] tag. */
+ p = store_tag_length (p, 0xa0, len[3]);
+
+ /* 4. Store a sequence. */
+ p = store_tag_length (p, TAG_SEQUENCE, len[4]);
+
+ /* 5. Store the x509Certificate OID. */
+ p = store_tag_length (p, TAG_OBJECT_ID,
+ DIM (oid_x509Certificate_for_pkcs_12));
+ memcpy (p, oid_x509Certificate_for_pkcs_12,
+ DIM (oid_x509Certificate_for_pkcs_12));
+ p += DIM (oid_x509Certificate_for_pkcs_12);
+
+ /* 6. Store a [0] tag. */
+ p = store_tag_length (p, 0xa0, len[6]);
+
+ /* 7. And the octet string with the actual certificate. */
+ p = store_tag_length (p, TAG_OCTET_STRING, buflen);
+ memcpy (p, buffer, buflen);
+ p += buflen;
+
+ /* Append the attributes whose length we calculated at step 2b. */
+ if (sha1hash)
+ {
+ memcpy (p, data_attrtemplate, DIM (data_attrtemplate));
+ for (i=0; i < 8; i++)
+ p[DATA_ATTRTEMPLATE_KEYID_OFF+2*i+1] = keyidstr[i];
+ p += DIM (data_attrtemplate);
+ memcpy (p, sha1hash, 20);
+ p += 20;
+ }
+
+ certseqlen = p - certseq;
+ if (needed != certseqlen)
+ log_debug ("length mismatch: %lu, %lu\n",
+ (unsigned long)needed, (unsigned long)certseqlen);
+
+ /* Append some pad characters; we already allocated extra space. */
+ n = 8 - certseqlen % 8;
+ for (i=0; i < n; i++, certseqlen++)
+ *p++ = n;
+
+ *r_length = certseqlen;
+ return certseq;
+}
+
+
+/* Expect the RSA key parameters in KPARMS and a password in PW.
+ Create a PKCS structure from it and return it as well as the length
+ in R_LENGTH; return NULL in case of an error. If CHARSET is not
+ NULL, re-encode PW to that character set. */
+unsigned char *
+p12_build (gcry_mpi_t *kparms, unsigned char *cert, size_t certlen,
+ const char *pw, const char *charset, size_t *r_length)
+{
+ unsigned char *buffer = NULL;
+ size_t n, buflen;
+ char salt[8];
+ struct buffer_s seqlist[3];
+ int seqlistidx = 0;
+ unsigned char sha1hash[20];
+ char keyidstr[8+1];
+ char *pwbuf = NULL;
+ size_t pwbufsize = 0;
+
+ n = buflen = 0; /* (avoid compiler warning). */
+ memset (sha1hash, 0, 20);
+ *keyidstr = 0;
+
+ if (charset && pw && *pw)
+ {
+ jnlib_iconv_t cd;
+ const char *inptr;
+ char *outptr;
+ size_t inbytes, outbytes;
+
+ /* We assume that the converted passphrase is at max 2 times
+ longer than its utf-8 encoding. */
+ pwbufsize = strlen (pw)*2 + 1;
+ pwbuf = gcry_malloc_secure (pwbufsize);
+ if (!pwbuf)
+ {
+ log_error ("out of secure memory while converting passphrase\n");
+ goto failure;
+ }
+
+ cd = jnlib_iconv_open (charset, "utf-8");
+ if (cd == (jnlib_iconv_t)(-1))
+ {
+ log_error ("can't convert passphrase to"
+ " requested charset `%s': %s\n",
+ charset, strerror (errno));
+ gcry_free (pwbuf);
+ goto failure;
+ }
+
+ inptr = pw;
+ inbytes = strlen (pw);
+ outptr = pwbuf;
+ outbytes = pwbufsize - 1;
+ if ( jnlib_iconv (cd, (const char **)&inptr, &inbytes,
+ &outptr, &outbytes) == (size_t)-1)
+ {
+ log_error ("error converting passphrase to"
+ " requested charset `%s': %s\n",
+ charset, strerror (errno));
+ gcry_free (pwbuf);
+ jnlib_iconv_close (cd);
+ goto failure;
+ }
+ *outptr = 0;
+ jnlib_iconv_close (cd);
+ pw = pwbuf;
+ }
+
+
+ if (cert && certlen)
+ {
+ /* Calculate the hash value we need for the bag attributes. */
+ gcry_md_hash_buffer (GCRY_MD_SHA1, sha1hash, cert, certlen);
+ sprintf (keyidstr, "%02x%02x%02x%02x",
+ sha1hash[16], sha1hash[17], sha1hash[18], sha1hash[19]);
+
+ /* Encode the certificate. */
+ buffer = build_cert_sequence (cert, certlen, sha1hash, keyidstr,
+ &buflen);
+ if (!buffer)
+ goto failure;
+
+ /* Encrypt it. */
+ gcry_randomize (salt, 8, GCRY_STRONG_RANDOM);
+ crypt_block (buffer, buflen, salt, 8, 2048, pw,
+ GCRY_CIPHER_RFC2268_40, 1);
+
+ /* Encode the encrypted stuff into a bag. */
+ seqlist[seqlistidx].buffer = build_cert_bag (buffer, buflen, salt, &n);
+ seqlist[seqlistidx].length = n;
+ gcry_free (buffer);
+ buffer = NULL;
+ if (!seqlist[seqlistidx].buffer)
+ goto failure;
+ seqlistidx++;
+ }
+
+
+ if (kparms)
+ {
+ /* Encode the key. */
+ buffer = build_key_sequence (kparms, &buflen);
+ if (!buffer)
+ goto failure;
+
+ /* Encrypt it. */
+ gcry_randomize (salt, 8, GCRY_STRONG_RANDOM);
+ crypt_block (buffer, buflen, salt, 8, 2048, pw, GCRY_CIPHER_3DES, 1);
+
+ /* Encode the encrypted stuff into a bag. */
+ if (cert && certlen)
+ seqlist[seqlistidx].buffer = build_key_bag (buffer, buflen, salt,
+ sha1hash, keyidstr, &n);
+ else
+ seqlist[seqlistidx].buffer = build_key_bag (buffer, buflen, salt,
+ NULL, NULL, &n);
+ seqlist[seqlistidx].length = n;
+ gcry_free (buffer);
+ buffer = NULL;
+ if (!seqlist[seqlistidx].buffer)
+ goto failure;
+ seqlistidx++;
+ }
+
+ seqlist[seqlistidx].buffer = NULL;
+ seqlist[seqlistidx].length = 0;
+
+ buffer = create_final (seqlist, pw, &buflen);
+
+ failure:
+ if (pwbuf)
+ {
+ wipememory (pwbuf, pwbufsize);
+ gcry_free (pwbuf);
+ }
+ for ( ; seqlistidx; seqlistidx--)
+ gcry_free (seqlist[seqlistidx].buffer);
+
+ *r_length = buffer? buflen : 0;
+ return buffer;
+}
+
+
+#ifdef TEST
+
+static void
+cert_cb (void *opaque, const unsigned char *cert, size_t certlen)
+{
+ printf ("got a certificate of %u bytes length\n", certlen);
+}
+
+int
+main (int argc, char **argv)
+{
+ FILE *fp;
+ struct stat st;
+ unsigned char *buf;
+ size_t buflen;
+ gcry_mpi_t *result;
+
+ if (argc != 3)
+ {
+ fprintf (stderr, "usage: testp12 file passphrase\n");
+ return 1;
+ }
+
+ gcry_control (GCRYCTL_DISABLE_SECMEM, NULL);
+ gcry_control (GCRYCTL_INITIALIZATION_FINISHED, NULL);
+
+ fp = fopen (argv[1], "rb");
+ if (!fp)
+ {
+ fprintf (stderr, "can't open `%s': %s\n", argv[1], strerror (errno));
+ return 1;
+ }
+
+ if (fstat (fileno(fp), &st))
+ {
+ fprintf (stderr, "can't stat `%s': %s\n", argv[1], strerror (errno));
+ return 1;
+ }
+
+ buflen = st.st_size;
+ buf = gcry_malloc (buflen+1);
+ if (!buf || fread (buf, buflen, 1, fp) != 1)
+ {
+ fprintf (stderr, "error reading `%s': %s\n", argv[1], strerror (errno));
+ return 1;
+ }
+ fclose (fp);
+
+ result = p12_parse (buf, buflen, argv[2], cert_cb, NULL);
+ if (result)
+ {
+ int i, rc;
+ unsigned char *tmpbuf;
+
+ for (i=0; result[i]; i++)
+ {
+ rc = gcry_mpi_aprint (GCRYMPI_FMT_HEX, &tmpbuf,
+ NULL, result[i]);
+ if (rc)
+ printf ("%d: [error printing number: %s]\n",
+ i, gpg_strerror (rc));
+ else
+ {
+ printf ("%d: %s\n", i, tmpbuf);
+ gcry_free (tmpbuf);
+ }
+ }
+ }
+
+ return 0;
+
+}
+
+/*
+Local Variables:
+compile-command: "gcc -Wall -O0 -g -DTEST=1 -o minip12 minip12.c ../common/libcommon.a -L /usr/local/lib -lgcrypt -lgpg-error"
+End:
+*/
+#endif /* TEST */
diff --git a/sm/minip12.h b/sm/minip12.h
new file mode 100644
index 000000000..f2af70957
--- /dev/null
+++ b/sm/minip12.h
@@ -0,0 +1,36 @@
+/* minip12.h - Global definitions for the minimal pkcs-12 implementation.
+ * Copyright (C) 2002, 2003 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 MINIP12_H
+#define MINIP12_H
+
+#include <gcrypt.h>
+
+gcry_mpi_t *p12_parse (const unsigned char *buffer, size_t length,
+ const char *pw,
+ void (*certcb)(void*, const unsigned char*, size_t),
+ void *certcbarg, int *r_badpass);
+
+unsigned char *p12_build (gcry_mpi_t *kparms,
+ unsigned char *cert, size_t certlen,
+ const char *pw, const char *charset,
+ size_t *r_length);
+
+
+#endif /*MINIP12_H*/