common: Consolidate Assuan server argument handling.
[gnupg.git] / sm / keylist.c
index 3994afe..dab1295 100644 (file)
@@ -1,12 +1,12 @@
 /* keylist.c - Print certificates in various formats.
- * Copyright (C) 1998, 1999, 2000, 2001, 2003,
- *               2004, 2005 Free Software Foundation, Inc.
+ * Copyright (C) 1998, 1999, 2000, 2001, 2003, 2004, 2005, 2008, 2009,
+ *               2010, 2011 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
+ * 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,
@@ -15,9 +15,7 @@
  * 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.
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
 #include <config.h>
@@ -25,7 +23,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
-#include <unistd.h> 
+#include <unistd.h>
 #include <time.h>
 #include <assert.h>
 
 #include "keydb.h"
 #include "../kbx/keybox.h" /* for KEYBOX_FLAG_* */
 #include "i18n.h"
+#include "tlv.h"
 
-struct list_external_parm_s {
+struct list_external_parm_s
+{
   ctrl_t ctrl;
-  FILE *fp;
+  estream_t fp;
   int print_header;
   int with_colons;
   int with_chain;
@@ -50,23 +50,24 @@ struct list_external_parm_s {
 
 /* This table is to map Extended Key Usage OIDs to human readable
    names.  */
-struct {
+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.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" },       
+  { "1.3.6.1.5.5.7.3.14", "wlanSSID" },
 
   { "2.16.840.1.113730.4.1", "serverGatedCrypto.ns" }, /* Netscape. */
   { "1.3.6.1.4.1.311.10.3.3", "serverGatedCrypto.ms"}, /* Microsoft. */
@@ -77,11 +78,18 @@ struct {
 };
 
 
+/* Do not print this extension in the list of extensions.  This is set
+   for oids which are already available via ksba fucntions. */
+#define OID_FLAG_SKIP 1
+/* The extension is a simple UTF8String and should be printed.  */
+#define OID_FLAG_UTF8 2
+
 /* A table mapping OIDs to a descriptive string. */
-static struct {
+static struct
+{
   char *oid;
   char *name;
-  unsigned int flag;
+  unsigned int flag; /* A flag as described above.  */
 } oidtranstbl[] = {
 
   /* Algorithms. */
@@ -114,6 +122,10 @@ static struct {
   { "0.2.262.1.10.12.4", "telesecCRLFilteredExt" },
   { "0.2.262.1.10.12.5", "telesecCRLFilterExt"},
   { "0.2.262.1.10.12.6", "telesecNamingAuthorityExt" },
+#define OIDSTR_restriction \
+    "1.3.36.8.3.8"
+  { OIDSTR_restriction,      "restriction", OID_FLAG_UTF8 },
+
 
   /* PKIX private extensions. */
   { "1.3.6.1.5.5.7.1.1", "authorityInfoAccess" },
@@ -134,28 +146,28 @@ static struct {
   { "1.3.6.1.5.5.7.48.5", "caRepository" },
 
   /* X.509 id-ce */
-  { "2.5.29.14", "subjectKeyIdentifier", 1},
-  { "2.5.29.15", "keyUsage", },
+  { "2.5.29.14", "subjectKeyIdentifier", OID_FLAG_SKIP},
+  { "2.5.29.15", "keyUsage", OID_FLAG_SKIP},
   { "2.5.29.16", "privateKeyUsagePeriod" },
-  { "2.5.29.17", "subjectAltName", },
-  { "2.5.29.18", "issuerAltName", },
-  { "2.5.29.19", "basicConstraints", 1},
+  { "2.5.29.17", "subjectAltName", OID_FLAG_SKIP},
+  { "2.5.29.18", "issuerAltName", OID_FLAG_SKIP},
+  { "2.5.29.19", "basicConstraints", OID_FLAG_SKIP},
   { "2.5.29.20", "cRLNumber" },
   { "2.5.29.21", "cRLReason" },
   { "2.5.29.22", "expirationDate" },
-  { "2.5.29.23", "instructionCode" }, 
+  { "2.5.29.23", "instructionCode" },
   { "2.5.29.24", "invalidityDate" },
   { "2.5.29.27", "deltaCRLIndicator" },
   { "2.5.29.28", "issuingDistributionPoint" },
   { "2.5.29.29", "certificateIssuer" },
   { "2.5.29.30", "nameConstraints" },
-  { "2.5.29.31", "cRLDistributionPoints", },
-  { "2.5.29.32", "certificatePolicies", },
+  { "2.5.29.31", "cRLDistributionPoints", OID_FLAG_SKIP},
+  { "2.5.29.32", "certificatePolicies", OID_FLAG_SKIP},
   { "2.5.29.32.0", "anyPolicy" },
   { "2.5.29.33", "policyMappings" },
-  { "2.5.29.35", "authorityKeyIdentifier", },
+  { "2.5.29.35", "authorityKeyIdentifier", OID_FLAG_SKIP},
   { "2.5.29.36", "policyConstraints" },
-  { "2.5.29.37", "extKeyUsage", },
+  { "2.5.29.37", "extKeyUsage", OID_FLAG_SKIP},
   { "2.5.29.46", "freshestCRL" },
   { "2.5.29.54", "inhibitAnyPolicy" },
 
@@ -174,12 +186,17 @@ static struct {
 
   /* GnuPG extensions */
   { "1.3.6.1.4.1.11591.2.1.1", "pkaAddress" },
+  { "1.3.6.1.4.1.11591.2.2.1", "standaloneCertificate" },
+  { "1.3.6.1.4.1.11591.2.2.2", "wellKnownPrivateKey" },
+
+  /* Extensions used by the Bundesnetzagentur.  */
+  { "1.3.6.1.4.1.8301.3.5", "validityModel" },
 
   { NULL }
 };
 
 
-/* Return the description for OID; if no description is available 
+/* Return the description for OID; if no description is available
    NULL is returned. */
 static const char *
 get_oid_desc (const char *oid, unsigned int *flag)
@@ -201,88 +218,93 @@ get_oid_desc (const char *oid, unsigned int *flag)
 
 
 static void
-print_key_data (ksba_cert_t cert, FILE *fp)
+print_key_data (ksba_cert_t cert, estream_t fp)
 {
-#if 0  
+#if 0
   int n = pk ? pubkey_get_npkey( pk->pubkey_algo ) : 0;
   int i;
 
-  for(i=0; i < n; i++ ) 
+  for(i=0; i < n; i++ )
     {
-      fprintf (fp, "pkd:%d:%u:", i, mpi_get_nbits( pk->pkey[i] ) );
+      es_fprintf (fp, "pkd:%d:%u:", i, mpi_get_nbits( pk->pkey[i] ) );
       mpi_print(stdout, pk->pkey[i], 1 );
       putchar(':');
       putchar('\n');
     }
+#else
+  (void)cert;
+  (void)fp;
 #endif
 }
 
 static void
-print_capabilities (ksba_cert_t cert, FILE *fp)
+print_capabilities (ksba_cert_t cert, estream_t fp)
 {
   gpg_error_t err;
   unsigned int use;
   size_t buflen;
   char buffer[1];
 
-  err = ksba_cert_get_user_data (cert, "is_qualified", 
+  err = ksba_cert_get_user_data (cert, "is_qualified",
                                  &buffer, sizeof (buffer), &buflen);
   if (!err && buflen)
     {
       if (*buffer)
-        putc ('q', fp);
-    }    
+        es_putc ('q', fp);
+    }
   else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
     ; /* Don't know - will not get marked as 'q' */
   else
     log_debug ("get_user_data(is_qualified) failed: %s\n",
-               gpg_strerror (err)); 
+               gpg_strerror (err));
 
   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);
+      es_putc ('e', fp);
+      es_putc ('s', fp);
+      es_putc ('c', fp);
+      es_putc ('E', fp);
+      es_putc ('S', fp);
+      es_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);
+    es_putc ('e', fp);
   if ((use & (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION)))
-    putc ('s', fp);
+    es_putc ('s', fp);
   if ((use & KSBA_KEYUSAGE_KEY_CERT_SIGN))
-    putc ('c', fp);
+    es_putc ('c', fp);
   if ((use & (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT)))
-    putc ('E', fp);
+    es_putc ('E', fp);
   if ((use & (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION)))
-    putc ('S', fp);
+    es_putc ('S', fp);
   if ((use & KSBA_KEYUSAGE_KEY_CERT_SIGN))
-    putc ('C', fp);
+    es_putc ('C', fp);
+
+  es_putc (':', fp);
 }
 
 
 static void
-print_time (gnupg_isotime_t t, FILE *fp)
+print_time (gnupg_isotime_t t, estream_t fp)
 {
   if (!t || !*t)
     ;
-  else 
-    fputs (t, fp);
+  else
+    es_fputs (t, fp);
 }
 
 
 /* Return an allocated string with the email address extracted from a
-   DN */
+   DN.  Note hat we use this code also in ../kbx/keybox-blob.c.  */
 static char *
 email_kludge (const char *name)
 {
@@ -307,7 +329,7 @@ email_kludge (const char *name)
 
   /* 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 */
+     OpenSSL generated keys get a nicer and usable listing.  */
   for (n=0, p=name; hexdigitp (p) && hexdigitp (p+1); p +=2, n++)
     ;
   if (!n)
@@ -329,7 +351,7 @@ email_kludge (const char *name)
 /* 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)
+                 estream_t fp, int have_secret)
 {
   int rc;
   int idx;
@@ -344,9 +366,10 @@ list_cert_colon (ctrl_t ctrl, ksba_cert_t cert, unsigned int validity,
   const char *chain_id;
   char *chain_id_buffer = NULL;
   int is_root = 0;
+  char *kludge_uid;
 
   if (ctrl->with_validation)
-    valerr = gpgsm_validate_chain (ctrl, cert, NULL, 1, NULL, 0);
+    valerr = gpgsm_validate_chain (ctrl, cert, "", NULL, 1, NULL, 0, NULL);
   else
     valerr = 0;
 
@@ -356,7 +379,7 @@ list_cert_colon (ctrl_t ctrl, ksba_cert_t cert, unsigned int validity,
   {
     ksba_cert_t next;
 
-    rc = gpgsm_walk_cert_chain (cert, &next);
+    rc = gpgsm_walk_cert_chain (ctrl, cert, &next);
     if (!rc) /* We known the issuer's certificate. */
       {
         p = gpgsm_get_fingerprint_hexstring (next, GCRY_MD_SHA1);
@@ -374,7 +397,7 @@ list_cert_colon (ctrl_t ctrl, ksba_cert_t cert, unsigned int validity,
   }
 
 
-  fputs (have_secret? "crs:":"crt:", fp);
+  es_fputs (have_secret? "crs:":"crt:", fp);
 
   /* Note: We can't use multiple flags, like "ei", because the
      validation check does only return one error.  */
@@ -385,20 +408,27 @@ list_cert_colon (ctrl_t ctrl, ksba_cert_t cert, unsigned int validity,
     *truststring = 'r';
   else if (gpg_err_code (valerr) == GPG_ERR_CERT_EXPIRED)
     *truststring = 'e';
-  else 
+  else
     {
       /* Lets also check whether the certificate under question
          expired.  This is merely a hack until we found a proper way
          to store the expiration flag in the keybox. */
       ksba_isotime_t current_time, not_after;
-  
+
       gnupg_get_isotime (current_time);
       if (!opt.ignore_expiration
           && !ksba_cert_get_validity (cert, 1, not_after)
           && *not_after && strcmp (current_time, not_after) > 0 )
         *truststring = 'e';
       else if (valerr)
-        *truststring = 'i';
+        {
+          if (gpgsm_cert_has_well_known_private_key (cert))
+            *truststring = 'w';  /* Well, this is dummy CA.  */
+          else
+            *truststring = 'i';
+        }
+      else if (ctrl->with_validation && !is_root)
+        *truststring = 'f';
     }
 
   /* If we have no truststring yet (i.e. the certificate might be
@@ -408,33 +438,37 @@ list_cert_colon (ctrl_t ctrl, ksba_cert_t cert, unsigned int validity,
     {
       struct rootca_flags_s dummy_flags;
 
-      rc = gpgsm_agent_istrusted (ctrl, cert, &dummy_flags);
-      if (!rc)
-        *truststring = 'u';  /* Yes, we trust this one (ultimately). */
-      else if (gpg_err_code (rc) == GPG_ERR_NOT_TRUSTED)
-        *truststring = 'n';  /* No, we do not trust this one. */
-      /* (in case of an error we can't tell anything.) */
+      if (gpgsm_cert_has_well_known_private_key (cert))
+        *truststring = 'w';  /* Well, this is dummy CA.  */
+      else
+        {
+          rc = gpgsm_agent_istrusted (ctrl, cert, NULL, &dummy_flags);
+          if (!rc)
+            *truststring = 'u';  /* Yes, we trust this one (ultimately). */
+          else if (gpg_err_code (rc) == GPG_ERR_NOT_TRUSTED)
+            *truststring = 'n';  /* No, we do not trust this one. */
+          /* (in case of an error we can't tell anything.) */
+        }
     }
-  
+
   if (*truststring)
-    fputs (truststring, fp);
+    es_fputs (truststring, fp);
 
   algo = gpgsm_get_key_algo_info (cert, &nbits);
-  fprintf (fp, ":%u:%d:%s:", nbits, algo, fpr+24);
+  es_fprintf (fp, ":%u:%d:%s:", nbits, algo, fpr+24);
 
-  /* We assume --fixed-list-mode for gpgsm */
   ksba_cert_get_validity (cert, 0, t);
   print_time (t, fp);
-  putc (':', fp);
+  es_putc (':', fp);
   ksba_cert_get_validity (cert, 1, t);
   print_time ( t, fp);
-  putc (':', fp);
+  es_putc (':', fp);
   /* Field 8, serial number: */
   if ((sexp = ksba_cert_get_serial (cert)))
     {
       int len;
       const unsigned char *s = sexp;
-      
+
       if (*s == '(')
         {
           s++;
@@ -442,34 +476,56 @@ list_cert_colon (ctrl_t ctrl, ksba_cert_t cert, unsigned int validity,
             len = len*10 + atoi_1 (s);
           if (*s == ':')
             for (s++; len; len--, s++)
-              fprintf (fp,"%02X", *s);
+              es_fprintf (fp,"%02X", *s);
         }
       xfree (sexp);
     }
-  putc (':', fp);
+  es_putc (':', fp);
   /* Field 9, ownertrust - not used here */
-  putc (':', fp);
+  es_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, ':');
+      es_write_sanitized (fp, p, strlen (p), ":", NULL);
       xfree (p);
     }
-  putc (':', fp);
-  /* Field 11, signature class - not used */ 
-  putc (':', fp);
-  /* Field 12, capabilities: */ 
+  es_putc (':', fp);
+  /* Field 11, signature class - not used */
+  es_putc (':', fp);
+  /* Field 12, capabilities: */
   print_capabilities (cert, fp);
-  putc (':', fp);
-  putc ('\n', fp);
+  es_putc (':', fp);
+  /* Field 13, not used: */
+  es_putc (':', fp);
+  if (have_secret || ctrl->with_secret)
+    {
+      char *cardsn;
+
+      p = gpgsm_get_keygrip_hexstring (cert);
+      if (!gpgsm_agent_keyinfo (ctrl, p, &cardsn)
+          && (cardsn || ctrl->with_secret))
+        {
+          /* Field 14, not used: */
+          es_putc (':', fp);
+          /* Field 15:  Token serial number or secret key indicator.  */
+          if (cardsn)
+            es_fputs (cardsn, fp);
+          else if (ctrl->with_secret)
+            es_putc ('+', fp);
+          es_putc (':', fp);
+        }
+      xfree (cardsn);
+      xfree (p);
+    }
+  es_putc ('\n', fp);
 
   /* FPR record */
-  fprintf (fp, "fpr:::::::::%s:::", fpr);
+  es_fprintf (fp, "fpr:::::::::%s:::", fpr);
   /* Print chaining ID (field 13)*/
   if (chain_id)
-    fputs (chain_id, fp);
-  putc (':', fp);
-  putc ('\n', fp);
+    es_fputs (chain_id, fp);
+  es_putc (':', fp);
+  es_putc ('\n', fp);
   xfree (fpr); fpr = NULL; chain_id = NULL;
   xfree (chain_id_buffer); chain_id_buffer = NULL;
 
@@ -477,52 +533,60 @@ list_cert_colon (ctrl_t ctrl, ksba_cert_t cert, unsigned int validity,
     {
       if ( (p = gpgsm_get_keygrip_hexstring (cert)))
         {
-          fprintf (fp, "grp:::::::::%s:\n", p);
+          es_fprintf (fp, "grp:::::::::%s:\n", p);
           xfree (p);
         }
       print_key_data (cert, fp);
     }
 
+  kludge_uid = NULL;
   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);
+      /* In the case that the same email address is in the subject DN
+         as well as in an alternate subject name we avoid printing it
+         a second time. */
+      if (kludge_uid && !strcmp (kludge_uid, p))
+        continue;
+
+      es_fprintf (fp, "uid:%s::::::::", truststring);
+      es_write_sanitized (fp, p, strlen (p), ":", NULL);
+      es_putc (':', fp);
+      es_putc (':', fp);
+      es_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)
+          kludge_uid = email_kludge (p);
+          if (kludge_uid)
             {
-              fprintf (fp, "uid:%s::::::::", truststring);
-              print_sanitized_string (fp, pp, ':');
-              putc (':', fp);
-              putc (':', fp);
-              putc ('\n', fp);
-              xfree (pp);
+              es_fprintf (fp, "uid:%s::::::::", truststring);
+              es_write_sanitized (fp, kludge_uid, strlen (kludge_uid),
+                                  ":", NULL);
+              es_putc (':', fp);
+              es_putc (':', fp);
+              es_putc ('\n', fp);
             }
         }
       xfree (p);
     }
+  xfree (kludge_uid);
 }
 
 
 static void
-print_name_raw (FILE *fp, const char *string)
+print_name_raw (estream_t fp, const char *string)
 {
   if (!string)
-    fputs ("[error]", fp);
+    es_fputs ("[error]", fp);
   else
-    print_sanitized_string (fp, string, 0);
+    es_write_sanitized (fp, string, strlen (string), NULL, NULL);
 }
 
 static void
-print_names_raw (FILE *fp, int indent, ksba_name_t name)
+print_names_raw (estream_t fp, int indent, ksba_name_t name)
 {
   int idx;
   const char *s;
@@ -533,27 +597,80 @@ print_names_raw (FILE *fp, int indent, ksba_name_t name)
 
   if (!name)
     {
-      fputs ("none\n", fp);
+      es_fputs ("none\n", fp);
       return;
     }
-  
+
   for (idx=0; (s = ksba_name_enum (name, idx)); idx++)
     {
       char *p = ksba_name_get_uri (name, idx);
-      printf ("%*s", idx||indent_all?indent:0, "");
-      print_sanitized_string (fp, p?p:s, 0);
-      putc ('\n', fp);
+      es_fprintf (fp, "%*s", idx||indent_all?indent:0, "");
+      es_write_sanitized (fp, p?p:s, strlen (p?p:s), NULL, NULL);
+      es_putc ('\n', fp);
       xfree (p);
     }
 }
 
 
+static void
+print_utf8_extn_raw (estream_t fp, int indent,
+                     const unsigned char *der, size_t derlen)
+{
+  gpg_error_t err;
+  int class, tag, constructed, ndef;
+  size_t objlen, hdrlen;
+
+  if (indent < 0)
+    indent = - indent;
+
+  err = parse_ber_header (&der, &derlen, &class, &tag, &constructed,
+                          &ndef, &objlen, &hdrlen);
+  if (!err && (objlen > derlen || tag != TAG_UTF8_STRING))
+    err = gpg_error (GPG_ERR_INV_OBJ);
+  if (err)
+    {
+      es_fprintf (fp, "%*s[%s]\n", indent, "", gpg_strerror (err));
+      return;
+    }
+  es_fprintf (fp, "%*s(%.*s)\n", indent, "", (int)objlen, der);
+}
+
+
+static void
+print_utf8_extn (estream_t fp, int indent,
+                 const unsigned char *der, size_t derlen)
+{
+  gpg_error_t err;
+  int class, tag, constructed, ndef;
+  size_t objlen, hdrlen;
+  int indent_all;
+
+  if ((indent_all = (indent < 0)))
+    indent = - indent;
+
+  err = parse_ber_header (&der, &derlen, &class, &tag, &constructed,
+                          &ndef, &objlen, &hdrlen);
+  if (!err && (objlen > derlen || tag != TAG_UTF8_STRING))
+    err = gpg_error (GPG_ERR_INV_OBJ);
+  if (err)
+    {
+      es_fprintf (fp, "%*s[%s%s]\n",
+                  indent_all? indent:0, "", _("Error - "), gpg_strerror (err));
+      return;
+    }
+  es_fprintf (fp, "%*s\"", indent_all? indent:0, "");
+  /* Fixme: we should implement word wrapping */
+  es_write_sanitized (fp, der, objlen, "\"", NULL);
+  es_fputs ("\"\n", fp);
+}
+
+
 /* List one certificate in raw mode useful to have a closer look at
    the certificate.  This one does no beautification and only minimal
    output sanitation.  It is mainly useful for debugging. */
 static void
 list_cert_raw (ctrl_t ctrl, KEYDB_HANDLE hd,
-               ksba_cert_t cert, FILE *fp, int have_secret,
+               ksba_cert_t cert, estream_t fp, int have_secret,
                int with_validation)
 {
   gpg_error_t err;
@@ -568,158 +685,165 @@ list_cert_raw (ctrl_t ctrl, KEYDB_HANDLE hd,
   const char *oid, *s;
   ksba_name_t name, name2;
   unsigned int reason;
+  const unsigned char *cert_der = NULL;
+
+  (void)have_secret;
+
+  es_fprintf (fp, "           ID: 0x%08lX\n",
+              gpgsm_get_short_fingerprint (cert, NULL));
 
   sexp = ksba_cert_get_serial (cert);
-  fputs ("Serial number: ", fp);
+  es_fputs ("          S/N: ", fp);
   gpgsm_print_serial (fp, sexp);
   ksba_free (sexp);
-  putc ('\n', fp);
+  es_putc ('\n', fp);
 
   dn = ksba_cert_get_issuer (cert, 0);
-  fputs ("       Issuer: ", fp);
+  es_fputs ("       Issuer: ", fp);
   print_name_raw (fp, dn);
   ksba_free (dn);
-  putc ('\n', fp);
+  es_putc ('\n', fp);
   for (idx=1; (dn = ksba_cert_get_issuer (cert, idx)); idx++)
     {
-      fputs ("          aka: ", fp);
+      es_fputs ("          aka: ", fp);
       print_name_raw (fp, dn);
       ksba_free (dn);
-      putc ('\n', fp);
+      es_putc ('\n', fp);
     }
 
   dn = ksba_cert_get_subject (cert, 0);
-  fputs ("      Subject: ", fp);
+  es_fputs ("      Subject: ", fp);
   print_name_raw (fp, dn);
   ksba_free (dn);
-  putc ('\n', fp);
+  es_putc ('\n', fp);
   for (idx=1; (dn = ksba_cert_get_subject (cert, idx)); idx++)
     {
-      fputs ("          aka: ", fp);
+      es_fputs ("          aka: ", fp);
       print_name_raw (fp, dn);
       ksba_free (dn);
-      putc ('\n', fp);
+      es_putc ('\n', fp);
     }
 
   dn = gpgsm_get_fingerprint_string (cert, 0);
-  fprintf (fp, "     sha1_fpr: %s\n", dn?dn:"error");
+  es_fprintf (fp, "     sha1_fpr: %s\n", dn?dn:"error");
   xfree (dn);
 
   dn = gpgsm_get_fingerprint_string (cert, GCRY_MD_MD5);
-  fprintf (fp, "      md5_fpr: %s\n", dn?dn:"error");
+  es_fprintf (fp, "      md5_fpr: %s\n", dn?dn:"error");
   xfree (dn);
 
   dn = gpgsm_get_certid (cert);
-  fprintf (fp, "       certid: %s\n", dn?dn:"error");
+  es_fprintf (fp, "       certid: %s\n", dn?dn:"error");
   xfree (dn);
 
   dn = gpgsm_get_keygrip_hexstring (cert);
-  fprintf (fp, "      keygrip: %s\n", dn?dn:"error");
+  es_fprintf (fp, "      keygrip: %s\n", dn?dn:"error");
   xfree (dn);
 
   ksba_cert_get_validity (cert, 0, t);
-  fputs ("    notBefore: ", fp);
+  es_fputs ("    notBefore: ", fp);
   gpgsm_print_time (fp, t);
-  putc ('\n', fp);
-  fputs ("     notAfter: ", fp);
+  es_putc ('\n', fp);
+  es_fputs ("     notAfter: ", fp);
   ksba_cert_get_validity (cert, 1, t);
   gpgsm_print_time (fp, t);
-  putc ('\n', fp);
+  es_putc ('\n', fp);
 
   oid = ksba_cert_get_digest_algo (cert);
   s = get_oid_desc (oid, NULL);
-  fprintf (fp, "     hashAlgo: %s%s%s%s\n", oid, s?" (":"",s?s:"",s?")":"");
+  es_fprintf (fp, "     hashAlgo: %s%s%s%s\n", oid, s?" (":"",s?s:"",s?")":"");
 
   {
     const char *algoname;
     unsigned int nbits;
 
     algoname = gcry_pk_algo_name (gpgsm_get_key_algo_info (cert, &nbits));
-    fprintf (fp, "      keyType: %u bit %s\n",  nbits, algoname? algoname:"?");
+    es_fprintf (fp, "      keyType: %u bit %s\n",
+                nbits, algoname? algoname:"?");
   }
 
   /* subjectKeyIdentifier */
-  fputs ("    subjKeyId: ", fp);
+  es_fputs ("    subjKeyId: ", fp);
   err = ksba_cert_get_subj_key_id (cert, NULL, &keyid);
   if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA)
     {
       if (gpg_err_code (err) == GPG_ERR_NO_DATA)
-        fputs ("[none]\n", fp);
+        es_fputs ("[none]\n", fp);
       else
         {
           gpgsm_print_serial (fp, keyid);
           ksba_free (keyid);
-          putc ('\n', fp);
+          es_putc ('\n', fp);
         }
     }
   else
-    fputs ("[?]\n", fp);
+    es_fputs ("[?]\n", fp);
 
 
   /* authorityKeyIdentifier */
-  fputs ("    authKeyId: ", fp);
+  es_fputs ("    authKeyId: ", fp);
   err = ksba_cert_get_auth_key_id (cert, &keyid, &name, &sexp);
   if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA)
     {
       if (gpg_err_code (err) == GPG_ERR_NO_DATA || !name)
-        fputs ("[none]\n", fp);
+        es_fputs ("[none]\n", fp);
       else
         {
           gpgsm_print_serial (fp, sexp);
           ksba_free (sexp);
-          putc ('\n', fp);
+          es_putc ('\n', fp);
           print_names_raw (fp, -15, name);
           ksba_name_release (name);
         }
       if (keyid)
         {
-          fputs (" authKeyId.ki: ", fp);
+          es_fputs (" authKeyId.ki: ", fp);
           gpgsm_print_serial (fp, keyid);
           ksba_free (keyid);
-          putc ('\n', fp);
+          es_putc ('\n', fp);
         }
     }
   else
-    fputs ("[?]\n", fp);
+    es_fputs ("[?]\n", fp);
 
-  fputs ("     keyUsage:", fp);
+  es_fputs ("     keyUsage:", fp);
   err = ksba_cert_get_key_usage (cert, &kusage);
   if (gpg_err_code (err) != GPG_ERR_NO_DATA)
     {
       if (err)
-        fprintf (fp, " [error: %s]", gpg_strerror (err));
+        es_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);
+            es_fputs (" digitalSignature", fp);
+          if ( (kusage & KSBA_KEYUSAGE_NON_REPUDIATION))
+            es_fputs (" nonRepudiation", fp);
+          if ( (kusage & KSBA_KEYUSAGE_KEY_ENCIPHERMENT))
+            es_fputs (" keyEncipherment", fp);
           if ( (kusage & KSBA_KEYUSAGE_DATA_ENCIPHERMENT))
-            fputs (" dataEncipherment", fp);
-          if ( (kusage & KSBA_KEYUSAGE_KEY_AGREEMENT))    
-            fputs (" keyAgreement", fp);
+            es_fputs (" dataEncipherment", fp);
+          if ( (kusage & KSBA_KEYUSAGE_KEY_AGREEMENT))
+            es_fputs (" keyAgreement", fp);
           if ( (kusage & KSBA_KEYUSAGE_KEY_CERT_SIGN))
-            fputs (" certSign", fp);
-          if ( (kusage & KSBA_KEYUSAGE_CRL_SIGN))  
-            fputs (" crlSign", fp);
+            es_fputs (" certSign", fp);
+          if ( (kusage & KSBA_KEYUSAGE_CRL_SIGN))
+            es_fputs (" crlSign", fp);
           if ( (kusage & KSBA_KEYUSAGE_ENCIPHER_ONLY))
-            fputs (" encipherOnly", fp);
-          if ( (kusage & KSBA_KEYUSAGE_DECIPHER_ONLY))  
-            fputs (" decipherOnly", fp);
+            es_fputs (" encipherOnly", fp);
+          if ( (kusage & KSBA_KEYUSAGE_DECIPHER_ONLY))
+            es_fputs (" decipherOnly", fp);
         }
-      putc ('\n', fp);
+      es_putc ('\n', fp);
     }
   else
