]> git.0d.be Git - empathy.git/blobdiff - libempathy/empathy-tls-verifier.c
Merge branch 'sasl'
[empathy.git] / libempathy / empathy-tls-verifier.c
index 07beda63bddbf797e457251f84004aae095749eb..ca1726144cd33c3f29b8bdabd8b18f3dadcd0701 100644 (file)
@@ -44,7 +44,7 @@ enum {
 };
 
 static const gchar* system_ca_paths[] = {
-  "/etc/ssl/certs/ca-certificates.crt",
+  GTLS_SYSTEM_CA_FILE,
   NULL,
 };
 
@@ -58,6 +58,7 @@ typedef struct {
   gchar *hostname;
 
   GSimpleAsyncResult *verify_result;
+  GHashTable *details;
 
   gboolean dispose_run;
 } EmpathyTLSVerifierPriv;
@@ -83,6 +84,8 @@ verification_output_to_reason (gint res,
 {
   gboolean retval = TRUE;
 
+  g_assert (reason != NULL);
+
   if (res != GNUTLS_E_SUCCESS)
     {
       retval = FALSE;
@@ -182,6 +185,8 @@ verify_certificate (EmpathyTLSVerifier *self,
 
   res = gnutls_x509_crt_verify (cert, &issuer, 1, 0, &verify_output);
 
+  DEBUG ("Verifying %p against %p, output %u", cert, issuer, verify_output);
+
   return verification_output_to_reason (res, verify_output, reason);
 }
 
@@ -194,7 +199,7 @@ complete_verification (EmpathyTLSVerifier *self)
 
   g_simple_async_result_complete_in_idle (priv->verify_result);
 
-  tp_clear_object (&priv->verify_result);  
+  tp_clear_object (&priv->verify_result);
 }
 
 static void
@@ -213,35 +218,6 @@ abort_verification (EmpathyTLSVerifier *self,
   tp_clear_object (&priv->verify_result);
 }
 
-static gchar *
-get_certified_hostname (gnutls_x509_crt_t cert)
-{
-  gchar dns_name[256];
-  gsize dns_name_size;
-  gint idx;
-  gint res = 0;
-
-  /* this is taken from GnuTLS */
-  for (idx = 0; res >= 0; idx++)
-    {
-      dns_name_size = sizeof (dns_name);
-      res = gnutls_x509_crt_get_subject_alt_name (cert, idx,
-          dns_name, &dns_name_size, NULL);
-
-      if (res == GNUTLS_SAN_DNSNAME || res == GNUTLS_SAN_IPADDRESS)
-        return g_strndup (dns_name, dns_name_size);
-    }
-
-  dns_name_size = sizeof (dns_name);
-  res = gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_X520_COMMON_NAME,
-      0, 0, dns_name, &dns_name_size);
-
-  if (res >= 0)
-    return g_strndup (dns_name, dns_name_size);
-
-  return NULL;
-}
-
 static void
 real_start_verification (EmpathyTLSVerifier *self)
 {
@@ -261,13 +237,16 @@ real_start_verification (EmpathyTLSVerifier *self)
     {
       gchar *certified_hostname;
 
-      certified_hostname = get_certified_hostname (first_cert);
+      reason = EMP_TLS_CERTIFICATE_REJECT_REASON_HOSTNAME_MISMATCH;
+      certified_hostname = empathy_get_x509_certificate_hostname (first_cert);
+      tp_asv_set_string (priv->details,
+          "expected-hostname", priv->hostname);
+      tp_asv_set_string (priv->details,
+          "certificate-hostname", certified_hostname);
+
       DEBUG ("Hostname mismatch: got %s but expected %s",
           certified_hostname, priv->hostname);
 
-      /* TODO: pass-through the expected hostname in the reject details */
-      reason = EMP_TLS_CERTIFICATE_REJECT_REASON_HOSTNAME_MISMATCH;
-
       g_free (certified_hostname);
       goto out;
     }
@@ -281,10 +260,13 @@ real_start_verification (EmpathyTLSVerifier *self)
       /* if the last certificate is self-signed, and we have a list of
        * trusted CAs, ignore it, as we want to check the chain against our
        * trusted CAs list first.
+       * if we have only one certificate in the chain, don't ignore it though,
+       * as it's the CA certificate itself.
        */
       last_cert = g_ptr_array_index (priv->cert_chain, num_certs - 1);
 
-      if (gnutls_x509_crt_check_issuer (last_cert, last_cert) > 0)
+      if (gnutls_x509_crt_check_issuer (last_cert, last_cert) > 0 &&
+          num_certs > 1)
         num_certs--;
     }
 
@@ -344,7 +326,8 @@ build_gnutls_cert_list (EmpathyTLSVerifier *self)
       NULL);
   num_certs = certificate_data->len;
 
