dirmngr: New Assuan option "http-crl".
[gnupg.git] / dirmngr / certcache.c
index 45be1f2..ff86f61 100644 (file)
@@ -1,5 +1,5 @@
 /* certcache.c - Certificate caching
- *      Copyright (C) 2004, 2005, 2007, 2008 g10 Code GmbH
+ * Copyright (C) 2004, 2005, 2007, 2008, 2017 g10 Code GmbH
  *
  * This file is part of DirMngr.
  *
@@ -14,7 +14,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, see <http://www.gnu.org/licenses/>.
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
  */
 
 #include <config.h>
 
 #include "dirmngr.h"
 #include "misc.h"
+#include "../common/ksba-io-support.h"
 #include "crlfetch.h"
 #include "certcache.h"
 
-
 #define MAX_EXTRA_CACHED_CERTS 1000
 
 /* Constants used to classify search patterns.  */
@@ -68,8 +68,12 @@ struct cert_item_s
   char *subject_dn;         /* The malloced subject DN - maybe NULL.  */
   struct
   {
-    unsigned int loaded:1;  /* It has been explicitly loaded.  */
+    unsigned int config:1;  /* This has been loaded from the configuration.  */
     unsigned int trusted:1; /* This is a trusted root certificate.  */
+    unsigned int systrust:1;/* The certifciate is trusted because it
+                             * is in the system's store of trusted
+                             * certificates (i.e. not configured using
+                             * GnuPG mechanisms.  */
   } flags;
 };
 typedef struct cert_item_s *cert_item_t;
@@ -88,10 +92,26 @@ static npth_rwlock_t cert_cache_lock;
 /* Flag to track whether the cache has been initialized.  */
 static int initialization_done;
 
-/* Total number of certificates loaded during initialization and
-   cached during operation.  */
-static unsigned int total_loaded_certificates;
+/* Total number of certificates loaded during initialization
+ * (ie. configured), extra certificates cached during operation,
+ * number of trusted and system trusted certificates.  */
+static unsigned int total_config_certificates;
 static unsigned int total_extra_certificates;
+static unsigned int total_trusted_certificates;
+static unsigned int total_system_trusted_certificates;
+
+
+#ifdef HAVE_W32_SYSTEM
+/* We load some functions dynamically.  Provide typedefs for tehse
+ * fucntions.  */
+typedef HCERTSTORE (WINAPI *CERTOPENSYSTEMSTORE)
+  (HCRYPTPROV hProv, LPCSTR szSubsystemProtocol);
+typedef PCCERT_CONTEXT (WINAPI *CERTENUMCERTIFICATESINSTORE)
+  (HCERTSTORE hCertStore, PCCERT_CONTEXT pPrevCertContext);
+typedef WINBOOL (WINAPI *CERTCLOSESTORE)
+  (HCERTSTORE hCertStore,DWORD dwFlags);
+#endif /*HAVE_W32_SYSTEM*/
+
 
 
 \f
@@ -154,8 +174,8 @@ compare_serialno (ksba_sexp_t serial1, ksba_sexp_t serial2 )
 
 
 /* Return a malloced canonical S-Expression with the serial number
  converted from the hex string HEXSN.  Return NULL on memory
  error. */
* converted from the hex string HEXSN.  Return NULL on memory
* error.  */
 ksba_sexp_t
 hexsn_to_sexp (const char *hexsn)
 {
@@ -205,6 +225,7 @@ cert_compute_fpr (ksba_cert_t cert, unsigned char *digest)
 }
 
 
+\f
 /* Cleanup one slot.  This releases all resourses but keeps the actual
    slot in the cache marked for reuse. */
 static void
@@ -229,13 +250,20 @@ clean_cache_slot (cert_item_t ci)
 
 
 /* Put the certificate CERT into the cache.  It is assumed that the
-   cache is locked while this function is called. If FPR_BUFFER is not
-   NULL the fingerprint of the certificate will be stored there.
-   FPR_BUFFER neds to point to a buffer of at least 20 bytes. The
-   fingerprint will be stored on success or when the function returns
-   gpg_err_code(GPG_ERR_DUP_VALUE). */
+ * cache is locked while this function is called.
+ *
+ * FROM_CONFIG indicates that CERT is a permanent certificate and
+ * should stay in the cache.  IS_TRUSTED requests that the trusted
+ * flag is set for the certificate; a value of 1 indicates the the
+ * cert is trusted due to GnuPG mechanisms, a value of 2 indicates
+ * that it is trusted because it has been taken from the system's
+ * store of trusted certificates.  If FPR_BUFFER is not NULL the
+ * fingerprint of the certificate will be stored there.  FPR_BUFFER
+ * needs to point to a buffer of at least 20 bytes.  The fingerprint
+ * will be stored on success or when the function returns
+ * GPG_ERR_DUP_VALUE.  */
 static gpg_error_t
