gnutls: Update for GTlsCertificate changes, implement verify
authorDan Winship <danw@gnome.org>
Wed, 1 Dec 2010 00:45:39 +0000 (19:45 -0500)
committerDan Winship <danw@gnome.org>
Mon, 6 Dec 2010 21:23:38 +0000 (22:23 +0100)
tls/gnutls/gtlscertificate-gnutls.c
tls/gnutls/gtlscertificate-gnutls.h
tls/gnutls/gtlsclientconnection-gnutls.c
tls/gnutls/gtlsconnection-gnutls.c

index 93580c2..29e992f 100644 (file)
 #include "gtlscertificate-gnutls.h"
 #include <glib/gi18n-lib.h>
 
-static void g_tls_certificate_gnutls_get_property (GObject      *object,
-                                                  guint         prop_id,
-                                                  GValue       *value,
-                                                  GParamSpec   *pspec);
-static void g_tls_certificate_gnutls_set_property (GObject      *object,
-                                                  guint         prop_id,
-                                                  const GValue *value,
-                                                  GParamSpec   *pspec);
-static void g_tls_certificate_gnutls_finalize     (GObject      *object);
-
 static void     g_tls_certificate_gnutls_initable_iface_init (GInitableIface  *iface);
-static gboolean g_tls_certificate_gnutls_initable_init       (GInitable       *initable,
-                                                             GCancellable    *cancellable,
-                                                             GError         **error);
 
 G_DEFINE_TYPE_WITH_CODE (GTlsCertificateGnutls, g_tls_certificate_gnutls, G_TYPE_TLS_CERTIFICATE,
                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
@@ -53,7 +40,8 @@ enum
   PROP_CERTIFICATE,
   PROP_CERTIFICATE_PEM,
   PROP_PRIVATE_KEY,
-  PROP_PRIVATE_KEY_PEM
+  PROP_PRIVATE_KEY_PEM,
+  PROP_ISSUER
 };
 
 struct _GTlsCertificateGnutlsPrivate
@@ -61,6 +49,8 @@ struct _GTlsCertificateGnutlsPrivate
   gnutls_x509_crt_t cert;
   gnutls_x509_privkey_t key;
 
+  GTlsCertificateGnutls *issuer;
+
   GError *construct_error;
 
   guint have_cert : 1;
@@ -68,23 +58,6 @@ struct _GTlsCertificateGnutlsPrivate
 };
 
 static void
-g_tls_certificate_gnutls_class_init (GTlsCertificateGnutlsClass *klass)
-{
-  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
-
-  g_type_class_add_private (klass, sizeof (GTlsCertificateGnutlsPrivate));
-
-  gobject_class->get_property = g_tls_certificate_gnutls_get_property;
-  gobject_class->set_property = g_tls_certificate_gnutls_set_property;
-  gobject_class->finalize     = g_tls_certificate_gnutls_finalize;
-
-  g_object_class_override_property (gobject_class, PROP_CERTIFICATE, "certificate");
-  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_PEM, "private-key-pem");
-}
-
-static void
 g_tls_certificate_gnutls_finalize (GObject *object)
 {
   GTlsCertificateGnutls *gnutls = G_TLS_CERTIFICATE_GNUTLS (object);
@@ -92,6 +65,9 @@ g_tls_certificate_gnutls_finalize (GObject *object)
   gnutls_x509_crt_deinit (gnutls->priv->cert);
   gnutls_x509_privkey_deinit (gnutls->priv->key);
 
+  if (gnutls->priv->issuer)
+    g_object_unref (gnutls->priv->issuer);
+
   g_clear_error (&gnutls->priv->construct_error);
 
   G_OBJECT_CLASS (g_tls_certificate_gnutls_parent_class)->finalize (object);
@@ -156,6 +132,10 @@ g_tls_certificate_gnutls_get_property (GObject    *object,
       g_value_take_string (value, certificate_pem);
       break;
 
+    case PROP_ISSUER:
+      g_value_set_object (value, gnutls->priv->issuer);
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
     }
@@ -256,6 +236,10 @@ g_tls_certificate_gnutls_set_property (GObject      *object,
        }
       break;
 
+    case PROP_ISSUER:
+      gnutls->priv->issuer = g_value_dup_object (value);
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
     }