-  priv->cert_chain = g_ptr_array_sized_new (num_certs);
+  priv->cert_chain = g_ptr_array_new_with_free_func (
+      (GDestroyNotify) gnutls_x509_crt_deinit);
 
   for (idx = 0; idx < num_certs; idx++)
     {
@@ -368,20 +351,21 @@ get_number_and_type_of_certificates (gnutls_datum_t *datum,
     gnutls_x509_crt_fmt_t *format)
 {
   gnutls_x509_crt_t fake;
-  gint retval = 1;
+  guint retval = 1;
   gint res;
 
-  res = gnutls_x509_crt_list_import (&fake, (guint *) &retval, datum,
-      GNUTLS_X509_FMT_PEM, 0);
+  res = gnutls_x509_crt_list_import (&fake, &retval, datum,
+      GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED);
 
   if (res == GNUTLS_E_SHORT_MEMORY_BUFFER || res > 0)
     {
+      DEBUG ("Found PEM, with %u certificates", retval);
       *format = GNUTLS_X509_FMT_PEM;
       return retval;
     }
 
   /* try DER */
-  res = gnutls_x509_crt_list_import (&fake, (guint *) &retval, datum,
+  res = gnutls_x509_crt_list_import (&fake, &retval, datum,
       GNUTLS_X509_FMT_DER, 0);
 
   if (res > 0)
@@ -399,10 +383,14 @@ build_gnutls_ca_and_crl_lists (GIOSchedulerJob *job,
     gpointer user_data)
 {
   gint idx;
+  gchar *user_certs_dir;
+  GDir *dir;
+  GError *error = NULL;
   EmpathyTLSVerifier *self = user_data;
   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
 
-  priv->trusted_ca_list = g_ptr_array_new ();
+  priv->trusted_ca_list = g_ptr_array_new_with_free_func
+    ((GDestroyNotify) gnutls_x509_crt_deinit);
 
   for (idx = 0; idx < (gint) G_N_ELEMENTS (system_ca_paths) - 1; idx++)
     {
@@ -413,15 +401,15 @@ build_gnutls_ca_and_crl_lists (GIOSchedulerJob *job,
       gnutls_x509_crt_t *cert_list;
       gnutls_datum_t datum = { NULL, 0 };
       gnutls_x509_crt_fmt_t format = 0;
-      GError *error = NULL;
 
       path = system_ca_paths[idx];
       g_file_get_contents (path, &contents, &length, &error);
 
       if (error != NULL)
         {
-          DEBUG ("Unable to read system CAs from path %s", path);
-          g_error_free (error);
+          DEBUG ("Unable to read system CAs from path %s: %s", path,
+              error->message);
+          g_clear_error (&error);
           continue;
         }
 
@@ -459,7 +447,71 @@ build_gnutls_ca_and_crl_lists (GIOSchedulerJob *job,
         g_ptr_array_add (priv->trusted_ca_list, cert_list[idx]);
 
       g_free (contents);
+      g_free (cert_list);
+    }
+
+  /* user certs */
+  user_certs_dir = g_build_filename (g_get_user_config_dir (),
+      "telepathy", "certs", NULL);
+  dir = g_dir_open (user_certs_dir, 0, &error);
+
+  if (error != NULL)
+    {
+      DEBUG ("Can't open the user certs dir at %s: %s", user_certs_dir,
+          error->message);
+
+      g_error_free (error);
     }
+  else
+    {
+      const gchar *cert_name;
+
+      while ((cert_name = g_dir_read_name (dir)) != NULL)
+        {
+          gchar *contents = NULL, *cert_path = NULL;
+          gsize length = 0;
+          gint res;
+          gnutls_datum_t datum = { NULL, 0 };
+          gnutls_x509_crt_t cert;
+
+          cert_path = g_build_filename (user_certs_dir, cert_name, NULL);
+
+          g_file_get_contents (cert_path, &contents, &length, &error);
+
+          if (error != NULL)
+            {
+              DEBUG ("Can't open the certificate file at path %s: %s",
+                  cert_path, error->message);
+
+              g_clear_error (&error);
+              g_free (cert_path);
+              continue;
+            }
+
+          datum.data = (guchar *) contents;
+          datum.size = length;
+
+          gnutls_x509_crt_init (&cert);
+          res = gnutls_x509_crt_import (cert, &datum, GNUTLS_X509_FMT_PEM);
+
+          if (res != GNUTLS_E_SUCCESS)
+            {
+              DEBUG ("Can't import the certificate at path %s: "
+                  "GnuTLS returned %d", cert_path, res);
+            }
+          else
+            {
+              g_ptr_array_add (priv->trusted_ca_list, cert);
+            }
+
+          g_free (contents);
+          g_free (cert_path);
+        }
+
+      g_dir_close (dir);
+    }
+
+  g_free (user_certs_dir);
 
   /* TODO: do the CRL too */
 
@@ -534,7 +586,10 @@ empathy_tls_verifier_finalize (GObject *object)
   EmpathyTLSVerifierPriv *priv = GET_PRIV (object);
 
   DEBUG ("%p", object);
-  
+
+  tp_clear_pointer (&priv->trusted_ca_list, g_ptr_array_unref);
+  tp_clear_pointer (&priv->cert_chain, g_ptr_array_unref);
+  tp_clear_boxed (G_TYPE_HASH_TABLE, &priv->details);
   g_free (priv->hostname);
 
   G_OBJECT_CLASS (empathy_tls_verifier_parent_class)->finalize (object);
@@ -546,7 +601,7 @@ empathy_tls_verifier_constructed (GObject *object)
   EmpathyTLSVerifier *self = EMPATHY_TLS_VERIFIER (object);
 
   build_gnutls_cert_list (self);
-  
+
   if (G_OBJECT_CLASS (empathy_tls_verifier_parent_class)->constructed != NULL)
     G_OBJECT_CLASS (empathy_tls_verifier_parent_class)->constructed (object);
 }
@@ -554,8 +609,11 @@ empathy_tls_verifier_constructed (GObject *object)
 static void
 empathy_tls_verifier_init (EmpathyTLSVerifier *self)
 {
-  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+  EmpathyTLSVerifierPriv *priv;
+
+  priv = self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
       EMPATHY_TYPE_TLS_VERIFIER, EmpathyTLSVerifierPriv);
+  priv->details = tp_asv_new (NULL, NULL);
 }
 
 static void
@@ -605,6 +663,8 @@ empathy_tls_verifier_verify_async (EmpathyTLSVerifier *self,
 {
   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
 
+  g_return_if_fail (priv->verify_result == NULL);
+
   priv->verify_result = g_simple_async_result_new (G_OBJECT (self),
       callback, user_data, NULL);
 
@@ -616,15 +676,30 @@ gboolean
 empathy_tls_verifier_verify_finish (EmpathyTLSVerifier *self,
     GAsyncResult *res,
     EmpTLSCertificateRejectReason *reason,
+    GHashTable **details,
     GError **error)
 {
+  EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
+
   if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res),
           error))
     {
-      *reason = (*error)->code;
+      if (reason != NULL)
+        *reason = (*error)->code;
+
+      if (details != NULL)
+        {
+          *details = tp_asv_new (NULL, NULL);
+          tp_g_hash_table_update (*details, priv->details,
+              (GBoxedCopyFunc) g_strdup,
+              (GBoxedCopyFunc) tp_g_value_slice_dup);
+        }
+
       return FALSE;
     }
 
-  *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN;
+  if (reason != NULL)
+    *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN;
+
   return TRUE;
 }