* keylist.c (list_internal_keys): Renamed from gpgsm_list_keys.
[gnupg.git] / sm / certchain.c
index e76ff6c..28c0be5 100644 (file)
@@ -84,6 +84,144 @@ allowed_ca (KsbaCert cert, int *pathlen)
   return 0;
 }
 
+
+static int
+check_cert_policy (KsbaCert cert)
+{
+  KsbaError 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 (err)
+    return map_ksba_err (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)
+    log_info ("certificate's policy list: %s\n", policies);
+
+  /* The check is very minimal but won't give false positives */
+  any_critical = !!strstr (policies, ":C");
+
+  if (!opt.policy_file)
+    { 
+      xfree (policies);
+      if (any_critical)
+        {
+          log_error ("critical marked policy without configured policies\n");
+          return GNUPG_No_Policy_Match;
+        }
+      return 0;
+    }
+
+  fp = fopen (opt.policy_file, "r");
+  if (!fp)
+    {
+      log_error ("failed to open `%s': %s\n",
+                 opt.policy_file, strerror (errno));
+      xfree (policies);
+      return GNUPG_Configuration_Error;
+    }
+
+  for (;;) 
+    {
+      int c;
+      char *p, line[256];
+      char *haystack, *allowed;
+
+      /* read line */
+      do
+        {
+          if (!fgets (line, DIM(line)-1, fp) )
+            {
+              xfree (policies);
+              if (feof (fp))
+                {
+                  fclose (fp);
+                  log_error (_("certificate policy not allowed\n"));
+                  /* with no critical policies this is only a warning */
+                  return any_critical? GNUPG_No_Policy_Match : 0;
+                }
+              fclose (fp);
+              return GNUPG_Read_Error;
+            }
+      
+          if (!*line || line[strlen(line)-1] != '\n')
+            {
+              /* eat until end of line */
+              while ( (c=getc (fp)) != EOF && c != '\n')
+                ;
+              fclose (fp);
+              xfree (policies);
+              return *line? GNUPG_Line_Too_Long: GNUPG_Incomplete_Line;
+            }
+          
+          /* Allow for empty lines and spaces */
+          for (p=line; spacep (p); p++)
+            ;
+        }
+      while (!*p || *p == '\n' || *p == '#');
+  
+      /* parse line */
+      for (allowed=line; spacep (allowed); allowed++)
+        ;
+      p = strpbrk (allowed, " :\n");
+      if (!*p || p == allowed)
+        {
+          fclose (fp);
+          xfree (policies);
+          return GNUPG_Configuration_Error;
+        }
+      *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 */
+          if (p[strlen (allowed)] != ':')
+            continue; /* the length does not match */
+          /* Yep - it does match so return okay */
+          fclose (fp);
+          xfree (policies);
+          return 0;
+        }
+    }
+}
+
+
+static int
+find_up (KEYDB_HANDLE kh, KsbaCert cert, const char *issuer)
+{
+  KsbaName authid;
+  KsbaSexp authidno;
+  int rc = -1;
+
+  if (!ksba_cert_get_auth_key_id (cert, NULL, &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);
+        }
+      ksba_name_release (authid);
+      xfree (authidno);
+    }
+  
+  if (rc)
+    rc = keydb_search_subject (kh, issuer);
+  return rc;
+}
+
+
 /* Return the next certificate up in the chain starting at START.
    Returns -1 when there are no more certificates. */
 int
@@ -122,11 +260,14 @@ gpgsm_walk_cert_chain (KsbaCert start, KsbaCert *r_next)
       rc = -1; /* we are at the root */
       goto leave; 
     }
-  rc = keydb_search_subject (kh, issuer);
+
+  rc = find_up (kh, start, issuer);
   if (rc)
     {
-      log_error ("failed to find issuer's certificate: rc=%d\n", 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;
       goto leave;
     }
@@ -145,17 +286,47 @@ gpgsm_walk_cert_chain (KsbaCert start, KsbaCert *r_next)
   return rc;
 }
 
+
+/* Check whether the CERT is a root certificate.  Returns True if this
+   is the case. */
+int
+gpgsm_is_root_cert (KsbaCert cert)
+{
+  char *issuer;
+  char *subject;
+  int yes;
+
+  issuer = ksba_cert_get_issuer (cert, 0);
+  subject = ksba_cert_get_subject (cert, 0);
+  yes = (issuer && subject && !strcmp (issuer, subject));
+  xfree (issuer);
+  xfree (subject);
+  return yes;
+}
+
 \f
