gnutls: Build the certificate chain recursively instead of using a loop 97/66897/1 accepted/tizen/common/20160422.172039 accepted/tizen/ivi/20160423.060543 accepted/tizen/mobile/20160423.055610 accepted/tizen/tv/20160423.060016 accepted/tizen/wearable/20160423.060307 submit/tizen/20160422.054856
authorSeonah Moon <seonah1.moon@samsung.com>
Fri, 22 Apr 2016 01:57:22 +0000 (10:57 +0900)
committerSeonah Moon <seonah1.moon@samsung.com>
Fri, 22 Apr 2016 01:57:38 +0000 (10:57 +0900)
We are currently checking every certificate in the chain and also
looking for an issuer in the database for the last certificate of the
chain. Now build_certificate_chain is called recursively so that for all
issuers that fail, we also try to find an issuer in the database,
instead of just for the last one. Pinned certificates are now handled by
the caller since they are done only once for the first certificate.
This fixes the case of fbcdn-dragon-a.akamaihd.net for which all the
certificates in the chain are not anchored, but we can find an issuer in
the database for the second certificate that is anchored.

 https://bugzilla.gnome.org/show_bug.cgi?id=750457

Change-Id: Ib3da8118b8873e3c5afec173bcd4160252b2a0b1
Signed-off-by: Seonah Moon <seonah1.moon@samsung.com>
tls/gnutls/gtlsdatabase-gnutls.c

index 0992362..018639f 100755 (executable)
 
 G_DEFINE_ABSTRACT_TYPE (GTlsDatabaseGnutls, g_tls_database_gnutls, G_TYPE_TLS_DATABASE);
 
+#define BUILD_CERTIFICATE_CHAIN_RECURSION_LIMIT 10
+
 enum {
   STATUS_FAILURE,
   STATUS_INCOMPLETE,
   STATUS_SELFSIGNED,
-  STATUS_PINNED,
   STATUS_ANCHORED,
+  STATUS_RECURSION_LIMIT_REACHED
 };
 
 static void
