Print status of CRL checks in the audit log.
[gnupg.git] / sm / certchain.c
index d1fca2b..e9a1aad 100644 (file)
@@ -1,11 +1,12 @@
-/* certpath.c - certificate chain validation
- *     Copyright (C) 2001, 2002 Free Software Foundation, Inc.
+/* certchain.c - certificate chain validation
+ * Copyright (C) 2001, 2002, 2003, 2004, 2005,
+ *               2006, 2007, 2008 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,
@@ -14,8 +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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
 #include <config.h>
 #include <errno.h>
 #include <unistd.h> 
 #include <time.h>
+#include <stdarg.h>
 #include <assert.h>
 
+#define JNLIB_NEED_LOG_LOGV /* We need log_logv. */
+
+#include "gpgsm.h"
 #include <gcrypt.h>
 #include <ksba.h>
 
-#include "gpgsm.h"
 #include "keydb.h"
+#include "../kbx/keybox.h" /* for KEYBOX_FLAG_* */
 #include "i18n.h"
+#include "tlv.h"
+
 
+/* Object to keep track of certain root certificates. */
+struct marktrusted_info_s
+{
+  struct marktrusted_info_s *next;
+  unsigned char fpr[20];
+};
+static struct marktrusted_info_s *marktrusted_info;
+
+
+/* While running the validation function we want to keep track of the
+   certificates in the chain.  This type is used for that.  */
+struct chain_item_s
+{
+  struct chain_item_s *next;
+  ksba_cert_t cert;      /* The certificate.  */
+  int is_root;           /* The certificate is the root certificate.  */
+};
+typedef struct chain_item_s *chain_item_t;
+
+
+static int is_root_cert (ksba_cert_t cert,
+                         const char *issuerdn, const char *subjectdn);
+static int get_regtp_ca_info (ctrl_t ctrl, ksba_cert_t cert, int *chainlen);
+
+
+/* This function returns true if we already asked during this session
+   whether the root certificate CERT shall be marked as trusted.  */
 static int
-unknown_criticals (KsbaCert cert)
+already_asked_marktrusted (ksba_cert_t cert)
+{
+  unsigned char fpr[20];
+  struct marktrusted_info_s *r;
+
+  gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, fpr, NULL);
+  /* No context switches in the loop! */
+  for (r=marktrusted_info; r; r= r->next)
+    if (!memcmp (r->fpr, fpr, 20))
+      return 1;
+  return 0;
+}
+
+/* Flag certificate CERT as already asked whether it shall be marked
+   as trusted.  */
+static void
+set_already_asked_marktrusted (ksba_cert_t cert)
+{
+ unsigned char fpr[20];
+ struct marktrusted_info_s *r;
+
+ gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, fpr, NULL);
+ for (r=marktrusted_info; r; r= r->next)
+   if (!memcmp (r->fpr, fpr, 20))
+     return; /* Already marked. */
+ r = xtrycalloc (1, sizeof *r);
+ if (!r)
+   return;
+ memcpy (r->fpr, fpr, 20);
+ r->next = marktrusted_info;
+ marktrusted_info = r;
+}
+
+/* If LISTMODE is true, print FORMAT using LISTMODE to FP.  If
+   LISTMODE is false, use the string to print an log_info or, if
+   IS_ERROR is true, and log_error. */
+static void
+do_list (int is_error, int listmode, estream_t fp, const char *format, ...)
+{
+  va_list arg_ptr;
+
+  va_start (arg_ptr, format) ;
+  if (listmode)
+    {
+      if (fp)
+        {
+          es_fputs ("  [", fp);
+          es_vfprintf (fp, format, arg_ptr);
+          es_fputs ("]\n", fp);
+        }
+    }
+  else
+    {
+      log_logv (is_error? JNLIB_LOG_ERROR: JNLIB_LOG_INFO, format, arg_ptr);
+      log_printf ("\n");
+    }
+  va_end (arg_ptr);
+}
+
+/* Return 0 if A and B are equal. */
+static int
+compare_certs (ksba_cert_t a, ksba_cert_t b)
+{
+  const unsigned char *img_a, *img_b;
+  size_t len_a, len_b;
+
+  img_a = ksba_cert_get_image (a, &len_a);
+  if (!img_a)
+    return 1;
+  img_b = ksba_cert_get_image (b, &len_b);
+  if (!img_b)
+    return 1;
+  return !(len_a == len_b && !memcmp (img_a, img_b, len_a));
+}
+
+
+/* Return true if CERT has the validityModel extensions and defines
+   the use of the chain model.  */
+static int
+has_validation_model_chain (ksba_cert_t cert, int listmode, estream_t listfp)
+{
+  gpg_error_t err;
+  int idx, yes;
+  const char *oid;
+  size_t off, derlen, objlen, hdrlen;
+  const unsigned char *der;
+  int class, tag, constructed, ndef;
+  char *oidbuf;
+
+  for (idx=0; !(err=ksba_cert_get_extension (cert, idx,
+                                             &oid, NULL, &off, &derlen));idx++)
+    if (!strcmp (oid, "1.3.6.1.4.1.8301.3.5") )
+      break;
+  if (err)
+    return 0; /* Not found.  */
+  der = ksba_cert_get_image (cert, NULL);
+  if (!der)
+    {
+      err = gpg_error (GPG_ERR_INV_OBJ); /* Oops  */
+      goto leave;
+    }
+  der += off;
+
+  err = parse_ber_header (&der, &derlen, &class, &tag, &constructed,
+                          &ndef, &objlen, &hdrlen);
+  if (!err && (objlen > derlen || tag != TAG_SEQUENCE))
+    err = gpg_error (GPG_ERR_INV_OBJ);
+  if (err)
+    goto leave;
+  derlen = objlen;
+  err = parse_ber_header (&der, &derlen, &class, &tag, &constructed,
+                          &ndef, &objlen, &hdrlen);
+  if (!err && (objlen > derlen || tag != TAG_OBJECT_ID))
+    err = gpg_error (GPG_ERR_INV_OBJ);
+  if (err)
+    goto leave;
+  oidbuf = ksba_oid_to_str (der, objlen);
+  if (!oidbuf)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+
+  if (opt.verbose)
+    do_list (0, listmode, listfp,
+             _("validation model requested by certificate: %s"), 
+              !strcmp (oidbuf, "1.3.6.1.4.1.8301.3.5.1")? _("chain") :
+              !strcmp (oidbuf, "1.3.6.1.4.1.8301.3.5.2")? _("shell") :
+              /* */                                       oidbuf);
+  yes = !strcmp (oidbuf, "1.3.6.1.4.1.8301.3.5.1");
+  ksba_free (oidbuf);
+  return yes;
+
+
+ leave:
+  log_error ("error parsing validityModel: %s\n", gpg_strerror (err));
+  return 0;
+}
+
+
+
+static int
+unknown_criticals (ksba_cert_t cert, int listmode, estream_t fp)
 {
   static const char *known[] = {
     "2.5.29.15", /* keyUsage */
+    "2.5.29.17", /* subjectAltName
+                    Japanese DoCoMo certs mark them as critical.  PKIX
+                    only requires them as critical if subjectName is
+                    empty.  I don't know whether our code gracefully
+                    handles such empry subjectNames but that is
+                    another story. */
     "2.5.29.19", /* basic Constraints */
     "2.5.29.32", /* certificatePolicies */
+    "2.5.29.37", /* extendedKeyUsage - handled by certlist.c */
+    "1.3.6.1.4.1.8301.3.5", /* validityModel - handled here. */
     NULL
   };
   int rc = 0, i, idx, crit;
   const char *oid;
-  KsbaError err;
+  gpg_error_t err;
 
   for (idx=0; !(err=ksba_cert_get_extension (cert, idx,
                                              &oid, &crit, NULL, NULL));idx++)
@@ -56,55 +239,73 @@ unknown_criticals (KsbaCert cert)
         ;
       if (!known[i])
         {
-          log_error (_("critical certificate extension %s is not supported\n"),
-                     oid);
-          rc = GNUPG_Unsupported_Certificate;
+          do_list (1, listmode, fp,
+                   _("critical certificate extension %s is not supported"),
+                   oid);
+          rc = gpg_error (GPG_ERR_UNSUPPORTED_CERT);
         }
     }
-  if (err && err != -1)
-    rc = map_ksba_err (err);
+  /* We ignore the error codes EOF as well as no-value. The later will
+     occur for certificates with no extensions at all. */
+  if (err
+      && gpg_err_code (err) != GPG_ERR_EOF
+      && gpg_err_code (err) != GPG_ERR_NO_VALUE)
+    rc = err;
 
   return rc;
 }
 
+
+/* Check whether CERT is an allowed certificate.  This requires that
+   CERT matches all requirements for such a CA, i.e. the
+   BasicConstraints extension.  The function returns 0 on success and
+   the awlloed length of the chain at CHAINLEN. */
 static int
-allowed_ca (KsbaCert cert, int *chainlen)
+allowed_ca (ctrl_t ctrl, 
+            ksba_cert_t cert, int *chainlen, int listmode, estream_t fp)
 {
-  KsbaError err;
+  gpg_error_t err;
   int flag;
 
   err = ksba_cert_is_ca (cert, &flag, chainlen);
   if (err)
-    return map_ksba_err (err);
+    return err;
   if (!flag)
     {
-      log_error (_("issuer certificate is not marked as a CA\n"));
-      return GNUPG_Bad_CA_Certificate;
+      if (get_regtp_ca_info (ctrl, cert, chainlen))
+        {
+          /* Note that dirmngr takes a different way to cope with such
+             certs. */
+          return 0; /* RegTP issued certificate. */
+        }
+
+      do_list (1, listmode, fp,_("issuer certificate is not marked as a CA"));
+      return gpg_error (GPG_ERR_BAD_CA_CERT);
     }
   return 0;
 }
 
 
 static int
