summaryrefslogtreecommitdiffstats
path: root/sm/import.c
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/import.c
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/import.c')
-rw-r--r--sm/import.c542
1 files changed, 334 insertions, 208 deletions
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)
{