@@ -56,137 +58,111 @@ is_self_signed (GTlsCertificateGnutls *certificate)
 
 static gint
 build_certificate_chain (GTlsDatabaseGnutls      *self,
-                         GTlsCertificateGnutls   *chain,
+                         GTlsCertificateGnutls   *certificate,
+                         GTlsCertificateGnutls   *previous,
+                         gboolean                 certificate_is_from_db,
+                         guint                    recursion_depth,
                          const gchar             *purpose,
                          GSocketConnectable      *identity,
                          GTlsInteraction         *interaction,
-                         GTlsDatabaseVerifyFlags  flags,
                          GCancellable            *cancellable,
                          GTlsCertificateGnutls  **anchor,
                          GError                 **error)
 {
-
-  GTlsCertificateGnutls *certificate;
-  GTlsCertificateGnutls *previous;
   GTlsCertificate *issuer;
-  gboolean certificate_is_from_db;
-
-  g_assert (anchor);
-  g_assert (chain);
-  g_assert (purpose);
-  g_assert (error);
-  g_assert (!*error);
+  gint status;
 
-  /*
-   * Remember that the first certificate never changes in the chain.
-   * When we find a self-signed, pinned or anchored certificate, all
-   * issuers are truncated from the chain.
-   */
+  if (recursion_depth++ > BUILD_CERTIFICATE_CHAIN_RECURSION_LIMIT)
+    return STATUS_RECURSION_LIMIT_REACHED;
 
-  *anchor = NULL;
-  previous = NULL;
-  certificate = chain;
-  certificate_is_from_db = FALSE;
+  if (g_cancellable_set_error_if_cancelled (cancellable, error))
+    return STATUS_FAILURE;
 
-  /* First check for pinned certificate */
+  /* Look up whether this certificate is an anchor */
   if (g_tls_database_gnutls_lookup_assertion (self, certificate,
-                                              G_TLS_DATABASE_GNUTLS_PINNED_CERTIFICATE,
+                                              G_TLS_DATABASE_GNUTLS_ANCHORED_CERTIFICATE,
                                               purpose, identity, cancellable, error))
     {
       g_tls_certificate_gnutls_set_issuer (certificate, NULL);
-      return STATUS_PINNED;
+      *anchor = certificate;
+      return STATUS_ANCHORED;
     }
   else if (*error)
     {
       return STATUS_FAILURE;
     }
 
-  for (;;)
+  /* Is it self-signed? */
+  if (is_self_signed (certificate))
     {
-      if (g_cancellable_set_error_if_cancelled (cancellable, error))
-        return STATUS_FAILURE;
-
-      /* Look up whether this certificate is an anchor */
-      if (g_tls_database_gnutls_lookup_assertion (self, certificate,
-                                                  G_TLS_DATABASE_GNUTLS_ANCHORED_CERTIFICATE,
-                                                  purpose, identity, cancellable, error))
+      /*
+       * Since at this point we would fail with 'self-signed', can we replace
+       * this certificate with one from the database and do better?
+       */
+      if (previous && !certificate_is_from_db)
         {
-          g_tls_certificate_gnutls_set_issuer (certificate, NULL);
-          *anchor = certificate;
-          return STATUS_ANCHORED;
-        }
-      else if (*error)
-        {
-          return STATUS_FAILURE;
-        }
-
-      /* Is it self-signed? */
-      if (is_self_signed (certificate))
-        {
-          /*
-           * Since at this point we would fail with 'self-signed', can we replace
-           * this certificate with one from the database and do better?
-           */
-          if (previous && !certificate_is_from_db)
+          issuer = g_tls_database_lookup_certificate_issuer (G_TLS_DATABASE (self),
+                                                             G_TLS_CERTIFICATE (previous),
+                                                             interaction,
+                                                             G_TLS_DATABASE_LOOKUP_NONE,
+                                                             cancellable, error);
+          if (*error)
             {
-              issuer = g_tls_database_lookup_certificate_issuer (G_TLS_DATABASE (self),
-                                                                 G_TLS_CERTIFICATE (previous),
-                                                                 interaction,
-                                                                 G_TLS_DATABASE_LOOKUP_NONE,
-                                                                 cancellable, error);
-              if (*error)
-                {
-                  return STATUS_FAILURE;
-                }
-              else if (issuer)
-                {
-                  /* Replaced with certificate in the db, restart step again with this certificate */
-                  g_return_val_if_fail (G_IS_TLS_CERTIFICATE_GNUTLS (issuer), STATUS_FAILURE);
-                  g_tls_certificate_gnutls_set_issuer (previous, G_TLS_CERTIFICATE_GNUTLS (issuer));
-                  certificate = G_TLS_CERTIFICATE_GNUTLS (issuer);
-                  certificate_is_from_db = TRUE;
-                  continue;
-                }
+              return STATUS_FAILURE;
+            }
+          else if (issuer)
+            {
+              /* Replaced with certificate in the db, restart step again with this certificate */
+              g_return_val_if_fail (G_IS_TLS_CERTIFICATE_GNUTLS (issuer), STATUS_FAILURE);
+              certificate = G_TLS_CERTIFICATE_GNUTLS (issuer);
+              g_tls_certificate_gnutls_set_issuer (previous, certificate);
+              g_object_unref (issuer);
+
+              return build_certificate_chain (self, certificate, previous, TRUE, recursion_depth,
+                                              purpose, identity, interaction, cancellable, anchor, error);
             }
-
-          g_tls_certificate_gnutls_set_issuer (certificate, NULL);
-          return STATUS_SELFSIGNED;
         }
 
-      previous = certificate;
+      g_tls_certificate_gnutls_set_issuer (certificate, NULL);
+      return STATUS_SELFSIGNED;
+    }
 
-      /* Bring over the next certificate in the chain */
-      issuer = g_tls_certificate_get_issuer (G_TLS_CERTIFICATE (certificate));
-      if (issuer)
-        {
-          g_return_val_if_fail (G_IS_TLS_CERTIFICATE_GNUTLS (issuer), STATUS_FAILURE);
-          certificate = G_TLS_CERTIFICATE_GNUTLS (issuer);
-          certificate_is_from_db = FALSE;
-        }
+  previous = certificate;
 
-      /* Search for the next certificate in chain */
-      else
+  /* Bring over the next certificate in the chain */
+  issuer = g_tls_certificate_get_issuer (G_TLS_CERTIFICATE (certificate));
+  if (issuer)
+    {
+      g_return_val_if_fail (G_IS_TLS_CERTIFICATE_GNUTLS (issuer), STATUS_FAILURE);
+      certificate = G_TLS_CERTIFICATE_GNUTLS (issuer);
+
+      status = build_certificate_chain (self, certificate, previous, FALSE, recursion_depth,
+                                        purpose, identity, interaction, cancellable, anchor, error);
+      if (status != STATUS_INCOMPLETE)
         {
-          issuer = g_tls_database_lookup_certificate_issuer (G_TLS_DATABASE (self),
-                                                             G_TLS_CERTIFICATE (certificate),
-                                                             interaction,
-                                                             G_TLS_DATABASE_LOOKUP_NONE,
-                                                             cancellable, error);
-          if (*error)
-            return STATUS_FAILURE;
-          else if (!issuer)
-            return STATUS_INCOMPLETE;
-
-          /* Found a certificate in chain, use for next step */
-          g_return_val_if_fail (G_IS_TLS_CERTIFICATE_GNUTLS (issuer), STATUS_FAILURE);
-          g_tls_certificate_gnutls_set_issuer (certificate, G_TLS_CERTIFICATE_GNUTLS (issuer));
-          certificate = G_TLS_CERTIFICATE_GNUTLS (issuer);
-          certificate_is_from_db = TRUE;
-          g_object_unref (issuer);
+          return status;
         }
     }
 
-  g_assert_not_reached ();
+  /* Search for the next certificate in chain */
+  issuer = g_tls_database_lookup_certificate_issuer (G_TLS_DATABASE (self),
+                                                     G_TLS_CERTIFICATE (certificate),
+                                                     interaction,
+                                                     G_TLS_DATABASE_LOOKUP_NONE,
+                                                     cancellable, error);
+  if (*error)
+    return STATUS_FAILURE;
+
+  if (!issuer)
+    return STATUS_INCOMPLETE;
+
+  g_return_val_if_fail (G_IS_TLS_CERTIFICATE_GNUTLS (issuer), STATUS_FAILURE);
+  g_tls_certificate_gnutls_set_issuer (certificate, G_TLS_CERTIFICATE_GNUTLS (issuer));
+  certificate = G_TLS_CERTIFICATE_GNUTLS (issuer);
+  g_object_unref (issuer);
+
+  return build_certificate_chain (self, certificate, previous, TRUE, recursion_depth,
+                                  purpose, identity, interaction, cancellable, anchor, error);
 }
 
 #if ENABLE(TIZEN_TV_ADJUST_TIME)