-put_cert (ksba_cert_t cert, int is_loaded, int is_trusted, void *fpr_buffer)
+put_cert (ksba_cert_t cert, int from_config, int is_trusted, void *fpr_buffer)
 {
   unsigned char help_fpr_buffer[20], *fpr;
   cert_item_t ci;
@@ -243,17 +271,17 @@ put_cert (ksba_cert_t cert, int is_loaded, int is_trusted, void *fpr_buffer)
   fpr = fpr_buffer? fpr_buffer : &help_fpr_buffer;
 
   /* If we already reached the caching limit, drop a couple of certs
-     from the cache.  Our dropping strategy is simple: We keep a
-     static index counter and use this to start looking for
-     certificates, then we drop 5 percent of the oldest certificates
-     starting at that index.  For a large cache this is a fair way of
-     removing items. An LRU strategy would be better of course.
-     Because we append new entries to the head of the list and we want
-     to remove old ones first, we need to do this from the tail.  The
-     implementation is not very efficient but compared to the long
-     time it takes to retrieve a certifciate from an external resource
-     it seems to be reasonable. */
-  if (!is_loaded && total_extra_certificates >= MAX_EXTRA_CACHED_CERTS)
+   * from the cache.  Our dropping strategy is simple: We keep a
+   * static index counter and use this to start looking for
+   * certificates, then we drop 5 percent of the oldest certificates
+   * starting at that index.  For a large cache this is a fair way of
+   * removing items.  An LRU strategy would be better of course.
+   * Because we append new entries to the head of the list and we want
+   * to remove old ones first, we need to do this from the tail.  The
+   * implementation is not very efficient but compared to the long
+   * time it takes to retrieve a certificate from an external resource
+   * it seems to be reasonable.  */
+  if (!from_config && total_extra_certificates >= MAX_EXTRA_CACHED_CERTS)
     {
       static int idx;
       cert_item_t ci_mark;
@@ -270,13 +298,17 @@ put_cert (ksba_cert_t cert, int is_loaded, int is_trusted, void *fpr_buffer)
         {
           ci_mark = NULL;
           for (ci = cert_cache[i]; ci; ci = ci->next)
-            if (ci->cert && !ci->flags.loaded)
+            if (ci->cert && !ci->flags.config)
               ci_mark = ci;
           if (ci_mark)
             {
               clean_cache_slot (ci_mark);
               drop_count--;
               total_extra_certificates--;
+              if (ci->flags.trusted)
+                total_trusted_certificates--;
+              if (ci->flags.systrust)
+                total_system_trusted_certificates--;
             }
         }
       if (i==idx)
@@ -316,11 +348,17 @@ put_cert (ksba_cert_t cert, int is_loaded, int is_trusted, void *fpr_buffer)
       return gpg_error (GPG_ERR_INV_CERT_OBJ);
     }
   ci->subject_dn = ksba_cert_get_subject (cert, 0);
-  ci->flags.loaded  = !!is_loaded;
+  ci->flags.config  = !!from_config;
   ci->flags.trusted = !!is_trusted;
+  ci->flags.systrust = (is_trusted && is_trusted == 2);
+
+  if (ci->flags.trusted)
+    total_trusted_certificates++;
+  if (ci->flags.systrust)
+    total_system_trusted_certificates++;
 
-  if (is_loaded)
-    total_loaded_certificates++;
+  if (from_config)
+    total_config_certificates++;
   else
     total_extra_certificates++;
 
@@ -348,9 +386,6 @@ load_certs_from_dir (const char *dirname, int are_trusted)
   dir = opendir (dirname);
   if (!dir)
     {
-      if (opt.system_daemon)
-        log_info (_("can't access directory '%s': %s\n"),
-                  dirname, strerror (errno));
       return 0; /* We do not consider this a severe error.  */
     }
 
@@ -393,7 +428,7 @@ load_certs_from_dir (const char *dirname, int are_trusted)
           continue;
         }
 