-    fputs ("[none]\n", fp);
+    es_fputs (" [none]\n", fp);
 
-  fputs ("  extKeyUsage: ", fp);
+  es_fputs ("  extKeyUsage: ", fp);
   err = ksba_cert_get_ext_key_usages (cert, &string);
   if (gpg_err_code (err) != GPG_ERR_NO_DATA)
-    { 
+    {
       if (err)
-        fprintf (fp, "[error: %s]", gpg_strerror (err));
+        es_fprintf (fp, "[error: %s]", gpg_strerror (err));
       else
         {
           p = string;
@@ -729,30 +853,30 @@ list_cert_raw (ctrl_t ctrl, KEYDB_HANDLE hd,
               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);
+              es_fputs (key_purpose_map[i].oid?key_purpose_map[i].name:p, fp);
               p = pend;
               if (*p != 'C')
-                fputs (" (suggested)", fp);
+                es_fputs (" (suggested)", fp);
               if ((p = strchr (p, '\n')))
                 {
                   p++;
-                  fputs ("\n               ", fp);
+                  es_fputs ("\n               ", fp);
                 }
             }
           xfree (string);
         }
-      putc ('\n', fp);
+      es_putc ('\n', fp);
     }
   else
-    fputs ("[none]\n", fp);
+    es_fputs ("[none]\n", fp);
 
 
-  fputs ("     policies: ", fp);
+  es_fputs ("     policies: ", fp);
   err = ksba_cert_get_cert_policies (cert, &string);
   if (gpg_err_code (err) != GPG_ERR_NO_DATA)
     {
       if (err)
-        fprintf (fp, "[error: %s]", gpg_strerror (err));
+        es_fprintf (fp, "[error: %s]", gpg_strerror (err));
       else
         {
           p = string;
@@ -762,106 +886,111 @@ list_cert_raw (ctrl_t ctrl, KEYDB_HANDLE hd,
               for (i=0; key_purpose_map[i].oid; i++)
                 if ( !strcmp (key_purpose_map[i].oid, p) )
                   break;
-              fputs (p, fp);
+              es_fputs (p, fp);
               p = pend;
               if (*p == 'C')
-                fputs (" (critical)", fp);
+                es_fputs (" (critical)", fp);
               if ((p = strchr (p, '\n')))
                 {
                   p++;
-                  fputs ("\n               ", fp);
+                  es_fputs ("\n               ", fp);
                 }
             }
           xfree (string);
         }
-      putc ('\n', fp);
+      es_putc ('\n', fp);
     }
   else