@@ -285,33 +261,49 @@ g_tls_database_gnutls_verify_chain (GTlsDatabase           *database,
 {
   GTlsDatabaseGnutls *self;
   GTlsCertificateFlags result;
+  GTlsCertificateGnutls *certificate;
   GError *err = NULL;
   GTlsCertificateGnutls *anchor;
   guint gnutls_result;
   gnutls_x509_crt_t *certs, *anchors;
   guint certs_length, anchors_length;
   gint status, gerr;
+  guint recursion_depth = 0;
 
   g_return_val_if_fail (G_IS_TLS_CERTIFICATE_GNUTLS (chain),
                         G_TLS_CERTIFICATE_GENERIC_ERROR);
+  g_assert (purpose);
 
   self = G_TLS_DATABASE_GNUTLS (database);
-  anchor = NULL;
+  certificate = G_TLS_CERTIFICATE_GNUTLS (chain);
 
-  status = build_certificate_chain (self, G_TLS_CERTIFICATE_GNUTLS (chain), purpose,
-                                    identity, interaction, flags, cancellable, &anchor, &err);
-  if (status == STATUS_FAILURE)
+  /* First check for pinned certificate */
+  if (g_tls_database_gnutls_lookup_assertion (self, certificate,
+                                              G_TLS_DATABASE_GNUTLS_PINNED_CERTIFICATE,
+                                              purpose, identity, cancellable, &err))
+    {
+      /*
+       * A pinned certificate is verified on its own, without any further
+       * verification.
+       */
+      g_tls_certificate_gnutls_set_issuer (certificate, NULL);
+      return 0;
+    }
+
+  if (err)
     {
       g_propagate_error (error, err);
       return G_TLS_CERTIFICATE_GENERIC_ERROR;
     }
 
-  /*
-   * A pinned certificate is verified on its own, without any further
-   * verification.
-   */
-  if (status == STATUS_PINNED)
-      return 0;
+  anchor = NULL;
+  status = build_certificate_chain (self, certificate, NULL, FALSE, recursion_depth,
+                                    purpose, identity, interaction, cancellable, &anchor, &err);
+  if (status == STATUS_FAILURE)
+    {
+      g_propagate_error (error, err);
+      return G_TLS_CERTIFICATE_GENERIC_ERROR;
+    }
 
   if (g_cancellable_set_error_if_cancelled (cancellable, error))
     return G_TLS_CERTIFICATE_GENERIC_ERROR;