Imported Upstream version 2.34.0
[platform/upstream/glib-networking.git] / tls / gnutls / gtlsbackend-gnutls.c
1 /* GIO - GLib Input, Output and Streaming Library
2  *
3  * Copyright 2010 Red Hat, Inc
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General
16  * Public License along with this library; if not, see
17  * <http://www.gnu.org/licenses/>.
18  */
19
20 #include "config.h"
21 #include "glib.h"
22
23 #include <errno.h>
24 #include <string.h>
25
26 #include <gnutls/gnutls.h>
27
28 #include "gtlsbackend-gnutls.h"
29 #include "gtlscertificate-gnutls.h"
30 #include "gtlsclientconnection-gnutls.h"
31 #include "gtlsfiledatabase-gnutls.h"
32 #include "gtlsserverconnection-gnutls.h"
33
34 struct _GTlsBackendGnutlsPrivate
35 {
36   GMutex mutex;
37   GTlsDatabase *default_database;
38 };
39
40 static void g_tls_backend_gnutls_interface_init (GTlsBackendInterface *iface);
41
42 G_DEFINE_DYNAMIC_TYPE_EXTENDED (GTlsBackendGnutls, g_tls_backend_gnutls, G_TYPE_OBJECT, 0,
43                                 G_IMPLEMENT_INTERFACE_DYNAMIC (G_TYPE_TLS_BACKEND,
44                                                                g_tls_backend_gnutls_interface_init);)
45
46 #ifdef GTLS_GNUTLS_DEBUG
47 static void
48 gtls_log_func (int level, const char *msg)
49 {
50   g_print ("GTLS: %s", msg);
51 }
52 #endif
53
54 static gpointer
55 gtls_gnutls_init (gpointer data)
56 {
57   gnutls_global_init ();
58
59 #ifdef GTLS_GNUTLS_DEBUG
60   gnutls_global_set_log_function (gtls_log_func);
61   gnutls_global_set_log_level (9);
62 #endif
63
64   /* Leak the module to keep it from being unloaded. */
65   g_type_plugin_use (g_type_get_plugin (G_TYPE_TLS_BACKEND_GNUTLS));
66   return NULL;
67 }
68
69 static GOnce gnutls_inited = G_ONCE_INIT;
70
71 static void
72 g_tls_backend_gnutls_init (GTlsBackendGnutls *backend)
73 {
74   /* Once we call gtls_gnutls_init(), we can't allow the module to be
75    * unloaded (since if gnutls gets unloaded but gcrypt doesn't, then
76    * gcrypt will have dangling pointers to gnutls's mutex functions).
77    * So we initialize it from here rather than at class init time so
78    * that it doesn't happen unless the app is actually using TLS (as
79    * opposed to just calling g_io_modules_scan_all_in_directory()).
80    */
81   g_once (&gnutls_inited, gtls_gnutls_init, NULL);
82
83   backend->priv = G_TYPE_INSTANCE_GET_PRIVATE (backend, G_TYPE_TLS_BACKEND_GNUTLS, GTlsBackendGnutlsPrivate);
84   g_mutex_init (&backend->priv->mutex);
85 }
86
87 static void
88 g_tls_backend_gnutls_finalize (GObject *object)
89 {
90   GTlsBackendGnutls *backend = G_TLS_BACKEND_GNUTLS (object);
91
92   if (backend->priv->default_database)
93     g_object_unref (backend->priv->default_database);
94   g_mutex_clear (&backend->priv->mutex);
95
96   G_OBJECT_CLASS (g_tls_backend_gnutls_parent_class)->finalize (object);
97 }
98
99 static GTlsDatabase*
100 g_tls_backend_gnutls_real_create_database (GTlsBackendGnutls  *self,
101                                            GError            **error)
102 {
103   const gchar *anchor_file = NULL;
104 #ifdef GTLS_SYSTEM_CA_FILE
105   anchor_file = GTLS_SYSTEM_CA_FILE;
106 #endif
107   return g_tls_file_database_new (anchor_file, error);
108 }
109
110 static void
111 g_tls_backend_gnutls_class_init (GTlsBackendGnutlsClass *backend_class)
112 {
113   GObjectClass *gobject_class = G_OBJECT_CLASS (backend_class);
114   gobject_class->finalize = g_tls_backend_gnutls_finalize;
115   backend_class->create_database = g_tls_backend_gnutls_real_create_database;
116   g_type_class_add_private (backend_class, sizeof (GTlsBackendGnutlsPrivate));
117 }
118
119 static void
120 g_tls_backend_gnutls_class_finalize (GTlsBackendGnutlsClass *backend_class)
121 {
122 }
123
124 static GTlsDatabase*
125 g_tls_backend_gnutls_get_default_database (GTlsBackend *backend)
126 {
127   GTlsBackendGnutls *self = G_TLS_BACKEND_GNUTLS (backend);
128   GTlsDatabase *result;
129   GError *error = NULL;
130
131   g_mutex_lock (&self->priv->mutex);
132
133   if (self->priv->default_database)
134     {
135       result = g_object_ref (self->priv->default_database);
136     }
137   else
138     {
139       g_assert (G_TLS_BACKEND_GNUTLS_GET_CLASS (self)->create_database);
140       result = G_TLS_BACKEND_GNUTLS_GET_CLASS (self)->create_database (self, &error);
141       if (error)
142         {
143           g_warning ("couldn't load TLS file database: %s",
144                      error->message);
145           g_clear_error (&error);
146         }
147       else
148         {
149           g_assert (result);
150           self->priv->default_database = g_object_ref (result);
151         }
152     }
153
154   g_mutex_unlock (&self->priv->mutex);
155
156   return result;
157 }
158
159 static void
160 g_tls_backend_gnutls_interface_init (GTlsBackendInterface *iface)
161 {
162   iface->get_certificate_type       = g_tls_certificate_gnutls_get_type;
163   iface->get_client_connection_type = g_tls_client_connection_gnutls_get_type;
164   iface->get_server_connection_type = g_tls_server_connection_gnutls_get_type;
165   iface->get_file_database_type =     g_tls_file_database_gnutls_get_type;
166   iface->get_default_database =       g_tls_backend_gnutls_get_default_database;
167 }
168
169 /* Session cache support; all the details are sort of arbitrary. Note
170  * that having session_cache_cleanup() be a little bit slow isn't the
171  * end of the world, since it will still be faster than the network
172  * is. (NSS uses a linked list for its cache...)
173  */
174
175 G_LOCK_DEFINE_STATIC (session_cache_lock);
176 GHashTable *client_session_cache, *server_session_cache;
177
178 #define SESSION_CACHE_MAX_SIZE 50
179 #define SESSION_CACHE_MAX_AGE (60 * 60) /* one hour */
180
181 typedef struct {
182   GBytes *session_id;
183   GBytes *session_data;
184   time_t  last_used;
185 } GTlsBackendGnutlsCacheData;
186
187 static void
188 session_cache_cleanup (GHashTable *cache)
189 {
190   GHashTableIter iter;
191   gpointer key, value;
192   GTlsBackendGnutlsCacheData *cache_data;
193   time_t expired = time (NULL) - SESSION_CACHE_MAX_AGE;
194
195   g_hash_table_iter_init (&iter, cache);
196   while (g_hash_table_iter_next (&iter, &key, &value))
197     {
198       cache_data = value;
199       if (cache_data->last_used < expired)
200         g_hash_table_iter_remove (&iter);
201     }
202 }
203
204 static void
205 cache_data_free (gpointer data)
206 {
207   GTlsBackendGnutlsCacheData *cache_data = data;
208
209   g_bytes_unref (cache_data->session_id);
210   g_bytes_unref (cache_data->session_data);
211   g_slice_free (GTlsBackendGnutlsCacheData, cache_data);
212 }
213
214 static GHashTable *
215 get_session_cache (gnutls_connection_end_t type,
216                    gboolean                create)
217 {
218   GHashTable **cache_p;
219
220   cache_p = (type == GNUTLS_CLIENT) ? &client_session_cache : &server_session_cache;
221   if (!*cache_p && create)
222     {
223       *cache_p = g_hash_table_new_full (g_bytes_hash, g_bytes_equal,
224                                         NULL, cache_data_free);
225     }
226   return *cache_p;
227 }
228
229 void
230 g_tls_backend_gnutls_store_session (gnutls_connection_end_t  type,
231                                     GBytes                  *session_id,
232                                     GBytes                  *session_data)
233 {
234   GTlsBackendGnutlsCacheData *cache_data;
235   GHashTable *cache;
236
237   G_LOCK (session_cache_lock);
238
239   cache = get_session_cache (type, TRUE);
240   cache_data = g_hash_table_lookup (cache, session_id);
241   if (cache_data)
242     {
243       if (!g_bytes_equal (cache_data->session_data, session_data))
244         {
245           g_bytes_unref (cache_data->session_data);
246           cache_data->session_data = g_bytes_ref (session_data);
247         }
248     }
249   else
250     {
251       if (g_hash_table_size (cache) >= SESSION_CACHE_MAX_SIZE)
252         session_cache_cleanup (cache);
253
254       cache_data = g_slice_new (GTlsBackendGnutlsCacheData);
255       cache_data->session_id = g_bytes_ref (session_id);
256       cache_data->session_data = g_bytes_ref (session_data);
257
258       g_hash_table_insert (cache, cache_data->session_id, cache_data);
259     }
260   cache_data->last_used = time (NULL);
261
262   G_UNLOCK (session_cache_lock);
263 }
264
265 void
266 g_tls_backend_gnutls_remove_session (gnutls_connection_end_t  type,
267                                      GBytes                  *session_id)
268 {
269   GHashTable *cache;
270
271   G_LOCK (session_cache_lock);
272
273   cache = get_session_cache (type, FALSE);
274   if (cache)
275     g_hash_table_remove (cache, session_id);
276
277   G_UNLOCK (session_cache_lock);
278 }
279
280 GBytes *
281 g_tls_backend_gnutls_lookup_session (gnutls_connection_end_t  type,
282                                      GBytes                  *session_id)
283 {
284   GTlsBackendGnutlsCacheData *cache_data;
285   GBytes *session_data = NULL;
286   GHashTable *cache;
287
288   G_LOCK (session_cache_lock);
289
290   cache = get_session_cache (type, FALSE);
291   if (cache)
292     {
293       cache_data = g_hash_table_lookup (cache, session_id);
294       if (cache_data)
295         {
296           cache_data->last_used = time (NULL);
297           session_data = g_bytes_ref (cache_data->session_data);
298         }
299     }
300
301   G_UNLOCK (session_cache_lock);
302
303   return session_data;
304 }
305
306 void
307 g_tls_backend_gnutls_register (GIOModule *module)
308 {
309   g_tls_backend_gnutls_register_type (G_TYPE_MODULE (module));
310   g_io_extension_point_implement (G_TLS_BACKEND_EXTENSION_POINT_NAME,
311                                   g_tls_backend_gnutls_get_type(),
312                                   "gnutls",
313                                   0);
314 }