@@ -272,12 +256,6 @@ g_tls_certificate_gnutls_init (GTlsCertificateGnutls *gnutls)
   gnutls_x509_privkey_init (&gnutls->priv->key);
 }
 
-static void
-g_tls_certificate_gnutls_initable_iface_init (GInitableIface  *iface)
-{
-  iface->init = g_tls_certificate_gnutls_initable_init;
-}
-
 static gboolean
 g_tls_certificate_gnutls_initable_init (GInitable       *initable,
                                        GCancellable    *cancellable,
@@ -301,6 +279,82 @@ g_tls_certificate_gnutls_initable_init (GInitable       *initable,
     return TRUE;
 }
 
+static GTlsCertificateFlags
+g_tls_certificate_gnutls_verify (GTlsCertificate     *cert,
+                                GSocketConnectable  *identity,
+                                GTlsCertificate     *trusted_ca)
+{
+  GTlsCertificateGnutls *cert_gnutls;
+  int status;
+  guint gnutls_flags, num_certs, i, num_cas;
+  gnutls_x509_crt_t *chain, ca;
+  GTlsCertificateFlags gtls_flags;
+  
+  cert_gnutls = G_TLS_CERTIFICATE_GNUTLS (cert);
+  for (num_certs = 0; cert_gnutls; cert_gnutls = cert_gnutls->priv->issuer)
+    num_certs++;
+  chain = g_new (gnutls_x509_crt_t, num_certs);
+  cert_gnutls = G_TLS_CERTIFICATE_GNUTLS (cert);
+  for (i = 0; cert_gnutls; cert_gnutls = cert_gnutls->priv->issuer, i++)
+    chain[i] = cert_gnutls->priv->cert;
+
+  if (trusted_ca)
+    {
+      cert_gnutls = G_TLS_CERTIFICATE_GNUTLS (trusted_ca);
+      ca = cert_gnutls->priv->cert;
+      num_cas = 1;
+    }
+  else
+    {
+      ca = NULL;
+      num_cas = 0;
+    }
+
+  status = gnutls_x509_crt_list_verify (chain, num_certs,
+                                       &ca, num_cas,
+                                       NULL, 0,
+                                       GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT,
+                                       &gnutls_flags);
+  g_free (chain);
+
+  if (status != 0)
+    return G_TLS_CERTIFICATE_GENERIC_ERROR;
+
+  gtls_flags = g_tls_certificate_gnutls_convert_flags (gnutls_flags);
+
+  if (identity)
+    gtls_flags |= g_tls_certificate_gnutls_verify_identity (G_TLS_CERTIFICATE_GNUTLS (cert), identity);
+
+  return gtls_flags;
+}
+
+static void
+g_tls_certificate_gnutls_class_init (GTlsCertificateGnutlsClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GTlsCertificateClass *certificate_class = G_TLS_CERTIFICATE_CLASS (klass);
+
+  g_type_class_add_private (klass, sizeof (GTlsCertificateGnutlsPrivate));
+
+  gobject_class->get_property = g_tls_certificate_gnutls_get_property;
+  gobject_class->set_property = g_tls_certificate_gnutls_set_property;
+  gobject_class->finalize     = g_tls_certificate_gnutls_finalize;
+
+  certificate_class->verify = g_tls_certificate_gnutls_verify;
+
+  g_object_class_override_property (gobject_class, PROP_CERTIFICATE, "certificate");
+  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_PEM, "private-key-pem");
+  g_object_class_override_property (gobject_class, PROP_ISSUER, "issuer");
+}
+
+static void
+g_tls_certificate_gnutls_initable_iface_init (GInitableIface  *iface)
+{
+  iface->init = g_tls_certificate_gnutls_initable_init;
+}
+
 GTlsCertificate *
 g_tls_certificate_gnutls_new (const gnutls_datum *datum,
                              GTlsCertificate    *issuer)