-check_cert_policy (KsbaCert cert)
+check_cert_policy (ksba_cert_t cert, int listmode, estream_t fplist)
 {
-  KsbaError err;
+  gpg_error_t err;
   char *policies;
   FILE *fp;
   int any_critical;
 
   err = ksba_cert_get_cert_policies (cert, &policies);
-  if (err == KSBA_No_Data)
-    return 0; /* no policy given */
+  if (gpg_err_code (err) == GPG_ERR_NO_DATA)
+    return 0; /* No policy given. */
   if (err)
-    return map_ksba_err (err);
+    return err;
 
-  /* STRING is a line delimited list of certifiate policies as stored
+  /* STRING is a line delimited list of certificate policies as stored
      in the certificate.  The line itself is colon delimited where the
      first field is the OID of the policy and the second field either
      N or C for normal or critical extension */
 
-  if (opt.verbose > 1)
+  if (opt.verbose > 1 && !listmode)
     log_info ("certificate's policy list: %s\n", policies);
 
   /* The check is very minimal but won't give false positives */
@@ -115,8 +316,9 @@ check_cert_policy (KsbaCert cert)
       xfree (policies);
       if (any_critical)
         {
-          log_error ("critical marked policy without configured policies\n");
-          return GNUPG_No_Policy_Match;
+          do_list (1, listmode, fplist,
+                   _("critical marked policy without configured policies"));
+          return gpg_error (GPG_ERR_NO_POLICY_MATCH);
         }
       return 0;
     }
@@ -124,10 +326,21 @@ check_cert_policy (KsbaCert cert)
   fp = fopen (opt.policy_file, "r");
   if (!fp)
     {
-      log_error ("failed to open `%s': %s\n",
-                 opt.policy_file, strerror (errno));
+      if (opt.verbose || errno != ENOENT)
+        log_info (_("failed to open `%s': %s\n"),
+                  opt.policy_file, strerror (errno));
       xfree (policies);
-      return GNUPG_No_Policy_Match;
+      /* With no critical policies this is only a warning */
+      if (!any_critical)
+        {
+          if (!opt.quiet)
+            do_list (0, listmode, fplist,
+                     _("note: non-critical certificate policy not allowed"));
+          return 0;
+        }
+      do_list (1, listmode, fplist,
+               _("certificate policy not allowed"));
+      return gpg_error (GPG_ERR_NO_POLICY_MATCH);
     }
 
   for (;;) 
@@ -141,21 +354,25 @@ check_cert_policy (KsbaCert cert)
         {
           if (!fgets (line, DIM(line)-1, fp) )
             {
+              gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
+
               xfree (policies);
               if (feof (fp))
                 {
                   fclose (fp);
-                  /* with no critical policies this is only a warning */
+                  /* With no critical policies this is only a warning */
                   if (!any_critical)
                     {
-                      log_info (_("note: certificate policy not allowed\n"));
+                      do_list (0, listmode, fplist,
+                     _("note: non-critical certificate policy not allowed"));
                       return 0;
                     }
-                  log_error (_("certificate policy not allowed\n"));
-                  return GNUPG_No_Policy_Match;
+                  do_list (1, listmode, fplist,
+                           _("certificate policy not allowed"));
+                  return gpg_error (GPG_ERR_NO_POLICY_MATCH);
                 }
               fclose (fp);
-              return GNUPG_Read_Error;
+              return tmperr;
             }
       
           if (!*line || line[strlen(line)-1] != '\n')
@@ -165,7 +382,8 @@ check_cert_policy (KsbaCert cert)
                 ;
               fclose (fp);
               xfree (policies);
-              return *line? GNUPG_Line_Too_Long: GNUPG_Incomplete_Line;
+              return gpg_error (*line? GPG_ERR_LINE_TOO_LONG
+                                     : GPG_ERR_INCOMPLETE_LINE);
             }
           
           /* Allow for empty lines and spaces */
@@ -182,17 +400,17 @@ check_cert_policy (KsbaCert cert)
         {
           fclose (fp);
           xfree (policies);
-          return GNUPG_Configuration_Error;
+          return gpg_error (GPG_ERR_CONFIGURATION);
         }
       *p = 0; /* strip the rest of the line */
       /* See whether we find ALLOWED (which is an OID) in POLICIES */
       for (haystack=policies; (p=strstr (haystack, allowed)); haystack = p+1)
         {
           if ( !(p == policies || p[-1] == '\n') )
-            continue; /* does not match the begin of a line */
+            continue; /* Does not match the begin of a line. */
           if (p[strlen (allowed)] != ':')
-            continue; /* the length does not match */
-          /* Yep - it does match so return okay */
+            continue; /* The length does not match. */
+          /* Yep - it does match so return okay. */
           fclose (fp);
           xfree (policies);
           return 0;
@@ -201,8 +419,44 @@ check_cert_policy (KsbaCert cert)
 }
 
 
+/* Helper function for find_up.  This resets the key handle and search
+   for an issuer ISSUER with a subjectKeyIdentifier of KEYID.  Returns
+   0 on success or -1 when not found. */
+static int
+find_up_search_by_keyid (KEYDB_HANDLE kh,
+                         const char *issuer, ksba_sexp_t keyid)
+{
+  int rc;
+  ksba_cert_t cert = NULL;
+  ksba_sexp_t subj = NULL;
+
+  keydb_search_reset (kh);
+  while (!(rc = keydb_search_subject (kh, issuer)))
+    {
+      ksba_cert_release (cert); cert = NULL;
+      rc = keydb_get_cert (kh, &cert);
+      if (rc)
+        {
+          log_error ("keydb_get_cert() failed: rc=%d\n", rc);
+          rc = -1;
+          break;
+        }
+      xfree (subj);
+      if (!ksba_cert_get_subj_key_id (cert, NULL, &subj))
+        {
+          if (!cmp_simple_canon_sexp (keyid, subj))
+            break; /* Found matching cert. */
+        }
+    }
+  
+  ksba_cert_release (cert);
+  xfree (subj);
+  return rc? -1:0;
+}
+
+
 static void
-find_up_store_certs_cb (void *cb_value, KsbaCert cert)
+find_up_store_certs_cb (void *cb_value, ksba_cert_t cert)
 {
   if (keydb_store_cert (cert, 1, NULL))
     log_error ("error storing issuer certificate as ephemeral\n");
@@ -210,23 +464,155 @@ find_up_store_certs_cb (void *cb_value, KsbaCert cert)
 }
 
 
+/* Helper for find_up().  Locate the certificate for ISSUER using an
+   external lookup.  KH is the keydb context we are currently using.
+   On success 0 is returned and the certificate may be retrieved from
+   the keydb using keydb_get_cert().  KEYID is the keyIdentifier from
+   the AKI or NULL.  */
+static int
+find_up_external (ctrl_t ctrl, KEYDB_HANDLE kh,
+                  const char *issuer, ksba_sexp_t keyid)
+{
+  int rc;
+  strlist_t names = NULL;
+  int count = 0;
+  char *pattern;
+  const char *s;
+      
+  if (opt.verbose)
+    log_info (_("looking up issuer at external location\n"));
+  /* The Dirmngr process is confused about unknown attributes.  As a
+     quick and ugly hack we locate the CN and use the issuer string
+     starting at this attribite.  Fixme: we should have far better
+     parsing for external lookups in the Dirmngr. */
+  s = strstr (issuer, "CN=");
+  if (!s || s == issuer || s[-1] != ',')
+    s = issuer;
+  pattern = xtrymalloc (strlen (s)+2);
+  if (!pattern)
+    return gpg_error_from_syserror ();
+  strcpy (stpcpy (pattern, "/"), s);
+  add_to_strlist (&names, pattern);
+  xfree (pattern);
+
+  rc = gpgsm_dirmngr_lookup (ctrl, names, 0, find_up_store_certs_cb, &count);
+  free_strlist (names);
+
+  if (opt.verbose)
+    log_info (_("number of issuers matching: %d\n"), count);
+  if (rc) 
+    {
+      log_error ("external key lookup failed: %s\n", gpg_strerror (rc));
+      rc = -1;
+    }
+  else if (!count)
+    rc = -1;
+  else
+    {
+      int old;
+      /* The issuers are currently stored in the ephemeral key DB, so
+         we temporary switch to ephemeral mode. */
+      old = keydb_set_ephemeral (kh, 1);
+      if (keyid)
+        rc = find_up_search_by_keyid (kh, issuer, keyid);
+      else
+        {
+          keydb_search_reset (kh);
+          rc = keydb_search_subject (kh, issuer);
+        }
+      keydb_set_ephemeral (kh, old);
+    }
+  return rc;
+}
+
+
+/* Helper for find_up().  Ask the dirmngr for the certificate for
+   ISSUER with optional SERIALNO.  KH is the keydb context we are
+   currently using.  With SUBJECT_MODE set, ISSUER is searched as the
+   subject.  On success 0 is returned and the certificate is available
+   in the ephemeral DB.  */
+static int
+find_up_dirmngr (ctrl_t ctrl, KEYDB_HANDLE kh,
+                 ksba_sexp_t serialno, const char *issuer, int subject_mode)
+{
+  int rc;
+  strlist_t names = NULL;
+  int count = 0;
+  char *pattern;
+
+  (void)kh;
+      
+  if (opt.verbose)
+    log_info (_("looking up issuer from the Dirmngr cache\n"));
+  if (subject_mode)
+    {
+      pattern = xtrymalloc (strlen (issuer)+2);
+      if (pattern)
+        strcpy (stpcpy (pattern, "/"), issuer);
+    }
+  else if (serialno)
+    pattern = gpgsm_format_sn_issuer (serialno, issuer);
+  else
+    {
+      pattern = xtrymalloc (strlen (issuer)+3);
+      if (pattern)
+        strcpy (stpcpy (pattern, "#/"), issuer);
+    }
+  if (!pattern)
+    return gpg_error_from_syserror ();
+  add_to_strlist (&names, pattern);
+  xfree (pattern);
+
+  rc = gpgsm_dirmngr_lookup (ctrl, names, 1, find_up_store_certs_cb, &count);
+  free_strlist (names);
+
+  if (opt.verbose)
+    log_info (_("number of matching certificates: %d\n"), count);
+  if (rc && !opt.quiet) 
+    log_info (_("dirmngr cache-only key lookup failed: %s\n"),
+              gpg_strerror (rc));
+  return (!rc && count)? 0 : -1;
+}
+
+
+
+/* Locate issuing certificate for CERT. ISSUER is the name of the
+   issuer used as a fallback if the other methods don't work.  If
+   FIND_NEXT is true, the function shall return the next possible
+   issuer.  The certificate itself is not directly returned but a
+   keydb_get_cert on the keyDb context KH will return it.  Returns 0
+   on success, -1 if not found or an error code.  */
 static int
