1
0
mirror of git://git.gnupg.org/gnupg.git synced 2024-11-10 21:38:50 +01:00
gnupg/sm/certdump.c

722 lines
17 KiB
C
Raw Normal View History

/* 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., 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>
2004-03-06 21:11:19 +01:00
#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 p)
{
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", *p);
}
}
}
void
gpgsm_dump_serial (ksba_const_sexp_t p)
{
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", *p);
}
}
}
char *
gpgsm_format_serial (ksba_const_sexp_t p)
{
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=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;
unsigned 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");
}
/* 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;
unsigned 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++)
{
*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))
{
*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);
}
void
gpgsm_print_name2 (FILE *fp, const char *name, int translate)
{
const unsigned char *s;
int i;
s = name;
if (!s)
{
fputs (_("[Error - No name]"), fp);
}
else if (*s == '<')
{
const unsigned char *s2 = strchr (s+1, '>');
if (s2)
{
if (translate)
print_sanitized_utf8_buffer (fp, s + 1, s2 - s - 1, 0);
else
print_sanitized_buffer (fp, s + 1, s2 - s - 1, 0);
}
}
else if (*s == '(')
fputs (_("[Error - unknown encoding]"), fp);
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 encodig. 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);
}
/* 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)
{
2004-03-06 21:11:19 +01:00
int rc;
char *name, *subject, *buffer, *p;
const char *s;
ksba_isotime_t t;
char created[20];
char *sn;
ksba_sexp_t sexp;
2004-03-06 21:11:19 +01:00
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;
2004-03-06 21:11:19 +01:00
#ifdef ENABLE_NLS
/* The Assuan agent protol requires us to transmit utf-8 strings */
orig_codeset = bind_textdomain_codeset (PACKAGE_GT, NULL);
2004-03-06 21:11:19 +01:00
#ifdef HAVE_LANGINFO_CODESET
if (!orig_codeset)
orig_codeset = nl_langinfo (CODESET);
#endif
if (orig_codeset)
2004-09-30 09:54:12 +02:00
{ /* 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. */
2004-03-06 21:11:19 +01:00
orig_codeset = xstrdup (orig_codeset);
if (!bind_textdomain_codeset (PACKAGE_GT, "utf-8"))
2004-03-06 21:11:19 +01:00
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),
2004-03-06 21:11:19 +01:00
created);
#ifdef ENABLE_NLS
if (orig_codeset)
bind_textdomain_codeset (PACKAGE_GT, orig_codeset);
2004-03-06 21:11:19 +01:00
#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;
}