@@ -360,3 +414,71 @@ g_tls_certificate_gnutls_copy_key  (GTlsCertificateGnutls *gnutls)
   gnutls_x509_privkey_cpy (key, gnutls->priv->key);
   return key;
 }
+
+static const struct {
+  int gnutls_flag;
+  GTlsCertificateFlags gtls_flag;
+} flags_map[] = {
+  { GNUTLS_CERT_SIGNER_NOT_FOUND | GNUTLS_CERT_SIGNER_NOT_CA, G_TLS_CERTIFICATE_UNKNOWN_CA },
+  { GNUTLS_CERT_NOT_ACTIVATED, G_TLS_CERTIFICATE_NOT_ACTIVATED },
+  { GNUTLS_CERT_EXPIRED, G_TLS_CERTIFICATE_EXPIRED },
+  { GNUTLS_CERT_REVOKED, G_TLS_CERTIFICATE_REVOKED },
+  { GNUTLS_CERT_INSECURE_ALGORITHM, G_TLS_CERTIFICATE_INSECURE }
+};
+static const int flags_map_size = G_N_ELEMENTS (flags_map);
+
+GTlsCertificateFlags
+g_tls_certificate_gnutls_convert_flags (guint gnutls_flags)
+{
+  int i;
+  GTlsCertificateFlags gtls_flags;
+
+  /* Convert GNUTLS status to GTlsCertificateFlags. GNUTLS sets
+   * GNUTLS_CERT_INVALID if it sets any other flag, so we want to
+   * strip that out unless it's the only flag set. Then we convert
+   * specific flags we recognize, and if there are any flags left over
+   * at the end, we add G_TLS_CERTIFICATE_GENERIC_ERROR.
+   */
+  gtls_flags = 0;
+
+  if (gnutls_flags != GNUTLS_CERT_INVALID)
+    gnutls_flags = gnutls_flags & ~GNUTLS_CERT_INVALID;
+  for (i = 0; i < flags_map_size && gnutls_flags != 0; i++)
+    {
+      if (gnutls_flags & flags_map[i].gnutls_flag)
+       {
+         gnutls_flags &= ~flags_map[i].gnutls_flag;
+         gtls_flags |= flags_map[i].gtls_flag;
+       }
+    }
+  if (gnutls_flags)
+    gtls_flags |= G_TLS_CERTIFICATE_GENERIC_ERROR;
+
+  return gtls_flags;
+}
+
+GTlsCertificateFlags
+g_tls_certificate_gnutls_verify_identity (GTlsCertificateGnutls *gnutls,
+                                         GSocketConnectable    *identity)
+{
+  const char *hostname;
+
+  if (G_IS_NETWORK_ADDRESS (identity))
+    hostname = g_network_address_get_hostname (G_NETWORK_ADDRESS (identity));
+  else if (G_IS_NETWORK_SERVICE (identity))
+    hostname = g_network_service_get_domain (G_NETWORK_SERVICE (identity));
+  else
+    hostname = NULL;
+
+  if (hostname)
+    {
+      if (gnutls_x509_crt_check_hostname (gnutls->priv->cert, hostname))
+       return 0;
+    }
+
+  /* FIXME: check sRVName and uniformResourceIdentifier
+   * subjectAltNames, if appropriate for @identity.
+   */
+
+  return G_TLS_CERTIFICATE_BAD_IDENTITY;
+}
index 32f9058..5e2cc70 100644 (file)
@@ -41,14 +41,20 @@ struct _GTlsCertificateGnutls
 
 GType g_tls_certificate_gnutls_get_type (void) G_GNUC_CONST;
 
-GTlsCertificate *            g_tls_certificate_gnutls_new       (const gnutls_datum    *datum,
-                                                                GTlsCertificate       *issuer);
+GTlsCertificate *            g_tls_certificate_gnutls_new             (const gnutls_datum    *datum,
+                                                                      GTlsCertificate       *issuer);
 