-find_up (KEYDB_HANDLE kh, KsbaCert cert, const char *issuer)
+find_up (ctrl_t ctrl, KEYDB_HANDLE kh, 
+         ksba_cert_t cert, const char *issuer, int find_next)
 {
-  KsbaName authid;
-  KsbaSexp authidno;
+  ksba_name_t authid;
+  ksba_sexp_t authidno;
+  ksba_sexp_t keyid;
   int rc = -1;
 
-  if (!ksba_cert_get_auth_key_id (cert, NULL, &authid, &authidno))
+  if (!ksba_cert_get_auth_key_id (cert, &keyid, &authid, &authidno))
     {
       const char *s = ksba_name_enum (authid, 0);
       if (s && *authidno)
         {
           rc = keydb_search_issuer_sn (kh, s, authidno);
           if (rc)
-              keydb_search_reset (kh);
-          if (rc == -1)
-            { /* And try the ephemeral DB. */
+            keydb_search_reset (kh);
+          
+          /* In case of an error, try to get the certificate from the
+             dirmngr.  That is done by trying to put that certifcate
+             into the ephemeral DB and let the code below do the
+             actual retrieve.  Thus there is no error checking.
+             Skipped in find_next mode as usual. */
+          if (rc == -1 && !find_next)
+            find_up_dirmngr (ctrl, kh, authidno, s, 0);
+
+          /* In case of an error try the ephemeral DB.  We can't do
+             that in find_next mode because we can't keep the search
+             state then. */
+          if (rc == -1 && !find_next)
+            { 
               int old = keydb_set_ephemeral (kh, 1);
               if (!old)
                 {
@@ -236,18 +622,96 @@ find_up (KEYDB_HANDLE kh, KsbaCert cert, const char *issuer)
                 }
               keydb_set_ephemeral (kh, old);
             }
+          if (rc) 
+            rc = -1; /* Need to make sure to have this error code. */
+        }
+
+      if (rc == -1 && keyid && !find_next)
+        {
+          /* Not found by AIK.issuer_sn.  Lets try the AIK.ki
+             instead. Loop over all certificates with that issuer as
+             subject and stop for the one with a matching
+             subjectKeyIdentifier. */
+          /* Fixme: Should we also search in the dirmngr?  */
+          rc = find_up_search_by_keyid (kh, issuer, keyid);
+          if (rc)
+            {
+              int old = keydb_set_ephemeral (kh, 1);
+              if (!old)
+                rc = find_up_search_by_keyid (kh, issuer, keyid);
+              keydb_set_ephemeral (kh, old);
+            }
+          if (rc) 
+            rc = -1; /* Need to make sure to have this error code. */
+        }
+
+      /* If we still didn't found it, try to find it via the subject
+         from the dirmngr-cache.  */
+      if (rc == -1 && !find_next)
+        {
+          if (!find_up_dirmngr (ctrl, kh, NULL, issuer, 1))
+            {
+              int old = keydb_set_ephemeral (kh, 1);
+              if (keyid)
+                rc = find_up_search_by_keyid (kh, issuer, keyid);
+              else
+                {
+                  keydb_search_reset (kh);
+                  rc = keydb_search_subject (kh, issuer);
+                }
+              keydb_set_ephemeral (kh, old);
+            }
+          if (rc) 
+            rc = -1; /* Need to make sure to have this error code. */
         }
+
+      /* If we still didn't found it, try an external lookup.  */
+      if (rc == -1 && opt.auto_issuer_key_retrieve && !find_next)
+        rc = find_up_external (ctrl, kh, issuer, 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. */
+      if (rc == -1 && opt.quiet)
+        ;
+      else if (rc == -1)
+        {
+          log_info ("%sissuer certificate ", find_next?"next ":"");
+          if (keyid)
+            {
+              log_printf ("{");
+              gpgsm_dump_serial (keyid);
+              log_printf ("} ");
+            }
+          if (authidno)
+            {
+              log_printf ("(#");
+              gpgsm_dump_serial (authidno);
+              log_printf ("/");
+              gpgsm_dump_string (s);
+              log_printf (") ");
+            }
+          log_printf ("not found using authorityKeyIdentifier\n");
+        }
+      else if (rc)
+        log_error ("failed to find authorityKeyIdentifier: rc=%d\n", rc);
+      xfree (keyid);
       ksba_name_release (authid);
       xfree (authidno);
-      /* Fixme: don't know how to do dirmngr lookup with serial+issuer. */
     }
   
-  if (rc) /* not found via authorithyKeyIdentifier, try regular issuer name */
-      rc = keydb_search_subject (kh, issuer);
-  if (rc == -1)
+  if (rc) /* Not found via authorithyKeyIdentifier, try regular issuer name. */
+    rc = keydb_search_subject (kh, issuer);
+  if (rc == -1 && !find_next)
     {
-      /* Not found, lets see whether we have one in the ephemeral key DB. */
-      int old = keydb_set_ephemeral (kh, 1);
+      int old;
+
+      /* Also try to get it from the Dirmngr cache.  The function
+         merely puts it into the ephemeral database.  */
+      find_up_dirmngr (ctrl, kh, NULL, issuer, 0);
+
+      /* Not found, let us see whether we have one in the ephemeral key DB. */
+      old = keydb_set_ephemeral (kh, 1);
       if (!old)
         {
           keydb_search_reset (kh);
@@ -256,51 +720,10 @@ find_up (KEYDB_HANDLE kh, KsbaCert cert, const char *issuer)
       keydb_set_ephemeral (kh, old);
     }
 
-  if (rc == -1 && opt.auto_issuer_key_retrieve)
-    {
-      STRLIST names = NULL;
-      int count = 0;
-      char *pattern;
-      const char *s;
-      
-      if (opt.verbose)
-        log_info (_("looking up issuer at external location\n"));
-      /* dirmngr is confused about unknown attributes so has a quick
-         and ugly hack we locate the CN and use this and the
-         following.  Fixme: we should have far ebtter parsing in the
-         dirmngr. */
-      s = strstr (issuer, "CN=");
-      if (!s || s == issuer || s[-1] != ',')
-        s = issuer;
-
-      pattern = xtrymalloc (strlen (s)+2);
-      if (!pattern)
-        return GNUPG_Out_Of_Core;
-      strcpy (stpcpy (pattern, "/"), s);
-      add_to_strlist (&names, pattern);
-      xfree (pattern);
-      rc = gpgsm_dirmngr_lookup (NULL, names, find_up_store_certs_cb, &count);
-      free_strlist (names);
-      if (opt.verbose)
-        log_info (_("number of issuers matching: %d\n"), count);
-      if (rc) 
-        {
-          log_error ("external key lookup failed: %s\n", gnupg_strerror (rc));
-          rc = -1;
-        }
-      else if (!count)
-        rc = -1;
-      else
-        {
-          int old;
-          /* The issuers are currently stored in the ephemeral key
-             DB, so we temporary switch to ephemeral mode. */
-          old = keydb_set_ephemeral (kh, 1);
-          keydb_search_reset (kh);
-          rc = keydb_search_subject (kh, issuer);
-          keydb_set_ephemeral (kh, old);
-        }
-    }
+  /* Still not found.  If enabled, try an external lookup.  */
+  if (rc == -1 && opt.auto_issuer_key_retrieve && !find_next)
+    rc = find_up_external (ctrl, kh, issuer, NULL);
+
   return rc;
 }
 
@@ -308,7 +731,7 @@ find_up (KEYDB_HANDLE kh, KsbaCert cert, const char *issuer)
 /* Return the next certificate up in the chain starting at START.
    Returns -1 when there are no more certificates. */
 int
-gpgsm_walk_cert_chain (KsbaCert start, KsbaCert *r_next)
+gpgsm_walk_cert_chain (ctrl_t ctrl, ksba_cert_t start, ksba_cert_t *r_next)
 {
   int rc = 0; 
   char *issuer = NULL;
@@ -319,7 +742,7 @@ gpgsm_walk_cert_chain (KsbaCert start, KsbaCert *r_next)
   if (!kh)
     {
       log_error (_("failed to allocated keyDB handle\n"));
-      rc = GNUPG_General_Error;
+      rc = gpg_error (GPG_ERR_GENERAL);
       goto leave;
     }
 
@@ -328,38 +751,38 @@ gpgsm_walk_cert_chain (KsbaCert start, KsbaCert *r_next)
   if (!issuer)
     {
       log_error ("no issuer found in certificate\n");
-      rc = GNUPG_Bad_Certificate;
+      rc = gpg_error (GPG_ERR_BAD_CERT);
       goto leave;
     }
   if (!subject)
     {
       log_error ("no subject found in certificate\n");
-      rc = GNUPG_Bad_Certificate;
+      rc = gpg_error (GPG_ERR_BAD_CERT);
       goto leave;
     }
 
-  if (!strcmp (issuer, subject))
+  if (is_root_cert (start, issuer, subject))
     {
       rc = -1; /* we are at the root */
       goto leave; 
     }
 
-  rc = find_up (kh, start, issuer);
+  rc = find_up (ctrl, kh, start, issuer, 0);
   if (rc)
     {
-      /* it is quite common not to have a certificate, so better don't
-         print an error here */
+      /* It is quite common not to have a certificate, so better don't
+         print an error here */
       if (rc != -1 && opt.verbose > 1)
         log_error ("failed to find issuer's certificate: rc=%d\n", rc);
-      rc = GNUPG_Missing_Certificate;
+      rc = gpg_error (GPG_ERR_MISSING_CERT);
       goto leave;
     }
 
   rc = keydb_get_cert (kh, r_next);
   if (rc)
     {
-      log_error ("failed to get cert: rc=%d\n", rc);
-      rc = GNUPG_General_Error;
+      log_error ("keydb_get_cert() failed: rc=%d\n", rc);
+      rc = gpg_error (GPG_ERR_GENERAL);
     }
 
  leave:
@@ -370,10 +793,79 @@ gpgsm_walk_cert_chain (KsbaCert start, KsbaCert *r_next)
 }
 
 
+/* Helper for gpgsm_is_root_cert.  This one is used if the subject and
+   issuer DNs are already known.  */
+static int
+is_root_cert (ksba_cert_t cert, const char *issuerdn, const char *subjectdn)
+{
+  gpg_error_t err;
+  int result = 0;
+  ksba_sexp_t serialno;
+  ksba_sexp_t ak_keyid;
+  ksba_name_t ak_name;
+  ksba_sexp_t ak_sn;
+  const char *ak_name_str;
+  ksba_sexp_t subj_keyid = NULL;
+
+  if (!issuerdn || !subjectdn)
+    return 0;  /* No.  */
+
+  if (strcmp (issuerdn, subjectdn))
+    return 0;  /* No.  */
+
+  err = ksba_cert_get_auth_key_id (cert, &ak_keyid, &ak_name, &ak_sn);
+  if (err)
+    {
+      if (gpg_err_code (err) == GPG_ERR_NO_DATA)
+        return 1; /* Yes. Without a authorityKeyIdentifier this needs
+                     to be the Root certifcate (our trust anchor).  */
+      log_error ("error getting authorityKeyIdentifier: %s\n",
+                 gpg_strerror (err));
+      return 0; /* Well, it is broken anyway.  Return No. */
+    }
+
+  serialno = ksba_cert_get_serial (cert);
+  if (!serialno)
+    {
+      log_error ("error getting serialno: %s\n", gpg_strerror (err));
+      goto leave;
+    }
+
+  /* Check whether the auth name's matches the issuer name+sn.  If
+     that is the case this is a root certificate.  */
+  ak_name_str = ksba_name_enum (ak_name, 0);
+  if (ak_name_str
+      && !strcmp (ak_name_str, issuerdn) 
+      && !cmp_simple_canon_sexp (ak_sn, serialno))
+    {
+      result = 1;  /* Right, CERT is self-signed.  */
+      goto leave;
+    } 
+   
+  /* Similar for the ak_keyid. */
+  if (ak_keyid && !ksba_cert_get_subj_key_id (cert, NULL, &subj_keyid)
+      && !cmp_simple_canon_sexp (ak_keyid, subj_keyid))
+    {
+      result = 1;  /* Right, CERT is self-signed.  */
+      goto leave;
+    } 
+
+
+ leave:
+  ksba_free (subj_keyid);
+  ksba_free (ak_keyid);
+  ksba_name_release (ak_name);
+  ksba_free (ak_sn);
+  ksba_free (serialno);
+  return result; 
+}
+
+
+
 /* Check whether the CERT is a root certificate.  Returns True if this
    is the case. */
 int
-gpgsm_is_root_cert (KsbaCert cert)
+gpgsm_is_root_cert (ksba_cert_t cert)
 {
   char *issuer;
   char *subject;
@@ -381,55 +873,401 @@ gpgsm_is_root_cert (KsbaCert cert)
 
   issuer = ksba_cert_get_issuer (cert, 0);
   subject = ksba_cert_get_subject (cert, 0);
-  yes = (issuer && subject && !strcmp (issuer, subject));
+  yes = is_root_cert (cert, issuer, subject);
   xfree (issuer);
   xfree (subject);
   return yes;
 }
 
+
+/* This is a helper for gpgsm_validate_chain. */
+static gpg_error_t 
+is_cert_still_valid (ctrl_t ctrl, int force_ocsp, int lm, estream_t fp,
+                     ksba_cert_t subject_cert, ksba_cert_t issuer_cert,
+                     int *any_revoked, int *any_no_crl, int *any_crl_too_old)
+{
+  gpg_error_t err;
+
+  if (opt.no_crl_check && !ctrl->use_ocsp)
+    {
+      audit_log_ok (ctrl->audit, AUDIT_CRL_CHECK, 
+                    gpg_error (GPG_ERR_NOT_ENABLED));
+      return 0;
+    }
+
+  err = gpgsm_dirmngr_isvalid (ctrl,
+                               subject_cert, issuer_cert, 
+                               force_ocsp? 2 : !!ctrl->use_ocsp);
+  audit_log_ok (ctrl->audit, AUDIT_CRL_CHECK, err);
+
+  if (err)
+    {
+      if (!lm)
+        gpgsm_cert_log_name (NULL, subject_cert);
+      switch (gpg_err_code (err))
+        {
+        case GPG_ERR_CERT_REVOKED:
+          do_list (1, lm, fp, _("certificate has been revoked"));
+          *any_revoked = 1;
+          /* Store that in the keybox so that key listings are able to
+             return the revoked flag.  We don't care about error,
+             though. */
+          keydb_set_cert_flags (subject_cert, 1, KEYBOX_FLAG_VALIDITY, 0,
+                                ~0, VALIDITY_REVOKED);
+          break;
+
+        case GPG_ERR_NO_CRL_KNOWN:
+          do_list (1, lm, fp, _("no CRL found for certificate"));
+          *any_no_crl = 1;
+          break;
+
+        case GPG_ERR_NO_DATA:
+          do_list (1, lm, fp, _("the status of the certificate is unknown"));
+          *any_no_crl = 1;
+          break;
+
+        case GPG_ERR_CRL_TOO_OLD:
+          do_list (1, lm, fp, _("the available CRL is too old"));
+          if (!lm)
+            log_info (_("please make sure that the "
+                        "\"dirmngr\" is properly installed\n"));
+          *any_crl_too_old = 1;
+          break;
+          
+        default:
+          do_list (1, lm, fp, _("checking the CRL failed: %s"),
+                   gpg_strerror (err));
+          return err;
+        }
+    }
+  return 0;
+}
+
+
+/* Helper for gpgsm_validate_chain to check the validity period of
+   SUBJECT_CERT.  The caller needs to pass EXPTIME which will be
+   updated to the nearest expiration time seen.  A DEPTH of 0 indicates
+   the target certifciate, -1 the final root certificate and other
+   values intermediate certificates. */ 
+static gpg_error_t
+check_validity_period (ksba_isotime_t current_time,
+                       ksba_cert_t subject_cert,
+                       ksba_isotime_t exptime,
+                       int listmode, estream_t listfp, int depth)
+{
+  gpg_error_t err;
+  ksba_isotime_t not_before, not_after;
+
+  err = ksba_cert_get_validity (subject_cert, 0, not_before);
+  if (!err)
+    err = ksba_cert_get_validity (subject_cert, 1, not_after);
+  if (err)
+    {
+      do_list (1, listmode, listfp,
+               _("certificate with invalid validity: %s"), gpg_strerror (err));
+      return gpg_error (GPG_ERR_BAD_CERT);
+    }
+
+  if (*not_after)
+    {
+      if (!*exptime)
+        gnupg_copy_time (exptime, not_after);
+      else if (strcmp (not_after, exptime) < 0 )
+        gnupg_copy_time (exptime, not_after);
+    }
+
+  if (*not_before && strcmp (current_time, not_before) < 0 )
+    {
+      do_list (1, listmode, listfp, 
+               depth ==  0 ? _("certificate not yet valid") :
+               depth == -1 ? _("root certificate not yet valid") :
+               /* other */   _("intermediate certificate not yet valid"));
+      if (!listmode)
+        {
+          log_info ("  (valid from ");
+          dump_isotime (not_before);
+          log_printf (")\n");
+        }
+      return gpg_error (GPG_ERR_CERT_TOO_YOUNG);
+    } 
+           
+  if (*not_after && strcmp (current_time, not_after) > 0 )
+    {
+      do_list (opt.ignore_expiration?0:1, listmode, listfp,
+               depth == 0  ? _("certificate has expired") :
+               depth == -1 ? _("root certificate has expired") :
+               /* other  */  _("intermediate certificate has expired"));
+      if (!listmode)
+        {
+          log_info ("  (expired at ");
+          dump_isotime (not_after);
+          log_printf (")\n");
+        }
+      if (opt.ignore_expiration)
+        log_info ("WARNING: ignoring expiration\n");
+      else
+        return gpg_error (GPG_ERR_CERT_EXPIRED);
+    }      
+      
+  return 0;
+}
+
+/* This is a variant of check_validity_period used with the chain
+   model.  The dextra contraint here is that notBefore and notAfter
+   must exists and if the additional argument CHECK_TIME is given this
+   time is used to check the validity period of SUBJECT_CERT.  */
+static gpg_error_t
+check_validity_period_cm (ksba_isotime_t current_time,
+                          ksba_isotime_t check_time,
+                          ksba_cert_t subject_cert,
+                          ksba_isotime_t exptime,
+                          int listmode, estream_t listfp, int depth)
+{
+  gpg_error_t err;
+  ksba_isotime_t not_before, not_after;
+
+  err = ksba_cert_get_validity (subject_cert, 0, not_before);
+  if (!err)
+    err = ksba_cert_get_validity (subject_cert, 1, not_after);
+  if (err)
+    {
+      do_list (1, listmode, listfp,
+               _("certificate with invalid validity: %s"), gpg_strerror (err));
+      return gpg_error (GPG_ERR_BAD_CERT);
+    }
+  if (!*not_before || !*not_after)
+    {
+      do_list (1, listmode, listfp,
+               _("required certificate attributes missing: %s%s%s"),
+               !*not_before? "notBefore":"",
+               (!*not_before && !*not_after)? ", ":"",
+               !*not_before? "notAfter":"");
+      return gpg_error (GPG_ERR_BAD_CERT);
+    }
+  if (strcmp (not_before, not_after) > 0 )
+    {
+      do_list (1, listmode, listfp,
+               _("certificate with invalid validity"));
+      log_info ("  (valid from ");
+      dump_isotime (not_before);
+      log_printf (" expired at ");
+      dump_isotime (not_after);
+      log_printf (")\n");
+      return gpg_error (GPG_ERR_BAD_CERT);
+    }
+  
+  if (!*exptime)
+    gnupg_copy_time (exptime, not_after);
+  else if (strcmp (not_after, exptime) < 0 )
+    gnupg_copy_time (exptime, not_after);
+
+  if (strcmp (current_time, not_before) < 0 )
+    {
+      do_list (1, listmode, listfp, 
+               depth ==  0 ? _("certificate not yet valid") :
+               depth == -1 ? _("root certificate not yet valid") :
+               /* other */   _("intermediate certificate not yet valid"));
+      if (!listmode)
+        {
+          log_info ("  (valid from ");
+          dump_isotime (not_before);
+          log_printf (")\n");
+        }
+      return gpg_error (GPG_ERR_CERT_TOO_YOUNG);
+    } 
+
+  if (*check_time
+      && (strcmp (check_time, not_before) < 0 
+          || strcmp (check_time, not_after) > 0))
+    {
+      /* Note that we don't need a case for the root certificate
+         because its own consitency has already been checked.  */
+      do_list(opt.ignore_expiration?0:1, listmode, listfp,
+              depth == 0 ? 
+              _("signature not created during lifetime of certificate") :
+              depth == 1 ?
+              _("certificate not created during lifetime of issuer") :
+              _("intermediate certificate not created during lifetime "
+                "of issuer"));
+      if (!listmode)
+        {
+          log_info (depth== 0? _("  (  signature created at ") :
+                    /* */      _("  (certificate created at ") );
+          dump_isotime (check_time);
+          log_printf (")\n");
+          log_info (depth==0? _("  (certificate valid from ") :
+                    /* */     _("  (     issuer valid from ") );
+          dump_isotime (not_before);
+          log_info (" to ");
+          dump_isotime (not_after);
+          log_printf (")\n");
+        }
+      if (opt.ignore_expiration)
+        log_info ("WARNING: ignoring expiration\n");
+      else
+        return gpg_error (GPG_ERR_CERT_EXPIRED);
+    }
+
+  return 0;
+}
+
+
+
+/* Ask the user whether he wants to mark the certificate CERT trusted.
+   Returns true if the CERT is the trusted.  We also check whether the
+   agent is at all enabled to allow marktrusted and don't call it in
+   this session again if it is not.  */
+static int
+ask_marktrusted (ctrl_t ctrl, ksba_cert_t cert, int listmode)
+{
+  static int no_more_questions; 
+  int rc;
+  char *fpr;
+  int success = 0;
+
+  fpr = gpgsm_get_fingerprint_string (cert, GCRY_MD_SHA1);
+  log_info (_("fingerprint=%s\n"), fpr? fpr : "?");
+  xfree (fpr);
+  
+  if (no_more_questions)
+    rc = gpg_error (GPG_ERR_NOT_SUPPORTED);
+  else
+    rc = gpgsm_agent_marktrusted (ctrl, cert);
+  if (!rc)
+    {
+      log_info (_("root certificate has now been marked as trusted\n"));
+      success = 1;
+    }
+  else if (!listmode)
+    {
+      gpgsm_dump_cert ("issuer", cert);
+      log_info ("after checking the fingerprint, you may want "
+                "to add it manually to the list of trusted certificates.\n");
+    }
+
+  if (gpg_err_code (rc) == GPG_ERR_NOT_SUPPORTED)
+    {
+      if (!no_more_questions)
+        log_info (_("interactive marking as trusted "
+                    "not enabled in gpg-agent\n"));
+      no_more_questions = 1;
+    }
+  else if (gpg_err_code (rc) == GPG_ERR_CANCELED)
+    {
+      log_info (_("interactive marking as trusted "
+                  "disabled for this session\n"));
+      no_more_questions = 1;
+    }
+  else
+    set_already_asked_marktrusted (cert);
+
+  return success;
+}
+
+
+
 \f
 /* Validate a chain and optionally return the nearest expiration time
-   in R_EXPTIME */
-int
-gpgsm_validate_chain (CTRL ctrl, KsbaCert cert, time_t *r_exptime)
+   in R_EXPTIME. With LISTMODE set to 1 a special listmode is
+   activated where only information about the certificate is printed
+   to LISTFP and no output is send to the usual log stream.  If
+   CHECKTIME_ARG is set, it is used only in the chain model instead of the
+   current time.
+
+   Defined flag bits
+
+   VALIDATE_FLAG_NO_DIRMNGR  - Do not do any dirmngr isvalid checks.
+   VALIDATE_FLAG_CHAIN_MODEL - Check according to chain model.
+*/
+static int
+do_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t checktime_arg,
+                   ksba_isotime_t r_exptime,
+                   int listmode, estream_t listfp, unsigned int flags,
+                   struct rootca_flags_s *rootca_flags)
 {
-  int rc = 0, depth = 0, maxdepth;
+  int rc = 0, depth, maxdepth;
   char *issuer = NULL;
   char *subject = NULL;
-  KEYDB_HANDLE kh = keydb_new (0);
-  KsbaCert subject_cert = NULL, issuer_cert = NULL;
-  time_t current_time = gnupg_get_time ();
-  time_t exptime = 0;
+  KEYDB_HANDLE kh = NULL;
+  ksba_cert_t subject_cert = NULL, issuer_cert = NULL;
+  ksba_isotime_t current_time;
+  ksba_isotime_t check_time;
+  ksba_isotime_t exptime;
   int any_expired = 0;
   int any_revoked = 0;
   int any_no_crl = 0;
   int any_crl_too_old = 0;
   int any_no_policy_match = 0;
+  int is_qualified = -1; /* Indicates whether the certificate stems
+                            from a qualified root certificate.
+                            -1 = unknown, 0 = no, 1 = yes. */
+  chain_item_t chain = NULL; /* A list of all certificates in the chain.  */
+
+
+  gnupg_get_isotime (current_time);
+
+  if ( (flags & VALIDATE_FLAG_CHAIN_MODEL) )
+    {
+      if (!strcmp (checktime_arg, "19700101T000000"))
+        {
+          do_list (1, listmode, listfp, 
+                   _("WARNING: creation time of signature not known - "
+                     "assuming current time"));
+          gnupg_copy_time (check_time, current_time);
+        }
+      else
+        gnupg_copy_time (check_time, checktime_arg);
+    }
+  else
+    *check_time = 0;
 
   if (r_exptime)
     *r_exptime = 0;
+  *exptime = 0;
 
-  if (opt.no_chain_validation)
+  if (opt.no_chain_validation && !listmode)
     {
       log_info ("WARNING: bypassing certificate chain validation\n");
       return 0;
     }
-  
+
+  kh = keydb_new (0);
   if (!kh)
     {
       log_error (_("failed to allocated keyDB handle\n"));
-      rc = GNUPG_General_Error;
+      rc = gpg_error (GPG_ERR_GENERAL);
       goto leave;
     }
 
-  if (DBG_X509)
-    gpgsm_dump_cert ("subject", cert);
+  if (DBG_X509 && !listmode)
+    gpgsm_dump_cert ("target", cert);
 
   subject_cert = cert;
+  ksba_cert_ref (subject_cert);
   maxdepth = 50;
+  depth = 0;
 
   for (;;)
     {
+      int is_root;
+      gpg_error_t istrusted_rc = -1;
+
+      /* Put the certificate on our list.  */
+      {
+        chain_item_t ci;
+
+        ci = xtrycalloc (1, sizeof *ci);
+        if (!ci)
+          {
+            rc = gpg_error_from_syserror ();
+            goto leave;
+          }
+        ksba_cert_ref (subject_cert);
+        ci->cert = subject_cert;
+        ci->next = chain;
+        chain = ci;
+      }
+
       xfree (issuer);
       xfree (subject);
       issuer = ksba_cert_get_issuer (subject_cert, 0);
@@ -437,56 +1275,60 @@ gpgsm_validate_chain (CTRL ctrl, KsbaCert cert, time_t *r_exptime)
 
       if (!issuer)
         {
-          log_error ("no issuer found in certificate\n");
-          rc = GNUPG_Bad_Certificate;
+          do_list (1, listmode, listfp,  _("no issuer found in certificate"));
+          rc = gpg_error (GPG_ERR_BAD_CERT);
           goto leave;
         }
 
-      {
-        time_t not_before, not_after;
 
-        not_before = ksba_cert_get_validity (subject_cert, 0);
-        not_after = ksba_cert_get_validity (subject_cert, 1);
-        if (not_before == (time_t)(-1) || not_after == (time_t)(-1))
-          {
-            log_error ("certificate with invalid validity\n");
-            rc = GNUPG_Bad_Certificate;
-            goto leave;
-          }
-
-        if (not_after)
-          {
-            if (!exptime)
-              exptime = not_after;
-            else if (not_after < exptime)
-              exptime = not_after;
-          }
+      /* Is this a self-issued certificate (i.e. the root certificate)?  */
+      is_root = is_root_cert (subject_cert, issuer, subject);
+      if (is_root)
+        {
+          chain->is_root = 1;
+          /* Check early whether the certificate is listed as trusted.
+             We used to do this only later but changed it to call the
+             check right here so that we can access special flags
+             associated with that specific root certificate.  */
+          istrusted_rc = gpgsm_agent_istrusted (ctrl, subject_cert, NULL,
+                                                rootca_flags);
+          audit_log_cert (ctrl->audit, AUDIT_ROOT_TRUSTED,
+                          subject_cert, istrusted_rc);
+          /* If the chain model extended attribute is used, make sure
+             that our chain model flag is set. */
+          if (has_validation_model_chain (subject_cert, listmode, listfp))
+            rootca_flags->chain_model = 1;
+        }
+      
 
-        if (not_before && current_time < not_before)
-          {
-            log_error ("certificate too young; valid from ");
-            gpgsm_dump_time (not_before);
-            log_printf ("\n");
-            rc = GNUPG_Certificate_Too_Young;
-            goto leave;
-          }            
-        if (not_after && current_time > not_after)
-          {
-            log_error ("certificate has expired at ");
-            gpgsm_dump_time (not_after);
-            log_printf ("\n");
-            any_expired = 1;
-          }            
-      }
+      /* Check the validity period. */
+      if ( (flags & VALIDATE_FLAG_CHAIN_MODEL) )
+        rc = check_validity_period_cm (current_time, check_time, subject_cert,
+                                       exptime, listmode, listfp,
+                                       (depth && is_root)? -1: depth);
+      else
+        rc = check_validity_period (current_time, subject_cert,
+                                    exptime, listmode, listfp,
+                                    (depth && is_root)? -1: depth);
+      if (gpg_err_code (rc) == GPG_ERR_CERT_EXPIRED)
+        {
+          any_expired = 1;
+          rc = 0;
+        }
+      else if (rc)
+        goto leave;
+        
 
-      rc = unknown_criticals (subject_cert);
+      /* Assert that we understand all critical extensions. */
+      rc = unknown_criticals (subject_cert, listmode, listfp);
       if (rc)
         goto leave;
 
+      /* Do a policy check. */
       if (!opt.no_policy_check)
         {
-          rc = check_cert_policy (subject_cert);
-          if (rc == GNUPG_No_Policy_Match)
+          rc = check_cert_policy (subject_cert, listmode, listfp);
+          if (gpg_err_code (rc) == GPG_ERR_NO_POLICY_MATCH)
             {
               any_no_policy_match = 1;
               rc = 1;
@@ -495,106 +1337,151 @@ gpgsm_validate_chain (CTRL ctrl, KsbaCert cert, time_t *r_exptime)
             goto leave;
         }
 
-      if (!opt.no_crl_check)
-        {
-          rc = gpgsm_dirmngr_isvalid (subject_cert);
-          if (rc)
+
+      /* If this is the root certificate we are at the end of the chain.  */
+      if (is_root)
+        { 
+          if (!istrusted_rc)
+            ; /* No need to check the certificate for a trusted one. */
+          else if (gpgsm_check_cert_sig (subject_cert, subject_cert) )
             {
-              switch (rc)
+              /* We only check the signature if the certificate is not
+                 trusted for better diagnostics. */
+              do_list (1, listmode, listfp,
+                       _("self-signed certificate has a BAD signature"));
+              if (DBG_X509)
                 {
-                case GNUPG_Certificate_Revoked:
-                  log_error (_("the certificate has been revoked\n"));
-                  any_revoked = 1;
-                  break;
-                case GNUPG_No_CRL_Known:
-                  log_error (_("no CRL found for certificate\n"));
-                  any_no_crl = 1;
-                  break;
-                case GNUPG_CRL_Too_Old:
-                  log_error (_("the available CRL is too old\n"));
-                  log_info (_("please make sure that the "
-                              "\"dirmngr\" is properly installed\n"));
-                  any_crl_too_old = 1;
-                  break;
-                default:
-                  log_error (_("checking the CRL failed: %s\n"),
-                             gnupg_strerror (rc));
-                  goto leave;
+                  gpgsm_dump_cert ("self-signing cert", subject_cert);
                 }
-              rc = 0;
+              rc = gpg_error (depth? GPG_ERR_BAD_CERT_CHAIN
+                                   : GPG_ERR_BAD_CERT);
+              goto leave;
             }
-        }
-
-      if (subject && !strcmp (issuer, subject))
-        {
-          if (gpgsm_check_cert_sig (subject_cert, subject_cert) )
+          if (!rootca_flags->relax)
             {
-              log_error ("selfsigned certificate has a BAD signatures\n");
-              rc = depth? GNUPG_Bad_Certificate_Path : GNUPG_Bad_Certificate;
-              goto leave;
+              rc = allowed_ca (ctrl, subject_cert, NULL, listmode, listfp);
+              if (rc)
+                goto leave;
             }
-          rc = allowed_ca (subject_cert, NULL);
-          if (rc)
-            goto leave;
-
-          rc = gpgsm_agent_istrusted (subject_cert);
-          if (!rc)
-            ;
-          else if (rc == GNUPG_Not_Trusted)
+              
+          
+          /* Set the flag for qualified signatures.  This flag is
+             deduced from a list of root certificates allowed for
+             qualified signatures. */
+          if (is_qualified == -1)
             {
-              int rc2;
-
-              char *fpr = gpgsm_get_fingerprint_string (subject_cert,
-                                                        GCRY_MD_SHA1);
-              log_info (_("root certificate is not marked trusted\n"));
-              log_info (_("fingerprint=%s\n"), fpr? fpr : "?");
-              xfree (fpr);
-              rc2 = gpgsm_agent_marktrusted (subject_cert);
-              if (!rc2)
+              gpg_error_t err;
+              size_t buflen;
+              char buf[1];
+              
+              if (!ksba_cert_get_user_data (cert, "is_qualified", 
+                                            &buf, sizeof (buf),
+                                            &buflen) && buflen)
                 {
-                  log_info (_("root certificate has now"
-                              " been marked as trusted\n"));
-                  rc = 0;
-                }
-              else 
+                  /* We already checked this for this certificate,
+                     thus we simply take it from the user data. */
+                  is_qualified = !!*buf;
+                }    
+              else
                 {
-                  gpgsm_dump_cert ("issuer", subject_cert);
-                  log_info ("after checking the fingerprint, you may want "
-                            "to enter it manually into "
-                            "\"~/.gnupg-test/trustlist.txt\"\n");
+                  /* Need to consult the list of root certificates for
+                     qualified signatures. */
+                  err = gpgsm_is_in_qualified_list (ctrl, subject_cert, NULL);
+                  if (!err)
+                    is_qualified = 1;
+                  else if ( gpg_err_code (err) == GPG_ERR_NOT_FOUND)
+                    is_qualified = 0;
+                  else
+                    log_error ("checking the list of qualified "
+                               "root certificates failed: %s\n",
+                               gpg_strerror (err));
+                  if ( is_qualified != -1 )
+                    {
+                      /* Cache the result but don't care too much
+                         about an error. */
+                      buf[0] = !!is_qualified;
+                      err = ksba_cert_set_user_data (subject_cert,
+                                                     "is_qualified", buf, 1);
+                      if (err)
+                        log_error ("set_user_data(is_qualified) failed: %s\n",
+                                   gpg_strerror (err)); 
+                    }
                 }
             }
+
+
+          /* Act on the check for a trusted root certificates. */
+          rc = istrusted_rc;
+          if (!rc)
+            ;
+          else if (gpg_err_code (rc) == GPG_ERR_NOT_TRUSTED)
+            {
+              do_list (0, listmode, listfp, 
+                       _("root certificate is not marked trusted"));
+              /* If we already figured out that the certificate is
+                 expired it does not make much sense to ask the user
+                 whether we wants to trust the root certificate.  We
+                 should do this only if the certificate under question
+                 will then be usable.  */
+              if ( !any_expired
+                   && (!listmode || !already_asked_marktrusted (subject_cert))
+                   && ask_marktrusted (ctrl, subject_cert, listmode) )
+                rc = 0;
+            }
           else 
             {
               log_error (_("checking the trust list failed: %s\n"),
-                         gnupg_strerror (rc));
+                         gpg_strerror (rc));
             }
           
-          break;  /* okay, a self-signed certicate is an end-point */
-        }
+          if (rc)
+            goto leave;
+
+          /* Check for revocations etc. */
+          if ((flags & VALIDATE_FLAG_NO_DIRMNGR))
+            ;
+          else if (opt.no_trusted_cert_crl_check || rootca_flags->relax)
+            ; 
+          else
+            rc = is_cert_still_valid (ctrl, 
+                                      (flags & VALIDATE_FLAG_CHAIN_MODEL),
+                                      listmode, listfp,
+                                      subject_cert, subject_cert,
+                                      &any_revoked, &any_no_crl,
+                                      &any_crl_too_old);
+          if (rc)
+            goto leave;
+
+          break;  /* Okay: a self-signed certicate is an end-point. */
+        } /* End is_root.  */
+
       
-      depth++;
-      if (depth > maxdepth)
+      /* Take care that the chain does not get too long. */
+      if ((depth+1) > maxdepth)
         {
-          log_error (_("certificate chain too long\n"));
-          rc = GNUPG_Bad_Certificate_Path;
+          do_list (1, listmode, listfp, _("certificate chain too long\n"));
+          rc = gpg_error (GPG_ERR_BAD_CERT_CHAIN);
           goto leave;
         }
 
-      /* find the next cert up the tree */
+      /* Find the next cert up the tree. */
       keydb_search_reset (kh);
-      rc = find_up (kh, subject_cert, issuer);
+      rc = find_up (ctrl, kh, subject_cert, issuer, 0);
       if (rc)
         {
           if (rc == -1)
             {
-              log_info ("issuer certificate (");
-              gpgsm_dump_string (issuer);
-              log_printf (") not found\n");
+              do_list (0, listmode, listfp, _("issuer certificate not found"));
+              if (!listmode)
+                {
+                  log_info ("issuer certificate: #/");
+                  gpgsm_dump_string (issuer);
+                  log_printf ("\n");
+                }
             }
           else
             log_error ("failed to find issuer's certificate: rc=%d\n", rc);
-          rc = GNUPG_Missing_Certificate;
+          rc = gpg_error (GPG_ERR_MISSING_CERT);
           goto leave;
         }
 
@@ -602,82 +1489,329 @@ gpgsm_validate_chain (CTRL ctrl, KsbaCert cert, time_t *r_exptime)
       rc = keydb_get_cert (kh, &issuer_cert);
       if (rc)
         {
-          log_error ("failed to get cert: rc=%d\n", rc);
-          rc = GNUPG_General_Error;
+          log_error ("keydb_get_cert() failed: rc=%d\n", rc);
+          rc = gpg_error (GPG_ERR_GENERAL);
           goto leave;
         }
 
+    try_another_cert:
       if (DBG_X509)
         {
           log_debug ("got issuer's certificate:\n");
           gpgsm_dump_cert ("issuer", issuer_cert);
         }
 
-      if (gpgsm_check_cert_sig (issuer_cert, subject_cert) )
+      rc = gpgsm_check_cert_sig (issuer_cert, subject_cert);
+      if (rc)
         {
-          log_error ("certificate has a BAD signatures\n");
-          rc = GNUPG_Bad_Certificate_Path;
+          do_list (0, listmode, listfp, _("certificate has a BAD signature"));
+          if (DBG_X509)
+            {
+              gpgsm_dump_cert ("signing issuer", issuer_cert);
+              gpgsm_dump_cert ("signed subject", subject_cert);
+            }
+          if (gpg_err_code (rc) == GPG_ERR_BAD_SIGNATURE)
+            {
+              /* We now try to find other issuer certificates which
+                 might have been used.  This is required because some
+                 CAs are reusing the issuer and subject DN for new
+                 root certificates. */
+              /* FIXME: Do this only if we don't have an
+                 AKI.keyIdentifier */
+              rc = find_up (ctrl, kh, subject_cert, issuer, 1);
+              if (!rc)
+                {
+                  ksba_cert_t tmp_cert;
+
+                  rc = keydb_get_cert (kh, &tmp_cert);
+                  if (rc || !compare_certs (issuer_cert, tmp_cert))
+                    {
+                      /* The find next did not work or returned an
+                         identical certificate.  We better stop here
+                         to avoid infinite checks. */
+                      rc = gpg_error (GPG_ERR_BAD_SIGNATURE);
+                      ksba_cert_release (tmp_cert);
+                    }
+                  else
+                    {
+                      do_list (0, listmode, listfp,
+                               _("found another possible matching "
+                                 "CA certificate - trying again"));
+                      ksba_cert_release (issuer_cert); 
+                      issuer_cert = tmp_cert;
+                      goto try_another_cert;
+                    }
+                }
+            }
+
+          /* We give a more descriptive error code than the one
+             returned from the signature checking. */
+          rc = gpg_error (GPG_ERR_BAD_CERT_CHAIN);
           goto leave;
         }
 
+      is_root = gpgsm_is_root_cert (issuer_cert);
+      istrusted_rc = -1;
+
+
+      /* Check that a CA is allowed to issue certificates. */
       {
         int chainlen;
-        rc = allowed_ca (issuer_cert, &chainlen);
+
+        rc = allowed_ca (ctrl, issuer_cert, &chainlen, listmode, listfp);
+        if (rc)
+          {
+            /* Not allowed.  Check whether this is a trusted root
+               certificate and whether we allow special exceptions.
+               We could carry the result of the test over to the
+               regular root check at the top of the loop but for
+               clarity we won't do that.  Given that the majority of
+               certificates carry proper BasicContraints our way of
+               overriding an error in the way is justified for
+               performance reasons. */
+            if (is_root)
+              {
+                istrusted_rc = gpgsm_agent_istrusted (ctrl, issuer_cert, NULL,
+                                                      rootca_flags);
+                if (!istrusted_rc && rootca_flags->relax)
+                  {
+                    /* Ignore the error due to the relax flag.  */
+                    rc = 0;
+                    chainlen = -1;
+                  }
+              }
+          }
         if (rc)
           goto leave;
-        if (chainlen >= 0 && (depth - 1) > chainlen)
+        if (chainlen >= 0 && depth > chainlen)
           {
-            log_error (_("certificate chain longer than allowed by CA (%d)\n"),
-                       chainlen);
-            rc = GNUPG_Bad_Certificate_Chain;
+            do_list (1, listmode, listfp,
+                     _("certificate chain longer than allowed by CA (%d)"),
+                     chainlen);
+            rc = gpg_error (GPG_ERR_BAD_CERT_CHAIN);
             goto leave;
           }
       }
 
-      rc = gpgsm_cert_use_cert_p (issuer_cert);
+      /* Is the certificate allowed to sign other certificates. */
+      if (!listmode)
+        {
+          rc = gpgsm_cert_use_cert_p (issuer_cert);
+          if (rc)
+            {
+              char numbuf[50];
+              sprintf (numbuf, "%d", rc);
+              gpgsm_status2 (ctrl, STATUS_ERROR, "certcert.issuer.keyusage",
+                             numbuf, NULL);
+              goto leave;
+            }
+        }
+
+      /* Check for revocations etc.  Note that for a root certificate
+         this test is done a second time later. This should eventually
+         be fixed. */
+      if ((flags & VALIDATE_FLAG_NO_DIRMNGR))
+        rc = 0;
+      else if (is_root && (opt.no_trusted_cert_crl_check
+                           || (!istrusted_rc && rootca_flags->relax)))
+        rc = 0; 
+      else
+        rc = is_cert_still_valid (ctrl, 
+                                  (flags & VALIDATE_FLAG_CHAIN_MODEL),
+                                  listmode, listfp,
+                                  subject_cert, issuer_cert,
+                                  &any_revoked, &any_no_crl, &any_crl_too_old);
       if (rc)
+        goto leave;
+
+
+      if (opt.verbose && !listmode)
+        log_info (depth == 0 ? _("certificate is good\n") :
+                  !is_root   ? _("intermediate certificate is good\n") :
+                  /* other */  _("root certificate is good\n"));
+
+      /* Under the chain model the next check time is the creation
+         time of the subject certificate.  */
+      if ( (flags & VALIDATE_FLAG_CHAIN_MODEL) )
         {
-          gpgsm_status2 (ctrl, STATUS_ERROR, "certpath.issuer.keyusage",
-                         gnupg_error_token (rc), NULL);
-          rc = 0;
+          rc = ksba_cert_get_validity (subject_cert, 0, check_time);
+          if (rc)
+            {
+              /* That will never happen as we have already checked
+                 this above.  */
+              BUG ();
+            }
         }
 
-      if (opt.verbose)
-        log_info ("certificate is good\n");
-      
+      /* For the next round the current issuer becomes the new subject.  */
       keydb_search_reset (kh);
+      ksba_cert_release (subject_cert);
       subject_cert = issuer_cert;
       issuer_cert = NULL;
-    }
+      depth++;
+    } /* End chain traversal. */
 
-  if (opt.no_policy_check)
-    log_info ("policies not checked due to --disable-policy-checks option\n");
-  if (opt.no_crl_check)
-    log_info ("CRLs not checked due to --disable-crl-checks option\n");
+  if (!listmode && !opt.quiet)
+    {
+      if (opt.no_policy_check)
+        log_info ("policies not checked due to %s option\n",
+                  "--disable-policy-checks");
+      if (opt.no_crl_check && !ctrl->use_ocsp)
+        log_info ("CRLs not checked due to %s option\n",
+                  "--disable-crl-checks");
+    }
 
   if (!rc)
     { /* If we encountered an error somewhere during the checks, set
          the error code to the most critical one */
       if (any_revoked)
-        rc = GNUPG_Certificate_Revoked;
+        rc = gpg_error (GPG_ERR_CERT_REVOKED);
+      else if (any_expired)
+        rc = gpg_error (GPG_ERR_CERT_EXPIRED);
       else if (any_no_crl)
-        rc = GNUPG_No_CRL_Known;
+        rc = gpg_error (GPG_ERR_NO_CRL_KNOWN);
       else if (any_crl_too_old)
-        rc = GNUPG_CRL_Too_Old;
+        rc = gpg_error (GPG_ERR_CRL_TOO_OLD);
       else if (any_no_policy_match)
-        rc = GNUPG_No_Policy_Match;
-      else if (any_expired)
-        rc = GNUPG_Certificate_Expired;
+        rc = gpg_error (GPG_ERR_NO_POLICY_MATCH);
     }
   
  leave:
+  /* If we have traversed a complete chain up to the root we will
+     reset the ephemeral flag for all these certificates.  This is done
+     regardless of any error because those errors may only be
+     transient. */
+  if (chain && chain->is_root)
+    {
+      gpg_error_t err;
+      chain_item_t ci;
+      
+      for (ci = chain; ci; ci = ci->next)
+        {
+          /* Note that it is possible for the last certificate in the
+             chain (i.e. our target certificate) that it has not yet
+             been stored in the keybox and thus the flag can't be set.
+             We ignore this error becuase it will later be stored
+             anyway.  */
+          err = keydb_set_cert_flags (ci->cert, 1, KEYBOX_FLAG_BLOB, 0,
+                                      KEYBOX_FLAG_BLOB_EPHEMERAL, 0);
+          if (!ci->next && gpg_err_code (err) == GPG_ERR_NOT_FOUND)
+            ;
+          else if (err)
+            log_error ("clearing ephemeral flag failed: %s\n",
+                       gpg_strerror (err)); 
+        }
+    }
+
+  /* If we have figured something about the qualified signature
+     capability of the certificate under question, store the result as
+     user data in all certificates of the chain.  We do this even if the
+     validation itself failed.  */
+  if (is_qualified != -1)
+    {
+      gpg_error_t err;
+      chain_item_t ci;
+      char buf[1];
+
+      buf[0] = !!is_qualified;
+      
+      for (ci = chain; ci; ci = ci->next)
+        {
+          err = ksba_cert_set_user_data (ci->cert, "is_qualified", buf, 1);
+          if (err)
+            {
+              log_error ("set_user_data(is_qualified) failed: %s\n",
+                         gpg_strerror (err)); 
+              if (!rc)
+                rc = err;
+            }
+        }
+    }
+
+  /* If auditing has been enabled, record what is in the chain.  */
+  if (ctrl->audit)
+    {
+      chain_item_t ci;
+
+      audit_log (ctrl->audit, AUDIT_CHAIN_BEGIN);
+      for (ci = chain; ci; ci = ci->next)
+        {
+          audit_log_cert (ctrl->audit,
+                          ci->is_root? AUDIT_CHAIN_ROOTCERT : AUDIT_CHAIN_CERT,
+                          ci->cert, 0);
+        }
+      audit_log (ctrl->audit, AUDIT_CHAIN_END);
+    }
+
   if (r_exptime)
-    *r_exptime = exptime;
+    gnupg_copy_time (r_exptime, exptime);
   xfree (issuer);
+  xfree (subject);
   keydb_release (kh); 
+  while (chain)
+    {
+      chain_item_t ci_next = chain->next;
+      ksba_cert_release (chain->cert);
+      xfree (chain);
+      chain = ci_next;
+    }
   ksba_cert_release (issuer_cert);
-  if (subject_cert != cert)
-    ksba_cert_release (subject_cert);
+  ksba_cert_release (subject_cert);
+  return rc;
+}
+
+
+/* Validate a certificate chain.  For a description see
+   do_validate_chain.  This function is a wrapper to handle a root
+   certificate with the chain_model flag set.  If RETFLAGS is not
+   NULL, flags indicating now the verification was done are stored
+   there.  The only defined flag for RETFLAGS is
+   VALIDATE_FLAG_CHAIN_MODEL.
+
+   If you are verifying a signature you should set CHECKTIME to the
+   creation time of the signature.  If your are verifying a
+   certificate, set it nil (i.e. the empty string).  If the creation
+   date of the signature is not known use the special date
+   "19700101T000000" which is treated in a special way here. */
+int
+gpgsm_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t checktime,
+                      ksba_isotime_t r_exptime,
+                      int listmode, estream_t listfp, unsigned int flags,
+                      unsigned int *retflags)
+{
+  int rc;
+  struct rootca_flags_s rootca_flags;
+  unsigned int dummy_retflags;
+
+  if (!retflags)
+    retflags = &dummy_retflags;
+
+  if (ctrl->validation_model == 1)
+    flags |= VALIDATE_FLAG_CHAIN_MODEL;
+
+  *retflags = (flags & VALIDATE_FLAG_CHAIN_MODEL);
+  memset (&rootca_flags, 0, sizeof rootca_flags);
+
+  rc = do_validate_chain (ctrl, cert, checktime, 
+                          r_exptime, listmode, listfp, flags,
+                          &rootca_flags);
+  if (gpg_err_code (rc) == GPG_ERR_CERT_EXPIRED
+      && !(flags & VALIDATE_FLAG_CHAIN_MODEL)
+      && (rootca_flags.valid && rootca_flags.chain_model))
+    {
+      do_list (0, listmode, listfp, _("switching to chain model"));
+      rc = do_validate_chain (ctrl, cert, checktime,
+                              r_exptime, listmode, listfp, 
+                              (flags |= VALIDATE_FLAG_CHAIN_MODEL),
+                              &rootca_flags);
+      *retflags |= VALIDATE_FLAG_CHAIN_MODEL;
+    }
+
+  if (opt.verbose)
+    do_list (0, listmode, listfp, _("validation model used: %s"), 
+             (*retflags & VALIDATE_FLAG_CHAIN_MODEL)?
+             _("chain"):_("shell"));
+  
   return rc;
 }
 
