1
0
Fork 0
mirror of git://git.gnupg.org/gnupg.git synced 2025-07-03 22:56:33 +02:00

Avoid using the protect-tool to import pkcs#12.

This commit is contained in:
Werner Koch 2010-06-17 15:44:44 +00:00
parent 28b3b74cbb
commit 006fd75aea
24 changed files with 1167 additions and 414 deletions

View file

@ -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;
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);
/* 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)
/* Check that n == p * q. */
gcry_mpi_mul (t, skey->p, skey->q);
if (gcry_mpi_cmp( t, skey->n) )
{
argv[i++] = "--passphrase";
argv[i++] = opt.fixed_passphrase;
log_error ("RSA oops: n != p * q\n");
err++;
}
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);
/* 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;
memset (&store_cert_parm, 0, sizeof store_cert_parm);
store_cert_parm.ctrl = ctrl;
store_cert_parm.stats = stats;
*retfp = NULL;
/* 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;
}
err = popen_protect_tool (ctrl, pgmname, tmpfp, certfp, &fp, &pid);
if (err)
{
pid = -1;
goto cleanup;
}
es_fclose (tmpfp);
tmpfp = NULL;
/* Read stderr of the protect tool. */
pos = 0;
cont_line = 0;
while ((c=es_getc (fp)) != EOF)
{
/* 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');
}
}
if (pos)
{
buffer[pos] = 0;
if (cont_line)
log_printf ("%s\n", buffer);
else
log_info ("%s\n", buffer);
}
/* 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);
cleanup:
es_fclose (tmpfp);
es_fclose (fp);
if (pid != -1)
{
if (!gnupg_wait_process (pgmname, pid, 0, NULL))
child_err = 0;
gnupg_release_process (pid);
}
if (!err)
err = child_err;
if (err)
{
es_fclose (certfp);
for (p12bufoff=18;
p12bufoff < p12buflen && p12buffer[p12bufoff] != '\n';
p12bufoff++)
;
p12bufoff++;
if (p12bufoff < p12buflen && p12buffer[p12bufoff] == '\n')
p12bufoff++;
}
else
*retfp = certfp;
p12bufoff = 0;
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)
{
log_error ("error parsing or decrypting the PKCS#12 file\n");
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
/* 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)
{
log_error ("failed to created S-expression from key: %s\n",
gpg_strerror (err));
goto leave;
}
/* Compute the keygrip. */
if (!gcry_pk_get_keygrip (s_key, grip))
{
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;
/* 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;
}
/* 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)
{
err = gpg_error_from_syserror ();
goto leave;
}
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)
{
stats->count++;
stats->secret_read++;
stats->secret_imported++;
}
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)
{