send full certificate chain during handshake
[platform/upstream/glib-networking.git] / tls / gnutls / gtlscertificate-gnutls.c
old mode 100644 (file)
new mode 100755 (executable)
index c963aa7..e47dcdd
@@ -25,6 +25,7 @@
 
 #include "gtlscertificate-gnutls.h"
 #include <glib/gi18n-lib.h>
+#include "TIZEN.h"
 
 static void     g_tls_certificate_gnutls_initable_iface_init (GInitableIface  *iface);
 
@@ -37,10 +38,8 @@ enum
   PROP_0,
 
   PROP_CERTIFICATE,
-  PROP_CERTIFICATE_BYTES,
   PROP_CERTIFICATE_PEM,
   PROP_PRIVATE_KEY,
-  PROP_PRIVATE_KEY_BYTES,
   PROP_PRIVATE_KEY_PEM,
   PROP_ISSUER
 };
@@ -75,69 +74,6 @@ g_tls_certificate_gnutls_finalize (GObject *object)
   G_OBJECT_CLASS (g_tls_certificate_gnutls_parent_class)->finalize (object);
 }
 
-static GByteArray *
-get_der_for_certificate (GTlsCertificateGnutls *self)
-{
-  GByteArray *certificate;
-  size_t size;
-  int status;
-
-  size = 0;
-  status = gnutls_x509_crt_export (self->priv->cert,
-                                   GNUTLS_X509_FMT_DER,
-                                   NULL, &size);
-  if (status != GNUTLS_E_SHORT_MEMORY_BUFFER)
-    {
-      certificate = NULL;
-    }
-  else
-    {
-      certificate = g_byte_array_sized_new (size);
-      certificate->len = size;
-      status = gnutls_x509_crt_export (self->priv->cert,
-                                       GNUTLS_X509_FMT_DER,
-                                       certificate->data, &size);
-      if (status != 0)
-        {
-          g_byte_array_free (certificate, TRUE);
-          certificate = NULL;
-        }
-    }
-
-  return certificate;
-}
-
-static gchar *
-get_pem_for_certificate (GTlsCertificateGnutls *self)
-{
-  char *certificate_pem;
-  int status;
-  size_t size;
-
-  size = 0;
-  status = gnutls_x509_crt_export (self->priv->cert,
-                                   GNUTLS_X509_FMT_PEM,
-                                   NULL, &size);
-  if (status != GNUTLS_E_SHORT_MEMORY_BUFFER)
-    {
-      certificate_pem = NULL;
-    }
-  else
-    {
-      certificate_pem = g_malloc (size);
-      status = gnutls_x509_crt_export (self->priv->cert,
-                                       GNUTLS_X509_FMT_PEM,
-                                       certificate_pem, &size);
-      if (status != 0)
-        {
-          g_free (certificate_pem);
-          certificate_pem = NULL;
-        }
-    }
-
-  return certificate_pem;
-}
-
 static void
 g_tls_certificate_gnutls_get_property (GObject    *object,
                                       guint       prop_id,
@@ -146,23 +82,55 @@ g_tls_certificate_gnutls_get_property (GObject    *object,
 {
   GTlsCertificateGnutls *gnutls = G_TLS_CERTIFICATE_GNUTLS (object);
   GByteArray *certificate;
+  char *certificate_pem;
+  int status;
+  size_t size;
 
   switch (prop_id)
     {
     case PROP_CERTIFICATE:
-      g_value_take_boxed (value, get_der_for_certificate (gnutls));
-      break;
-
-    case PROP_CERTIFICATE_BYTES:
-      certificate = get_der_for_certificate (gnutls);
-      if (certificate == NULL)
-        g_value_take_boxed (value, NULL);
+      size = 0;
+      status = gnutls_x509_crt_export (gnutls->priv->cert,
+                                      GNUTLS_X509_FMT_DER,
+                                      NULL, &size);
+      if (status != GNUTLS_E_SHORT_MEMORY_BUFFER)
+       certificate = NULL;
       else
-        g_value_take_boxed (value, g_byte_array_free_to_bytes (certificate));
+       {
+         certificate = g_byte_array_sized_new (size);
+         certificate->len = size;
+         status = gnutls_x509_crt_export (gnutls->priv->cert,
+                                          GNUTLS_X509_FMT_DER,
+                                          certificate->data, &size);
+         if (status != 0)
+           {
+             g_byte_array_free (certificate, TRUE);
+             certificate = NULL;
+           }
+       }
+      g_value_take_boxed (value, certificate);
       break;
 
     case PROP_CERTIFICATE_PEM:
-      g_value_take_string (value, get_pem_for_certificate (gnutls));
+      size = 0;
+      status = gnutls_x509_crt_export (gnutls->priv->cert,
+                                      GNUTLS_X509_FMT_PEM,
+                                      NULL, &size);
+      if (status != GNUTLS_E_SHORT_MEMORY_BUFFER)
+       certificate_pem = NULL;
+      else
+       {
+         certificate_pem = g_malloc (size);
+         status = gnutls_x509_crt_export (gnutls->priv->cert,
+                                          GNUTLS_X509_FMT_PEM,
+                                          certificate_pem, &size);
+         if (status != 0)
+           {
+             g_free (certificate_pem);
+             certificate_pem = NULL;
+           }
+       }
+      g_value_take_string (value, certificate_pem);
       break;
 
     case PROP_ISSUER:
@@ -175,182 +143,120 @@ g_tls_certificate_gnutls_get_property (GObject    *object,
 }
 
 static void
-set_certificate_from_der (GTlsCertificateGnutls *self,
-                          const guchar *der,
-                          gsize len)
-{
-  gnutls_datum_t data;
-  int status;
-
-  g_return_if_fail (self->priv->have_cert == FALSE);
-  data.data = (guchar *)der;
-  data.size = len;
-  status = gnutls_x509_crt_import (self->priv->cert, &data,
-                                   GNUTLS_X509_FMT_DER);
-  if (status == 0)
-    {
-      self->priv->have_cert = TRUE;
-    }
-  else if (!self->priv->construct_error)
-    {
-      self->priv->construct_error =
-        g_error_new (G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
-                     _("Could not parse DER certificate: %s"),
-                     gnutls_strerror (status));
-    }
-}
-
-static void
-set_certificate_from_pem (GTlsCertificateGnutls *self,
-                          const gchar *string)
-{
-  gnutls_datum_t data;
-  int status;
-
-  g_return_if_fail (self->priv->have_cert == FALSE);
-  data.data = (guchar *)string;
-  data.size = strlen (string);
-  status = gnutls_x509_crt_import (self->priv->cert, &data,
-                                   GNUTLS_X509_FMT_PEM);
-  if (status == 0)
-    {
-      self->priv->have_cert = TRUE;
-    }
-  else if (!self->priv->construct_error)
-    {
-      self->priv->construct_error =
-        g_error_new (G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
-                     _("Could not parse PEM certificate: %s"),
-                     gnutls_strerror (status));
-    }
-}
-
-static void
-set_private_key_from_der (GTlsCertificateGnutls *self,
-                          const guchar *der,
-                          gsize len)
-{
-  gnutls_datum_t data;
-  int status;
-
-  g_return_if_fail (self->priv->have_key == FALSE);
-  data.data = (guchar *)der;
-  data.size = len;
-  if (!self->priv->key)
-    gnutls_x509_privkey_init (&self->priv->key);
-  status = gnutls_x509_privkey_import (self->priv->key, &data,
-                                       GNUTLS_X509_FMT_DER);
-  if (status != 0)
-    {
-      int pkcs8_status =
-        gnutls_x509_privkey_import_pkcs8 (self->priv->key, &data,
-                                          GNUTLS_X509_FMT_DER, NULL,
-                                          GNUTLS_PKCS_PLAIN);
-      if (pkcs8_status == 0)
-        status = 0;
-    }
-  if (status == 0)
-    {
-      self->priv->have_key = TRUE;
-    }
-  else if (!self->priv->construct_error)
-    {
-      self->priv->construct_error =
-        g_error_new (G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
-                     _("Could not parse DER private key: %s"),
-                     gnutls_strerror (status));
-    }
-}
-
-static void
-set_private_key_from_pem (GTlsCertificateGnutls *self,
-                          const gchar *string)
-{
-  gnutls_datum_t data;
-  int status;
-
-  g_return_if_fail (self->priv->have_key == FALSE);
-  data.data = (guchar *)string;
-  data.size = strlen (string);
-  if (!self->priv->key)
-    gnutls_x509_privkey_init (&self->priv->key);
-  status = gnutls_x509_privkey_import (self->priv->key, &data,
-                                       GNUTLS_X509_FMT_PEM);
-  if (status != 0)
-    {
-      int pkcs8_status =
-        gnutls_x509_privkey_import_pkcs8 (self->priv->key, &data,
-                                          GNUTLS_X509_FMT_PEM, NULL,
-                                          GNUTLS_PKCS_PLAIN);
-      if (pkcs8_status == 0)
-        status = 0;
-    }
-  if (status == 0)
-    {
-      self->priv->have_key = TRUE;
-    }
-  else if (!self->priv->construct_error)
-    {
-      self->priv->construct_error =
-        g_error_new (G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
-                     _("Could not parse PEM private key: %s"),
-                     gnutls_strerror (status));
-    }
-}
-
-static void
 g_tls_certificate_gnutls_set_property (GObject      *object,
                                       guint         prop_id,
                                       const GValue *value,
                                       GParamSpec   *pspec)
 {
   GTlsCertificateGnutls *gnutls = G_TLS_CERTIFICATE_GNUTLS (object);
-  GByteArray *byte_array;
+  GByteArray *bytes;
   const char *string;
-  GBytes *bytes;
+  gnutls_datum_t data;
+  int status;
 
   switch (prop_id)
     {
     case PROP_CERTIFICATE:
-      byte_array = g_value_get_boxed (value);
-      if (byte_array)
-        set_certificate_from_der (gnutls, byte_array->data, byte_array->len);
-      break;
-
-    case PROP_CERTIFICATE_BYTES:
       bytes = g_value_get_boxed (value);
-      if (bytes)
-        {
-          set_certificate_from_der (gnutls, g_bytes_get_data (bytes, NULL),
-                                    g_bytes_get_size (bytes));
-        }
+      if (!bytes)
+       break;
+      g_return_if_fail (gnutls->priv->have_cert == FALSE);
+      data.data = bytes->data;
+      data.size = bytes->len;
+      status = gnutls_x509_crt_import (gnutls->priv->cert, &data,
+                                      GNUTLS_X509_FMT_DER);
+      if (status == 0)
+       gnutls->priv->have_cert = TRUE;
+      else if (!gnutls->priv->construct_error)
+       {
+         gnutls->priv->construct_error =
+           g_error_new (G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
+                        _("Could not parse DER certificate: %s"),
+                        gnutls_strerror (status));
+       }
+
       break;
 
     case PROP_CERTIFICATE_PEM:
       string = g_value_get_string (value);
-      if (string)
-        set_certificate_from_pem (gnutls, string);
+      if (!string)
+       break;
+      g_return_if_fail (gnutls->priv->have_cert == FALSE);
+      data.data = (void *)string;
+      data.size = strlen (string);
+      status = gnutls_x509_crt_import (gnutls->priv->cert, &data,
+                                      GNUTLS_X509_FMT_PEM);
+      if (status == 0)
+       gnutls->priv->have_cert = TRUE;
+      else if (!gnutls->priv->construct_error)
+       {
+         gnutls->priv->construct_error =
+           g_error_new (G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
+                        _("Could not parse PEM certificate: %s"),
+                        gnutls_strerror (status));
+       }
       break;
 
     case PROP_PRIVATE_KEY:
-      byte_array = g_value_get_boxed (value);
-      if (byte_array)
-        set_private_key_from_der (gnutls, byte_array->data, byte_array->len);
-      break;
-
-    case PROP_PRIVATE_KEY_BYTES:
       bytes = g_value_get_boxed (value);
-      if (bytes)
-        {
-          set_private_key_from_der (gnutls, g_bytes_get_data (bytes, NULL),
-                                    g_bytes_get_size (bytes));
-        }
+      if (!bytes)
+       break;
+      g_return_if_fail (gnutls->priv->have_key == FALSE);
+      data.data = bytes->data;
+      data.size = bytes->len;
+      if (!gnutls->priv->key)
+        gnutls_x509_privkey_init (&gnutls->priv->key);
+      status = gnutls_x509_privkey_import (gnutls->priv->key, &data,
+                                          GNUTLS_X509_FMT_DER);
+      if (status != 0)
+       {
+         int pkcs8_status =
+           gnutls_x509_privkey_import_pkcs8 (gnutls->priv->key, &data,
+                                             GNUTLS_X509_FMT_DER, NULL,
+                                             GNUTLS_PKCS_PLAIN);
+         if (pkcs8_status == 0)
+           status = 0;
+       }
+      if (status == 0)
+       gnutls->priv->have_key = TRUE;
+      else if (!gnutls->priv->construct_error)
+       {
+         gnutls->priv->construct_error =
+           g_error_new (G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
+                        _("Could not parse DER private key: %s"),
+                        gnutls_strerror (status));
+       }
       break;
 
     case PROP_PRIVATE_KEY_PEM:
       string = g_value_get_string (value);
-      if (string)
-        set_private_key_from_pem (gnutls, string);
+      if (!string)
+       break;
+      g_return_if_fail (gnutls->priv->have_key == FALSE);
+      data.data = (void *)string;
+      data.size = strlen (string);
+      if (!gnutls->priv->key)
+        gnutls_x509_privkey_init (&gnutls->priv->key);
+      status = gnutls_x509_privkey_import (gnutls->priv->key, &data,
+                                          GNUTLS_X509_FMT_PEM);
+      if (status != 0)
+       {
+         int pkcs8_status =
+           gnutls_x509_privkey_import_pkcs8 (gnutls->priv->key, &data,
+                                             GNUTLS_X509_FMT_PEM, NULL,
+                                             GNUTLS_PKCS_PLAIN);
+         if (pkcs8_status == 0)
+           status = 0;
+       }
+      if (status == 0)
+       gnutls->priv->have_key = TRUE;
+      else if (!gnutls->priv->construct_error)
+       {
+         gnutls->priv->construct_error =
+           g_error_new (G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
+                        _("Could not parse PEM private key: %s"),
+                        gnutls_strerror (status));
+       }
       break;
 
     case PROP_ISSUER:
@@ -362,6 +268,16 @@ g_tls_certificate_gnutls_set_property (GObject      *object,
     }
 }
 
+#if ENABLE(TIZEN_TV_ADJUST_TIME)
+extern double soupTimeOffset;
+
+static time_t
+correct_time_func(time_t *t)
+{
+  return time(NULL) + (time_t)(soupTimeOffset / 1000);
+}
+#endif
+
 static void
 g_tls_certificate_gnutls_init (GTlsCertificateGnutls *gnutls)
 {
@@ -370,6 +286,9 @@ g_tls_certificate_gnutls_init (GTlsCertificateGnutls *gnutls)
                                              GTlsCertificateGnutlsPrivate);
 
   gnutls_x509_crt_init (&gnutls->priv->cert);
+#if ENABLE(TIZEN_TV_ADJUST_TIME)
+  gnutls_global_set_time_function(correct_time_func);
+#endif
 }
 
 static gboolean
@@ -405,6 +324,10 @@ g_tls_certificate_gnutls_verify (GTlsCertificate     *cert,
   gnutls_x509_crt_t *chain;
   GTlsCertificateFlags gtls_flags;
   time_t t, now;
+#if ENABLE(TIZEN_TV_DLOG)
+  char timebuf[256];
+#endif
+
   
   cert_gnutls = G_TLS_CERTIFICATE_GNUTLS (cert);
   for (num_certs = 0; cert_gnutls; cert_gnutls = cert_gnutls->priv->issuer)
@@ -441,13 +364,38 @@ g_tls_certificate_gnutls_verify (GTlsCertificate     *cert,
    * won't bother if it gets an UNKNOWN_CA.
    */
   now = time (NULL);
+#if ENABLE(TIZEN_TV_ADJUST_TIME)
+  now = time (NULL) + (time_t)(soupTimeOffset / 1000);
+#endif
   for (i = 0; i < num_certs; i++)
     {
       t = gnutls_x509_crt_get_activation_time (chain[i]);
+
+#if ENABLE(TIZEN_TV_DLOG)
+      ctime_r(&now, timebuf);
+      TIZEN_LOGI("[Certificate] TV borad time is: %s", timebuf);
+      if (t != (time_t) -1) {
+       ctime_r(&t, timebuf);
+        TIZEN_LOGI("[Certificate] CA activation time is: %s", timebuf);
+      }
+      else
+        TIZEN_LOGI("[Certificate] gnutls_x509_crt_get_activation_time ERROR");
+#endif
+
       if (t == (time_t) -1 || t > now)
        gtls_flags |= G_TLS_CERTIFICATE_NOT_ACTIVATED;
 
       t = gnutls_x509_crt_get_expiration_time (chain[i]);
+
+#if ENABLE(TIZEN_TV_DLOG)
+      if (t != (time_t) -1) {
+       ctime_r(&t, timebuf);
+        TIZEN_LOGI("[Certificate] CA expiration time is: %s", timebuf);
+      }
+      else
+        TIZEN_LOGI("[Certificate] gnutls_x509_crt_get_expiration_time ERROR");
+#endif
+
       if (t == (time_t) -1 || t < now)
        gtls_flags |= G_TLS_CERTIFICATE_EXPIRED;
     }
@@ -465,24 +413,45 @@ g_tls_certificate_gnutls_real_copy (GTlsCertificateGnutls    *gnutls,
                                     const gchar              *interaction_id,
                                     gnutls_retr2_st          *st)
 {
+  GTlsCertificateGnutls *chain;
   gnutls_x509_crt_t cert;
-  gnutls_datum data;
+  gnutls_datum_t data;
+  guint num_certs = 0;
   size_t size = 0;
 
-  gnutls_x509_crt_export (gnutls->priv->cert, GNUTLS_X509_FMT_DER,
-                          NULL, &size);
-  data.data = g_malloc (size);
-  data.size = size;
-  gnutls_x509_crt_export (gnutls->priv->cert, GNUTLS_X509_FMT_DER,
-                          data.data, &size);
+  /* We will do this loop twice. It's probably more efficient than
+   * re-allocating memory.
+   */
+  chain = gnutls;
+  while (chain != NULL)
+    {
+      num_certs++;
+      chain = chain->priv->issuer;
+    }
+
+  st->ncerts = 0;
+  st->cert.x509 = gnutls_malloc (sizeof (gnutls_x509_crt_t) * num_certs);
 
-  gnutls_x509_crt_init (&cert);
-  gnutls_x509_crt_import (cert, &data, GNUTLS_X509_FMT_DER);
-  g_free (data.data);
+/* Now do the actual copy of the whole chain. */
+  chain = gnutls;
+  while (chain != NULL)
+    {
+      gnutls_x509_crt_export (chain->priv->cert, GNUTLS_X509_FMT_DER,
+                              NULL, &size);
+      data.data = g_malloc (size);
+      data.size = size;
+      gnutls_x509_crt_export (chain->priv->cert, GNUTLS_X509_FMT_DER,
+                              data.data, &size);
 
-  st->ncerts = 1;
-  st->cert.x509 = gnutls_malloc (sizeof (gnutls_x509_crt_t));
-  st->cert.x509[0] = cert;
+      gnutls_x509_crt_init (&cert);
+      gnutls_x509_crt_import (cert, &data, GNUTLS_X509_FMT_DER);
+      g_free (data.data);
+
+      st->cert.x509[st->ncerts] = cert;
+      st->ncerts++;
+
+      chain = chain->priv->issuer;
+    }
 
   if (gnutls->priv->key != NULL)
     {
@@ -511,10 +480,8 @@ g_tls_certificate_gnutls_class_init (GTlsCertificateGnutlsClass *klass)
   klass->copy = g_tls_certificate_gnutls_real_copy;
 
   g_object_class_override_property (gobject_class, PROP_CERTIFICATE, "certificate");
-  g_object_class_override_property (gobject_class, PROP_CERTIFICATE_BYTES, "certificate-bytes");
   g_object_class_override_property (gobject_class, PROP_CERTIFICATE_PEM, "certificate-pem");
   g_object_class_override_property (gobject_class, PROP_PRIVATE_KEY, "private-key");
-  g_object_class_override_property (gobject_class, PROP_PRIVATE_KEY_BYTES, "private-key-bytes");
   g_object_class_override_property (gobject_class, PROP_PRIVATE_KEY_PEM, "private-key-pem");
   g_object_class_override_property (gobject_class, PROP_ISSUER, "issuer");
 }
@@ -526,8 +493,8 @@ g_tls_certificate_gnutls_initable_iface_init (GInitableIface  *iface)
 }
 
 GTlsCertificate *
-g_tls_certificate_gnutls_new (const gnutls_datum *datum,
-                             GTlsCertificate    *issuer)
+g_tls_certificate_gnutls_new (const gnutls_datum_t *datum,
+                             GTlsCertificate      *issuer)
 {
   GTlsCertificateGnutls *gnutls;
 
@@ -541,7 +508,7 @@ g_tls_certificate_gnutls_new (const gnutls_datum *datum,
 
 void
 g_tls_certificate_gnutls_set_data (GTlsCertificateGnutls *gnutls,
-                                   const gnutls_datum *datum)
+                                   const gnutls_datum_t  *datum)
 {
   g_return_if_fail (G_IS_TLS_CERTIFICATE_GNUTLS (gnutls));
   g_return_if_fail (!gnutls->priv->have_cert);
@@ -638,6 +605,9 @@ g_tls_certificate_gnutls_verify_identity (GTlsCertificateGnutls *gnutls,
   /* FIXME: check sRVName and uniformResourceIdentifier
    * subjectAltNames, if appropriate for @identity.
    */
+#if ENABLE(TIZEN_TV_DLOG)
+  TIZEN_LOGI("[Network] SSL HandShake - Bad Identity");
+#endif
 
   return G_TLS_CERTIFICATE_BAD_IDENTITY;
 }
@@ -656,3 +626,112 @@ g_tls_certificate_gnutls_set_issuer (GTlsCertificateGnutls *gnutls,
   gnutls->priv->issuer = issuer;
   g_object_notify (G_OBJECT (gnutls), "issuer");
 }
+
+GBytes *
+g_tls_certificate_gnutls_get_bytes (GTlsCertificateGnutls *gnutls)
+{
+  GByteArray *array;
+
+  g_return_val_if_fail (G_IS_TLS_CERTIFICATE_GNUTLS (gnutls), NULL);
+
+  g_object_get (gnutls, "certificate", &array, NULL);
+  return g_byte_array_free_to_bytes (array);
+}
+
+static gnutls_x509_crt_t *
+convert_data_to_gnutls_certs (const gnutls_datum_t  *certs,
+                              guint                  num_certs,
+                              gnutls_x509_crt_fmt_t  format)
+{
+  gnutls_x509_crt_t *gnutls_certs;
+  guint i;
+
+  gnutls_certs = g_new (gnutls_x509_crt_t, num_certs);
+
+  for (i = 0; i < num_certs; i++)
+    {
+      if (gnutls_x509_crt_init (&gnutls_certs[i]) < 0)
+        {
+          i--;
+          goto error;
+        }
+    }
+
+  for (i = 0; i < num_certs; i++)
+    {
+      if (gnutls_x509_crt_import (gnutls_certs[i], &certs[i], format) < 0)
+        {
+          i = num_certs - 1;
+          goto error;
+        }
+    }
+
+  return gnutls_certs;
+
+error:
+  for (; i != G_MAXUINT; i--)
+    gnutls_x509_crt_deinit (gnutls_certs[i]);
+  g_free (gnutls_certs);
+  return NULL;
+}
+
+GTlsCertificateGnutls *
+g_tls_certificate_gnutls_build_chain (const gnutls_datum_t  *certs,
+                                      guint                  num_certs,
+                                      gnutls_x509_crt_fmt_t  format)
+{
+  GPtrArray *glib_certs;
+  gnutls_x509_crt_t *gnutls_certs;
+  GTlsCertificateGnutls *issuer;
+  GTlsCertificateGnutls *result;
+  guint i, j;
+
+  g_return_val_if_fail (certs, NULL);
+
+  gnutls_certs = convert_data_to_gnutls_certs (certs, num_certs, format);
+  if (!gnutls_certs)
+    return NULL;
+
+  glib_certs = g_ptr_array_new_full (num_certs, g_object_unref);
+  for (i = 0; i < num_certs; i++)
+    g_ptr_array_add (glib_certs, g_tls_certificate_gnutls_new (&certs[i], NULL));
+
+  /* Some servers send certs out of order, or will send duplicate
+   * certs, so we need to be careful when assigning the issuer of
+   * our new GTlsCertificateGnutls.
+   */
+  for (i = 0; i < num_certs; i++)
+    {
+      issuer = NULL;
+
+      if (i < num_certs - 1 &&
+          gnutls_x509_crt_check_issuer (gnutls_certs[i], gnutls_certs[i + 1]))
+        {
+          issuer = glib_certs->pdata[i + 1];
+        }
+      else
+        {
+          for (j = 0; j < num_certs; j++)
+            {
+              if (j != i &&
+                  gnutls_x509_crt_check_issuer (gnutls_certs[i], gnutls_certs[j]))
+                {
+                  issuer = glib_certs->pdata[j];
+                  break;
+                }
+            }
+        }
+
+      if (issuer)
+        g_tls_certificate_gnutls_set_issuer (glib_certs->pdata[i], issuer);
+    }
+
+  result = g_object_ref (glib_certs->pdata[0]);
+  g_ptr_array_unref (glib_certs);
+
+  for (i = 0; i < num_certs; i++)
+    gnutls_x509_crt_deinit (gnutls_certs[i]);
+  g_free (gnutls_certs);
+
+  return result;
+}