-      err = put_cert (cert, 1, are_trusted, NULL);
+      err = put_cert (cert, 1, !!are_trusted, NULL);
       if (gpg_err_code (err) == GPG_ERR_DUP_VALUE)
         log_info (_("certificate '%s' already cached\n"), fname);
       else if (!err)
@@ -424,6 +459,243 @@ load_certs_from_dir (const char *dirname, int are_trusted)
 }
 
 
+#ifndef HAVE_W32_SYSTEM
+/* Load certificates from FILE.  The certifciates are expected to be
+ * PEM encoded so that it is possible to load several certificates.
+ * All certificates are considered to be system provided trusted
+ * certificates.  The cache should be in a locked state when calling
+ * this function.  */
+static gpg_error_t
+load_certs_from_file (const char *fname)
+{
+  gpg_error_t err;
+  estream_t fp = NULL;
+  gnupg_ksba_io_t ioctx = NULL;
+  ksba_reader_t reader;
+  ksba_cert_t cert = NULL;
+
+  fp = es_fopen (fname, "rb");
+  if (!fp)
+    {
+      err = gpg_error_from_syserror ();
+      log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err));
+      goto leave;
+    }
+
+  err = gnupg_ksba_create_reader (&ioctx,
+                                  (GNUPG_KSBA_IO_PEM | GNUPG_KSBA_IO_MULTIPEM),
+                                  fp, &reader);
+  if (err)
+    {
+      log_error ("can't create reader: %s\n", gpg_strerror (err));
+      goto leave;
+    }
+
+  /* Loop to read all certificates from the file.  */
+  do
+    {
+      ksba_cert_release (cert);
+      cert = NULL;
+      err = ksba_cert_new (&cert);
+      if (!err)
+        err = ksba_cert_read_der (cert, reader);
+      if (err)
+        {
+          if (gpg_err_code (err) == GPG_ERR_EOF)
+            err = 0;
+          else
+            log_error (_("can't parse certificate '%s': %s\n"),
+                       fname, gpg_strerror (err));
+          goto leave;
+        }
+
+      err = put_cert (cert, 1, 2, NULL);
+      if (gpg_err_code (err) == GPG_ERR_DUP_VALUE)
+        log_info (_("certificate '%s' already cached\n"), fname);
+      else if (err)
+        log_error (_("error loading certificate '%s': %s\n"),
+                   fname, gpg_strerror (err));
+      else if (opt.verbose > 1)
+        {
+          char *p;
+
+          log_info (_("trusted certificate '%s' loaded\n"), fname);
+          p = get_fingerprint_hexstring_colon (cert);
+          log_info (_("  SHA1 fingerprint = %s\n"), p);
+          xfree (p);
+
+          cert_log_name    (_("   issuer ="), cert);
+          cert_log_subject (_("  subject ="), cert);
+        }
+
+      ksba_reader_clear (reader, NULL, NULL);
+    }
+  while (!gnupg_ksba_reader_eof_seen (ioctx));
+
+ leave:
+  ksba_cert_release (cert);
+  gnupg_ksba_destroy_reader (ioctx);
+  es_fclose (fp);
+
+  return err;
+}
+#endif /*!HAVE_W32_SYSTEM*/
+
+#ifdef HAVE_W32_SYSTEM
+/* Load all certificates from the Windows store named STORENAME.  All
+ * certificates are considered to be system provided trusted
+ * certificates.  The cache should be in a locked state when calling
+ * this function.  */
+static void
+load_certs_from_w32_store (const char *storename)
+{
+  static int init_done;
+  static CERTOPENSYSTEMSTORE pCertOpenSystemStore;
+  static CERTENUMCERTIFICATESINSTORE pCertEnumCertificatesInStore;
+  static CERTCLOSESTORE pCertCloseStore;
+  gpg_error_t err;
+  HCERTSTORE w32store;
+  const CERT_CONTEXT *w32cert;
+  ksba_cert_t cert = NULL;
+  unsigned int count = 0;
+
+  /* Initialize on the first use.  */
+  if (!init_done)
+    {
+      static HANDLE hCrypt32;
+
+      init_done = 1;
+
+      hCrypt32 = LoadLibrary ("Crypt32.dll");
+      if (!hCrypt32)
+        {
+          log_error ("can't load Crypt32.dll: %s\n",  w32_strerror (-1));
+          return;
+        }
+
+      pCertOpenSystemStore = (CERTOPENSYSTEMSTORE)
+        GetProcAddress (hCrypt32, "CertOpenSystemStoreA");
+      pCertEnumCertificatesInStore = (CERTENUMCERTIFICATESINSTORE)
+        GetProcAddress (hCrypt32, "CertEnumCertificatesInStore");
+      pCertCloseStore = (CERTCLOSESTORE)
+        GetProcAddress (hCrypt32, "CertCloseStore");
+      if (   !pCertOpenSystemStore
+          || !pCertEnumCertificatesInStore
+          || !pCertCloseStore)
+        {
+          log_error ("can't load crypt32.dll: %s\n", "missing function");
+          pCertOpenSystemStore = NULL;
+        }
+    }
+
+  if (!pCertOpenSystemStore)
+    return;  /* Not initialized.  */
+
+
+  w32store = pCertOpenSystemStore (0, storename);
+  if (!w32store)
+    {
+      log_error ("can't open certificate store '%s': %s\n",
+                 storename, w32_strerror (-1));
+      return;
+    }
+
+  w32cert = NULL;
+  while ((w32cert = pCertEnumCertificatesInStore (w32store, w32cert)))
+    {
+      if (w32cert->dwCertEncodingType == X509_ASN_ENCODING)
+        {
+          ksba_cert_release (cert);
+          cert = NULL;
+          err = ksba_cert_new (&cert);
+          if (!err)
+            err = ksba_cert_init_from_mem (cert,
+                                           w32cert->pbCertEncoded,
+                                           w32cert->cbCertEncoded);
+          if (err)
+            {
+              log_error (_("can't parse certificate '%s': %s\n"),
+                         storename, gpg_strerror (err));
+              break;
+            }
+
+          err = put_cert (cert, 1, 2, NULL);
+          if (!err)
+            count++;
+          if (gpg_err_code (err) == GPG_ERR_DUP_VALUE)
+            log_info (_("certificate '%s' already cached\n"), storename);
+          else if (err)
+            log_error (_("error loading certificate '%s': %s\n"),
+                       storename, gpg_strerror (err));
+          else if (opt.verbose > 1)
+            {
+              char *p;
+
+              log_info (_("trusted certificate '%s' loaded\n"), storename);
+              p = get_fingerprint_hexstring_colon (cert);
+              log_info (_("  SHA1 fingerprint = %s\n"), p);
+              xfree (p);
+
+              cert_log_name    (_("   issuer ="), cert);
+              cert_log_subject (_("  subject ="), cert);
+            }
+        }
+    }
+
+  ksba_cert_release (cert);
+  pCertCloseStore (w32store, 0);
+
+  if (DBG_X509)
+    log_debug ("number of certs loaded from store '%s': %u\n",
+               storename, count);
+
+}
+#endif /*HAVE_W32_SYSTEM*/
+
+
+/* Load the trusted certificates provided by the system.  */
+static gpg_error_t
+load_certs_from_system (void)
+{
+#ifdef HAVE_W32_SYSTEM
+
+  load_certs_from_w32_store ("ROOT");
+  load_certs_from_w32_store ("CA");
+
+  return 0;
+
+#else /*!HAVE_W32_SYSTEM*/
+
+  /* A list of certificate bundles to try.  */
+  static struct {
+    const char *name;
+  } table[] = {
+#ifdef DEFAULT_TRUST_STORE_FILE
+    { DEFAULT_TRUST_STORE_FILE }
+#else
+    { "/etc/ssl/ca-bundle.pem" },
+    { "/etc/ssl/certs/ca-certificates.crt" },
+    { "/etc/pki/tls/cert.pem" },
+    { "/usr/local/share/certs/ca-root-nss.crt" },
+    { "/etc/ssl/cert.pem" }
+#endif /*!DEFAULT_TRUST_STORE_FILE*/
+  };
+  int idx;
+  gpg_error_t err = 0;
+
+  for (idx=0; idx < DIM (table); idx++)
+    if (!access (table[idx].name, F_OK))
+      {
+        /* Take the first available bundle.  */
+        err = load_certs_from_file (table[idx].name);
+        break;
+      }
+
+  return err;
+#endif /*!HAVE_W32_SYSTEM*/
+}
+
+
 /* Initialize the certificate cache if not yet done.  */
 void
 cert_cache_init (void)
