2006-06-09 Marcus Brinkmann <marcus@g10code.de>
[gnupg.git] / sm / certchain.c
index 73f7576..44d72ef 100644 (file)
@@ -1,5 +1,6 @@
 /* certchain.c - certificate chain validation
- *     Copyright (C) 2001, 2002 Free Software Foundation, Inc.
+ * Copyright (C) 2001, 2002, 2003, 2004, 2005,
+ *               2006 Free Software Foundation, Inc.
  *
  * This file is part of GnuPG.
  *
 #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"
 
+
+static int get_regtp_ca_info (ksba_cert_t cert, int *chainlen);
+
+
+
+/* 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, FILE *fp, const char *format, ...)
+{
+  va_list arg_ptr;
+
+  va_start (arg_ptr, format) ;
+  if (listmode)
+    {
+      if (fp)
+        {
+          fputs ("  [", fp);
+          vfprintf (fp, format, arg_ptr);
+          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));
+}
+
+
 static int
-unknown_criticals (KsbaCert cert)
+unknown_criticals (ksba_cert_t cert, int listmode, FILE *fp)
 {
   static const char *known[] = {
     "2.5.29.15", /* keyUsage */
     "2.5.29.19", /* basic Constraints */
     "2.5.29.32", /* certificatePolicies */
+    "2.5.29.37", /* extendedKeyUsage - handled by certlist.c */
     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 +110,61 @@ 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);
+  if (err && gpg_err_code (err) != GPG_ERR_EOF)
+    rc = err;
 
   return rc;
 }
 
 static int
-allowed_ca (KsbaCert cert, int *chainlen)
+allowed_ca (ksba_cert_t cert, int *chainlen, int listmode, FILE *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 (cert, chainlen))
+        {
+          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, FILE *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)
+  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
      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 +175,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 +185,20 @@ 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)
+        {
+          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 +212,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 +240,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 +258,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 +277,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 obn 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,14 +322,83 @@ 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 (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 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_errno (errno);
+  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", 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;
+}
+
+
+/* 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 (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)
@@ -225,8 +406,12 @@ find_up (KEYDB_HANDLE kh, KsbaCert cert, const char *issuer)
           rc = keydb_search_issuer_sn (kh, s, authidno);
           if (rc)
               keydb_search_reset (kh);
-          if (rc == -1)
-            { /* And try the ephemeral DB. */
+          
+          /* 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,15 +421,63 @@ find_up (KEYDB_HANDLE kh, KsbaCert cert, const char *issuer)
                 }
               keydb_set_ephemeral (kh, old);
             }
+
+        }
+
+      if (rc == -1 && keyid && !find_next)
+        {
+          /* Not found by AIK.issuer_sn.  Lets try the AIY.ki
+             instead. Loop over all certificates with that issuer as
+             subject and stop for the one with a matching
+             subjectKeyIdentifier. */
+          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 an external lookup.  */
+      if (rc == -1 && opt.auto_issuer_key_retrieve && !find_next)
+        rc = find_up_external (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)
+        {
+          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);
@@ -256,51 +489,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 (kh, issuer, NULL);
+
   return rc;
 }
 
@@ -308,7 +500,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 (ksba_cert_t start, ksba_cert_t *r_next)
 {
   int rc = 0; 
   char *issuer = NULL;
@@ -319,7 +511,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,13 +520,13 @@ 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;
     }
 
@@ -344,22 +536,22 @@ gpgsm_walk_cert_chain (KsbaCert start, KsbaCert *r_next)
       goto leave; 
     }
 
