/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* * GIO - GLib Input, Output and Streaming Library * * Copyright 2010 Collabora, Ltd * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, see * . * * In addition, when the library is used with OpenSSL, a special * exception applies. Refer to the LICENSE_EXCEPTION file for details. * * Author: Stef Walter */ #include "config.h" #include "gtlsfiledatabase-gnutls.h" #include #include #include #include "gtlscertificate-gnutls.h" enum { PROP_0, PROP_ANCHORS, }; struct _GTlsFileDatabaseGnutls { GTlsDatabaseGnutls parent_instance; /* read-only after construct */ gchar *anchor_filename; gnutls_x509_trust_list_t trust_list; /* protected by mutex */ GMutex mutex; /* * These are hash tables of GBytes -> GPtrArray. The values of * the ptr array are full DER encoded certificate values. The keys are byte * arrays containing either subject DNs, issuer DNs, or full DER encoded certs */ GHashTable *subjects; GHashTable *issuers; /* * This is a table of GBytes -> GBytes. The values and keys are * DER encoded certificate values. */ GHashTable *complete; /* * This is a table of gchar * -> GPtrArray. The values of * the ptr array are full DER encoded certificate values. The keys are the * string handles. This array is populated on demand. */ GHashTable *handles; }; static void g_tls_file_database_gnutls_file_database_interface_init (GTlsFileDatabaseInterface *iface); static void g_tls_file_database_gnutls_initable_interface_init (GInitableIface *iface); G_DEFINE_TYPE_WITH_CODE (GTlsFileDatabaseGnutls, g_tls_file_database_gnutls, G_TYPE_TLS_DATABASE_GNUTLS, G_IMPLEMENT_INTERFACE (G_TYPE_TLS_FILE_DATABASE, g_tls_file_database_gnutls_file_database_interface_init); G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, g_tls_file_database_gnutls_initable_interface_init); ); static GHashTable * bytes_multi_table_new (void) { return g_hash_table_new_full (g_bytes_hash, g_bytes_equal, (GDestroyNotify)g_bytes_unref, (GDestroyNotify)g_ptr_array_unref); } static void bytes_multi_table_insert (GHashTable *table, GBytes *key, GBytes *value) { GPtrArray *multi; multi = g_hash_table_lookup (table, key); if (multi == NULL) { multi = g_ptr_array_new_with_free_func ((GDestroyNotify)g_bytes_unref); g_hash_table_insert (table, g_bytes_ref (key), multi); } g_ptr_array_add (multi, g_bytes_ref (value)); } static GBytes * bytes_multi_table_lookup_ref_one (GHashTable *table, GBytes *key) { GPtrArray *multi; multi = g_hash_table_lookup (table, key); if (multi == NULL) return NULL; g_assert (multi->len > 0); return g_bytes_ref (multi->pdata[0]); } static GList * bytes_multi_table_lookup_ref_all (GHashTable *table, GBytes *key) { GPtrArray *multi; GList *list = NULL; guint i; multi = g_hash_table_lookup (table, key); if (multi == NULL) return NULL; for (i = 0; i < multi->len; i++) list = g_list_prepend (list, g_bytes_ref (multi->pdata[i])); return g_list_reverse (list); } static gchar * create_handle_for_certificate (const gchar *filename, GBytes *der) { gchar *bookmark; gchar *uri_part; gchar *uri; /* * Here we create a URI that looks like: * file:///etc/ssl/certs/ca-certificates.crt#11b2641821252596420e468c275771f5e51022c121a17bd7a89a2f37b6336c8f */ uri_part = g_filename_to_uri (filename, NULL, NULL); if (!uri_part) return NULL; bookmark = g_compute_checksum_for_bytes (G_CHECKSUM_SHA256, der); uri = g_strconcat (uri_part, "#", bookmark, NULL); g_free (bookmark); g_free (uri_part); return uri; } static GHashTable * create_handles_array_unlocked (const gchar *filename, GHashTable *complete) { GHashTable *handles; GHashTableIter iter; GBytes *der; gchar *handle; handles = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_bytes_unref); g_hash_table_iter_init (&iter, complete); while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&der)) { handle = create_handle_for_certificate (filename, der); if (handle != NULL) g_hash_table_insert (handles, handle, g_bytes_ref (der)); } return handles; } static gboolean load_anchor_file (const gchar *filename, GHashTable *subjects, GHashTable *issuers, GHashTable *complete, GError **error) { GList *list, *l; gnutls_x509_crt_t cert; gnutls_datum_t dn; GBytes *der; GBytes *subject; GBytes *issuer; gint gerr; GError *my_error = NULL; list = g_tls_certificate_list_new_from_file (filename, &my_error); if (my_error) { g_propagate_error (error, my_error); return FALSE; } for (l = list; l; l = l->next) { cert = g_tls_certificate_gnutls_get_cert (l->data); gerr = gnutls_x509_crt_get_raw_dn (cert, &dn); if (gerr < 0) { g_warning ("failed to get subject of anchor certificate: %s", gnutls_strerror (gerr)); continue; } subject = g_bytes_new_with_free_func (dn.data, dn.size, gnutls_free, dn.data); gerr = gnutls_x509_crt_get_raw_issuer_dn (cert, &dn); if (gerr < 0) { g_warning ("failed to get issuer of anchor certificate: %s", gnutls_strerror (gerr)); continue; } issuer = g_bytes_new_with_free_func (dn.data, dn.size, gnutls_free, dn.data); der = g_tls_certificate_gnutls_get_bytes (l->data); g_return_val_if_fail (der != NULL, FALSE); /* Three different ways of looking up same certificate */ bytes_multi_table_insert (subjects, subject, der); bytes_multi_table_insert (issuers, issuer, der); g_hash_table_insert (complete, g_bytes_ref (der), g_bytes_ref (der)); g_bytes_unref (der); g_bytes_unref (subject); g_bytes_unref (issuer); g_object_unref (l->data); } g_list_free (list); return TRUE; } static void g_tls_file_database_gnutls_finalize (GObject *object) { GTlsFileDatabaseGnutls *self = G_TLS_FILE_DATABASE_GNUTLS (object); g_clear_pointer (&self->subjects, g_hash_table_destroy); g_clear_pointer (&self->issuers, g_hash_table_destroy); g_clear_pointer (&self->complete, g_hash_table_destroy); g_clear_pointer (&self->handles, g_hash_table_destroy); if (self->anchor_filename) { g_free (self->anchor_filename); gnutls_x509_trust_list_deinit (self->trust_list, 1); } g_mutex_clear (&self->mutex); G_OBJECT_CLASS (g_tls_file_database_gnutls_parent_class)->finalize (object); } static void g_tls_file_database_gnutls_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GTlsFileDatabaseGnutls *self = G_TLS_FILE_DATABASE_GNUTLS (object); switch (prop_id) { case PROP_ANCHORS: g_value_set_string (value, self->anchor_filename); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void g_tls_file_database_gnutls_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GTlsFileDatabaseGnutls *self = G_TLS_FILE_DATABASE_GNUTLS (object); const char *anchor_path; switch (prop_id) { case PROP_ANCHORS: anchor_path = g_value_get_string (value); if (anchor_path && !g_path_is_absolute (anchor_path)) { g_warning ("The anchor file name used with a GTlsFileDatabase " "must be an absolute path, and not relative: %s", anchor_path); return; } if (self->anchor_filename) { g_free (self->anchor_filename); gnutls_x509_trust_list_deinit (self->trust_list, 1); } self->anchor_filename = g_strdup (anchor_path); gnutls_x509_trust_list_init (&self->trust_list, 0); gnutls_x509_trust_list_add_trust_file (self->trust_list, anchor_path, NULL, GNUTLS_X509_FMT_PEM, 0, 0); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void g_tls_file_database_gnutls_init (GTlsFileDatabaseGnutls *self) { g_mutex_init (&self->mutex); } static gchar * g_tls_file_database_gnutls_create_certificate_handle (GTlsDatabase *database, GTlsCertificate *certificate) { GTlsFileDatabaseGnutls *self = G_TLS_FILE_DATABASE_GNUTLS (database); GBytes *der; gboolean contains; gchar *handle = NULL; der = g_tls_certificate_gnutls_get_bytes (G_TLS_CERTIFICATE_GNUTLS (certificate)); g_return_val_if_fail (der != NULL, FALSE); g_mutex_lock (&self->mutex); /* At the same time look up whether this certificate is in list */ contains = g_hash_table_lookup (self->complete, der) ? TRUE : FALSE; g_mutex_unlock (&self->mutex); /* Certificate is in the database */ if (contains) handle = create_handle_for_certificate (self->anchor_filename, der); g_bytes_unref (der); return handle; } static GTlsCertificate * g_tls_file_database_gnutls_lookup_certificate_for_handle (GTlsDatabase *database, const gchar *handle, GTlsInteraction *interaction, GTlsDatabaseLookupFlags flags, GCancellable *cancellable, GError **error) { GTlsFileDatabaseGnutls *self = G_TLS_FILE_DATABASE_GNUTLS (database); GTlsCertificate *cert; GBytes *der; gnutls_datum_t datum; gsize length; if (g_cancellable_set_error_if_cancelled (cancellable, error)) return NULL; if (!handle) return NULL; g_mutex_lock (&self->mutex); /* Create the handles table if not already done */ if (!self->handles) self->handles = create_handles_array_unlocked (self->anchor_filename, self->complete); der = g_hash_table_lookup (self->handles, handle); if (der != NULL) g_bytes_ref (der); g_mutex_unlock (&self->mutex); if (der == NULL) return NULL; datum.data = (unsigned char *)g_bytes_get_data (der, &length); datum.size = length; if (g_cancellable_set_error_if_cancelled (cancellable, error)) cert = NULL; else cert = g_tls_certificate_gnutls_new (&datum, NULL); g_bytes_unref (der); return cert; } static GTlsCertificate * g_tls_file_database_gnutls_lookup_certificate_issuer (GTlsDatabase *database, GTlsCertificate *certificate, GTlsInteraction *interaction, GTlsDatabaseLookupFlags flags, GCancellable *cancellable, GError **error) { GTlsFileDatabaseGnutls *self = G_TLS_FILE_DATABASE_GNUTLS (database); gnutls_datum_t dn = { NULL, 0 }; GBytes *subject, *der; gnutls_datum_t datum; GTlsCertificate *issuer = NULL; gnutls_x509_crt_t cert; gsize length; int gerr; g_return_val_if_fail (G_IS_TLS_CERTIFICATE_GNUTLS (certificate), NULL); if (g_cancellable_set_error_if_cancelled (cancellable, error)) return NULL; if (flags & G_TLS_DATABASE_LOOKUP_KEYPAIR) return NULL; /* Dig out the issuer of this certificate */ cert = g_tls_certificate_gnutls_get_cert (G_TLS_CERTIFICATE_GNUTLS (certificate)); gerr = gnutls_x509_crt_get_raw_issuer_dn (cert, &dn); if (gerr < 0) { g_warning ("failed to get issuer of certificate: %s", gnutls_strerror (gerr)); return NULL; } subject = g_bytes_new_with_free_func (dn.data, dn.size, gnutls_free, dn.data); /* Find the full DER value of the certificate */ g_mutex_lock (&self->mutex); der = bytes_multi_table_lookup_ref_one (self->subjects, subject); g_mutex_unlock (&self->mutex); g_bytes_unref (subject); if (g_cancellable_set_error_if_cancelled (cancellable, error)) { issuer = NULL; } else if (der != NULL) { datum.data = (unsigned char *)g_bytes_get_data (der, &length); datum.size = length; issuer = g_tls_certificate_gnutls_new (&datum, NULL); } if (der != NULL) g_bytes_unref (der); return issuer; } static GList * g_tls_file_database_gnutls_lookup_certificates_issued_by (GTlsDatabase *database, GByteArray *issuer_raw_dn, GTlsInteraction *interaction, GTlsDatabaseLookupFlags flags, GCancellable *cancellable, GError **error) { GTlsFileDatabaseGnutls *self = G_TLS_FILE_DATABASE_GNUTLS (database); GBytes *issuer; gnutls_datum_t datum; GList *issued = NULL; GList *ders; gsize length; GList *l; if (g_cancellable_set_error_if_cancelled (cancellable, error)) return NULL; /* We don't have any private keys here */ if (flags & G_TLS_DATABASE_LOOKUP_KEYPAIR) return NULL; issuer = g_bytes_new_static (issuer_raw_dn->data, issuer_raw_dn->len); /* Find the full DER value of the certificate */ g_mutex_lock (&self->mutex); ders = bytes_multi_table_lookup_ref_all (self->issuers, issuer); g_mutex_unlock (&self->mutex); g_bytes_unref (issuer); for (l = ders; l != NULL; l = g_list_next (l)) { if (g_cancellable_set_error_if_cancelled (cancellable, error)) { g_list_free_full (issued, g_object_unref); issued = NULL; break; } datum.data = (unsigned char *)g_bytes_get_data (l->data, &length); datum.size = length; issued = g_list_prepend (issued, g_tls_certificate_gnutls_new (&datum, NULL)); } g_list_free_full (ders, (GDestroyNotify)g_bytes_unref); return issued; } static void convert_certificate_chain_to_gnutls (GTlsCertificateGnutls *chain, gnutls_x509_crt_t **gnutls_chain, guint *gnutls_chain_length) { GTlsCertificate *cert; guint i; g_assert (gnutls_chain); g_assert (gnutls_chain_length); for (*gnutls_chain_length = 0, cert = G_TLS_CERTIFICATE (chain); cert; cert = g_tls_certificate_get_issuer (cert)) ++(*gnutls_chain_length); *gnutls_chain = g_new0 (gnutls_x509_crt_t, *gnutls_chain_length); for (i = 0, cert = G_TLS_CERTIFICATE (chain); cert; cert = g_tls_certificate_get_issuer (cert), ++i) (*gnutls_chain)[i] = g_tls_certificate_gnutls_get_cert (G_TLS_CERTIFICATE_GNUTLS (cert)); g_assert (i == *gnutls_chain_length); } static GTlsCertificateFlags g_tls_file_database_gnutls_verify_chain (GTlsDatabase *database, GTlsCertificate *chain, const gchar *purpose, GSocketConnectable *identity, GTlsInteraction *interaction, GTlsDatabaseVerifyFlags flags, GCancellable *cancellable, GError **error) { GTlsFileDatabaseGnutls *self; GTlsCertificateFlags result; guint gnutls_result; gnutls_x509_crt_t *certs; guint certs_length; const char *hostname = NULL; char *free_hostname = NULL; int gerr; g_return_val_if_fail (G_IS_TLS_CERTIFICATE_GNUTLS (chain), G_TLS_CERTIFICATE_GENERIC_ERROR); g_assert (purpose); if (g_cancellable_set_error_if_cancelled (cancellable, error)) return G_TLS_CERTIFICATE_GENERIC_ERROR; self = G_TLS_FILE_DATABASE_GNUTLS (database); convert_certificate_chain_to_gnutls (G_TLS_CERTIFICATE_GNUTLS (chain), &certs, &certs_length); gerr = gnutls_x509_trust_list_verify_crt (self->trust_list, certs, certs_length, 0, &gnutls_result, NULL); if (gerr != 0 || g_cancellable_set_error_if_cancelled (cancellable, error)) { g_free (certs); return G_TLS_CERTIFICATE_GENERIC_ERROR; } result = g_tls_certificate_gnutls_convert_flags (gnutls_result); 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 if (G_IS_INET_SOCKET_ADDRESS (identity)) { GInetAddress *addr; addr = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (identity)); hostname = free_hostname = g_inet_address_to_string (addr); } if (hostname) { if (!gnutls_x509_crt_check_hostname (certs[0], hostname)) result |= G_TLS_CERTIFICATE_BAD_IDENTITY; g_free (free_hostname); } g_free (certs); return result; } static void g_tls_file_database_gnutls_class_init (GTlsFileDatabaseGnutlsClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GTlsDatabaseClass *database_class = G_TLS_DATABASE_CLASS (klass); gobject_class->get_property = g_tls_file_database_gnutls_get_property; gobject_class->set_property = g_tls_file_database_gnutls_set_property; gobject_class->finalize = g_tls_file_database_gnutls_finalize; database_class->create_certificate_handle = g_tls_file_database_gnutls_create_certificate_handle; database_class->lookup_certificate_for_handle = g_tls_file_database_gnutls_lookup_certificate_for_handle; database_class->lookup_certificate_issuer = g_tls_file_database_gnutls_lookup_certificate_issuer; database_class->lookup_certificates_issued_by = g_tls_file_database_gnutls_lookup_certificates_issued_by; database_class->verify_chain = g_tls_file_database_gnutls_verify_chain; g_object_class_override_property (gobject_class, PROP_ANCHORS, "anchors"); } static void g_tls_file_database_gnutls_file_database_interface_init (GTlsFileDatabaseInterface *iface) { } static gboolean g_tls_file_database_gnutls_initable_init (GInitable *initable, GCancellable *cancellable, GError **error) { GTlsFileDatabaseGnutls *self = G_TLS_FILE_DATABASE_GNUTLS (initable); GHashTable *subjects, *issuers, *complete; gboolean result; if (g_cancellable_set_error_if_cancelled (cancellable, error)) return FALSE; subjects = bytes_multi_table_new (); issuers = bytes_multi_table_new (); complete = g_hash_table_new_full (g_bytes_hash, g_bytes_equal, (GDestroyNotify)g_bytes_unref, (GDestroyNotify)g_bytes_unref); if (self->anchor_filename) result = load_anchor_file (self->anchor_filename, subjects, issuers, complete, error); else result = TRUE; if (g_cancellable_set_error_if_cancelled (cancellable, error)) result = FALSE; if (result) { g_mutex_lock (&self->mutex); if (!self->subjects) { self->subjects = subjects; subjects = NULL; } if (!self->issuers) { self->issuers = issuers; issuers = NULL; } if (!self->complete) { self->complete = complete; complete = NULL; } g_mutex_unlock (&self->mutex); } if (subjects != NULL) g_hash_table_unref (subjects); if (issuers != NULL) g_hash_table_unref (issuers); if (complete != NULL) g_hash_table_unref (complete); return result; } static void g_tls_file_database_gnutls_initable_interface_init (GInitableIface *iface) { iface->init = g_tls_file_database_gnutls_initable_init; }