Factored utf8 switching code out to i18n.c.
[gnupg.git] / sm / certdump.c
1 /* certdump.c - Dump a certificate for debugging
2  * Copyright (C) 2001, 2004, 2007 Free Software Foundation, Inc.
3  *
4  * This file is part of GnuPG.
5  *
6  * GnuPG is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * GnuPG is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <config.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <errno.h>
25 #include <unistd.h> 
26 #include <time.h>
27 #include <assert.h>
28 #ifdef HAVE_LOCALE_H
29 #include <locale.h>
30 #endif
31 #ifdef HAVE_LANGINFO_CODESET
32 #include <langinfo.h>
33 #endif
34
35 #include "gpgsm.h"
36 #include <gcrypt.h>
37 #include <ksba.h>
38
39 #include "keydb.h"
40 #include "i18n.h"
41
42 #ifdef HAVE_FOPENCOOKIE
43 typedef ssize_t my_funopen_hook_ret_t;
44 #else
45 typedef int     my_funopen_hook_ret_t;
46 #endif
47
48
49 struct dn_array_s {
50   char *key;
51   char *value;
52   int   multivalued;
53   int   done;
54 };
55
56
57 /* Print the first element of an S-Expression. */
58 void
59 gpgsm_print_serial (estream_t fp, ksba_const_sexp_t sn)
60 {
61   const char *p = (const char *)sn;
62   unsigned long n;
63   char *endp;
64
65   if (!p)
66     es_fputs (_("none"), fp);
67   else if (*p != '(')
68     es_fputs ("[Internal error - not an S-expression]", fp);
69   else
70     {
71       p++;
72       n = strtoul (p, &endp, 10);
73       p = endp;
74       if (*p++ != ':')
75         es_fputs ("[Internal Error - invalid S-expression]", fp);
76       else
77         es_write_hexstring (fp, p, n, 0, NULL);
78     }
79 }
80
81
82 /* Dump the serial number or any other simple S-expression. */
83 void
84 gpgsm_dump_serial (ksba_const_sexp_t sn)
85 {
86   const char *p = (const char *)sn;
87   unsigned long n;
88   char *endp;
89
90   if (!p)
91     log_printf ("none");
92   else if (*p != '(')
93     log_printf ("ERROR - not an S-expression");
94   else
95     {
96       p++;
97       n = strtoul (p, &endp, 10);
98       p = endp;
99       if (*p!=':')
100         log_printf ("ERROR - invalid S-expression");
101       else
102         {
103           for (p++; n; n--, p++)
104             log_printf ("%02X", *(const unsigned char *)p);
105         }
106     }
107 }
108
109
110 char *
111 gpgsm_format_serial (ksba_const_sexp_t sn)
112 {
113   const char *p = (const char *)sn;
114   unsigned long n;
115   char *endp;
116   char *buffer;
117   int i;
118
119   if (!p)
120     return NULL;
121
122   if (*p != '(')
123     BUG (); /* Not a valid S-expression. */
124
125   p++;
126   n = strtoul (p, &endp, 10);
127   p = endp;
128   if (*p!=':')
129     BUG (); /* Not a valid S-expression. */
130   p++;
131
132   buffer = xtrymalloc (n*2+1);
133   if (buffer)
134     {
135       for (i=0; n; n--, p++, i+=2)
136         sprintf (buffer+i, "%02X", *(unsigned char *)p);
137       buffer[i] = 0;
138     }
139   return buffer;
140 }
141
142
143
144
145 void
146 gpgsm_print_time (estream_t fp, ksba_isotime_t t)
147 {
148   if (!t || !*t)
149     es_fputs (_("none"), fp);
150   else
151     es_fprintf (fp, "%.4s-%.2s-%.2s %.2s:%.2s:%s",
152                 t, t+4, t+6, t+9, t+11, t+13);
153 }
154
155
156 void
157 gpgsm_dump_time (ksba_isotime_t t)
158 {
159   if (!t || !*t)
160     log_printf (_("[none]"));
161   else
162     log_printf ("%.4s-%.2s-%.2s %.2s:%.2s:%s",
163                 t, t+4, t+6, t+9, t+11, t+13);
164 }
165
166
167
168
169 void
170 gpgsm_dump_string (const char *string)
171 {
172
173   if (!string)
174     log_printf ("[error]");
175   else
176     {
177       const unsigned char *s;
178
179       for (s=(const unsigned char*)string; *s; s++)
180         {
181           if (*s < ' ' || (*s >= 0x7f && *s <= 0xa0))
182             break;
183         }
184       if (!*s && *string != '[')
185         log_printf ("%s", string);
186       else
187         {
188           log_printf ( "[ ");
189           log_printhex (NULL, string, strlen (string));
190           log_printf ( " ]");
191         }
192     }
193 }
194
195
196 /* This simple dump function is mainly used for debugging purposes. */
197 void 
198 gpgsm_dump_cert (const char *text, ksba_cert_t cert)
199 {
200   ksba_sexp_t sexp;
201   char *p;
202   char *dn;
203   ksba_isotime_t t;
204
205   log_debug ("BEGIN Certificate `%s':\n", text? text:"");
206   if (cert)
207     {
208       sexp = ksba_cert_get_serial (cert);
209       log_debug ("     serial: ");
210       gpgsm_dump_serial (sexp);
211       ksba_free (sexp);
212       log_printf ("\n");
213
214       ksba_cert_get_validity (cert, 0, t);
215       log_debug ("  notBefore: ");
216       gpgsm_dump_time (t);
217       log_printf ("\n");
218       ksba_cert_get_validity (cert, 1, t);
219       log_debug ("   notAfter: ");
220       gpgsm_dump_time (t);
221       log_printf ("\n");
222
223       dn = ksba_cert_get_issuer (cert, 0);
224       log_debug ("     issuer: ");
225       gpgsm_dump_string (dn);
226       ksba_free (dn);
227       log_printf ("\n");
228     
229       dn = ksba_cert_get_subject (cert, 0);
230       log_debug ("    subject: ");
231       gpgsm_dump_string (dn);
232       ksba_free (dn);
233       log_printf ("\n");
234
235       log_debug ("  hash algo: %s\n", ksba_cert_get_digest_algo (cert));
236
237       p = gpgsm_get_fingerprint_string (cert, 0);
238       log_debug ("  SHA1 Fingerprint: %s\n", p);
239       xfree (p);
240     }
241   log_debug ("END Certificate\n");
242 }
243
244
245 /* Log the certificate's name in "#SN/ISSUERDN" format along with
246    TEXT. */
247 void 
248 gpgsm_cert_log_name (const char *text, ksba_cert_t cert)
249 {
250   log_info ("%s", text? text:"certificate" );
251   if (cert)
252     {
253       ksba_sexp_t sn;
254       char *p;
255
256       p = ksba_cert_get_issuer (cert, 0);
257       sn = ksba_cert_get_serial (cert);
258       if (p && sn)
259         {
260           log_printf (" #");
261           gpgsm_dump_serial (sn);
262           log_printf ("/");
263           gpgsm_dump_string (p);
264         }
265       else
266         log_printf (" [invalid]");
267       ksba_free (sn);
268       xfree (p);
269     }
270   log_printf ("\n");
271 }
272
273
274
275 \f
276 /* helper for the rfc2253 string parser */
277 static const unsigned char *
278 parse_dn_part (struct dn_array_s *array, const unsigned char *string)
279 {
280   static struct {
281     const char *label;
282     const char *oid;
283   } label_map[] = {
284     /* Warning: When adding new labels, make sure that the buffer
285        below we be allocated large enough. */
286     {"EMail",        "1.2.840.113549.1.9.1" },
287     {"T",            "2.5.4.12" },
288     {"GN",           "2.5.4.42" },
289     {"SN",           "2.5.4.4" },
290     {"NameDistinguisher", "0.2.262.1.10.7.20"}, 
291     {"ADDR",         "2.5.4.16" },
292     {"BC",           "2.5.4.15" },
293     {"D",            "2.5.4.13" },
294     {"PostalCode",   "2.5.4.17" },
295     {"Pseudo",       "2.5.4.65" },
296     {"SerialNumber", "2.5.4.5" },
297     {NULL, NULL}
298   };
299   const unsigned char *s, *s1;
300   size_t n;
301   char *p;
302   int i;
303
304   /* Parse attributeType */
305   for (s = string+1; *s && *s != '='; s++)
306     ;
307   if (!*s)
308     return NULL; /* error */
309   n = s - string;
310   if (!n)
311     return NULL; /* empty key */
312
313   /* We need to allocate a few bytes more due to the possible mapping
314      from the shorter OID to the longer label. */
315   array->key = p = xtrymalloc (n+10);
316   if (!array->key)
317     return NULL;
318   memcpy (p, string, n); 
319   p[n] = 0;
320   trim_trailing_spaces (p);
321
322   if (digitp (p))
323     {
324       for (i=0; label_map[i].label; i++ )
325         if ( !strcmp (p, label_map[i].oid) )
326           {
327             strcpy (p, label_map[i].label);
328             break;
329           }
330     }
331   string = s + 1;
332
333   if (*string == '#')
334     { /* hexstring */
335       string++;
336       for (s=string; hexdigitp (s); s++)
337         s++;
338       n = s - string;
339       if (!n || (n & 1))
340         return NULL; /* Empty or odd number of digits. */
341       n /= 2;
342       array->value = p = xtrymalloc (n+1);
343       if (!p)
344         return NULL;
345       for (s1=string; n; s1 += 2, n--, p++)
346         {
347           *(unsigned char *)p = xtoi_2 (s1);
348           if (!*p)
349             *p = 0x01; /* Better print a wrong value than truncating
350                           the string. */
351         }
352       *p = 0;
353    }
354   else
355     { /* regular v3 quoted string */
356       for (n=0, s=string; *s; s++)
357         {
358           if (*s == '\\')
359             { /* pair */
360               s++;
361               if (*s == ',' || *s == '=' || *s == '+'
362                   || *s == '<' || *s == '>' || *s == '#' || *s == ';' 
363                   || *s == '\\' || *s == '\"' || *s == ' ')
364                 n++;
365               else if (hexdigitp (s) && hexdigitp (s+1))
366                 {
367                   s++;
368                   n++;
369                 }
370               else
371                 return NULL; /* invalid escape sequence */
372             }
373           else if (*s == '\"')
374             return NULL; /* invalid encoding */
375           else if (*s == ',' || *s == '=' || *s == '+'
376                    || *s == '<' || *s == '>' || *s == ';' )
377             break; 
378           else
379             n++;
380         }
381
382       array->value = p = xtrymalloc (n+1);
383       if (!p)
384         return NULL;
385       for (s=string; n; s++, n--)
386         {
387           if (*s == '\\')
388             { 
389               s++;
390               if (hexdigitp (s))
391                 {
392                   *(unsigned char *)p++ = xtoi_2 (s);
393                   s++;
394                 }
395               else
396                 *p++ = *s;
397             }
398           else
399             *p++ = *s;
400         }
401       *p = 0;
402     }
403   return s;
404 }
405
406
407 /* Parse a DN and return an array-ized one.  This is not a validating
408    parser and it does not support any old-stylish syntax; KSBA is
409    expected to return only rfc2253 compatible strings. */
410 static struct dn_array_s *
411 parse_dn (const unsigned char *string)
412 {
413   struct dn_array_s *array;
414   size_t arrayidx, arraysize;
415   int i;
416
417   arraysize = 7; /* C,ST,L,O,OU,CN,email */
418   arrayidx = 0;
419   array = xtrymalloc ((arraysize+1) * sizeof *array);
420   if (!array)
421     return NULL;
422   while (*string)
423     {
424       while (*string == ' ')
425         string++;
426       if (!*string)
427         break; /* ready */
428       if (arrayidx >= arraysize)
429         { 
430           struct dn_array_s *a2;
431
432           arraysize += 5;
433           a2 = xtryrealloc (array, (arraysize+1) * sizeof *array);
434           if (!a2)
435             goto failure;
436           array = a2;
437         }
438       array[arrayidx].key = NULL;
439       array[arrayidx].value = NULL;
440       string = parse_dn_part (array+arrayidx, string);
441       if (!string)
442         goto failure;
443       while (*string == ' ')
444         string++;
445       array[arrayidx].multivalued = (*string == '+');
446       array[arrayidx].done = 0;
447       arrayidx++;
448       if (*string && *string != ',' && *string != ';' && *string != '+')
449         goto failure; /* invalid delimiter */
450       if (*string)
451         string++;
452     }
453   array[arrayidx].key = NULL;
454   array[arrayidx].value = NULL;
455   return array;
456
457  failure:
458   for (i=0; i < arrayidx; i++)
459     {
460       xfree (array[i].key);
461       xfree (array[i].value);
462     }
463   xfree (array);
464   return NULL;
465 }
466
467
468 /* Print a DN part to STREAM or if STREAM is NULL to FP. */
469 static void
470 print_dn_part (FILE *fp, estream_t stream,
471                struct dn_array_s *dn, const char *key, int translate)
472 {
473   struct dn_array_s *first_dn = dn;
474
475   for (; dn->key; dn++)
476     {
477       if (!dn->done && !strcmp (dn->key, key))
478         {
479           /* Forward to the last multi-valued RDN, so that we can
480              print them all in reverse in the correct order.  Note
481              that this overrides the the standard sequence but that
482              seems to a reasonable thing to do with multi-valued
483              RDNs. */
484           while (dn->multivalued && dn[1].key)
485             dn++;
486         next:
487           if (!dn->done && dn->value && *dn->value)
488             {
489               if (stream)
490                 {
491                   es_fprintf (stream, "/%s=", dn->key);
492                   if (translate)
493                     es_write_sanitized_utf8_buffer (stream, dn->value,
494                                                     strlen (dn->value),
495                                                     "/", NULL);
496                   else
497                     es_write_sanitized (stream, dn->value, strlen (dn->value),
498                                         "/", NULL);
499                 }
500               else
501                 {
502                   fprintf (fp, "/%s=", dn->key);
503                   if (translate)
504                     print_sanitized_utf8_string (fp, dn->value, '/');
505                   else
506                     print_sanitized_string (fp, dn->value, '/');
507                 }
508             }
509           dn->done = 1;
510           if (dn > first_dn && dn[-1].multivalued)
511             {
512               dn--;
513               goto next;
514             }
515         }
516     }
517 }
518
519 /* Print all parts of a DN in a "standard" sequence.  We first print
520    all the known parts, followed by the uncommon ones */
521 static void
522 print_dn_parts (FILE *fp, estream_t stream,
523                 struct dn_array_s *dn, int translate)
524 {
525   const char *stdpart[] = {
526     "CN", "OU", "O", "STREET", "L", "ST", "C", "EMail", NULL 
527   };
528   int i;
529   
530   for (i=0; stdpart[i]; i++)
531       print_dn_part (fp, stream, dn, stdpart[i], translate);
532
533   /* Now print the rest without any specific ordering */
534   for (; dn->key; dn++)
535     print_dn_part (fp, stream, dn, dn->key, translate);
536 }
537
538
539 /* Print the S-Expression in BUF, which has a valid length of BUFLEN,
540    as a human readable string in one line to FP. */
541 static void
542 pretty_print_sexp (FILE *fp, const unsigned char *buf, size_t buflen)
543 {
544   size_t len;
545   gcry_sexp_t sexp;
546   char *result, *p;
547
548   if ( gcry_sexp_sscan (&sexp, NULL, (const char*)buf, buflen) )
549     {
550       fputs (_("[Error - invalid encoding]"), fp);
551       return;
552     }
553   len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, NULL, 0);
554   assert (len);
555   result = xtrymalloc (len);
556   if (!result)
557     {
558       fputs (_("[Error - out of core]"), fp);
559       gcry_sexp_release (sexp);
560       return;
561     }
562   len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, result, len);
563   assert (len);
564   for (p = result; len; len--, p++)
565     {
566       if (*p == '\n')
567         {
568           if (len > 1) /* Avoid printing the trailing LF. */
569             fputs ("\\n", fp);
570         }
571       else if (*p == '\r')
572         fputs ("\\r", fp);
573       else if (*p == '\v')
574         fputs ("\\v", fp);
575       else if (*p == '\t')
576         fputs ("\\t", fp);
577       else
578         putc (*p, fp);
579     }
580   xfree (result);
581   gcry_sexp_release (sexp);
582 }
583
584 /* Print the S-Expression in BUF to extended STREAM, which has a valid
585    length of BUFLEN, as a human readable string in one line to FP. */
586 static void
587 pretty_es_print_sexp (estream_t fp, const unsigned char *buf, size_t buflen)
588 {
589   size_t len;
590   gcry_sexp_t sexp;
591   char *result, *p;
592
593   if ( gcry_sexp_sscan (&sexp, NULL, (const char*)buf, buflen) )
594     {
595       es_fputs (_("[Error - invalid encoding]"), fp);
596       return;
597     }
598   len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, NULL, 0);
599   assert (len);
600   result = xtrymalloc (len);
601   if (!result)
602     {
603       es_fputs (_("[Error - out of core]"), fp);
604       gcry_sexp_release (sexp);
605       return;
606     }
607   len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, result, len);
608   assert (len);
609   for (p = result; len; len--, p++)
610     {
611       if (*p == '\n')
612         {
613           if (len > 1) /* Avoid printing the trailing LF. */
614             es_fputs ("\\n", fp);
615         }
616       else if (*p == '\r')
617         es_fputs ("\\r", fp);
618       else if (*p == '\v')
619         es_fputs ("\\v", fp);
620       else if (*p == '\t')
621         es_fputs ("\\t", fp);
622       else
623         es_putc (*p, fp);
624     }
625   xfree (result);
626   gcry_sexp_release (sexp);
627 }
628
629
630
631
632 void
633 gpgsm_print_name2 (FILE *fp, const char *name, int translate)
634 {
635   const unsigned char *s = (const unsigned char *)name;
636   int i;
637
638   if (!s)
639     {
640       fputs (_("[Error - No name]"), fp);
641     }
642   else if (*s == '<')
643     {
644       const char *s2 = strchr ( (char*)s+1, '>');
645       if (s2)
646         {
647           if (translate)
648             print_sanitized_utf8_buffer (fp, s + 1, s2 - (char*)s - 1, 0);
649           else
650             print_sanitized_buffer (fp, s + 1, s2 - (char*)s - 1, 0);
651         }
652     }
653   else if (*s == '(')
654     {
655       pretty_print_sexp (fp, s, gcry_sexp_canon_len (s, 0, NULL, NULL));
656     }
657   else if (!((*s >= '0' && *s < '9')
658              || (*s >= 'A' && *s <= 'Z')
659              || (*s >= 'a' && *s <= 'z')))
660     fputs (_("[Error - invalid encoding]"), fp);
661   else
662     {
663       struct dn_array_s *dn = parse_dn (s);
664       if (!dn)
665         fputs (_("[Error - invalid DN]"), fp);
666       else 
667         {
668           print_dn_parts (fp, NULL, dn, translate);          
669           for (i=0; dn[i].key; i++)
670             {
671               xfree (dn[i].key);
672               xfree (dn[i].value);
673             }
674           xfree (dn);
675         }
676     }
677 }
678
679
680 void
681 gpgsm_print_name (FILE *fp, const char *name)
682 {
683   gpgsm_print_name2 (fp, name, 1);
684 }
685
686
687 /* This is avariant of gpgsm_print_name sending it output to an estream. */
688 void
689 gpgsm_es_print_name (estream_t fp, const char *name)
690 {
691   const unsigned char *s = (const unsigned char *)name;
692   int i;
693
694   if (!s)
695     {
696       es_fputs (_("[Error - No name]"), fp);
697     }
698   else if (*s == '<')
699     {
700       const char *s2 = strchr ( (char*)s+1, '>');
701
702       if (s2)
703         es_write_sanitized_utf8_buffer (fp, s + 1, s2 - (char*)s - 1,
704                                         NULL, NULL);
705     }
706   else if (*s == '(')
707     {
708       pretty_es_print_sexp (fp, s, gcry_sexp_canon_len (s, 0, NULL, NULL));
709     }
710   else if (!((*s >= '0' && *s < '9')
711              || (*s >= 'A' && *s <= 'Z')
712              || (*s >= 'a' && *s <= 'z')))
713     es_fputs (_("[Error - invalid encoding]"), fp);
714   else
715     {
716       struct dn_array_s *dn = parse_dn (s);
717
718       if (!dn)
719         es_fputs (_("[Error - invalid DN]"), fp);
720       else 
721         {
722           print_dn_parts (NULL, fp, dn, 1);          
723           for (i=0; dn[i].key; i++)
724             {
725               xfree (dn[i].key);
726               xfree (dn[i].value);
727             }
728           xfree (dn);
729         }
730     }
731 }
732
733
734
735
736 #if defined (HAVE_FOPENCOOKIE) || defined (HAVE_FUNOPEN)
737 /* A cookie structure used for the memory stream. */
738 struct format_name_cookie 
739 {
740   char *buffer;         /* Malloced buffer with the data to deliver. */
741   size_t size;          /* Allocated size of this buffer. */
742   size_t len;           /* strlen (buffer). */
743   int error;            /* system error code if any. */
744 };
745
746 /* The writer function for the memory stream. */
747 static my_funopen_hook_ret_t
748 format_name_writer (void *cookie, const char *buffer, size_t size)
749 {
750   struct format_name_cookie *c = cookie;
751   char *p;
752
753   if (c->buffer)
754     p = xtryrealloc (c->buffer, c->size + size + 1);
755   else
756     p = xtrymalloc (size + 1);
757   if (!p)
758     {
759       c->error = errno;
760       xfree (c->buffer);
761       errno = c->error;
762       return -1;
763     }
764   c->buffer = p;
765   memcpy (p + c->len, buffer, size);
766   c->len += size;
767   p[c->len] = 0; /* Terminate string. */ 
768
769   return size;
770 }
771 #endif /*HAVE_FOPENCOOKIE || HAVE_FUNOPEN*/
772
773
774 /* Format NAME which is expected to be in rfc2253 format into a better
775    human readable format. Caller must free the returned string.  NULL
776    is returned in case of an error.  With TRANSLATE set to true the
777    name will be translated to the native encoding.  Note that NAME is
778    internally always UTF-8 encoded. */
779 char *
780 gpgsm_format_name2 (const char *name, int translate)
781 {
782 #if defined (HAVE_FOPENCOOKIE) || defined (HAVE_FUNOPEN)
783   FILE *fp;
784   struct format_name_cookie cookie;
785
786   memset (&cookie, 0, sizeof cookie);
787
788 #ifdef HAVE_FOPENCOOKIE
789   {
790     cookie_io_functions_t io = { NULL };
791     io.write = format_name_writer;
792     
793     fp = fopencookie (&cookie, "w", io);
794   }
795 #else /*!HAVE_FOPENCOOKIE*/
796   {
797     fp = funopen (&cookie, NULL, format_name_writer, NULL, NULL);
798   }
799 #endif /*!HAVE_FOPENCOOKIE*/
800   if (!fp)
801     {
802       int save_errno = errno;
803       log_error ("error creating memory stream: %s\n", strerror (errno));
804       errno = save_errno;
805       return NULL;
806     }
807   gpgsm_print_name2 (fp, name, translate);
808   fclose (fp);
809   if (cookie.error || !cookie.buffer)
810     {
811       xfree (cookie.buffer);
812       errno = cookie.error;
813       return NULL;
814     }
815   return cookie.buffer;
816 #else /* No fun - use the name verbatim. */
817   return xtrystrdup (name);
818 #endif /* No fun. */
819 }
820
821 char *
822 gpgsm_format_name (const char *name)
823 {
824   return gpgsm_format_name2 (name, 1);
825 }
826
827
828 /* Return fingerprint and a percent escaped name in a human readable
829    format suitable for status messages like GOODSIG.  May return NULL
830    on error (out of core). */
831 char *
832 gpgsm_fpr_and_name_for_status (ksba_cert_t cert)
833 {
834   char *fpr, *name, *p;
835   char *buffer;
836
837   fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
838   if (!fpr)
839     return NULL;
840   
841   name = ksba_cert_get_subject (cert, 0);
842   if (!name)
843     {
844       xfree (fpr);
845       return NULL;
846     }
847
848   p = gpgsm_format_name2 (name, 0);
849   ksba_free (name);
850   name = p;
851   if (!name)
852     {
853       xfree (fpr);
854       return NULL;
855     }
856
857   buffer = xtrymalloc (strlen (fpr) + 1 + 3*strlen (name) + 1);
858   if (buffer)
859     {
860       const unsigned char *s;
861
862       p = stpcpy (stpcpy (buffer, fpr), " ");
863       for (s = name; *s; s++)
864         {
865           if (*s < ' ')
866             {
867               sprintf (p, "%%%02X", *s);
868               p += 3;
869             }
870           else
871             *p++ = *s;
872         }
873       *p = 0;
874     }
875   xfree (fpr);
876   xfree (name);
877   return buffer;
878 }
879
880
881 /* Create a key description for the CERT, this may be passed to the
882    pinentry.  The caller must free the returned string. NULL may be
883    returned on error. */
884 char *
885 gpgsm_format_keydesc (ksba_cert_t cert)
886 {
887   int rc;
888   char *name, *subject, *buffer, *p;
889   const char *s;
890   ksba_isotime_t t;
891   char created[20];
892   char *sn;
893   ksba_sexp_t sexp;
894   char *orig_codeset;
895
896   name = ksba_cert_get_subject (cert, 0);
897   subject = name? gpgsm_format_name2 (name, 0) : NULL;
898   ksba_free (name); name = NULL;
899
900   sexp = ksba_cert_get_serial (cert);
901   sn = sexp? gpgsm_format_serial (sexp) : NULL;
902   ksba_free (sexp);
903
904   ksba_cert_get_validity (cert, 0, t);
905   if (t && *t)
906     sprintf (created, "%.4s-%.2s-%.2s", t, t+4, t+6);
907   else
908     *created = 0;
909
910   orig_codeset = i18n_switchto_utf8 ();
911
912   rc = asprintf (&name,
913                  _("Please enter the passphrase to unlock the"
914                    " secret key for:\n"
915                    "\"%s\"\n"
916                    "S/N %s, ID 0x%08lX, created %s" ),
917                  subject? subject:"?",
918                  sn? sn: "?",
919                  gpgsm_get_short_fingerprint (cert),
920                  created);
921
922   i18n_switchback (orig_codeset);
923
924   if (rc < 0)
925     {
926       int save_errno = errno;
927       xfree (subject);
928       xfree (sn);
929       errno = save_errno;
930       return NULL;
931     }
932   
933   xfree (subject);
934   xfree (sn);
935
936   buffer = p = xtrymalloc (strlen (name) * 3 + 1);
937   for (s=name; *s; s++)
938     {
939       if (*s < ' ' || *s == '+')
940         {
941           sprintf (p, "%%%02X", *(unsigned char *)s);
942           p += 3;
943         }
944       else if (*s == ' ')
945         *p++ = '+';
946       else
947         *p++ = *s;
948     }
949   *p = 0;
950   free (name); 
951
952   return buffer;
953 }
954