+/* Validate a path and optionally return the nearest expiration time
+   in R_EXPTIME */
 int
-gpgsm_validate_path (KsbaCert cert)
+gpgsm_validate_path (KsbaCert cert, time_t *r_exptime)
 {
   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 = time (NULL);
+  time_t current_time = gnupg_get_time ();
+  time_t exptime = 0;
 
+  if (r_exptime)
+    *r_exptime = 0;
+
+  if ((opt.debug & 4096))
+    {
+      log_info ("WARNING: bypassing path validation\n");
+      return 0;
+    }
+  
   if (!kh)
     {
       log_error (_("failed to allocated keyDB handle\n"));
@@ -195,7 +366,15 @@ gpgsm_validate_path (KsbaCert cert)
             goto leave;
           }
 
-        if (current_time < not_before)
+        if (not_after)
+          {
+            if (!exptime)
+              exptime = not_after;
+            else if (not_after < exptime)
+              exptime = not_after;
+          }
+
+        if (not_before && current_time < not_before)
           {
             log_error ("certificate to young; valid from ");
             gpgsm_dump_time (not_before);
@@ -203,7 +382,7 @@ gpgsm_validate_path (KsbaCert cert)
             rc = GNUPG_Certificate_Too_Young;
             goto leave;
           }            
-        if (current_time > not_after)
+        if (not_after && current_time > not_after)
           {
             log_error ("certificate has expired at ");
             gpgsm_dump_time (not_after);
@@ -216,7 +395,14 @@ gpgsm_validate_path (KsbaCert cert)
       rc = unknown_criticals (subject_cert);
       if (rc)
         goto leave;
-        
+
+      if (!opt.no_policy_check)
+        {
+          rc = check_cert_policy (subject_cert);
+          if (rc)
+            goto leave;
+        }
+
       if (!opt.no_crl_check)
         {
           rc = gpgsm_dirmngr_isvalid (subject_cert);
@@ -302,7 +488,7 @@ gpgsm_validate_path (KsbaCert cert)
 
       /* find the next cert up the tree */
       keydb_search_reset (kh);
-      rc = keydb_search_subject (kh, issuer);
+      rc = find_up (kh, subject_cert, issuer);
       if (rc)
         {
           if (rc == -1)
@@ -353,18 +539,22 @@ gpgsm_validate_path (KsbaCert cert)
           }
       }
 
-      log_info ("certificate is good\n");
+      if (opt.verbose)
+        log_info ("certificate is good\n");
       
       keydb_search_reset (kh);
       subject_cert = issuer_cert;
       issuer_cert = NULL;
     }
 
+  if (opt.no_policy_check)
+    log_info ("policies not checked due to --disable-policy-checks option\n");
   if (opt.no_crl_check)
-    log_info ("CRL was not checked due to --no-crl-cechk option\n");
-
+    log_info ("CRLs not checked due to --disable-crl-checks option\n");
   
  leave:
+  if (r_exptime)
+    *r_exptime = exptime;
   xfree (issuer);
   keydb_release (kh); 
   ksba_cert_release (issuer_cert);
@@ -387,6 +577,12 @@ gpgsm_basic_cert_check (KsbaCert cert)
   KEYDB_HANDLE kh = keydb_new (0);
   KsbaCert issuer_cert = NULL;
 
+  if ((opt.debug & 4096))
+    {
+      log_info ("WARNING: bypassing basic certificate checks\n");
+      return 0;
+    }
+
   if (!kh)
     {
       log_error (_("failed to allocated keyDB handle\n"));
@@ -398,8 +594,7 @@ gpgsm_basic_cert_check (KsbaCert cert)
   subject = ksba_cert_get_subject (cert, 0);
   if (!issuer)
     {
-      if (DBG_X509)
-        log_debug ("ERROR: issuer missing\n");
+      log_error ("no issuer found in certificate\n");
       rc = GNUPG_Bad_Certificate;
       goto leave;
     }
@@ -417,7 +612,7 @@ gpgsm_basic_cert_check (KsbaCert cert)
     {
       /* find the next cert up the tree */
       keydb_search_reset (kh);
-      rc = keydb_search_subject (kh, issuer);
+      rc = find_up (kh, cert, issuer);
       if (rc)
         {
           if (rc == -1)