]> git.ipfire.org Git - thirdparty/gnutls.git/commitdiff
Make it possible to trust intermediary certificates.
authorSimon Josefsson <simon@josefsson.org>
Mon, 2 Feb 2009 16:21:17 +0000 (17:21 +0100)
committerSimon Josefsson <simon@josefsson.org>
Mon, 2 Feb 2009 16:21:17 +0000 (17:21 +0100)
Based on tiny patch from "Douglas E. Engert" <deengert@anl.gov>
in <http://thread.gmane.org/gmane.comp.encryption.gpg.gnutls.devel/3351/focus=3376>.

NEWS
doc/TODO
lib/x509/verify.c
tests/chainverify.c

diff --git a/NEWS b/NEWS
index a78281d1198628221ae41d58a29e0a6cd4f300cd..6854cca35b4cca9838fae8afd69653834781ef6f 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -5,6 +5,19 @@ See the end for copying conditions.
 
 * Version 2.7.5 (unreleased)
 
+** libgnutls: Accept chains where intermediary certs are trusted.
+Before GnuTLS needed to validate the entire chain back to a
+self-signed certificate.  GnuTLS will now stop looking when it has
+found an intermediary trusted certificate.  The new behaviour is
+useful when chains, for example, contains a top-level CA, an
+intermediary CA signed using RSA-MD5, and an end-entity certificate.
+To avoid chain validation errors due to the RSA-MD5 cert, you can
+explicitly add the intermediary RSA-MD5 cert to your trusted certs.
+The signature on trusted certificates are not checked, so the chain
+has a chance to validate correctly.  Reported by "Douglas E. Engert"
+<deengert@anl.gov> in
+<http://thread.gmane.org/gmane.comp.encryption.gpg.gnutls.devel/3351>.
+
 ** libgnutls: result_size in gnutls_hex_encode now holds
 the size of the result. Report by John Brooks <special@dereferenced.net>.
 
index d9c900a0239dc787c9df24c09c29621f3920eb7d..beec4237d8396bee78a66e0005bd12686b2c8206 100644 (file)
--- a/doc/TODO
+++ b/doc/TODO
@@ -48,8 +48,6 @@ Current list:
 - Support non-blocking gnutls_{dh,rsa}_params_generate2 for when there
   is not enough entropy available. 
 - Chain verifications.
-  - Short-cut the certificate verification algorithm before the
-    root if a middle-CA is trusted.
   - Check path length constraints.
   - Check keyCertSign key usages.
   - Reject extensions in v1 certificates.
index c4fab1df3e14987f016f4a9aed52107f4ef8390c..ee66060227256922e6ecc1bdba412a9495cbcdee 100644 (file)
@@ -51,6 +51,38 @@ static int _gnutls_verify_crl2 (gnutls_x509_crl_t crl,
                                int tcas_size, unsigned int flags,
                                unsigned int *output);
 
