gnupg/sm/certdump.c

857 lines
20 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.

/* certdump.c - Dump a certificate for debugging
* Copyright (C) 2001, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* 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>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#ifdef HAVE_LANGINFO_CODESET
#include <langinfo.h>
#endif
#include "gpgsm.h"
#include <gcrypt.h>
#include <ksba.h>
#include "keydb.h"
#include "i18n.h"
struct dn_array_s {
char *key;
char *value;
int multivalued;
int done;
};
/* print the first element of an S-Expression */
void
gpgsm_print_serial (FILE *fp, ksba_const_sexp_t sn)
{
const char *p = (const char *)sn;
unsigned long n;
char *endp;
if (!p)
fputs (_("none"), fp);
else if (*p != '(')
fputs ("[Internal error - not an S-expression]", fp);
else
{
p++;
n = strtoul (p, &endp, 10);
p = endp;
if (*p!=':')
fputs ("[Internal Error - invalid S-expression]", fp);
else
{
for (p++; n; n--, p++)
fprintf (fp, "%02X", *(const unsigned char*)p);
}
}
}
/* Dump the serial number or any other simple S-expression. */
void
gpgsm_dump_serial (ksba_const_sexp_t sn)
{
const char *p = (const char *)sn;
unsigned long n;
char *endp;
if (!p)
log_printf ("none");
else if (*p != '(')
log_printf ("ERROR - not an S-expression");
else
{
p++;
n = strtoul (p, &endp, 10);
p = endp;
if (*p!=':')
log_printf ("ERROR - invalid S-expression");
else
{
for (p++; n; n--, p++)
log_printf ("%02X", *(const unsigned char *)p);
}
}
}
char *
gpgsm_format_serial (ksba_const_sexp_t sn)
{
const char *p = (const char *)sn;
unsigned long n;
char *endp;
char *buffer;
int i;
if (!p)
return NULL;
if (*p != '(')
BUG (); /* Not a valid S-expression. */
p++;
n = strtoul (p, &endp, 10);
p = endp;
if (*p!=':')
BUG (); /* Not a valid S-expression. */
p++;
buffer = xtrymalloc (n*2+1);
if (buffer)
{
for (i=0; n; n--, p++, i+=2)
sprintf (buffer+i, "%02X", *(unsigned char *)p);
buffer[i] = 0;
}
return buffer;
}
void
gpgsm_print_time (FILE *fp, ksba_isotime_t t)
{
if (!t || !*t)
fputs (_("none"), fp);
else
fprintf (fp, "%.4s-%.2s-%.2s %.2s:%.2s:%s", t, t+4, t+6, t+9, t+11, t+13);
}
void
gpgsm_dump_time (ksba_isotime_t t)
{
if (!t || !*t)
log_printf (_("[none]"));
else
log_printf ("%.4s-%.2s-%.2s %.2s:%.2s:%s",
t, t+4, t+6, t+9, t+11, t+13);
}
void
gpgsm_dump_string (const char *string)
{
if (!string)
log_printf ("[error]");
else
{
const unsigned char *s;
for (s=(const unsigned char*)string; *s; s++)
{
if (*s < ' ' || (*s >= 0x7f && *s <= 0xa0))
break;
}
if (!*s && *string != '[')
log_printf ("%s", string);
else
{
log_printf ( "[ ");
log_printhex (NULL, string, strlen (string));
log_printf ( " ]");
}
}
}
/* This simple dump function is mainly used for debugging purposes. */
void
gpgsm_dump_cert (const char *text, ksba_cert_t cert)
{
ksba_sexp_t sexp;
char *p;
char *dn;
ksba_isotime_t t;
log_debug ("BEGIN Certificate `%s':\n", text? text:"");
if (cert)
{
sexp = ksba_cert_get_serial (cert);
log_debug (" serial: ");
gpgsm_dump_serial (sexp);
ksba_free (sexp);
log_printf ("\n");
ksba_cert_get_validity (cert, 0, t);
log_debug (" notBefore: ");
gpgsm_dump_time (t);
log_printf ("\n");
ksba_cert_get_validity (cert, 1, t);
log_debug (" notAfter: ");
gpgsm_dump_time (t);
log_printf ("\n");
dn = ksba_cert_get_issuer (cert, 0);
log_debug (" issuer: ");
gpgsm_dump_string (dn);
ksba_free (dn);
log_printf ("\n");
dn = ksba_cert_get_subject (cert, 0);
log_debug (" subject: ");
gpgsm_dump_string (dn);
ksba_free (dn);
log_printf ("\n");
log_debug (" hash algo: %s\n", ksba_cert_get_digest_algo (cert));
p = gpgsm_get_fingerprint_string (cert, 0);
log_debug (" SHA1 Fingerprint: %s\n", p);
xfree (p);
}
log_debug ("END Certificate\n");
}
/* Log the certificate's name in "#SN/ISSUERDN" format along with
TEXT. */
void
gpgsm_cert_log_name (const char *text, ksba_cert_t cert)
{
log_info ("%s", text? text:"certificate" );
if (cert)
{
ksba_sexp_t sn;
char *p;
p = ksba_cert_get_issuer (cert, 0);
sn = ksba_cert_get_serial (cert);
if (p && sn)
{
log_printf (" #");
gpgsm_dump_serial (sn);
log_printf ("/");
gpgsm_dump_string (p);
}
else
log_printf (" [invalid]");
ksba_free (sn);
xfree (p);
}
log_printf ("\n");
}
/* helper for the rfc2253 string parser */
static const unsigned char *
parse_dn_part (struct dn_array_s *array, const unsigned char *string)
{
static struct {
const char *label;
const char *oid;
} label_map[] = {
/* Warning: When adding new labels, make sure that the buffer
below we be allocated large enough. */
{"EMail", "1.2.840.113549.1.9.1" },
{"T", "2.5.4.12" },
{"GN", "2.5.4.42" },
{"SN", "2.5.4.4" },
{"NameDistinguisher", "0.2.262.1.10.7.20"},
{"ADDR", "2.5.4.16" },
{"BC", "2.5.4.15" },
{"D", "2.5.4.13" },
{"PostalCode", "2.5.4.17" },
{"Pseudo", "2.5.4.65" },
{"SerialNumber", "2.5.4.5" },
{NULL, NULL}
};
const unsigned char *s, *s1;
size_t n;
char *p;
int i;
/* Parse attributeType */
for (s = string+1; *s && *s != '='; s++)
;
if (!*s)
return NULL; /* error */
n = s - string;
if (!n)
return NULL; /* empty key */
/* We need to allocate a few bytes more due to the possible mapping
from the shorter OID to the longer label. */
array->key = p = xtrymalloc (n+10);
if (!array->key)
return NULL;
memcpy (p, string, n);
p[n] = 0;
trim_trailing_spaces (p);
if (digitp (p))
{
for (i=0; label_map[i].label; i++ )
if ( !strcmp (p, label_map[i].oid) )
{
strcpy (p, label_map[i].label);
break;
}
}
string = s + 1;
if (*string == '#')
{ /* hexstring */
string++;
for (s=string; hexdigitp (s); s++)
s++;
n = s - string;
if (!n || (n & 1))
return NULL; /* Empty or odd number of digits. */
n /= 2;
array->value = p = xtrymalloc (n+1);
if (!p)
return NULL;
for (s1=string; n; s1 += 2, n--, p++)
{
*(unsigned char *)p = xtoi_2 (s1);
if (!*p)
*p = 0x01; /* Better print a wrong value than truncating
the string. */
}
*p = 0;
}
else
{ /* regular v3 quoted string */
for (n=0, s=string; *s; s++)
{
if (*s == '\\')
{ /* pair */
s++;
if (*s == ',' || *s == '=' || *s == '+'
|| *s == '<' || *s == '>' || *s == '#' || *s == ';'
|| *s == '\\' || *s == '\"' || *s == ' ')
n++;
else if (hexdigitp (s) && hexdigitp (s+1))
{
s++;
n++;
}
else
return NULL; /* invalid escape sequence */
}
else if (*s == '\"')
return NULL; /* invalid encoding */
else if (*s == ',' || *s == '=' || *s == '+'
|| *s == '<' || *s == '>' || *s == '#' || *s == ';' )
break;
else
n++;
}
array->value = p = xtrymalloc (n+1);
if (!p)
return NULL;
for (s=string; n; s++, n--)
{
if (*s == '\\')
{
s++;
if (hexdigitp (s))
{
*(unsigned char *)p++ = xtoi_2 (s);
s++;
}
else
*p++ = *s;
}
else
*p++ = *s;
}
*p = 0;
}
return s;
}
/* Parse a DN and return an array-ized one. This is not a validating
parser and it does not support any old-stylish syntax; KSBA is
expected to return only rfc2253 compatible strings. */
static struct dn_array_s *
parse_dn (const unsigned char *string)
{
struct dn_array_s *array;
size_t arrayidx, arraysize;
int i;
arraysize = 7; /* C,ST,L,O,OU,CN,email */
arrayidx = 0;
array = xtrymalloc ((arraysize+1) * sizeof *array);
if (!array)
return NULL;
while (*string)
{
while (*string == ' ')
string++;
if (!*string)
break; /* ready */
if (arrayidx >= arraysize)
{
struct dn_array_s *a2;
arraysize += 5;
a2 = xtryrealloc (array, (arraysize+1) * sizeof *array);
if (!a2)
goto failure;
array = a2;
}
array[arrayidx].key = NULL;
array[arrayidx].value = NULL;
string = parse_dn_part (array+arrayidx, string);
if (!string)
goto failure;
while (*string == ' ')
string++;
array[arrayidx].multivalued = (*string == '+');
array[arrayidx].done = 0;
arrayidx++;
if (*string && *string != ',' && *string != ';' && *string != '+')
goto failure; /* invalid delimiter */
if (*string)
string++;
}
array[arrayidx].key = NULL;
array[arrayidx].value = NULL;
return array;
failure:
for (i=0; i < arrayidx; i++)
{
xfree (array[i].key);
xfree (array[i].value);
}
xfree (array);
return NULL;
}
static void
print_dn_part (FILE *fp, struct dn_array_s *dn, const char *key, int translate)
{
struct dn_array_s *first_dn = dn;
for (; dn->key; dn++)
{
if (!dn->done && !strcmp (dn->key, key))
{
/* Forward to the last multi-valued RDN, so that we can
print them all in reverse in the correct order. Note
that this overrides the the standard sequence but that
seems to a reasonable thing to do with multi-valued
RDNs. */
while (dn->multivalued && dn[1].key)
dn++;
next:
if (!dn->done && dn->value && *dn->value)
{
fprintf (fp, "/%s=", dn->key);
if (translate)
print_sanitized_utf8_string (fp, dn->value, '/');
else
print_sanitized_string (fp, dn->value, '/');
}
dn->done = 1;
if (dn > first_dn && dn[-1].multivalued)
{
dn--;
goto next;
}
}
}
}
/* Print all parts of a DN in a "standard" sequence. We first print
all the known parts, followed by the uncommon ones */
static void
print_dn_parts (FILE *fp, struct dn_array_s *dn, int translate)
{
const char *stdpart[] = {
"CN", "OU", "O", "STREET", "L", "ST", "C", "EMail", NULL
};
int i;
for (i=0; stdpart[i]; i++)
print_dn_part (fp, dn, stdpart[i], translate);
/* Now print the rest without any specific ordering */
for (; dn->key; dn++)
print_dn_part (fp, dn, dn->key, translate);
}
/* Print the S-Expression in BUF, which has a valid length of BUFLEN,
as a human readable string in one line to FP. */
static void
pretty_print_sexp (FILE *fp, const unsigned char *buf, size_t buflen)
{
size_t len;
gcry_sexp_t sexp;
char *result, *p;
if ( gcry_sexp_sscan (&sexp, NULL, (const char*)buf, buflen) )
{
fputs (_("[Error - invalid encoding]"), fp);
return;
}
len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, NULL, 0);
assert (len);
result = xtrymalloc (len);
if (!result)
{
fputs (_("[Error - out of core]"), fp);
gcry_sexp_release (sexp);
return;
}
len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, result, len);
assert (len);
for (p = result; len; len--, p++)
{
if (*p == '\n')
{
if (len > 1) /* Avoid printing the trailing LF. */
fputs ("\\n", fp);
}
else if (*p == '\r')
fputs ("\\r", fp);
else if (*p == '\v')
fputs ("\\v", fp);
else if (*p == '\t')
fputs ("\\t", fp);
else
putc (*p, fp);
}
xfree (result);
gcry_sexp_release (sexp);
}
void
gpgsm_print_name2 (FILE *fp, const char *name, int translate)
{
const unsigned char *s = (const unsigned char *)name;
int i;
if (!s)
{
fputs (_("[Error - No name]"), fp);
}
else if (*s == '<')
{
const char *s2 = strchr ( (char*)s+1, '>');
if (s2)
{
if (translate)
print_sanitized_utf8_buffer (fp, s + 1, s2 - (char*)s - 1, 0);
else
print_sanitized_buffer (fp, s + 1, s2 - (char*)s - 1, 0);
}
}
else if (*s == '(')
{
pretty_print_sexp (fp, s, gcry_sexp_canon_len (s, 0, NULL, NULL));
}
else if (!((*s >= '0' && *s < '9')
|| (*s >= 'A' && *s <= 'Z')
|| (*s >= 'a' && *s <= 'z')))
fputs (_("[Error - invalid encoding]"), fp);
else
{
struct dn_array_s *dn = parse_dn (s);
if (!dn)
fputs (_("[Error - invalid DN]"), fp);
else
{
print_dn_parts (fp, dn, translate);
for (i=0; dn[i].key; i++)
{
xfree (dn[i].key);
xfree (dn[i].value);
}
xfree (dn);
}
}
}
void
gpgsm_print_name (FILE *fp, const char *name)
{
gpgsm_print_name2 (fp, name, 1);
}
/* A cookie structure used for the memory stream. */
struct format_name_cookie
{
char *buffer; /* Malloced buffer with the data to deliver. */
size_t size; /* Allocated size of this buffer. */
size_t len; /* strlen (buffer). */
int error; /* system error code if any. */
};
/* The writer function for the memory stream. */
static int
format_name_writer (void *cookie, const char *buffer, size_t size)
{
struct format_name_cookie *c = cookie;
char *p;
if (c->buffer)
p = xtryrealloc (c->buffer, c->size + size + 1);
else
p = xtrymalloc (size + 1);
if (!p)
{
c->error = errno;
xfree (c->buffer);
errno = c->error;
return -1;
}
c->buffer = p;
memcpy (p + c->len, buffer, size);
c->len += size;
p[c->len] = 0; /* Terminate string. */
return size;
}
/* Format NAME which is expected to be in rfc2253 format into a better
human readable format. Caller must free the returned string. NULL
is returned in case of an error. With TRANSLATE set to true the
name will be translated to the native encoding. Note that NAME is
internally always UTF-8 encoded. */
char *
gpgsm_format_name2 (const char *name, int translate)
{
#if defined (HAVE_FOPENCOOKIE) || defined (HAVE_FUNOPEN)
FILE *fp;
struct format_name_cookie cookie;
memset (&cookie, 0, sizeof cookie);
#ifdef HAVE_FOPENCOOKIE
{
cookie_io_functions_t io = { NULL };
io.write = format_name_writer;
fp = fopencookie (&cookie, "w", io);
}
#else /*!HAVE_FOPENCOOKIE*/
{
fp = funopen (&cookie, NULL, format_name_writer, NULL, NULL);
}
#endif /*!HAVE_FOPENCOOKIE*/
if (!fp)
{
int save_errno = errno;
log_error ("error creating memory stream: %s\n", strerror (errno));
errno = save_errno;
return NULL;
}
gpgsm_print_name2 (fp, name, translate);
fclose (fp);
if (cookie.error || !cookie.buffer)
{
xfree (cookie.buffer);
errno = cookie.error;
return NULL;
}
return cookie.buffer;
#else /* No fun - use the name verbatim. */
return xtrystrdup (name);
#endif /* No fun. */
}
char *
gpgsm_format_name (const char *name)
{
return gpgsm_format_name2 (name, 1);
}
/* Return fingerprint and a percent escaped name in a human readable
format suitable for status messages like GOODSIG. May return NULL
on error (out of core). */
char *
gpgsm_fpr_and_name_for_status (ksba_cert_t cert)
{
char *fpr, *name, *p;
char *buffer;
fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
if (!fpr)
return NULL;
name = ksba_cert_get_subject (cert, 0);
if (!name)
{
xfree (fpr);
return NULL;
}
p = gpgsm_format_name2 (name, 0);
ksba_free (name);
name = p;
if (!name)
{
xfree (fpr);
return NULL;
}
buffer = xtrymalloc (strlen (fpr) + 1 + 3*strlen (name) + 1);
if (buffer)
{
const unsigned char *s;
p = stpcpy (stpcpy (buffer, fpr), " ");
for (s = name; *s; s++)
{
if (*s < ' ')
{
sprintf (p, "%%%02X", *s);
p += 3;
}
else
*p++ = *s;
}
*p = 0;
}
xfree (fpr);
xfree (name);
return buffer;
}
/* Create a key description for the CERT, this may be passed to the
pinentry. The caller must free the returned string. NULL may be
returned on error. */
char *
gpgsm_format_keydesc (ksba_cert_t cert)
{
int rc;
char *name, *subject, *buffer, *p;
const char *s;
ksba_isotime_t t;
char created[20];
char *sn;
ksba_sexp_t sexp;
char *orig_codeset = NULL;
name = ksba_cert_get_subject (cert, 0);
subject = name? gpgsm_format_name2 (name, 0) : NULL;
ksba_free (name); name = NULL;
sexp = ksba_cert_get_serial (cert);
sn = sexp? gpgsm_format_serial (sexp) : NULL;
ksba_free (sexp);
ksba_cert_get_validity (cert, 0, t);
if (t && *t)
sprintf (created, "%.4s-%.2s-%.2s", t, t+4, t+6);
else
*created = 0;
#ifdef ENABLE_NLS
/* The Assuan agent protocol requires us to transmit utf-8 strings */
orig_codeset = bind_textdomain_codeset (PACKAGE_GT, NULL);
#ifdef HAVE_LANGINFO_CODESET
if (!orig_codeset)
orig_codeset = nl_langinfo (CODESET);
#endif
if (orig_codeset)
{ /* We only switch when we are able to restore the codeset later.
Note that bind_textdomain_codeset does only return on memory
errors but not if a codeset is not available. Thus we don't
bother printing a diagnostic here. */
orig_codeset = xstrdup (orig_codeset);
if (!bind_textdomain_codeset (PACKAGE_GT, "utf-8"))
orig_codeset = NULL;
}
#endif
rc = asprintf (&name,
_("Please enter the passphrase to unlock the"
" secret key for:\n"
"\"%s\"\n"
"S/N %s, ID %08lX, created %s" ),
subject? subject:"?",
sn? sn: "?",
gpgsm_get_short_fingerprint (cert),
created);
#ifdef ENABLE_NLS
if (orig_codeset)
bind_textdomain_codeset (PACKAGE_GT, orig_codeset);
#endif
xfree (orig_codeset);
if (rc < 0)
{
int save_errno = errno;
xfree (subject);
xfree (sn);
errno = save_errno;
return NULL;
}
xfree (subject);
xfree (sn);
buffer = p = xtrymalloc (strlen (name) * 3 + 1);
for (s=name; *s; s++)
{
if (*s < ' ' || *s == '+')
{
sprintf (p, "%%%02X", *(unsigned char *)s);
p += 3;
}
else if (*s == ' ')
*p++ = '+';
else
*p++ = *s;
}
*p = 0;
free (name);
return buffer;
}