@@ -435,6 +707,8 @@ cert_cache_init (void)
   init_cache_lock ();
   acquire_cache_write_lock ();
 
+  load_certs_from_system ();
+
   dname = make_filename (gnupg_sysconfdir (), "trusted-certs", NULL);
   load_certs_from_dir (dname, 1);
   xfree (dname);
@@ -479,8 +753,10 @@ cert_cache_deinit (int full)
         }
     }
 
-  total_loaded_certificates = 0;
+  total_config_certificates = 0;
   total_extra_certificates = 0;
+  total_trusted_certificates = 0;
+  total_system_trusted_certificates = 0;
   initialization_done = 0;
   release_cache_lock ();
 }
@@ -490,9 +766,11 @@ void
 cert_cache_print_stats (void)
 {
   log_info (_("permanently loaded certificates: %u\n"),
-            total_loaded_certificates);
+            total_config_certificates);
   log_info (_("    runtime cached certificates: %u\n"),
             total_extra_certificates);
+  log_info (_("           trusted certificates: %u (%u)\n"),
+            total_trusted_certificates, total_system_trusted_certificates);
 }
 
 
@@ -984,7 +1262,7 @@ get_certs_bypattern (const char *pattern,
 
 \f
 /* Return the certificate matching ISSUER_DN and SERIALNO; if it is
  not already in the cache, try to find it from other resources.  */
* not already in the cache, try to find it from other resources.  */
 ksba_cert_t
 find_cert_bysn (ctrl_t ctrl, const char *issuer_dn, ksba_sexp_t serialno)
 {
@@ -999,23 +1277,23 @@ find_cert_bysn (ctrl_t ctrl, const char *issuer_dn, ksba_sexp_t serialno)
     return cert;
 
   /* Ask back to the service requester to return the certificate.
-     This is because we can assume that he already used the
-     certificate while checking for the CRL. */
+   * This is because we can assume that he already used the
+   * certificate while checking for the CRL.  */
   hexsn = serial_hex (serialno);
   if (!hexsn)
     {
       log_error ("serial_hex() failed\n");
       return NULL;
     }
-  buf = xtrymalloc (1 + strlen (hexsn) + 1 + strlen (issuer_dn) + 1);
+  buf = strconcat ("#", hexsn, "/", issuer_dn, NULL);
   if (!buf)
     {
       log_error ("can't allocate enough memory: %s\n", strerror (errno));
       xfree (hexsn);
       return NULL;
     }
-  strcpy (stpcpy (stpcpy (stpcpy (buf, "#"), hexsn),"/"), issuer_dn);
   xfree (hexsn);
+
   cert = get_cert_local (ctrl, buf);
   xfree (buf);
   if (cert)
@@ -1096,10 +1374,10 @@ find_cert_bysn (ctrl_t ctrl, const char *issuer_dn, ksba_sexp_t serialno)
 
 
 /* Return the certificate matching SUBJECT_DN and (if not NULL)
  KEYID. If it is not already in the cache, try to find it from other
  resources.  Note, that the external search does not work for user
  certificates because the LDAP lookup is on the caCertificate
  attribute. For our purposes this is just fine.  */
* KEYID. If it is not already in the cache, try to find it from other
* resources.  Note, that the external search does not work for user
* certificates because the LDAP lookup is on the caCertificate
* attribute. For our purposes this is just fine.  */
 ksba_cert_t
 find_cert_bysubject (ctrl_t ctrl, const char *subject_dn, ksba_sexp_t keyid)
 {
@@ -1110,11 +1388,11 @@ find_cert_bysubject (ctrl_t ctrl, const char *subject_dn, ksba_sexp_t keyid)
   ksba_sexp_t subj;
 
   /* If we have certificates from an OCSP request we first try to use
-     them.  This is because these certificates will really be the
-     required ones and thus even in the case that they can't be
-     uniquely located by the following code we can use them.  This is
-     for example required by Telesec certificates where a keyId is
-     used but the issuer certificate comes without a subject keyId! */
+   * them.  This is because these certificates will really be the
+   * required ones and thus even in the case that they can't be
+   * uniquely located by the following code we can use them.  This is
+   * for example required by Telesec certificates where a keyId is
+   * used but the issuer certificate comes without a subject keyId! */
   if (ctrl->ocsp_certs && subject_dn)
     {
       cert_item_t ci;
@@ -1139,8 +1417,7 @@ find_cert_bysubject (ctrl_t ctrl, const char *subject_dn, ksba_sexp_t keyid)
         log_debug ("find_cert_bysubject: certificate not in ocsp_certs\n");
     }
 
-
-  /* First we check whether the certificate is cached.  */
+  /* No check whether the certificate is cached.  */
   for (seq=0; (cert = get_cert_bysubject (subject_dn, seq)); seq++)
     {
       if (!keyid)
@@ -1161,24 +1438,23 @@ find_cert_bysubject (ctrl_t ctrl, const char *subject_dn, ksba_sexp_t keyid)
     log_debug ("find_cert_bysubject: certificate not in cache\n");
 
   /* Ask back to the service requester to return the certificate.
-     This is because we can assume that he already used the
-     certificate while checking for the CRL. */
+   * This is because we can assume that he already used the
+   * certificate while checking for the CRL. */
   if (keyid)
     cert = get_cert_local_ski (ctrl, subject_dn, keyid);
   else
     {
       /* In contrast to get_cert_local_ski, get_cert_local uses any
-         passed pattern, so we need to make sure that an exact subject
-         search is done. */
+       * passed pattern, so we need to make sure that an exact subject
+       * search is done.  */
       char *buf;
 
-      buf = xtrymalloc (1 + strlen (subject_dn) + 1);
+      buf = strconcat ("/", subject_dn, NULL);
       if (!buf)
         {
           log_error ("can't allocate enough memory: %s\n", strerror (errno));
           return NULL;
         }
-      strcpy (stpcpy (buf, "/"), subject_dn);
       cert = get_cert_local (ctrl, buf);
       xfree (buf);
     }
@@ -1267,12 +1543,12 @@ find_cert_bysubject (ctrl_t ctrl, const char *subject_dn, ksba_sexp_t keyid)
 }
 
 
-
-/* Return 0 if the certificate is a trusted certificate. Returns
-   GPG_ERR_NOT_TRUSTED if it is not trusted or other error codes in
  case of systems errors. */
+/* Return 0 if the certificate is a trusted certificate.  Returns
+ * GPG_ERR_NOT_TRUSTED if it is not trusted or other error codes in
+ * case of systems errors.  If WITH_SYSTRUST is set also system
* provided certificates are considered trusted.  */
 gpg_error_t
-is_trusted_cert (ksba_cert_t cert)
+is_trusted_cert (ksba_cert_t cert, int with_systrust)
 {
   unsigned char fpr[20];
   cert_item_t ci;
@@ -1283,7 +1559,7 @@ is_trusted_cert (ksba_cert_t cert)
   for (ci=cert_cache[*fpr]; ci; ci = ci->next)
     if (ci->cert && !memcmp (ci->fpr, fpr, 20))
       {
-        if (ci->flags.trusted)
+        if (ci->flags.trusted && (with_systrust || !ci->flags.systrust))
           {
             release_cache_lock ();
             return 0; /* Yes, it is trusted. */
@@ -1298,8 +1574,8 @@ is_trusted_cert (ksba_cert_t cert)
 
 \f
 /* Given the certificate CERT locate the issuer for this certificate
  and return it at R_CERT.  Returns 0 on success or
  GPG_ERR_NOT_FOUND.  */
* and return it at R_CERT.  Returns 0 on success or
* GPG_ERR_NOT_FOUND.  */
 gpg_error_t
 find_issuing_cert (ctrl_t ctrl, ksba_cert_t cert, ksba_cert_t *r_cert)
 {
@@ -1335,16 +1611,18 @@ find_issuing_cert (ctrl_t ctrl, ksba_cert_t cert, ksba_cert_t *r_cert)
         {
           issuer_cert = find_cert_bysn (ctrl, s, authidno);
         }
+
       if (!issuer_cert && keyid)
         {
           /* Not found by issuer+s/n.  Now that we have an AKI
-             keyIdentifier look for a certificate with a matching
-             SKI. */
+           * keyIdentifier look for a certificate with a matching
+           * SKI. */
           issuer_cert = find_cert_bysubject (ctrl, issuer_dn, keyid);
         }
+
       /* Print a note so that the user does not feel too helpless when
-         an issuer certificate was found and gpgsm prints BAD
-         signature because it is not the correct one. */
+       * an issuer certificate was found and gpgsm prints BAD
+       * signature because it is not the correct one.  */
       if (!issuer_cert)
         {
           log_info ("issuer certificate ");
@@ -1370,8 +1648,8 @@ find_issuing_cert (ctrl_t ctrl, ksba_cert_t cert, ksba_cert_t *r_cert)
     }
 
   /* If this did not work, try just with the issuer's name and assume
-     that there is only one such certificate.  We only look into our
-     cache then. */
+   * that there is only one such certificate.  We only look into our
+   * cache then.  */
   if (err || !issuer_cert)
     {
       issuer_cert = get_cert_bysubject (issuer_dn, 0);
@@ -1392,3 +1670,92 @@ find_issuing_cert (ctrl_t ctrl, ksba_cert_t cert, ksba_cert_t *r_cert)
 
   return err;
 }
+
+
+\f
+/* Read a list of certificates in PEM format from stream FP and store
+ * them on success at R_CERTLIST.  On error NULL is stored at R_CERT
+ * list and an error code returned.  Note that even on success an
+ * empty list of certificates can be returned (i.e. NULL stored at
+ * R_CERTLIST) iff the input stream has no certificates.  */
+gpg_error_t
+read_certlist_from_stream (certlist_t *r_certlist, estream_t fp)
+{
+  gpg_error_t err;
+  gnupg_ksba_io_t ioctx = NULL;
+  ksba_reader_t reader;
+  ksba_cert_t cert = NULL;
+  certlist_t certlist = NULL;
+  certlist_t cl, *cltail;
+
+  *r_certlist = NULL;
+
+  err = gnupg_ksba_create_reader (&ioctx,
+                                  (GNUPG_KSBA_IO_PEM | GNUPG_KSBA_IO_MULTIPEM),
+                                  fp, &reader);
+  if (err)
+    goto leave;
+
+  /* Loop to read all certificates from the stream.  */
+  cltail = &certlist;
+  do
+    {
+      ksba_cert_release (cert);
+      cert = NULL;
+      err = ksba_cert_new (&cert);
+      if (!err)
+        err = ksba_cert_read_der (cert, reader);
+      if (err)
+        {
+          if (gpg_err_code (err) == GPG_ERR_EOF)
+            err = 0;
+          goto leave;
+        }
+
+      /* Append the certificate to the list.  We also store the
+       * fingerprint and check whether we have a cached certificate;
+       * in that case the cached certificate is put into the list to
+       * take advantage of a validation result which might be stored
+       * in the cached certificate.  */
+      cl = xtrycalloc (1, sizeof *cl);
+      if (!cl)
+        {
+          err = gpg_error_from_syserror ();
+          goto leave;
+        }
+      cert_compute_fpr (cert, cl->fpr);
+      cl->cert = get_cert_byfpr (cl->fpr);
+      if (!cl->cert)
+        {
+          cl->cert = cert;
+          cert = NULL;
+        }
+      *cltail = cl;
+      cltail = &cl->next;
+      ksba_reader_clear (reader, NULL, NULL);
+    }
+  while (!gnupg_ksba_reader_eof_seen (ioctx));
+
+ leave:
+  ksba_cert_release (cert);
+  gnupg_ksba_destroy_reader (ioctx);
+  if (err)
+    release_certlist (certlist);
+  else
+    *r_certlist = certlist;
+
+  return err;
+}
+
+
+/* Release the certificate list CL.  */
+void
+release_certlist (certlist_t cl)
+{
+  while (cl)
+    {
+      certlist_t next = cl->next;
+      ksba_cert_release (cl->cert);
+      cl = next;
+    }
+}