+/* Checks if two certs are identical.  Return 0 onn match. */
+static int
+check_if_same_cert (gnutls_x509_crt_t cert1, gnutls_x509_crt_t cert2)
+{
+  gnutls_datum_t cert1bin = { NULL, 0 }, cert2bin = { NULL, 0 };
+  int result;
+
+  result = _gnutls_x509_der_encode (cert1->cert, "", &cert1bin, 0);
+  if (result < 0)
+    {
+      gnutls_assert ();
+      goto cleanup;
+    }
+
+  result = _gnutls_x509_der_encode (cert2->cert, "", &cert2bin, 0);
+  if (result < 0)
+    {
+      gnutls_assert ();
+      goto cleanup;
+    }
+
+  if ((cert1bin.size == cert2bin.size) &&
+      (memcmp (cert1bin.data, cert2bin.data, cert1bin.size) == 0))
+    result = 0;
+  else
+    result = 1;
+
+ cleanup:
+  _gnutls_free_datum (&cert1bin);
+  _gnutls_free_datum (&cert2bin);
+  return result;
+}
 
 /* Checks if the issuer of a certificate is a
  * Certificate Authority, or if the certificate is the same
@@ -365,16 +397,12 @@ gnutls_x509_crt_check_issuer (gnutls_x509_crt_t cert,
 }
 
 
-/* The algorithm used is:
- * 1. Check last certificate in the chain. If it is not verified return.
- * 2. Check if any certificates in the chain are revoked. If yes return.
- * 3. Try to verify the rest of certificates in the chain. If not verified return.
- * 4. Return 0.
+/* Verify X.509 certificate chain.
  *
  * Note that the return value is an OR of GNUTLS_CERT_* elements.
  *
- * This function verifies a X.509 certificate list. The certificate list should
- * lead to a trusted CA in order to be trusted.
+ * This function verifies a X.509 certificate list. The certificate
+ * list should lead to a trusted certificate in order to be trusted.
  */
 static unsigned int
 _gnutls_x509_verify_certificate (const gnutls_x509_crt_t * certificate_list,
@@ -387,16 +415,32 @@ _gnutls_x509_verify_certificate (const gnutls_x509_crt_t * certificate_list,
   int i = 0, ret;
   unsigned int status = 0, output;
 
+  /* Check for revoked certificates in the chain
+   */
+#ifdef ENABLE_PKI
+  for (i = 0; i < clist_size; i++)
+    {
+      ret = gnutls_x509_crt_check_revocation (certificate_list[i],
+                                             CRLs, crls_size);
+      if (ret == 1)
+       {                       /* revoked */
+         status |= GNUTLS_CERT_REVOKED;
+         status |= GNUTLS_CERT_INVALID;
+         return status;
+       }
+    }
+#endif
+
   if (clist_size > 1)
     {
       /* Check if the last certificate in the path is self signed.
        * In that case ignore it (a certificate is trusted only if it
        * leads to a trusted party by us, not the server's).
        *
-       * This in addition prevents from verifying self signed certificates
-       * against themselves. This although not bad caused verification
-       * failures on some root self signed certificates that use the MD2
-       * algorithm.
+       * This prevents from verifying self signed certificates against
+       * themselves. This (although not bad) caused verification
+       * failures on some root self signed certificates that use the
+       * MD2 algorithm.
        */
       if (gnutls_x509_crt_check_issuer (certificate_list[clist_size - 1],
                                    certificate_list[clist_size - 1]) > 0)
@@ -405,16 +449,38 @@ _gnutls_x509_verify_certificate (const gnutls_x509_crt_t * certificate_list,
        }
     }
 
+  /* We want to shorten the chain by removing the cert that matches
+   * one of the certs we trust and all the certs after that i.e. if
+   * cert chain is A signed-by B signed-by C signed-by D (signed-by
+   * self-signed E but already removed above), and we trust B, remove
+   * B, C and D.  We must leave the first cert on chain. */
+  if (clist_size > 1 && !(flags & GNUTLS_VERIFY_DO_NOT_ALLOW_SAME))
+    {
+      for (i = 1; i < clist_size; i++)
+       {
+         int j;
+
+         for (j = 0; j < tcas_size; j++)
+           {
+             if (check_if_same_cert (certificate_list[i],
+                                     trusted_cas[j]) == 0)
+               {
+                 clist_size = i;
+                 break;
+               }
+           }
+         /* clist_size may have been changed which gets out of loop */
+       }
+    }
+
   /* Verify the last certificate in the certificate path
    * against the trusted CA certificate list.
    *
    * If no CAs are present returns CERT_INVALID. Thus works
    * in self signed etc certificates.
    */
-  ret =
-    _gnutls_verify_certificate2 (certificate_list[clist_size - 1],
-                                trusted_cas, tcas_size, flags, &output);
-
+  ret = _gnutls_verify_certificate2 (certificate_list[clist_size - 1],
+                                    trusted_cas, tcas_size, flags, &output);
   if (ret == 0)
     {
       /* if the last certificate in the certificate
@@ -427,23 +493,7 @@ _gnutls_x509_verify_certificate (const gnutls_x509_crt_t * certificate_list,
       return status;
     }
 
-  /* Check for revoked certificates in the chain
-   */
-#ifdef ENABLE_PKI
-  for (i = 0; i < clist_size; i++)
-    {
-      ret = gnutls_x509_crt_check_revocation (certificate_list[i],
-                                             CRLs, crls_size);
-      if (ret == 1)
-       {                       /* revoked */
-         status |= GNUTLS_CERT_REVOKED;
-         status |= GNUTLS_CERT_INVALID;
-         return status;
-       }
-    }
-#endif
-
-  /* Verify the certificate path (chain) 
+  /* Verify the certificate path (chain)
    */
   for (i = clist_size - 1; i > 0; i--)
     {
index 7647d59f522233b858dbb6a2f626900b0ed53a46..2da18ff1633c76ddb87f5707e66364f055fd760b 100644 (file)
@@ -703,6 +703,11 @@ static struct
     0, GNUTLS_CERT_INSECURE_ALGORITHM | GNUTLS_CERT_INVALID },
   { "cacertrsamd5 ok", cacertrsamd5, &cacertrsamd5[2],
     GNUTLS_VERIFY_ALLOW_SIGN_RSA_MD5, 0 },
+  { "cacertrsamd5 short-cut not ok", cacertrsamd5, &cacertrsamd5[1],
+    GNUTLS_VERIFY_DO_NOT_ALLOW_SAME,
+    GNUTLS_CERT_SIGNER_NOT_FOUND | GNUTLS_CERT_INVALID },
+  { "cacertrsamd5 short-cut ok", cacertrsamd5, &cacertrsamd5[1],
+    0, 0 },
   { NULL, NULL, NULL, 0}
 };
 /* *INDENT-ON* */