/* certdump.c - Dump a certificate for debugging * Copyright (C) 2001, 2004, 2007 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 . */ #include #include #include #include #include #include #include #include #ifdef HAVE_LOCALE_H #include #endif #ifdef HAVE_LANGINFO_CODESET #include #endif #include "gpgsm.h" #include #include #include "keydb.h" #include "i18n.h" #ifdef HAVE_FOPENCOOKIE typedef ssize_t my_funopen_hook_ret_t; #else typedef int my_funopen_hook_ret_t; #endif struct dn_array_s { char *key; char *value; int multivalued; int done; }; /* Print the first element of an S-Expression. */ void gpgsm_print_serial (estream_t fp, ksba_const_sexp_t sn) { const char *p = (const char *)sn; unsigned long n; char *endp; if (!p) es_fputs (_("none"), fp); else if (*p != '(') es_fputs ("[Internal error - not an S-expression]", fp); else { p++; n = strtoul (p, &endp, 10); p = endp; if (*p++ != ':') es_fputs ("[Internal Error - invalid S-expression]", fp); else es_write_hexstring (fp, p, n, 0, NULL); } } /* 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 (estream_t fp, ksba_isotime_t t) { if (!t || !*t) es_fputs (_("none"), fp); else es_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 == ';' ) 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; } /* Print a DN part to STREAM or if STREAM is NULL to FP. */ static void print_dn_part (FILE *fp, estream_t stream, 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) { if (stream) { es_fprintf (stream, "/%s=", dn->key); if (translate) es_write_sanitized_utf8_buffer (stream, dn->value, strlen (dn->value), "/", NULL); else es_write_sanitized (stream, dn->value, strlen (dn->value), "/", NULL); } else { 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, estream_t stream, 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, stream, dn, stdpart[i], translate); /* Now print the rest without any specific ordering */ for (; dn->key; dn++) print_dn_part (fp, stream, 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); } /* Print the S-Expression in BUF to extended STREAM, which has a valid length of BUFLEN, as a human readable string in one line to FP. */ static void pretty_es_print_sexp (estream_t 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) ) { es_fputs (_("[Error - invalid encoding]"), fp); return; } len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, NULL, 0); assert (len); result = xtrymalloc (len); if (!result) { es_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. */ es_fputs ("\\n", fp); } else if (*p == '\r') es_fputs ("\\r", fp); else if (*p == '\v') es_fputs ("\\v", fp); else if (*p == '\t') es_fputs ("\\t", fp); else es_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, NULL, 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); } /* This is avariant of gpgsm_print_name sending it output to an estream. */ void gpgsm_es_print_name (estream_t fp, const char *name) { const unsigned char *s = (const unsigned char *)name; int i; if (!s) { es_fputs (_("[Error - No name]"), fp); } else if (*s == '<') { const char *s2 = strchr ( (char*)s+1, '>'); if (s2) es_write_sanitized_utf8_buffer (fp, s + 1, s2 - (char*)s - 1, NULL, NULL); } else if (*s == '(') { pretty_es_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'))) es_fputs (_("[Error - invalid encoding]"), fp); else { struct dn_array_s *dn = parse_dn (s); if (!dn) es_fputs (_("[Error - invalid DN]"), fp); else { print_dn_parts (NULL, fp, dn, 1); for (i=0; dn[i].key; i++) { xfree (dn[i].key); xfree (dn[i].value); } xfree (dn); } } } #if defined (HAVE_FOPENCOOKIE) || defined (HAVE_FUNOPEN) /* 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 my_funopen_hook_ret_t 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; } #endif /*HAVE_FOPENCOOKIE || HAVE_FUNOPEN*/ /* 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; #ifdef ENABLE_NLS char *orig_codeset = NULL; #endif 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")) { xfree (orig_codeset); orig_codeset = NULL; } } #endif rc = asprintf (&name, _("Please enter the passphrase to unlock the" " secret key for:\n" "\"%s\"\n" "S/N %s, ID 0x%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); xfree (orig_codeset); } #endif 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; }