-  rc = find_up (kh, start, issuer);
+  rc = find_up (kh, start, issuer, 0);
   if (rc)
     {
       /* 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:
@@ -373,7 +565,7 @@ gpgsm_walk_cert_chain (KsbaCert start, KsbaCert *r_next)
 /* 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;
@@ -387,43 +579,106 @@ gpgsm_is_root_cert (KsbaCert cert)
   return yes;
 }
 
+
+/* This is a helper for gpgsm_validate_chain. */
+static gpg_error_t 
+is_cert_still_valid (ctrl_t ctrl, int lm, FILE *fp,
+                     ksba_cert_t subject_cert, ksba_cert_t issuer_cert,
+                     int *any_revoked, int *any_no_crl, int *any_crl_too_old)
+{
+  if (!opt.no_crl_check || ctrl->use_ocsp)
+    {
+      gpg_error_t err;
+
+      err = gpgsm_dirmngr_isvalid (ctrl,
+                                   subject_cert, issuer_cert, ctrl->use_ocsp);
+      if (err)
+        {
+          /* Fixme: We should change the wording because we may
+             have used OCSP. */
+          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, KEYBOX_FLAG_VALIDITY, 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_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;
+}
+
+
 \f
 /* Validate a chain and optionally return the nearest expiration time
-   in R_EXPTIME */
+   in R_EXPTIME. With LISTMODE set to 1 a special listmode is
+   activated where only information about the certificate is printed
+   to FP and no output is send to the usual log stream. 
+
+   Defined flag bits: 0 - do not do any dirmngr isvalid checks.
+*/
 int
-gpgsm_validate_chain (CTRL ctrl, KsbaCert cert, time_t *r_exptime)
+gpgsm_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t r_exptime,
+                      int listmode, FILE *fp, unsigned int flags)
 {
   int rc = 0, depth = 0, 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 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. */
+  int lm = listmode;
 
+  gnupg_get_isotime (current_time);
   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;
   maxdepth = 50;
@@ -437,56 +692,70 @@ 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, lm, fp,  _("no issuer found in certificate"));
+          rc = gpg_error (GPG_ERR_BAD_CERT);
           goto leave;
         }
 
       {
-        time_t not_before, not_after;
+        ksba_isotime_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))
+        rc = ksba_cert_get_validity (subject_cert, 0, not_before);
+        if (!rc)
+          rc = ksba_cert_get_validity (subject_cert, 1, not_after);
+        if (rc)
           {
-            log_error ("certificate with invalid validity\n");
-            rc = GNUPG_Bad_Certificate;
+            do_list (1, lm, fp, _("certificate with invalid validity: %s"),
+                     gpg_strerror (rc));
+            rc = gpg_error (GPG_ERR_BAD_CERT);
             goto leave;
           }
 
-        if (not_after)
+        if (*not_after)
           {
-            if (!exptime)
-              exptime = not_after;
-            else if (not_after < exptime)
-              exptime = 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 && current_time < not_before)
+        if (*not_before && strcmp (current_time, not_before) < 0 )
           {
-            log_error ("certificate too young; valid from ");
-            gpgsm_dump_time (not_before);
-            log_printf ("\n");
-            rc = GNUPG_Certificate_Too_Young;
+            do_list (1, lm, fp, _("certificate not yet valid"));
+            if (!lm)
+              {
+                log_info ("(valid from ");
+                gpgsm_dump_time (not_before);
+                log_printf (")\n");
+              }
+            rc = gpg_error (GPG_ERR_CERT_TOO_YOUNG);
             goto leave;
           }            
-        if (not_after && current_time > not_after)
+        if (*not_after && strcmp (current_time, not_after) > 0 )
           {
-            log_error ("certificate has expired at ");
-            gpgsm_dump_time (not_after);
-            log_printf ("\n");
-            any_expired = 1;
+            do_list (opt.ignore_expiration?0:1, lm, fp,
+                     _("certificate has expired"));
+            if (!lm)
+              {
+                log_info ("(expired at ");
+                gpgsm_dump_time (not_after);
+                log_printf (")\n");
+              }
+            if (opt.ignore_expiration)
+                log_info ("WARNING: ignoring expiration\n");
+            else
+              any_expired = 1;
           }            
       }
 
-      rc = unknown_criticals (subject_cert);
+      rc = unknown_criticals (subject_cert, listmode, fp);
       if (rc)
         goto leave;
 
       if (!opt.no_policy_check)
         {
-          rc = check_cert_policy (subject_cert);
-          if (rc == GNUPG_No_Policy_Match)
+          rc = check_cert_policy (subject_cert, listmode, fp);
+          if (gpg_err_code (rc) == GPG_ERR_NO_POLICY_MATCH)
             {
               any_no_policy_match = 1;
               rc = 1;
@@ -495,106 +764,158 @@ 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)
-            {
-              switch (rc)
-                {
-                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;
-                }
-              rc = 0;
-            }
-        }
 
+      /* Is this a self-issued certificate? */
       if (subject && !strcmp (issuer, subject))