-    fputs ("[none]\n", fp);
+    es_fputs ("[none]\n", fp);
 
-  fputs ("  chainLength: ", fp);
+  es_fputs ("  chainLength: ", fp);
   err = ksba_cert_is_ca (cert, &is_ca, &chainlen);
   if (err || is_ca)
     {
-      if (err)
-        fprintf (fp, "[error: %s]", gpg_strerror (err));
+      if (gpg_err_code (err) == GPG_ERR_NO_VALUE )
+        es_fprintf (fp, "[none]");
+      else if (err)
+        es_fprintf (fp, "[error: %s]", gpg_strerror (err));
       else if (chainlen == -1)
-        fputs ("unlimited", fp);
+        es_fputs ("unlimited", fp);
       else
-        fprintf (fp, "%d", chainlen);
-      putc ('\n', fp);
+        es_fprintf (fp, "%d", chainlen);
+      es_putc ('\n', fp);
     }
   else
-    fputs ("not a CA\n", fp);
+    es_fputs ("not a CA\n", fp);
 
 
   /* CRL distribution point */
   for (idx=0; !(err=ksba_cert_get_crl_dist_point (cert, idx, &name, &name2,
                                                   &reason)) ;idx++)
     {
-      fputs ("        crlDP: ", fp);
+      es_fputs ("        crlDP: ", fp);
       print_names_raw (fp, 15, name);
       if (reason)
         {
-          fputs ("               reason: ", fp);
+          es_fputs ("               reason: ", fp);
           if ( (reason & KSBA_CRLREASON_UNSPECIFIED))
-            fputs (" unused", stdout);
+            es_fputs (" unused", fp);
           if ( (reason & KSBA_CRLREASON_KEY_COMPROMISE))
-            fputs (" keyCompromise", stdout);
+            es_fputs (" keyCompromise", fp);
           if ( (reason & KSBA_CRLREASON_CA_COMPROMISE))
-            fputs (" caCompromise", stdout);
+            es_fputs (" caCompromise", fp);
           if ( (reason & KSBA_CRLREASON_AFFILIATION_CHANGED))
-            fputs (" affiliationChanged", stdout);
+            es_fputs (" affiliationChanged", fp);
           if ( (reason & KSBA_CRLREASON_SUPERSEDED))
-            fputs (" superseded", stdout);
+            es_fputs (" superseded", fp);
           if ( (reason & KSBA_CRLREASON_CESSATION_OF_OPERATION))
-            fputs (" cessationOfOperation", stdout);
+            es_fputs (" cessationOfOperation", fp);
           if ( (reason & KSBA_CRLREASON_CERTIFICATE_HOLD))
-            fputs (" certificateHold", stdout);
-          putchar ('\n');
+            es_fputs (" certificateHold", fp);
+          es_putc ('\n', fp);
         }
-      fputs ("               issuer: ", fp);
+      es_fputs ("               issuer: ", fp);
       print_names_raw (fp, 23, name2);
       ksba_name_release (name);
       ksba_name_release (name2);
     }
