1
0
mirror of git://git.gnupg.org/gnupg.git synced 2024-12-22 10:19:57 +01:00
gnupg/sm/export.c
Werner Koch 096e7457ec Change all quotes in strings and comments to the new GNU standard.
The asymmetric quotes used by GNU in the past (`...') don't render
nicely on modern systems.  We now use two \x27 characters ('...').

The proper solution would be to use the correct Unicode symmetric
quotes here.  However this has the disadvantage that the system
requires Unicode support.  We don't want that today.  If Unicode is
available a generated po file can be used to output proper quotes.  A
simple sed script like the one used for en@quote is sufficient to
change them.

The changes have been done by applying

  sed -i "s/\`\([^'\`]*\)'/'\1'/g"

to most files and fixing obvious problems by hand.  The msgid strings in
the po files were fixed with a similar command.
2012-06-05 19:29:22 +02:00

722 lines
18 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* export.c - Export certificates and private keys.
* Copyright (C) 2002, 2003, 2004, 2007, 2009,
* 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <assert.h>
#include "gpgsm.h"
#include <gcrypt.h>
#include <ksba.h>
#include "keydb.h"
#include "exechelp.h"
#include "i18n.h"
#include "sysutils.h"
#include "minip12.h"
/* A table to store a fingerprint as used in a duplicates table. We
don't need to hash here because a fingerprint is already a perfect
hash value. This we use the most significant bits to index the
table and then use a linked list for the overflow. Possible
enhancement for very large number of certificates: Add a second
level table and then resort to a linked list. */
struct duptable_s
{
struct duptable_s *next;
/* Note that we only need to store 19 bytes because the first byte
is implictly given by the table index (we require at least 8
bits). */
unsigned char fpr[19];
};
typedef struct duptable_s *duptable_t;
#define DUPTABLE_BITS 12
#define DUPTABLE_SIZE (1 << DUPTABLE_BITS)
static void print_short_info (ksba_cert_t cert, estream_t stream);
static gpg_error_t export_p12 (ctrl_t ctrl,
const unsigned char *certimg, size_t certimglen,
const char *prompt, const char *keygrip,
void **r_result, size_t *r_resultlen);
/* Create a table used to indetify duplicated certificates. */
static duptable_t *
create_duptable (void)
{
return xtrycalloc (DUPTABLE_SIZE, sizeof (duptable_t));
}
static void
destroy_duptable (duptable_t *table)
{
int idx;
duptable_t t, t2;
if (table)
{
for (idx=0; idx < DUPTABLE_SIZE; idx++)
for (t = table[idx]; t; t = t2)
{
t2 = t->next;
xfree (t);
}
xfree (table);
}
}
/* Insert the 20 byte fingerprint FPR into TABLE. Sets EXITS to true
if the fingerprint already exists in the table. */
static gpg_error_t
insert_duptable (duptable_t *table, unsigned char *fpr, int *exists)
{
size_t idx;
duptable_t t;
*exists = 0;
idx = fpr[0];
#if DUPTABLE_BITS > 16 || DUPTABLE_BITS < 8
#error cannot handle a table larger than 16 bits or smaller than 8 bits
#elif DUPTABLE_BITS > 8
idx <<= (DUPTABLE_BITS - 8);
idx |= (fpr[1] & ~(~0 << 4));
#endif
for (t = table[idx]; t; t = t->next)
if (!memcmp (t->fpr, fpr+1, 19))
break;
if (t)
{
*exists = 1;
return 0;
}
/* Insert that fingerprint. */
t = xtrymalloc (sizeof *t);
if (!t)
return gpg_error_from_syserror ();
memcpy (t->fpr, fpr+1, 19);
t->next = table[idx];
table[idx] = t;
return 0;
}
/* Export all certificates or just those given in NAMES. The output
is written to STREAM. */
void
gpgsm_export (ctrl_t ctrl, strlist_t names, estream_t stream)
{
KEYDB_HANDLE hd = NULL;
KEYDB_SEARCH_DESC *desc = NULL;
int ndesc;
Base64Context b64writer = NULL;
ksba_writer_t writer;
strlist_t sl;
ksba_cert_t cert = NULL;
int rc=0;
int count = 0;
int i;
duptable_t *dtable;
dtable = create_duptable ();
if (!dtable)
{
log_error ("creating duplicates table failed: %s\n", strerror (errno));
goto leave;
}
hd = keydb_new (0);
if (!hd)
{
log_error ("keydb_new failed\n");
goto leave;
}
if (!names)
ndesc = 1;
else
{
for (sl=names, ndesc=0; sl; sl = sl->next, ndesc++)
;
}
desc = xtrycalloc (ndesc, sizeof *desc);
if (!ndesc)
{
log_error ("allocating memory for export failed: %s\n",
gpg_strerror (out_of_core ()));
goto leave;
}
if (!names)
desc[0].mode = KEYDB_SEARCH_MODE_FIRST;
else
{
for (ndesc=0, sl=names; sl; sl = sl->next)
{
rc = classify_user_id (sl->d, desc+ndesc, 0);
if (rc)
{
log_error ("key '%s' not found: %s\n",
sl->d, gpg_strerror (rc));
rc = 0;
}
else
ndesc++;
}
}
/* If all specifications are done by fingerprint or keygrip, we
switch to ephemeral mode so that _all_ currently available and
matching certificates are exported. */
if (names && ndesc)
{
for (i=0; (i < ndesc
&& (desc[i].mode == KEYDB_SEARCH_MODE_FPR
|| desc[i].mode == KEYDB_SEARCH_MODE_FPR20
|| desc[i].mode == KEYDB_SEARCH_MODE_FPR16
|| desc[i].mode == KEYDB_SEARCH_MODE_KEYGRIP)); i++)
;
if (i == ndesc)
keydb_set_ephemeral (hd, 1);
}
while (!(rc = keydb_search (hd, desc, ndesc)))
{
unsigned char fpr[20];
int exists;
if (!names)
desc[0].mode = KEYDB_SEARCH_MODE_NEXT;
rc = keydb_get_cert (hd, &cert);
if (rc)
{
log_error ("keydb_get_cert failed: %s\n", gpg_strerror (rc));
goto leave;
}
gpgsm_get_fingerprint (cert, 0, fpr, NULL);
rc = insert_duptable (dtable, fpr, &exists);
if (rc)
{
log_error ("inserting into duplicates table failed: %s\n",
gpg_strerror (rc));
goto leave;
}
if (!exists && count && !ctrl->create_pem)
{
log_info ("exporting more than one certificate "
"is not possible in binary mode\n");
log_info ("ignoring other certificates\n");
break;
}
if (!exists)
{
const unsigned char *image;
size_t imagelen;
image = ksba_cert_get_image (cert, &imagelen);
if (!image)
{
log_error ("ksba_cert_get_image failed\n");
goto leave;
}
if (ctrl->create_pem)
{
if (count)
es_putc ('\n', stream);
print_short_info (cert, stream);
es_putc ('\n', stream);
}
count++;
if (!b64writer)
{
ctrl->pem_name = "CERTIFICATE";
rc = gpgsm_create_writer (&b64writer, ctrl, stream, &writer);
if (rc)
{
log_error ("can't create writer: %s\n", gpg_strerror (rc));
goto leave;
}
}
rc = ksba_writer_write (writer, image, imagelen);
if (rc)
{
log_error ("write error: %s\n", gpg_strerror (rc));
goto leave;
}
if (ctrl->create_pem)
{
/* We want one certificate per PEM block */
rc = gpgsm_finish_writer (b64writer);
if (rc)
{
log_error ("write failed: %s\n", gpg_strerror (rc));
goto leave;
}
gpgsm_destroy_writer (b64writer);
b64writer = NULL;
}
}
ksba_cert_release (cert);
cert = NULL;
}
if (rc && rc != -1)
log_error ("keydb_search failed: %s\n", gpg_strerror (rc));
else if (b64writer)
{
rc = gpgsm_finish_writer (b64writer);
if (rc)
{
log_error ("write failed: %s\n", gpg_strerror (rc));
goto leave;
}
}
leave:
gpgsm_destroy_writer (b64writer);
ksba_cert_release (cert);
xfree (desc);
keydb_release (hd);
destroy_duptable (dtable);
}
/* Export a certificate and its private key. */
void
gpgsm_p12_export (ctrl_t ctrl, const char *name, estream_t stream)
{
gpg_error_t err = 0;
KEYDB_HANDLE hd;
KEYDB_SEARCH_DESC *desc = NULL;
Base64Context b64writer = NULL;
ksba_writer_t writer;
ksba_cert_t cert = NULL;
const unsigned char *image;
size_t imagelen;
char *keygrip = NULL;
char *prompt;
void *data;
size_t datalen;
hd = keydb_new (0);
if (!hd)
{
log_error ("keydb_new failed\n");
goto leave;
}
desc = xtrycalloc (1, sizeof *desc);
if (!desc)
{
log_error ("allocating memory for export failed: %s\n",
gpg_strerror (out_of_core ()));
goto leave;
}
err = classify_user_id (name, desc, 0);
if (err)
{
log_error ("key '%s' not found: %s\n",
name, gpg_strerror (err));
goto leave;
}
/* Lookup the certificate and make sure that it is unique. */
err = keydb_search (hd, desc, 1);
if (!err)
{
err = keydb_get_cert (hd, &cert);
if (err)
{
log_error ("keydb_get_cert failed: %s\n", gpg_strerror (err));
goto leave;
}
next_ambiguous:
err = keydb_search (hd, desc, 1);
if (!err)
{
ksba_cert_t cert2 = NULL;
if (!keydb_get_cert (hd, &cert2))
{
if (gpgsm_certs_identical_p (cert, cert2))
{
ksba_cert_release (cert2);
goto next_ambiguous;
}
ksba_cert_release (cert2);
}
err = gpg_error (GPG_ERR_AMBIGUOUS_NAME);
}
else if (err == -1 || gpg_err_code (err) == GPG_ERR_EOF)
err = 0;
if (err)
{
log_error ("key '%s' not found: %s\n",
name, gpg_strerror (err));
goto leave;
}
}
keygrip = gpgsm_get_keygrip_hexstring (cert);
if (!keygrip || gpgsm_agent_havekey (ctrl, keygrip))
{
/* Note, that the !keygrip case indicates a bad certificate. */
err = gpg_error (GPG_ERR_NO_SECKEY);
log_error ("can't export key '%s': %s\n", name, gpg_strerror (err));
goto leave;
}
image = ksba_cert_get_image (cert, &imagelen);
if (!image)
{
log_error ("ksba_cert_get_image failed\n");
goto leave;
}
if (ctrl->create_pem)
{
print_short_info (cert, stream);
es_putc ('\n', stream);
}
if (opt.p12_charset && ctrl->create_pem)
{
es_fprintf (stream, "The passphrase is %s encoded.\n\n",
opt.p12_charset);
}
ctrl->pem_name = "PKCS12";
err = gpgsm_create_writer (&b64writer, ctrl, stream, &writer);
if (err)
{
log_error ("can't create writer: %s\n", gpg_strerror (err));
goto leave;
}
prompt = gpgsm_format_keydesc (cert);
err = export_p12 (ctrl, image, imagelen, prompt, keygrip, &data, &datalen);
xfree (prompt);
if (err)
goto leave;
err = ksba_writer_write (writer, data, datalen);
xfree (data);
if (err)
{
log_error ("write failed: %s\n", gpg_strerror (err));
goto leave;
}
if (ctrl->create_pem)
{
/* We want one certificate per PEM block */
err = gpgsm_finish_writer (b64writer);
if (err)
{
log_error ("write failed: %s\n", gpg_strerror (err));
goto leave;
}
gpgsm_destroy_writer (b64writer);
b64writer = NULL;
}
ksba_cert_release (cert);
cert = NULL;
leave:
gpgsm_destroy_writer (b64writer);
ksba_cert_release (cert);
xfree (desc);
keydb_release (hd);
}
/* Print some info about the certifciate CERT to FP or STREAM */
static void
print_short_info (ksba_cert_t cert, estream_t stream)
{
char *p;
ksba_sexp_t sexp;
int idx;
for (idx=0; (p = ksba_cert_get_issuer (cert, idx)); idx++)
{
es_fputs ((!idx
? "Issuer ...: "
: "\n aka ...: "), stream);
gpgsm_es_print_name (stream, p);
xfree (p);
}
es_putc ('\n', stream);
es_fputs ("Serial ...: ", stream);
sexp = ksba_cert_get_serial (cert);
if (sexp)
{
int len;
const unsigned char *s = sexp;
if (*s == '(')
{
s++;
for (len=0; *s && *s != ':' && digitp (s); s++)
len = len*10 + atoi_1 (s);
if (*s == ':')
es_write_hexstring (stream, s+1, len, 0, NULL);
}
xfree (sexp);
}
es_putc ('\n', stream);
for (idx=0; (p = ksba_cert_get_subject (cert, idx)); idx++)
{
es_fputs ((!idx
? "Subject ..: "
: "\n aka ..: "), stream);
gpgsm_es_print_name (stream, p);
xfree (p);
}
es_putc ('\n', stream);
}
/* Parse a private key S-expression and retutn a malloced array with
the RSA paramaters in pkcs#12 order. The caller needs to
deep-release this array. */
static gcry_mpi_t *
sexp_to_kparms (gcry_sexp_t sexp)
{
gcry_sexp_t list, l2;
const char *name;
const char *s;
size_t n;
int idx;
const char *elems;
gcry_mpi_t *array;
list = gcry_sexp_find_token (sexp, "private-key", 0 );
if(!list)
return NULL;
l2 = gcry_sexp_cadr (list);
gcry_sexp_release (list);
list = l2;
name = gcry_sexp_nth_data (list, 0, &n);
if(!name || n != 3 || memcmp (name, "rsa", 3))
{
gcry_sexp_release (list);
return NULL;
}
/* Parameter names used with RSA in the pkcs#12 order. */
elems = "nedqp--u";
array = xtrycalloc (strlen(elems) + 1, sizeof *array);
if (!array)
{
gcry_sexp_release (list);
return NULL;
}
for (idx=0, s=elems; *s; s++, idx++ )
{
if (*s == '-')
continue; /* Computed below */
l2 = gcry_sexp_find_token (list, s, 1);
if (l2)
{
array[idx] = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG);
gcry_sexp_release (l2);
}
if (!array[idx]) /* Required parameter not found or invalid. */
{
for (idx=0; array[idx]; idx++)
gcry_mpi_release (array[idx]);
xfree (array);
gcry_sexp_release (list);
return NULL;
}
}
gcry_sexp_release (list);
array[5] = gcry_mpi_snew (0); /* compute d mod (q-1) */
gcry_mpi_sub_ui (array[5], array[3], 1);
gcry_mpi_mod (array[5], array[2], array[5]);
array[6] = gcry_mpi_snew (0); /* compute d mod (p-1) */
gcry_mpi_sub_ui (array[6], array[4], 1);
gcry_mpi_mod (array[6], array[3], array[6]);
return array;
}
static gpg_error_t
export_p12 (ctrl_t ctrl, const unsigned char *certimg, size_t certimglen,
const char *prompt, const char *keygrip,
void **r_result, size_t *r_resultlen)
{
gpg_error_t err = 0;
void *kek = NULL;
size_t keklen;
unsigned char *wrappedkey = NULL;
size_t wrappedkeylen;
gcry_cipher_hd_t cipherhd = NULL;
gcry_sexp_t s_skey = NULL;
gcry_mpi_t *kparms = NULL;
unsigned char *key = NULL;
size_t keylen;
char *passphrase = NULL;
unsigned char *result = NULL;
size_t resultlen;
int i;
*r_result = NULL;
/* Get the current KEK. */
err = gpgsm_agent_keywrap_key (ctrl, 1, &kek, &keklen);
if (err)
{
log_error ("error getting the KEK: %s\n", gpg_strerror (err));
goto leave;
}
/* Receive the wrapped key from the agent. */
err = gpgsm_agent_export_key (ctrl, keygrip, prompt,
&wrappedkey, &wrappedkeylen);
if (err)
goto leave;
/* Unwrap 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;
if (wrappedkeylen < 24)
{
err = gpg_error (GPG_ERR_INV_LENGTH);
goto leave;
}
keylen = wrappedkeylen - 8;
key = xtrymalloc_secure (keylen);
if (!key)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gcry_cipher_decrypt (cipherhd, key, keylen, wrappedkey, wrappedkeylen);
if (err)
goto leave;
xfree (wrappedkey);
wrappedkey = NULL;
gcry_cipher_close (cipherhd);
cipherhd = NULL;
/* Convert to a gcrypt S-expression. */
err = gcry_sexp_create (&s_skey, key, keylen, 0, xfree_fnc);
if (err)
goto leave;
key = NULL; /* Key is now owned by S_KEY. */
/* Get the parameters from the S-expression. */
kparms = sexp_to_kparms (s_skey);
gcry_sexp_release (s_skey);
s_skey = NULL;
if (!kparms)
{
log_error ("error converting key parameters\n");
err = GPG_ERR_BAD_SECKEY;
goto leave;
}
err = gpgsm_agent_ask_passphrase
(ctrl,
i18n_utf8 ("Please enter the passphrase to protect the "
"new PKCS#12 object."),
1, &passphrase);
if (err)
goto leave;
result = p12_build (kparms, certimg, certimglen, passphrase,
opt.p12_charset, &resultlen);
xfree (passphrase);
passphrase = NULL;
if (!result)
err = gpg_error (GPG_ERR_GENERAL);
leave:
xfree (key);
gcry_sexp_release (s_skey);
if (kparms)
{
for (i=0; kparms[i]; i++)
gcry_mpi_release (kparms[i]);
xfree (kparms);
}
gcry_cipher_close (cipherhd);
xfree (wrappedkey);
xfree (kek);
if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE)
{
/* During export this is the passphrase used to unprotect the
key and not the pkcs#12 thing as in export. Therefore we can
issue the regular passphrase status. FIXME: replace the all
zero keyid by a regular one. */
gpgsm_status (ctrl, STATUS_BAD_PASSPHRASE, "0000000000000000");
}
if (err)
{
xfree (result);
}
else
{
*r_result = result;
*r_resultlen = resultlen;
}
return err;
}