-const gnutls_x509_crt_t      g_tls_certificate_gnutls_get_cert  (GTlsCertificateGnutls *gnutls);
-const gnutls_x509_privkey_t  g_tls_certificate_gnutls_get_key   (GTlsCertificateGnutls *gnutls);
+const gnutls_x509_crt_t      g_tls_certificate_gnutls_get_cert        (GTlsCertificateGnutls *gnutls);
+const gnutls_x509_privkey_t  g_tls_certificate_gnutls_get_key         (GTlsCertificateGnutls *gnutls);
+
+gnutls_x509_crt_t            g_tls_certificate_gnutls_copy_cert       (GTlsCertificateGnutls *gnutls);
+gnutls_x509_privkey_t        g_tls_certificate_gnutls_copy_key        (GTlsCertificateGnutls *gnutls);
+
+GTlsCertificateFlags         g_tls_certificate_gnutls_verify_identity (GTlsCertificateGnutls *gnutls,
+                                                                      GSocketConnectable    *identity);
+
+GTlsCertificateFlags         g_tls_certificate_gnutls_convert_flags   (guint                  gnutls_flags);
 
-gnutls_x509_crt_t            g_tls_certificate_gnutls_copy_cert (GTlsCertificateGnutls *gnutls);
-gnutls_x509_privkey_t        g_tls_certificate_gnutls_copy_key  (GTlsCertificateGnutls *gnutls);
 
 G_END_DECLS
 
index ebe871f..b4f1b38 100644 (file)
@@ -27,6 +27,7 @@
 #include <string.h>
 
 #include "gtlsclientconnection-gnutls.h"
+#include "gtlscertificate-gnutls.h"
 #include <glib/gi18n-lib.h>
 
 enum