-  if (err && gpg_err_code (err) != GPG_ERR_EOF)
-    fputs ("        crlDP: [error]\n", fp);
+  if (err && gpg_err_code (err) != GPG_ERR_EOF
+      && gpg_err_code (err) != GPG_ERR_NO_VALUE)
+    es_fputs ("        crlDP: [error]\n", fp);
   else if (!idx)
-    fputs ("        crlDP: [none]\n", fp);
+    es_fputs ("        crlDP: [none]\n", fp);
 
 
   /* authorityInfoAccess. */
   for (idx=0; !(err=ksba_cert_get_authority_info_access (cert, idx, &string,
                                                          &name)); idx++)
     {
-      fputs ("     authInfo: ", fp);
+      es_fputs ("     authInfo: ", fp);
       s = get_oid_desc (string, NULL);
-      fprintf (fp, "%s%s%s%s\n", string, s?" (":"", s?s:"", s?")":"");
+      es_fprintf (fp, "%s%s%s%s\n", string, s?" (":"", s?s:"", s?")":"");
       print_names_raw (fp, -15, name);
       ksba_name_release (name);
       ksba_free (string);
     }
-  if (err && gpg_err_code (err) != GPG_ERR_EOF)
-    fputs ("     authInfo: [error]\n", fp);
+  if (err && gpg_err_code (err) != GPG_ERR_EOF
+      && gpg_err_code (err) != GPG_ERR_NO_VALUE)
+    es_fputs ("     authInfo: [error]\n", fp);
   else if (!idx)
