gnupg/sm/keylist.c

720 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.

/* keylist.c
* Copyright (C) 1998, 1999, 2000, 2001, 2003,
* 2004 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 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <assert.h>
#include "gpgsm.h"
#include <gcrypt.h>
#include <ksba.h>
#include "keydb.h"
#include "../kbx/keybox.h" /* for KEYBOX_FLAG_* */
#include "i18n.h"
struct list_external_parm_s {
ctrl_t ctrl;
FILE *fp;
int print_header;
int with_colons;
int with_chain;
};
/* This table is to map Extended Key Usage OIDs to human readable
names. */
struct {
const char *oid;
const char *name;
} key_purpose_map[] = {
{ "1.3.6.1.5.5.7.3.1", "serverAuth" },
{ "1.3.6.1.5.5.7.3.2", "clientAuth" },
{ "1.3.6.1.5.5.7.3.3", "codeSigning" },
{ "1.3.6.1.5.5.7.3.4", "emailProtection" },
{ "1.3.6.1.5.5.7.3.5", "ipsecEndSystem" },
{ "1.3.6.1.5.5.7.3.6", "ipsecTunnel" },
{ "1.3.6.1.5.5.7.3.7", "ipsecUser" },
{ "1.3.6.1.5.5.7.3.8", "timeStamping" },
{ "1.3.6.1.5.5.7.3.9", "ocspSigning" },
{ "1.3.6.1.5.5.7.3.10", "dvcs" },
{ "1.3.6.1.5.5.7.3.11", "sbgpCertAAServerAuth" },
{ "1.3.6.1.5.5.7.3.13", "eapOverPPP" },
{ "1.3.6.1.5.5.7.3.14", "wlanSSID" },
{ NULL, NULL }
};
static void
print_key_data (ksba_cert_t cert, FILE *fp)
{
#if 0
int n = pk ? pubkey_get_npkey( pk->pubkey_algo ) : 0;
int i;
for(i=0; i < n; i++ )
{
fprintf (fp, "pkd:%d:%u:", i, mpi_get_nbits( pk->pkey[i] ) );
mpi_print(stdout, pk->pkey[i], 1 );
putchar(':');
putchar('\n');
}
#endif
}
static void
print_capabilities (ksba_cert_t cert, FILE *fp)
{
gpg_error_t err;
unsigned int use;
err = ksba_cert_get_key_usage (cert, &use);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
{
putc ('e', fp);
putc ('s', fp);
putc ('c', fp);
putc ('E', fp);
putc ('S', fp);
putc ('C', fp);
return;
}
if (err)
{
log_error (_("error getting key usage information: %s\n"),
gpg_strerror (err));
return;
}
if ((use & (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT)))
putc ('e', fp);
if ((use & (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION)))
putc ('s', fp);
if ((use & KSBA_KEYUSAGE_KEY_CERT_SIGN))
putc ('c', fp);
if ((use & (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT)))
putc ('E', fp);
if ((use & (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION)))
putc ('S', fp);
if ((use & KSBA_KEYUSAGE_KEY_CERT_SIGN))
putc ('C', fp);
}
static void
print_time (gnupg_isotime_t t, FILE *fp)
{
if (!t || !*t)
;
else
fputs (t, fp);
}
/* return an allocated string with the email address extracted from a
DN */
static char *
email_kludge (const char *name)
{
const unsigned char *p;
unsigned char *buf;
int n;
if (strncmp (name, "1.2.840.113549.1.9.1=#", 22))
return NULL;
/* This looks pretty much like an email address in the subject's DN
we use this to add an additional user ID entry. This way,
openSSL generated keys get a nicer and usable listing */
name += 22;
for (n=0, p=name; hexdigitp (p) && hexdigitp (p+1); p +=2, n++)
;
if (*p != '#' || !n)
return NULL;
buf = xtrymalloc (n+3);
if (!buf)
return NULL; /* oops, out of core */
*buf = '<';
for (n=1, p=name; *p != '#'; p +=2, n++)
buf[n] = xtoi_2 (p);
buf[n++] = '>';
buf[n] = 0;
return buf;
}
/* List one certificate in colon mode */
static void
list_cert_colon (ctrl_t ctrl, ksba_cert_t cert, unsigned int validity,
FILE *fp, int have_secret)
{
int idx;
char truststring[2];
char *p;
ksba_sexp_t sexp;
char *fpr;
ksba_isotime_t t;
gpg_error_t valerr;
if (ctrl->with_validation)
valerr = gpgsm_validate_chain (ctrl, cert, NULL, 1, NULL);
else
valerr = 0;
fputs (have_secret? "crs:":"crt:", fp);
truststring[0] = 0;
truststring[1] = 0;
if ((validity & VALIDITY_REVOKED)
|| gpg_err_code (valerr) == GPG_ERR_CERT_REVOKED)
*truststring = 'r';
else if (gpg_err_code (valerr) == GPG_ERR_CERT_EXPIRED)
*truststring = 'e';
else if (valerr)
*truststring = 'i';
if (*truststring)
fputs (truststring, fp);
fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
fprintf (fp, ":%u:%d:%s:",
/*keylen_of_cert (cert)*/1024,
/* pubkey_algo_of_cert (cert)*/1,
fpr+24);
/* we assume --fixed-list-mode for gpgsm */
ksba_cert_get_validity (cert, 0, t);
print_time (t, fp);
putc (':', fp);
ksba_cert_get_validity (cert, 1, t);
print_time ( t, fp);
putc (':', fp);
/* field 8, serial number: */
if ((sexp = ksba_cert_get_serial (cert)))
{
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 == ':')
for (s++; len; len--, s++)
fprintf (fp,"%02X", *s);
}
xfree (sexp);
}
putc (':', fp);
/* field 9, ownertrust - not used here */
putc (':', fp);
/* field 10, old user ID - we use it here for the issuer DN */
if ((p = ksba_cert_get_issuer (cert,0)))
{
print_sanitized_string (fp, p, ':');
xfree (p);
}
putc (':', fp);
/* field 11, signature class - not used */
putc (':', fp);
/* field 12, capabilities: */
print_capabilities (cert, fp);
putc (':', fp);
putc ('\n', fp);
/* FPR record */
fprintf (fp, "fpr:::::::::%s:::", fpr);
xfree (fpr); fpr = NULL;
/* print chaining ID (field 13)*/
{
ksba_cert_t next;
if (!gpgsm_walk_cert_chain (cert, &next))
{
p = gpgsm_get_fingerprint_hexstring (next, GCRY_MD_SHA1);
fputs (p, fp);
xfree (p);
ksba_cert_release (next);
}
}
putc (':', fp);
putc ('\n', fp);
if (opt.with_key_data)
{
if ( (p = gpgsm_get_keygrip_hexstring (cert)))
{
fprintf (fp, "grp:::::::::%s:\n", p);
xfree (p);
}
print_key_data (cert, fp);
}
for (idx=0; (p = ksba_cert_get_subject (cert,idx)); idx++)
{
fprintf (fp, "uid:%s::::::::", truststring);
print_sanitized_string (fp, p, ':');
putc (':', fp);
putc (':', fp);
putc ('\n', fp);
if (!idx)
{
/* It would be better to get the faked email address from
the keydb. But as long as we don't have a way to pass
the meta data back, we just check it the same way as the
code used to create the keybox meta data does */
char *pp = email_kludge (p);
if (pp)
{
fprintf (fp, "uid:%s::::::::", truststring);
print_sanitized_string (fp, pp, ':');
putc (':', fp);
putc (':', fp);
putc ('\n', fp);
xfree (pp);
}
}
xfree (p);
}
}
/* List one certificate in standard mode */
static void
list_cert_std (ctrl_t ctrl, ksba_cert_t cert, FILE *fp, int have_secret,
int with_validation)
{
gpg_error_t err;
ksba_sexp_t sexp;
char *dn;
ksba_isotime_t t;
int idx, i;
int is_ca, chainlen;
unsigned int kusage;
char *string, *p, *pend;
sexp = ksba_cert_get_serial (cert);
fputs ("Serial number: ", fp);
gpgsm_print_serial (fp, sexp);
ksba_free (sexp);
putc ('\n', fp);
dn = ksba_cert_get_issuer (cert, 0);
fputs (" Issuer: ", fp);
gpgsm_print_name (fp, dn);
ksba_free (dn);
putc ('\n', fp);
for (idx=1; (dn = ksba_cert_get_issuer (cert, idx)); idx++)
{
fputs (" aka: ", fp);
gpgsm_print_name (fp, dn);
ksba_free (dn);
putc ('\n', fp);
}
dn = ksba_cert_get_subject (cert, 0);
fputs (" Subject: ", fp);
gpgsm_print_name (fp, dn);
ksba_free (dn);
putc ('\n', fp);
for (idx=1; (dn = ksba_cert_get_subject (cert, idx)); idx++)
{
fputs (" aka: ", fp);
gpgsm_print_name (fp, dn);
ksba_free (dn);
putc ('\n', fp);
}
ksba_cert_get_validity (cert, 0, t);
fputs (" validity: ", fp);
gpgsm_print_time (fp, t);
fputs (" through ", fp);
ksba_cert_get_validity (cert, 1, t);
gpgsm_print_time (fp, t);
putc ('\n', fp);
err = ksba_cert_get_key_usage (cert, &kusage);
if (gpg_err_code (err) != GPG_ERR_NO_DATA)
{
fputs (" key usage:", fp);
if (err)
fprintf (fp, " [error: %s]", gpg_strerror (err));
else
{
if ( (kusage & KSBA_KEYUSAGE_DIGITAL_SIGNATURE))
fputs (" digitalSignature", fp);
if ( (kusage & KSBA_KEYUSAGE_NON_REPUDIATION))
fputs (" nonRepudiation", fp);
if ( (kusage & KSBA_KEYUSAGE_KEY_ENCIPHERMENT))
fputs (" keyEncipherment", fp);
if ( (kusage & KSBA_KEYUSAGE_DATA_ENCIPHERMENT))
fputs (" dataEncipherment", fp);
if ( (kusage & KSBA_KEYUSAGE_KEY_AGREEMENT))
fputs (" keyAgreement", fp);
if ( (kusage & KSBA_KEYUSAGE_KEY_CERT_SIGN))
fputs (" certSign", fp);
if ( (kusage & KSBA_KEYUSAGE_CRL_SIGN))
fputs (" crlSign", fp);
if ( (kusage & KSBA_KEYUSAGE_ENCIPHER_ONLY))
fputs (" encipherOnly", fp);
if ( (kusage & KSBA_KEYUSAGE_DECIPHER_ONLY))
fputs (" decipherOnly", fp);
}
putc ('\n', fp);
}
err = ksba_cert_get_ext_key_usages (cert, &string);
if (gpg_err_code (err) != GPG_ERR_NO_DATA)
{
fputs ("ext key usage: ", fp);
if (err)
fprintf (fp, "[error: %s]", gpg_strerror (err));
else
{
p = string;
while (p && (pend=strchr (p, ':')))
{
*pend++ = 0;
for (i=0; key_purpose_map[i].oid; i++)
if ( !strcmp (key_purpose_map[i].oid, p) )
break;
fputs (key_purpose_map[i].oid?key_purpose_map[i].name:p, fp);
p = pend;
if (*p != 'C')
fputs (" (suggested)", fp);
if ((p = strchr (p, '\n')))
{
p++;
fputs (", ", fp);
}
}
xfree (string);
}
putc ('\n', fp);
}
err = ksba_cert_get_cert_policies (cert, &string);
if (gpg_err_code (err) != GPG_ERR_NO_DATA)
{
fputs (" policies: ", fp);
if (err)
fprintf (fp, "[error: %s]", gpg_strerror (err));
else
{
for (p=string; *p; p++)
{
if (*p == '\n')
*p = ',';
}
print_sanitized_string (fp, string, 0);
xfree (string);
}
putc ('\n', fp);
}
err = ksba_cert_is_ca (cert, &is_ca, &chainlen);
if (err || is_ca)
{
fputs (" chain length: ", fp);
if (err)
fprintf (fp, "[error: %s]", gpg_strerror (err));
else if (chainlen == -1)
fputs ("unlimited", fp);
else
fprintf (fp, "%d", chainlen);
putc ('\n', fp);
}
if (opt.with_md5_fingerprint)
{
dn = gpgsm_get_fingerprint_string (cert, GCRY_MD_MD5);
fprintf (fp, " md5 fpr: %s\n", dn?dn:"error");
xfree (dn);
}
dn = gpgsm_get_fingerprint_string (cert, 0);
fprintf (fp, " fingerprint: %s\n", dn?dn:"error");
xfree (dn);
if (with_validation)
{
err = gpgsm_validate_chain (ctrl, cert, NULL, 1, fp);
if (!err)
fprintf (fp, " [certificate is good]\n");
else
fprintf (fp, " [certificate is bad: %s]\n", gpg_strerror (err));
}
}
/* Same as standard mode mode list all certifying certs too. */
static void
list_cert_chain (ctrl_t ctrl, ksba_cert_t cert, FILE *fp, int with_validation)
{
ksba_cert_t next = NULL;
list_cert_std (ctrl, cert, fp, 0, with_validation);
ksba_cert_ref (cert);
while (!gpgsm_walk_cert_chain (cert, &next))
{
ksba_cert_release (cert);
fputs ("Certified by\n", fp);
list_cert_std (ctrl, next, fp, 0, with_validation);
cert = next;
}
ksba_cert_release (cert);
putc ('\n', fp);
}
/* List all internal keys or just the key given as NAMES.
*/
static gpg_error_t
list_internal_keys (CTRL ctrl, STRLIST names, FILE *fp, unsigned int mode)
{
KEYDB_HANDLE hd;
KEYDB_SEARCH_DESC *desc = NULL;
STRLIST sl;
int ndesc;
ksba_cert_t cert = NULL;
gpg_error_t rc = 0;
const char *lastresname, *resname;
int have_secret;
hd = keydb_new (0);
if (!hd)
{
log_error ("keydb_new failed\n");
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
if (!names)
ndesc = 1;
else
{
for (sl=names, ndesc=0; sl; sl = sl->next, ndesc++)
;
}
desc = xtrycalloc (ndesc, sizeof *desc);
if (!ndesc)
{
rc = gpg_error_from_errno (errno);
log_error ("out of core\n");
goto leave;
}
if (!names)
desc[0].mode = KEYDB_SEARCH_MODE_FIRST;
else
{
for (ndesc=0, sl=names; sl; sl = sl->next)
{
rc = keydb_classify_name (sl->d, desc+ndesc);
if (rc)
{
log_error ("key `%s' not found: %s\n",
sl->d, gpg_strerror (rc));
rc = 0;
}
else
ndesc++;
}
}
/* It would be nice to see which of the given users did actually
match one in the keyring. To implement this we need to have a
found flag for each entry in desc and to set this we must check
all those entries after a match to mark all matched one -
currently we stop at the first match. To do this we need an
extra flag to enable this feature so */
lastresname = NULL;
while (!(rc = keydb_search (hd, desc, ndesc)))
{
unsigned int validity;
if (!names)
desc[0].mode = KEYDB_SEARCH_MODE_NEXT;
rc = keydb_get_flags (hd, KEYBOX_FLAG_VALIDITY, 0, &validity);
if (rc)
{
log_error ("keydb_get_flags failed: %s\n", gpg_strerror (rc));
goto leave;
}
rc = keydb_get_cert (hd, &cert);
if (rc)
{
log_error ("keydb_get_cert failed: %s\n", gpg_strerror (rc));
goto leave;
}
resname = keydb_get_resource_name (hd);
if (lastresname != resname )
{
int i;
if (ctrl->no_server)
{
fprintf (fp, "%s\n", resname );
for (i=strlen(resname); i; i-- )
putchar('-');
putc ('\n', fp);
lastresname = resname;
}
}
have_secret = 0;
if (mode)
{
char *p = gpgsm_get_keygrip_hexstring (cert);
if (p)
{
rc = gpgsm_agent_havekey (p);
if (!rc)
have_secret = 1;
else if ( gpg_err_code (rc) != GPG_ERR_NO_SECKEY)
goto leave;
rc = 0;
xfree (p);
}
}
if (!mode
|| ((mode & 1) && !have_secret)
|| ((mode & 2) && have_secret) )
{
if (ctrl->with_colons)
list_cert_colon (ctrl, cert, validity, fp, have_secret);
else if (ctrl->with_chain)
list_cert_chain (ctrl, cert, fp, ctrl->with_validation);
else
{
list_cert_std (ctrl, cert, fp, have_secret,
ctrl->with_validation);
putc ('\n', fp);
}
}
ksba_cert_release (cert);
cert = NULL;
}
if (gpg_err_code (rc) == GPG_ERR_EOF || rc == -1 )
rc = 0;
if (rc)
log_error ("keydb_search failed: %s\n", gpg_strerror (rc));
leave:
ksba_cert_release (cert);
xfree (desc);
keydb_release (hd);
return rc;
}
static void
list_external_cb (void *cb_value, ksba_cert_t cert)
{
struct list_external_parm_s *parm = cb_value;
if (keydb_store_cert (cert, 1, NULL))
log_error ("error storing certificate as ephemeral\n");
if (parm->print_header)
{
const char *resname = "[external keys]";
int i;
fprintf (parm->fp, "%s\n", resname );
for (i=strlen(resname); i; i-- )
putchar('-');
putc ('\n', parm->fp);
parm->print_header = 0;
}
if (parm->with_colons)
list_cert_colon (parm->ctrl, cert, 0, parm->fp, 0);
else if (parm->with_chain)
list_cert_chain (parm->ctrl, cert, parm->fp, 0);
else
{
list_cert_std (parm->ctrl, cert, parm->fp, 0, 0);
putc ('\n', parm->fp);
}
}
/* List external keys similar to internal one. Note: mode does not
make sense here because it would be unwise to list external secret
keys */
static gpg_error_t
list_external_keys (CTRL ctrl, STRLIST names, FILE *fp)
{
int rc;
struct list_external_parm_s parm;
parm.fp = fp;
parm.ctrl = ctrl,
parm.print_header = ctrl->no_server;
parm.with_colons = ctrl->with_colons;
parm.with_chain = ctrl->with_chain;
rc = gpgsm_dirmngr_lookup (ctrl, names, list_external_cb, &parm);
if (rc)
log_error ("listing external keys failed: %s\n", gpg_strerror (rc));
return rc;
}
/* List all keys or just the key given as NAMES.
MODE controls the operation mode:
Bit 0-2:
0 = list all public keys but don't flag secret ones
1 = list only public keys
2 = list only secret keys
3 = list secret and public keys
Bit 6: list internal keys
Bit 7: list external keys
*/
gpg_error_t
gpgsm_list_keys (CTRL ctrl, STRLIST names, FILE *fp, unsigned int mode)
{
gpg_error_t err = 0;
if ((mode & (1<<6)))
err = list_internal_keys (ctrl, names, fp, (mode & 3));
if (!err && (mode & (1<<7)))
err = list_external_keys (ctrl, names, fp);
return err;
}