-        {
+        {  /* Yes. */
           if (gpgsm_check_cert_sig (subject_cert, subject_cert) )
             {
-              log_error ("selfsigned certificate has a BAD signatures\n");
-              rc = depth? GNUPG_Bad_Certificate_Path : GNUPG_Bad_Certificate;
+              do_list (1, lm, fp,
+                       _("self-signed certificate has a BAD signature"));
+              if (DBG_X509)
+                {
+                  gpgsm_dump_cert ("self-signing cert", subject_cert);
+                }
+              rc = gpg_error (depth? GPG_ERR_BAD_CERT_CHAIN
+                                   : GPG_ERR_BAD_CERT);
               goto leave;
             }
-          rc = allowed_ca (subject_cert, NULL);
+          rc = allowed_ca (subject_cert, NULL, listmode, fp);
           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;
+                  /* We already checked this for this certificate,
+                     thus we simply take it from the user data. */
+                  is_qualified = !!*buf;
+                }    
+              else
+                {
+                  /* 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)); 
+                    }
                 }
-              else 
+            }
+
+
+          /* Check whether we really trust this root certificate. */
+          rc = gpgsm_agent_istrusted (ctrl, subject_cert);
+          if (!rc)
+            ;
+          else if (gpg_err_code (rc) == GPG_ERR_NOT_TRUSTED)
+            {
+              do_list (0, lm, fp, _("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.  He
+                 should do this only if the certificate under question
+                 will then be usable. */
+              if (!lm && !any_expired)
                 {
-                  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");
+                  int rc2;
+                  char *fpr = gpgsm_get_fingerprint_string (subject_cert,
+                                                            GCRY_MD_SHA1);
+                  log_info (_("fingerprint=%s\n"), fpr? fpr : "?");
+                  xfree (fpr);
+                  rc2 = gpgsm_agent_marktrusted (ctrl, subject_cert);
+                  if (!rc2)
+                    {
+                      log_info (_("root certificate has now"
+                                  " been marked as trusted\n"));
+                      rc = 0;
+                    }
+                  else 
+                    {
+                      gpgsm_dump_cert ("issuer", subject_cert);
+                      log_info ("after checking the fingerprint, you may want "
+                                "to add it manually to the list of trusted "
+                                "certificates.\n");
+                    }
                 }
             }
           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 & 1))
+            ;
+          else if (opt.no_trusted_cert_crl_check)
+            ; 
+          else
+            rc = is_cert_still_valid (ctrl, lm, fp,
+                                      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. */
         }
       
       depth++;
       if (depth > maxdepth)
         {
-          log_error (_("certificate chain too long\n"));
-          rc = GNUPG_Bad_Certificate_Path;
+          do_list (1, lm, fp, _("certificate chain too long\n"));
+          rc = gpg_error (GPG_ERR_BAD_CERT_CHAIN);
           goto leave;
         }
 
       /* find the next cert up the tree */
       keydb_search_reset (kh);
-      rc = find_up (kh, subject_cert, issuer);
+      rc = find_up (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, lm, fp, _("issuer certificate not found"));
+              if (!lm)
+                {
+                  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,77 +923,162 @@ 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, lm, fp, _("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 (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, lm, fp, _("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;
         }
 
       {
         int chainlen;
-        rc = allowed_ca (issuer_cert, &chainlen);
+        rc = allowed_ca (issuer_cert, &chainlen, listmode, fp);
         if (rc)
           goto leave;
         if (chainlen >= 0 && (depth - 1) > chainlen)
           {
-            log_error (_("certificate chain longer than allowed by CA (%d)\n"),
-                       chainlen);
-            rc = GNUPG_Bad_Certificate_Chain;
+            do_list (1, lm, fp,
+                     _("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);
-      if (rc)
+      if (!listmode)
         {
-          gpgsm_status2 (ctrl, STATUS_ERROR, "certpath.issuer.keyusage",
-                         gnupg_error_token (rc), NULL);
-          rc = 0;
+          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;
+            }
         }
 
-      if (opt.verbose)
+      /* Check for revocations etc. */
+      if ((flags & 1))
+        rc = 0;
+      else
+        rc = is_cert_still_valid (ctrl, lm, fp,
+                                  subject_cert, issuer_cert,
+                                  &any_revoked, &any_no_crl, &any_crl_too_old);
+      if (rc)
+        goto leave;
+
+
+      if (opt.verbose && !listmode)
         log_info ("certificate is good\n");
       
       keydb_search_reset (kh);
       subject_cert = issuer_cert;
       issuer_cert = NULL;
-    }
+    } /* 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)
+    {
+      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 (is_qualified != -1)
+    {
+      /* We figured something about the qualified signature capability
+         of the certificate under question.  Store the result as user
+         data in the certificate object.  We do this even if the
+         validation itself failed. */
+      /* Fixme: We should set this flag for all certificates in the
+         chain for optimizing reasons. */
+      char buf[1];
+      gpg_error_t err;
+
+      buf[0] = !!is_qualified;
+      err = ksba_cert_set_user_data (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 (r_exptime)
-    *r_exptime = exptime;
+    gnupg_copy_time (r_exptime, exptime);
   xfree (issuer);
   keydb_release (kh); 
   ksba_cert_release (issuer_cert);
@@ -687,24 +1093,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 (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 +1120,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 (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 (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,15 +1162,22 @@ 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)
@@ -771,3 +1191,110 @@ gpgsm_basic_cert_check (KsbaCert 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 (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 (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;
+}