@@ -164,6 +165,7 @@ g_tls_client_connection_gnutls_set_property (GObject      *object,
                                             GParamSpec   *pspec)
 {
   GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (object);
+  const char *hostname;
 
   switch (prop_id)
     {
@@ -177,11 +179,16 @@ g_tls_client_connection_gnutls_set_property (GObject      *object,
       gnutls->priv->server_identity = g_value_dup_object (value);
 
       if (G_IS_NETWORK_ADDRESS (gnutls->priv->server_identity))
+       hostname = g_network_address_get_hostname (G_NETWORK_ADDRESS (gnutls->priv->server_identity));
+      else if (G_IS_NETWORK_SERVICE (gnutls->priv->server_identity))
+       hostname = g_network_service_get_domain (G_NETWORK_SERVICE (gnutls->priv->server_identity));
+      else
+       hostname = NULL;
+
+      if (hostname)
        {
-         const char *hostname;
          gnutls_session_t session = g_tls_connection_gnutls_get_session (G_TLS_CONNECTION_GNUTLS (gnutls));
 
-         hostname = g_network_address_get_hostname (G_NETWORK_ADDRESS (gnutls->priv->server_identity));
          gnutls_server_name_set (session, GNUTLS_NAME_DNS,
                                  hostname, strlen (hostname));
        }
@@ -233,41 +240,24 @@ g_tls_client_connection_gnutls_retrieve_function (gnutls_session_t             s
 static gboolean
 validate_handshake (GTlsClientConnectionGnutls *gnutls)
 {
+  GTlsCertificate *peer;
   GTlsCertificateFlags errors;
   gboolean accepted;
 
+  peer = g_tls_connection_get_peer_certificate (G_TLS_CONNECTION (gnutls));
+
   errors = g_tls_connection_gnutls_validate_peer (G_TLS_CONNECTION_GNUTLS (gnutls));
 
-  /* FIXME: implement the full hostname/servicename/URI check
-   * according to draft-saintandre-tls-server-id-check
-   */
   if ((gnutls->priv->validation_flags & G_TLS_CERTIFICATE_BAD_IDENTITY) &&
-      gnutls->priv->server_identity &&
-      G_IS_NETWORK_ADDRESS (gnutls->priv->server_identity))
+      gnutls->priv->server_identity)
     {
-      gnutls_session session;
-      gnutls_x509_crt x509_cert;
-      const gnutls_datum_t *certs;
-      const char *hostname;
-      unsigned int num_certs;
-
-      session = g_tls_connection_gnutls_get_session (G_TLS_CONNECTION_GNUTLS (gnutls));
-      hostname = g_network_address_get_hostname (G_NETWORK_ADDRESS (gnutls->priv->server_identity));
-
-      gnutls_x509_crt_init (&x509_cert);
-      certs = gnutls_certificate_get_peers (session, &num_certs);
-      gnutls_x509_crt_import (x509_cert, &certs[0], GNUTLS_X509_FMT_DER);
-      if (!gnutls_x509_crt_check_hostname (x509_cert, hostname))
-       errors |= G_TLS_CERTIFICATE_BAD_IDENTITY;
-      gnutls_x509_crt_deinit (x509_cert);
+      errors |= g_tls_certificate_gnutls_verify_identity (G_TLS_CERTIFICATE_GNUTLS (peer),
+                                                         gnutls->priv->server_identity);
     }
 
   errors &= gnutls->priv->validation_flags;
   if (errors)
-    {
-      GTlsCertificate *peer = g_tls_connection_get_peer_certificate (G_TLS_CONNECTION (gnutls));
-      accepted = g_tls_connection_emit_accept_certificate (G_TLS_CONNECTION (gnutls), peer, errors);
-    }
+    accepted = g_tls_connection_emit_accept_certificate (G_TLS_CONNECTION (gnutls), peer, errors);
   else
     accepted = TRUE;
 
index 22bf34c..75685f8 100644 (file)
@@ -397,48 +397,13 @@ g_tls_connection_gnutls_get_certificate (GTlsConnectionGnutls *gnutls,
     st->ncerts = 0;
 }
 
-static const struct {
-  int gnutls_flag;
-  GTlsCertificateFlags gtls_flag;
-} flags_map[] = {
-  { GNUTLS_CERT_SIGNER_NOT_FOUND | GNUTLS_CERT_SIGNER_NOT_CA, G_TLS_CERTIFICATE_UNKNOWN_CA },
-  { GNUTLS_CERT_NOT_ACTIVATED, G_TLS_CERTIFICATE_NOT_ACTIVATED },
-  { GNUTLS_CERT_EXPIRED, G_TLS_CERTIFICATE_EXPIRED },
-  { GNUTLS_CERT_REVOKED, G_TLS_CERTIFICATE_REVOKED },
-  { GNUTLS_CERT_INSECURE_ALGORITHM, G_TLS_CERTIFICATE_INSECURE }
-};
-static const int flags_map_size = G_N_ELEMENTS (flags_map);
-
 GTlsCertificateFlags
 g_tls_connection_gnutls_validate_peer (GTlsConnectionGnutls *gnutls)
 {
-  int status, i;
-  GTlsCertificateFlags gtls_errors;
+  int status;
 
   status = gnutls_certificate_verify_peers (gnutls->priv->session);
-
-  /* Convert GNUTLS status to GTlsCertificateFlags. GNUTLS sets
-   * GNUTLS_CERT_INVALID if it sets any other flag, so we want to
-   * strip that out unless it's the only flag set. Then we convert
-   * specific flags we recognize, and if there are any flags left over
-   * at the end, we add G_TLS_CERTIFICATE_GENERIC_ERROR.
-   */
-  gtls_errors = 0;
-
-  if (status != GNUTLS_CERT_INVALID)
-    status = status & ~GNUTLS_CERT_INVALID;
-  for (i = 0; i < flags_map_size && status != 0; i++)
-    {
-      if (status & flags_map[i].gnutls_flag)
-       {
-         status &= ~flags_map[i].gnutls_flag;
-         gtls_errors |= flags_map[i].gtls_flag;
-       }
-    }
-  if (status)
-    gtls_errors |= G_TLS_CERTIFICATE_GENERIC_ERROR;
-
-  return gtls_errors;
+  return g_tls_certificate_gnutls_convert_flags (status);
 }
 
 static void