-    fputs ("     authInfo: [none]\n", fp);
+    es_fputs ("     authInfo: [none]\n", fp);
 
   /* subjectInfoAccess. */
   for (idx=0; !(err=ksba_cert_get_subject_info_access (cert, idx, &string,
                                                          &name)); idx++)
     {
-      fputs ("  subjectInfo: ", fp);
+      es_fputs ("  subjectInfo: ", fp);
       s = get_oid_desc (string, NULL);
-      fprintf (fp, "%s%s%s%s\n", string, s?" (":"", s?s:"", s?")":"");
+      es_fprintf (fp, "%s%s%s%s\n", string, s?" (":"", s?s:"", s?")":"");
       print_names_raw (fp, -15, name);
       ksba_name_release (name);
       ksba_free (string);
     }
-  if (err && gpg_err_code (err) != GPG_ERR_EOF)
-    fputs ("     subjInfo: [error]\n", fp);
+  if (err && gpg_err_code (err) != GPG_ERR_EOF
+      && gpg_err_code (err) != GPG_ERR_NO_VALUE)
+    es_fputs ("     subjInfo: [error]\n", fp);
   else if (!idx)
-    fputs ("     subjInfo: [none]\n", fp);
+    es_fputs ("     subjInfo: [none]\n", fp);
 
 
   for (idx=0; !(err=ksba_cert_get_extension (cert, idx,
@@ -870,32 +999,40 @@ list_cert_raw (ctrl_t ctrl, KEYDB_HANDLE hd,
       unsigned int flag;
 
       s = get_oid_desc (oid, &flag);
+      if ((flag & OID_FLAG_SKIP))
+        continue;
 
-      if (!(flag & 1))
-        fprintf (fp, "     %s: %s%s%s%s  [%d octets]\n",
-                 i? "critExtn":"    extn",
-                 oid, s?" (":"", s?s:"", s?")":"", (int)len);
+      es_fprintf (fp, "     %s: %s%s%s%s  [%d octets]\n",
+                  i? "critExtn":"    extn",
+                  oid, s?" (":"", s?s:"", s?")":"", (int)len);
+      if ((flag & OID_FLAG_UTF8))
+        {
+          if (!cert_der)
+            cert_der = ksba_cert_get_image (cert, NULL);
+          assert (cert_der);
+          print_utf8_extn_raw (fp, -15, cert_der+off, len);
+        }
     }
 
 
   if (with_validation)
     {
-      err = gpgsm_validate_chain (ctrl, cert, NULL, 1, fp, 0);
+      err = gpgsm_validate_chain (ctrl, cert, "", NULL, 1, fp, 0, NULL);
       if (!err)
-        fprintf (fp, "  [certificate is good]\n");
+        es_fprintf (fp, "  [certificate is good]\n");
       else
-        fprintf (fp, "  [certificate is bad: %s]\n", gpg_strerror (err));
+        es_fprintf (fp, "  [certificate is bad: %s]\n", gpg_strerror (err));
     }
 
-  if (opt.with_ephemeral_keys && hd)
+  if (hd)
     {
       unsigned int blobflags;
 
       err = keydb_get_flags (hd, KEYBOX_FLAG_BLOB, 0, &blobflags);
       if (err)
-        fprintf (fp, "  [error getting keyflags: %s]\n", gpg_strerror (err));
-      else if ((blobflags & 2))
-        fprintf (fp, "  [stored as ephemeral]\n");
+        es_fprintf (fp, "  [error getting keyflags: %s]\n",gpg_strerror (err));
+      else if ((blobflags & KEYBOX_FLAG_BLOB_EPHEMERAL))
+        es_fprintf (fp, "  [stored as ephemeral]\n");
     }
 
 }
@@ -905,7 +1042,7 @@ list_cert_raw (ctrl_t ctrl, KEYDB_HANDLE hd,
 
 /* List one certificate in standard mode */
 static void
-list_cert_std (ctrl_t ctrl, ksba_cert_t cert, FILE *fp, int have_secret,
+list_cert_std (ctrl_t ctrl, ksba_cert_t cert, estream_t fp, int have_secret,
                int with_validation)
 {
   gpg_error_t err;
@@ -916,46 +1053,53 @@ list_cert_std (ctrl_t ctrl, ksba_cert_t cert, FILE *fp, int have_secret,
   int is_ca, chainlen;
   unsigned int kusage;
   char *string, *p, *pend;
+  size_t off, len;
+  const char *oid;
+  const unsigned char *cert_der = NULL;
+
+
+  es_fprintf (fp, "           ID: 0x%08lX\n",
+              gpgsm_get_short_fingerprint (cert, NULL));
 
   sexp = ksba_cert_get_serial (cert);
-  fputs ("Serial number: ", fp);
+  es_fputs ("          S/N: ", fp);
   gpgsm_print_serial (fp, sexp);
   ksba_free (sexp);
-  putc ('\n', fp);
+  es_putc ('\n', fp);
 
   dn = ksba_cert_get_issuer (cert, 0);
-  fputs ("       Issuer: ", fp);
-  gpgsm_print_name (fp, dn);
+  es_fputs ("       Issuer: ", fp);
+  gpgsm_es_print_name (fp, dn);
   ksba_free (dn);
-  putc ('\n', fp);
+  es_putc ('\n', fp);
   for (idx=1; (dn = ksba_cert_get_issuer (cert, idx)); idx++)
     {
-      fputs ("          aka: ", fp);
-      gpgsm_print_name (fp, dn);
+      es_fputs ("          aka: ", fp);
+      gpgsm_es_print_name (fp, dn);
       ksba_free (dn);
-      putc ('\n', fp);
+      es_putc ('\n', fp);
     }
 
   dn = ksba_cert_get_subject (cert, 0);
-  fputs ("      Subject: ", fp);
-  gpgsm_print_name (fp, dn);
+  es_fputs ("      Subject: ", fp);
+  gpgsm_es_print_name (fp, dn);
   ksba_free (dn);
-  putc ('\n', fp);
+  es_putc ('\n', fp);
   for (idx=1; (dn = ksba_cert_get_subject (cert, idx)); idx++)
     {
-      fputs ("          aka: ", fp);
-      gpgsm_print_name (fp, dn);
+      es_fputs ("          aka: ", fp);
+      gpgsm_es_print_name (fp, dn);
       ksba_free (dn);
-      putc ('\n', fp);
+      es_putc ('\n', fp);
     }
 
   ksba_cert_get_validity (cert, 0, t);
-  fputs ("     validity: ", fp);
+  es_fputs ("     validity: ", fp);
   gpgsm_print_time (fp, t);
-  fputs (" through ", fp);
+  es_fputs (" through ", fp);
   ksba_cert_get_validity (cert, 1, t);
   gpgsm_print_time (fp, t);
-  putc ('\n', fp);
+  es_putc ('\n', fp);
 
 
   {
@@ -963,46 +1107,47 @@ list_cert_std (ctrl_t ctrl, ksba_cert_t cert, FILE *fp, int have_secret,
     unsigned int nbits;
 
     algoname = gcry_pk_algo_name (gpgsm_get_key_algo_info (cert, &nbits));
-    fprintf (fp, "     key type: %u bit %s\n", nbits, algoname? algoname:"?");
+    es_fprintf (fp, "     key type: %u bit %s\n",
+                nbits, algoname? algoname:"?");
   }
 
 
   err = ksba_cert_get_key_usage (cert, &kusage);
   if (gpg_err_code (err) != GPG_ERR_NO_DATA)
     {
-      fputs ("    key usage:", fp);
+      es_fputs ("    key usage:", fp);
       if (err)
-        fprintf (fp, " [error: %s]", gpg_strerror (err));
+        es_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);
+            es_fputs (" digitalSignature", fp);
+          if ( (kusage & KSBA_KEYUSAGE_NON_REPUDIATION))
+            es_fputs (" nonRepudiation", fp);
+          if ( (kusage & KSBA_KEYUSAGE_KEY_ENCIPHERMENT))
+            es_fputs (" keyEncipherment", fp);
           if ( (kusage & KSBA_KEYUSAGE_DATA_ENCIPHERMENT))
-            fputs (" dataEncipherment", fp);
-          if ( (kusage & KSBA_KEYUSAGE_KEY_AGREEMENT))    
-            fputs (" keyAgreement", fp);
+            es_fputs (" dataEncipherment", fp);
+          if ( (kusage & KSBA_KEYUSAGE_KEY_AGREEMENT))
+            es_fputs (" keyAgreement", fp);
           if ( (kusage & KSBA_KEYUSAGE_KEY_CERT_SIGN))
-            fputs (" certSign", fp);
-          if ( (kusage & KSBA_KEYUSAGE_CRL_SIGN))  
-            fputs (" crlSign", fp);
+            es_fputs (" certSign", fp);
+          if ( (kusage & KSBA_KEYUSAGE_CRL_SIGN))
+            es_fputs (" crlSign", fp);
           if ( (kusage & KSBA_KEYUSAGE_ENCIPHER_ONLY))
-            fputs (" encipherOnly", fp);
-          if ( (kusage & KSBA_KEYUSAGE_DECIPHER_ONLY))  
-            fputs (" decipherOnly", fp);
+            es_fputs (" encipherOnly", fp);
+          if ( (kusage & KSBA_KEYUSAGE_DECIPHER_ONLY))
+            es_fputs (" decipherOnly", fp);
         }
-      putc ('\n', fp);
+      es_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);
+    {
+      es_fputs ("ext key usage: ", fp);
       if (err)
-        fprintf (fp, "[error: %s]", gpg_strerror (err));
+        es_fprintf (fp, "[error: %s]", gpg_strerror (err));
       else
         {
           p = string;
@@ -1012,27 +1157,42 @@ list_cert_std (ctrl_t ctrl, ksba_cert_t cert, FILE *fp, int have_secret,
               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);
+              es_fputs (key_purpose_map[i].oid?key_purpose_map[i].name:p, fp);
               p = pend;
               if (*p != 'C')
-                fputs (" (suggested)", fp);
+                es_fputs (" (suggested)", fp);
               if ((p = strchr (p, '\n')))
                 {
                   p++;
-                  fputs (", ", fp);
+                  es_fputs (", ", fp);
                 }
             }
           xfree (string);
         }
-      putc ('\n', fp);
+      es_putc ('\n', fp);
+    }
+
+  /* Print restrictions.  */
+  for (idx=0; !(err=ksba_cert_get_extension (cert, idx,
+                                             &oid, NULL, &off, &len));idx++)
+    {
+      if (!strcmp (oid, OIDSTR_restriction) )
+        {
+          if (!cert_der)
+            cert_der = ksba_cert_get_image (cert, NULL);
+          assert (cert_der);
+          es_fputs ("  restriction: ", fp);
+          print_utf8_extn (fp, 15, cert_der+off, len);
+        }
     }
 
+  /* Print policies.  */
   err = ksba_cert_get_cert_policies (cert, &string);
   if (gpg_err_code (err) != GPG_ERR_NO_DATA)
     {
-      fputs ("     policies: ", fp);
+      es_fputs ("     policies: ", fp);
       if (err)
-        fprintf (fp, "[error: %s]", gpg_strerror (err));
+        es_fprintf (fp, "[error: %s]", gpg_strerror (err));
       else
         {
           for (p=string; *p; p++)
@@ -1040,62 +1200,83 @@ list_cert_std (ctrl_t ctrl, ksba_cert_t cert, FILE *fp, int have_secret,
               if (*p == '\n')
                 *p = ',';
             }
-          print_sanitized_string (fp, string, 0);
+          es_write_sanitized (fp, string, strlen (string), NULL, NULL);
           xfree (string);
         }
-      putc ('\n', fp);
+      es_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));
+      es_fputs (" chain length: ", fp);
+      if (gpg_err_code (err) == GPG_ERR_NO_VALUE )
+        es_fprintf (fp, "none");
+      else if (err)
+        es_fprintf (fp, "[error: %s]", gpg_strerror (err));
       else if (chainlen == -1)
-        fputs ("unlimited", fp);
+        es_fputs ("unlimited", fp);
       else
-        fprintf (fp, "%d", chainlen);
-      putc ('\n', fp);
+        es_fprintf (fp, "%d", chainlen);
+      es_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");
+      es_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");
+  es_fprintf (fp, "  fingerprint: %s\n", dn?dn:"error");
   xfree (dn);
 
+  if (opt.with_keygrip)
+    {
+      dn = gpgsm_get_keygrip_hexstring (cert);
+      if (dn)
+        {
+          es_fprintf (fp, "      keygrip: %s\n", dn);
+          xfree (dn);
+        }
+    }
+
+  if (have_secret)
+    {
+      char *cardsn;
 
+      p = gpgsm_get_keygrip_hexstring (cert);
+      if (!gpgsm_agent_keyinfo (ctrl, p, &cardsn) && cardsn)
+        es_fprintf (fp, "     card s/n: %s\n", cardsn);
+      xfree (cardsn);
+      xfree (p);
+    }
 
   if (with_validation)
     {
       gpg_error_t tmperr;
       size_t buflen;
       char buffer[1];
-      
-      err = gpgsm_validate_chain (ctrl, cert, NULL, 1, fp, 0);
-      tmperr = ksba_cert_get_user_data (cert, "is_qualified", 
+
+      err = gpgsm_validate_chain (ctrl, cert, "", NULL, 1, fp, 0, NULL);
+      tmperr = ksba_cert_get_user_data (cert, "is_qualified",
                                         &buffer, sizeof (buffer), &buflen);
       if (!tmperr && buflen)
         {
           if (*buffer)
-            fputs ("  [qualified]\n", fp);
-        }    
+            es_fputs ("  [qualified]\n", fp);
+        }
       else if (gpg_err_code (tmperr) == GPG_ERR_NOT_FOUND)
         ; /* Don't know - will not get marked as 'q' */
       else
         log_debug ("get_user_data(is_qualified) failed: %s\n",
-                   gpg_strerror (tmperr)); 
+                   gpg_strerror (tmperr));
 
       if (!err)
-        fprintf (fp, "  [certificate is good]\n");
+        es_fprintf (fp, "  [certificate is good]\n");
       else
-        fprintf (fp, "  [certificate is bad: %s]\n", gpg_strerror (err));
+        es_fprintf (fp, "  [certificate is bad: %s]\n", gpg_strerror (err));
     }
 }
 
@@ -1104,7 +1285,7 @@ list_cert_std (ctrl_t ctrl, ksba_cert_t cert, FILE *fp, int have_secret,
 static void
 list_cert_chain (ctrl_t ctrl, KEYDB_HANDLE hd,
                  ksba_cert_t cert, int raw_mode,
-                 FILE *fp, int with_validation)
+                 estream_t fp, int with_validation)
 {
   ksba_cert_t next = NULL;
 
@@ -1113,10 +1294,10 @@ list_cert_chain (ctrl_t ctrl, KEYDB_HANDLE hd,
   else
     list_cert_std (ctrl, cert, fp, 0, with_validation);
   ksba_cert_ref (cert);
-  while (!gpgsm_walk_cert_chain (cert, &next))
+  while (!gpgsm_walk_cert_chain (ctrl, cert, &next))
     {
       ksba_cert_release (cert);
-      fputs ("Certified by\n", fp);
+      es_fputs ("Certified by\n", fp);
       if (raw_mode)
         list_cert_raw (ctrl, hd, next, fp, 0, with_validation);
       else
@@ -1124,7 +1305,7 @@ list_cert_chain (ctrl_t ctrl, KEYDB_HANDLE hd,
       cert = next;
     }
   ksba_cert_release (cert);
-  putc ('\n', fp);
+  es_putc ('\n', fp);
 }
 
 
@@ -1135,17 +1316,19 @@ list_cert_chain (ctrl_t ctrl, KEYDB_HANDLE hd,
    output mode will be used instead of the standard beautified one.
  */
 static gpg_error_t
-list_internal_keys (ctrl_t ctrl, STRLIST names, FILE *fp,
+list_internal_keys (ctrl_t ctrl, strlist_t names, estream_t fp,
                     unsigned int mode, int raw_mode)
 {
   KEYDB_HANDLE hd;
   KEYDB_SEARCH_DESC *desc = NULL;
-  STRLIST sl;
+  strlist_t sl;
   int ndesc;
   ksba_cert_t cert = NULL;
+  ksba_cert_t lastcert = NULL;
   gpg_error_t rc = 0;
   const char *lastresname, *resname;
   int have_secret;
+  int want_ephemeral = ctrl->with_ephemeral_keys;
 
   hd = keydb_new (0);
   if (!hd)
@@ -1159,7 +1342,7 @@ list_internal_keys (ctrl_t ctrl, STRLIST names, FILE *fp,
     ndesc = 1;
   else
     {
-      for (sl=names, ndesc=0; sl; sl = sl->next, ndesc++) 
+      for (sl=names, ndesc=0; sl; sl = sl->next, ndesc++)
         ;
     }
 
@@ -1173,24 +1356,41 @@ list_internal_keys (ctrl_t ctrl, STRLIST names, FILE *fp,
 
   if (!names)
     desc[0].mode = KEYDB_SEARCH_MODE_FIRST;
-  else 
+  else
     {
-      for (ndesc=0, sl=names; sl; sl = sl->next) 
+      for (ndesc=0, sl=names; sl; sl = sl->next)
         {
-          rc = keydb_classify_name (sl->d, desc+ndesc);
+          rc = classify_user_id (sl->d, desc+ndesc, 0);
           if (rc)
             {
-              log_error ("key `%s' not found: %s\n",
+              log_error ("key '%s' not found: %s\n",
                          sl->d, gpg_strerror (rc));
               rc = 0;
             }
           else
             ndesc++;
         }
-      
+
     }
 
-  if (opt.with_ephemeral_keys)
+  /* If all specifications are done by fingerprint or keygrip, we
+     switch to ephemeral mode so that _all_ currently available and
+     matching certificates are listed.  */
+  if (!want_ephemeral && names && ndesc)
+    {
+      int i;
+
+      for (i=0; (i < ndesc
+                 && (desc[i].mode == KEYDB_SEARCH_MODE_FPR
+                     || desc[i].mode == KEYDB_SEARCH_MODE_FPR20
+                     || desc[i].mode == KEYDB_SEARCH_MODE_FPR16
+                     || desc[i].mode == KEYDB_SEARCH_MODE_KEYGRIP)); i++)
+        ;
+      if (i == ndesc)
+        want_ephemeral = 1;
+    }
+
+  if (want_ephemeral)
     keydb_set_ephemeral (hd, 1);
 
   /* It would be nice to see which of the given users did actually
@@ -1200,12 +1400,13 @@ list_internal_keys (ctrl_t ctrl, STRLIST names, FILE *fp,
      currently we stop at the first match.  To do this we need an
      extra flag to enable this feature so */
 
+  /* Suppress duplicates at least when they follow each other.  */
   lastresname = NULL;
   while (!(rc = keydb_search (hd, desc, ndesc)))
     {
       unsigned int validity;
 
-      if (!names) 
+      if (!names)
         desc[0].mode = KEYDB_SEARCH_MODE_NEXT;
 
       rc = keydb_get_flags (hd, KEYBOX_FLAG_VALIDITY, 0, &validity);
@@ -1215,24 +1416,33 @@ list_internal_keys (ctrl_t ctrl, STRLIST names, FILE *fp,
           goto leave;
         }
       rc = keydb_get_cert (hd, &cert);
-      if (rc) 
+      if (rc)
         {
           log_error ("keydb_get_cert failed: %s\n", gpg_strerror (rc));
           goto leave;
         }
-      
+      /* Skip duplicated certificates, at least if they follow each
+        others.  This works best if a single key is searched for and
+        expected.  FIXME: Non-sequential duplicates remain.  */
+      if (gpgsm_certs_identical_p (cert, lastcert))
+       {
+         ksba_cert_release (cert);
+          cert = NULL;
+         continue;
+       }
+
       resname = keydb_get_resource_name (hd);
-      
-      if (lastresname != resname ) 
+
+      if (lastresname != resname )
         {
           int i;
-          
+
           if (ctrl->no_server)
             {
-              fprintf (fp, "%s\n", resname );
+              es_fprintf (fp, "%s\n", resname );
               for (i=strlen(resname); i; i-- )
-                putc ('-', fp);
-              putc ('\n', fp);
+                es_putc ('-', fp);
+              es_putc ('\n', fp);
               lastresname = resname;
             }
         }
@@ -1243,8 +1453,8 @@ list_internal_keys (ctrl_t ctrl, STRLIST names, FILE *fp,
           char *p = gpgsm_get_keygrip_hexstring (cert);
           if (p)
             {
-              rc = gpgsm_agent_havekey (ctrl, p); 
-             if (!rc)
+              rc = gpgsm_agent_havekey (ctrl, p);
+              if (!rc)
                 have_secret = 1;
               else if ( gpg_err_code (rc) != GPG_ERR_NO_SECKEY)
                 goto leave;
@@ -1270,19 +1480,22 @@ list_internal_keys (ctrl_t ctrl, STRLIST names, FILE *fp,
               else
                 list_cert_std (ctrl, cert, fp, have_secret,
                                ctrl->with_validation);
-              putc ('\n', fp);
+              es_putc ('\n', fp);
             }
         }
-      ksba_cert_release (cert); 
+
+      ksba_cert_release (lastcert);
+      lastcert = 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);
+  ksba_cert_release (lastcert);
   xfree (desc);
   keydb_release (hd);
   return rc;
@@ -1303,10 +1516,10 @@ list_external_cb (void *cb_value, ksba_cert_t cert)
       const char *resname = "[external keys]";
       int i;
 
-      fprintf (parm->fp, "%s\n", resname );
+      es_fprintf (parm->fp, "%s\n", resname );
       for (i=strlen(resname); i; i-- )
-        putchar('-');
-      putc ('\n', parm->fp);
+        es_putc('-', parm->fp);
+      es_putc ('\n', parm->fp);
       parm->print_header = 0;
     }
 
@@ -1320,7 +1533,7 @@ list_external_cb (void *cb_value, ksba_cert_t cert)
         list_cert_raw (parm->ctrl, NULL, cert, parm->fp, 0, 0);
       else
         list_cert_std (parm->ctrl, cert, parm->fp, 0, 0);
-      putc ('\n', parm->fp);
+      es_putc ('\n', parm->fp);
     }
 }
 
@@ -1329,7 +1542,7 @@ list_external_cb (void *cb_value, ksba_cert_t cert)
    make sense here because it would be unwise to list external secret
    keys */
 static gpg_error_t
-list_external_keys (ctrl_t ctrl, STRLIST names, FILE *fp, int raw_mode)
+list_external_keys (ctrl_t ctrl, strlist_t names, estream_t fp, int raw_mode)
 {
   int rc;
   struct list_external_parm_s parm;
@@ -1341,14 +1554,17 @@ list_external_keys (ctrl_t ctrl, STRLIST names, FILE *fp, int raw_mode)
   parm.with_chain = ctrl->with_chain;
   parm.raw_mode  = raw_mode;
 
-  rc = gpgsm_dirmngr_lookup (ctrl, names, list_external_cb, &parm);
+  rc = gpgsm_dirmngr_lookup (ctrl, names, 0, list_external_cb, &parm);
+  if (gpg_err_code (rc) == GPG_ERR_EOF || rc == -1
+      || gpg_err_code (rc) == GPG_ERR_NOT_FOUND)
+    rc = 0; /* "Not found" is not an error here. */
   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: 
+   MODE controls the operation mode:
     Bit 0-2:
       0 = list all public keys but don't flag secret ones
       1 = list only public keys
@@ -1359,13 +1575,14 @@ list_external_keys (ctrl_t ctrl, STRLIST names, FILE *fp, int raw_mode)
     Bit 8: Do a raw format dump.
  */
 gpg_error_t
-gpgsm_list_keys (ctrl_t ctrl, STRLIST names, FILE *fp, unsigned int mode)
+gpgsm_list_keys (ctrl_t ctrl, strlist_t names, estream_t fp,
+                 unsigned int mode)
 {
   gpg_error_t err = 0;
 
   if ((mode & (1<<6)))
     err = list_internal_keys (ctrl, names, fp, (mode & 3), (mode&256));
   if (!err && (mode & (1<<7)))
-    err = list_external_keys (ctrl, names, fp, (mode&256)); 
+    err = list_external_keys (ctrl, names, fp, (mode&256));
   return err;
 }