@@ -687,24 +1821,25 @@ gpgsm_validate_chain (CTRL ctrl, KsbaCert cert, time_t *r_exptime)
    the DB and that this one is valid; which it should be because it
    has been checked using this function. */
 int
-gpgsm_basic_cert_check (KsbaCert cert)
+gpgsm_basic_cert_check (ctrl_t ctrl, ksba_cert_t cert)
 {
   int rc = 0;
   char *issuer = NULL;
   char *subject = NULL;
-  KEYDB_HANDLE kh = keydb_new (0);
-  KsbaCert issuer_cert = NULL;
-
+  KEYDB_HANDLE kh;
+  ksba_cert_t issuer_cert = NULL;
+  
   if (opt.no_chain_validation)
     {
       log_info ("WARNING: bypassing basic certificate checks\n");
       return 0;
     }
 
+  kh = keydb_new (0);
   if (!kh)
     {
       log_error (_("failed to allocated keyDB handle\n"));
-      rc = GNUPG_General_Error;
+      rc = gpg_error (GPG_ERR_GENERAL);
       goto leave;
     }
 
@@ -713,35 +1848,41 @@ gpgsm_basic_cert_check (KsbaCert cert)
   if (!issuer)
     {
       log_error ("no issuer found in certificate\n");
-      rc = GNUPG_Bad_Certificate;
+      rc = gpg_error (GPG_ERR_BAD_CERT);
       goto leave;
     }
 
-  if (subject && !strcmp (issuer, subject))
+  if (is_root_cert (cert, issuer, subject))
     {
-      if (gpgsm_check_cert_sig (cert, cert) )
+      rc = gpgsm_check_cert_sig (cert, cert);
+      if (rc)
         {
-          log_error ("selfsigned certificate has a BAD signatures\n");
-          rc = GNUPG_Bad_Certificate;
+          log_error ("self-signed certificate has a BAD signature: %s\n",
+                     gpg_strerror (rc));
+          if (DBG_X509)
+            {
+              gpgsm_dump_cert ("self-signing cert", cert);
+            }
+          rc = gpg_error (GPG_ERR_BAD_CERT);
           goto leave;
         }
     }
   else
     {
-      /* find the next cert up the tree */
+      /* Find the next cert up the tree. */
       keydb_search_reset (kh);
-      rc = find_up (kh, cert, issuer);
+      rc = find_up (ctrl, kh, cert, issuer, 0);
       if (rc)
         {
           if (rc == -1)
             {
-              log_info ("issuer certificate (");
+              log_info ("issuer certificate (#/");
               gpgsm_dump_string (issuer);
               log_printf (") not found\n");
             }
           else
             log_error ("failed to find issuer's certificate: rc=%d\n", rc);
-          rc = GNUPG_Missing_Certificate;
+          rc = gpg_error (GPG_ERR_MISSING_CERT);
           goto leave;
         }
       
@@ -749,25 +1890,140 @@ gpgsm_basic_cert_check (KsbaCert cert)
       rc = keydb_get_cert (kh, &issuer_cert);
       if (rc)
         {
-          log_error ("failed to get cert: rc=%d\n", rc);
-          rc = GNUPG_General_Error;
+          log_error ("keydb_get_cert() failed: rc=%d\n", rc);
+          rc = gpg_error (GPG_ERR_GENERAL);
           goto leave;
         }
 
-      if (gpgsm_check_cert_sig (issuer_cert, cert) )
+      rc = gpgsm_check_cert_sig (issuer_cert, cert);
+      if (rc)
         {
-          log_error ("certificate has a BAD signatures\n");
-          rc = GNUPG_Bad_Certificate;
+          log_error ("certificate has a BAD signature: %s\n",
+                     gpg_strerror (rc));
+          if (DBG_X509)
+            {
+              gpgsm_dump_cert ("signing issuer", issuer_cert);
+              gpgsm_dump_cert ("signed subject", cert);
+            }
+          rc = gpg_error (GPG_ERR_BAD_CERT);
           goto leave;
         }
       if (opt.verbose)
-        log_info ("certificate is good\n");
+        log_info (_("certificate is good\n"));
     }
 
  leave:
   xfree (issuer);
+  xfree (subject);
   keydb_release (kh); 
   ksba_cert_release (issuer_cert);
   return rc;
 }
 
+
+
+/* Check whether the certificate CERT has been issued by the German
+   authority for qualified signature.  They do not set the
+   basicConstraints and thus we need this workaround.  It works by
+   looking up the root certificate and checking whether that one is
+   listed as a qualified certificate for Germany. 
+
+   We also try to cache this data but as long as don't keep a
+   reference to the certificate this won't be used.
+
+   Returns: True if CERT is a RegTP issued CA cert (i.e. the root
+   certificate itself or one of the CAs).  In that case CHAINLEN will
+   receive the length of the chain which is either 0 or 1.
+*/
+static int
+get_regtp_ca_info (ctrl_t ctrl, ksba_cert_t cert, int *chainlen)
+{
+  gpg_error_t err;
+  ksba_cert_t next;
+  int rc = 0;
+  int i, depth;
+  char country[3];
+  ksba_cert_t array[4];
+  char buf[2];
+  size_t buflen;
+  int dummy_chainlen;
+
+  if (!chainlen)
+    chainlen = &dummy_chainlen;
+
+  *chainlen = 0;
+  err = ksba_cert_get_user_data (cert, "regtp_ca_chainlen", 
+                                 &buf, sizeof (buf), &buflen);
+  if (!err)
+    {
+      /* Got info. */
+      if (buflen < 2 || !*buf)
+        return 0; /* Nothing found. */
+      *chainlen = buf[1];
+      return 1; /* This is a regtp CA. */
+    }
+  else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
+    {
+      log_error ("ksba_cert_get_user_data(%s) failed: %s\n",
+                 "regtp_ca_chainlen", gpg_strerror (err));
+      return 0; /* Nothing found.  */
+    }
+
+  /* Need to gather the info.  This requires to walk up the chain
+     until we have found the root.  Because we are only interested in
+     German Bundesnetzagentur (former RegTP) derived certificates 3
+     levels are enough.  (The German signature law demands a 3 tier
+     hierachy; thus there is only one CA between the EE and the Root
+     CA.)  */
+  memset (&array, 0, sizeof array);
+
+  depth = 0;
+  ksba_cert_ref (cert);
+  array[depth++] = cert;
+  ksba_cert_ref (cert);
+  while (depth < DIM(array) && !(rc=gpgsm_walk_cert_chain (ctrl, cert, &next)))
+    {
+      ksba_cert_release (cert);
+      ksba_cert_ref (next);
+      array[depth++] = next;
+      cert = next;
+    }
+  ksba_cert_release (cert);
+  if (rc != -1 || !depth || depth == DIM(array) )
+    {
+      /* We did not reached the root. */
+      goto leave;
+    }
+
+  /* If this is a German signature law issued certificate, we store
+     additional additional information. */
+  if (!gpgsm_is_in_qualified_list (NULL, array[depth-1], country)
+      && !strcmp (country, "de"))
+    {
+      /* Setting the pathlen for the root CA and the CA flag for the
+         next one is all what we need to do. */
+      err = ksba_cert_set_user_data (array[depth-1], "regtp_ca_chainlen",
+                                     "\x01\x01", 2);
+      if (!err && depth > 1)
+        err = ksba_cert_set_user_data (array[depth-2], "regtp_ca_chainlen",
+                                       "\x01\x00", 2);
+      if (err)
+        log_error ("ksba_set_user_data(%s) failed: %s\n",
+                   "regtp_ca_chainlen", gpg_strerror (err)); 
+      for (i=0; i < depth; i++)
+        ksba_cert_release (array[i]);
+      *chainlen = (depth>1? 0:1);
+      return 1;
+    }
+
+ leave:
+  /* Nothing special with this certificate. Mark the target
+     certificate anyway to avoid duplicate lookups. */ 
+  err = ksba_cert_set_user_data (cert, "regtp_ca_chainlen", "", 1);
+  if (err)
+    log_error ("ksba_set_user_data(%s) failed: %s\n",
+               "regtp_ca_chainlen", gpg_strerror (err)); 
+  for (i=0; i < depth; i++)
+    ksba_cert_release (array[i]);
+  return 0;
+}