gnutls: add a session cache
[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, write to the
17  * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21 #include "config.h"
22 #include "glib.h"
23
24 #include <errno.h>
25
26 #include <gnutls/gnutls.h>
27 #include <gcrypt.h>
28 #ifndef G_OS_WIN32
29 #include <pthread.h>
30 #endif
31
32 #include "gtlsbackend-gnutls.h"
33 #include "gtlscertificate-gnutls.h"
34 #include "gtlsclientconnection-gnutls.h"
35 #include "gtlsserverconnection-gnutls.h"
36
37 static void g_tls_backend_gnutls_interface_init (GTlsBackendInterface *iface);
38
39 G_DEFINE_DYNAMIC_TYPE_EXTENDED (GTlsBackendGnutls, g_tls_backend_gnutls, G_TYPE_OBJECT, 0,
40                                 G_IMPLEMENT_INTERFACE_DYNAMIC (G_TYPE_TLS_BACKEND,
41                                                                g_tls_backend_gnutls_interface_init);)
42
43 #if defined(GCRY_THREAD_OPTION_PTHREAD_IMPL) && !defined(G_OS_WIN32)
44 GCRY_THREAD_OPTION_PTHREAD_IMPL;
45 #endif
46
47 #ifdef G_OS_WIN32
48
49 static int
50 gtls_gcry_win32_mutex_init (void **priv)
51 {
52         int err = 0;
53         CRITICAL_SECTION *lock = (CRITICAL_SECTION*)malloc (sizeof (CRITICAL_SECTION));
54
55         if (!lock)
56                 err = ENOMEM;
57         if (!err) {
58                 InitializeCriticalSection (lock);
59                 *priv = lock;
60         }
61         return err;
62 }
63
64 static int
65 gtls_gcry_win32_mutex_destroy (void **lock)
66 {
67         DeleteCriticalSection ((CRITICAL_SECTION*)*lock);
68         free (*lock);
69         return 0;
70 }
71
72 static int
73 gtls_gcry_win32_mutex_lock (void **lock)
74 {
75         EnterCriticalSection ((CRITICAL_SECTION*)*lock);
76         return 0;
77 }
78
79 static int
80 gtls_gcry_win32_mutex_unlock (void **lock)
81 {
82         LeaveCriticalSection ((CRITICAL_SECTION*)*lock);
83         return 0;
84 }
85
86
87 static struct gcry_thread_cbs gtls_gcry_threads_win32 = {                \
88         (GCRY_THREAD_OPTION_USER | (GCRY_THREAD_OPTION_VERSION << 8)),   \
89         NULL, gtls_gcry_win32_mutex_init, gtls_gcry_win32_mutex_destroy, \
90         gtls_gcry_win32_mutex_lock, gtls_gcry_win32_mutex_unlock,        \
91         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
92
93 #endif
94
95 static gpointer
96 gtls_gnutls_init (gpointer data)
97 {
98 #if defined(GCRY_THREAD_OPTION_PTHREAD_IMPL) && !defined(G_OS_WIN32)
99   gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
100 #elif defined(G_OS_WIN32)
101   gcry_control (GCRYCTL_SET_THREAD_CBS, &gtls_gcry_threads_win32);
102 #endif
103   gnutls_global_init ();
104
105   /* Leak the module to keep it from being unloaded. */
106   g_type_plugin_use (g_type_get_plugin (G_TYPE_TLS_BACKEND_GNUTLS));
107   return NULL;
108 }
109
110 static GOnce gnutls_inited = G_ONCE_INIT;
111
112 static void
113 g_tls_backend_gnutls_init (GTlsBackendGnutls *backend)
114 {
115   /* Once we call gtls_gnutls_init(), we can't allow the module to be
116    * unloaded, since that would break the pointers to the mutex
117    * functions we set for gcrypt. So we initialize it from here rather
118    * than at class init time so that it doesn't happen unless the app
119    * is actually using TLS (as opposed to just calling
120    * g_io_modules_scan_all_in_directory()).
121    */
122   g_once (&gnutls_inited, gtls_gnutls_init, NULL);
123 }
124
125 static void
126 g_tls_backend_gnutls_class_init (GTlsBackendGnutlsClass *backend_class)
127 {
128 }
129
130 static void
131 g_tls_backend_gnutls_class_finalize (GTlsBackendGnutlsClass *backend_class)
132 {
133 }
134
135 static void
136 g_tls_backend_gnutls_interface_init (GTlsBackendInterface *iface)
137 {
138   iface->get_certificate_type       = g_tls_certificate_gnutls_get_type;
139   iface->get_client_connection_type = g_tls_client_connection_gnutls_get_type;
140   iface->get_server_connection_type = g_tls_server_connection_gnutls_get_type;
141 }
142
143 #ifdef GTLS_SYSTEM_CA_FILE
144 /* Parsing the system CA list takes a noticeable amount of time.
145  * So we only do it once, and only when we actually need to see it.
146  */
147 static const GList *
148 get_ca_lists (gnutls_x509_crt_t **cas,
149               int                *num_cas)
150 {
151   static gnutls_x509_crt_t *ca_list_gnutls;
152   static int ca_list_length;
153   static GList *ca_list;
154
155   if (g_once_init_enter ((volatile gsize *)&ca_list_gnutls))
156     {
157       GError *error = NULL;
158       gnutls_x509_crt_t *x509_crts;
159       GList *c;
160       int i;
161
162       ca_list = g_tls_certificate_list_new_from_file (GTLS_SYSTEM_CA_FILE, &error);
163       if (error)
164         {
165           g_warning ("Failed to read system CA file %s: %s.",
166                      GTLS_SYSTEM_CA_FILE, error->message);
167           g_error_free (error);
168           /* Note that this is not a security problem, since if
169            * G_TLS_VALIDATE_CA is set, then this just means validation
170            * will always fail, and if it isn't set, then it doesn't
171            * matter that we couldn't read the CAs.
172            */
173         }
174
175       ca_list_length = g_list_length (ca_list);
176       x509_crts = g_new (gnutls_x509_crt_t, ca_list_length);
177       for (c = ca_list, i = 0; c; c = c->next, i++)
178         x509_crts[i] = g_tls_certificate_gnutls_get_cert (c->data);
179
180       g_once_init_leave ((volatile gsize *)&ca_list_gnutls, GPOINTER_TO_SIZE (x509_crts));
181     }
182
183   if (cas)
184     *cas = ca_list_gnutls;
185   if (num_cas)
186     *num_cas = ca_list_length;
187   
188   return ca_list;
189 }
190 #endif
191
192 const GList *
193 g_tls_backend_gnutls_get_system_ca_list_gtls (void)
194 {
195 #ifdef GTLS_SYSTEM_CA_FILE
196   return get_ca_lists (NULL, NULL);
197 #else
198   return NULL;
199 #endif
200 }
201
202 void
203 g_tls_backend_gnutls_get_system_ca_list_gnutls (gnutls_x509_crt_t **cas,
204                                                 int                *num_cas)
205 {
206 #ifdef GTLS_SYSTEM_CA_FILE
207   get_ca_lists (cas, num_cas);
208 #else
209   *cas = NULL;
210   *num_cas = 0;
211 #endif
212 }
213
214 /* Session cache support; all the details are sort of arbitrary. Note
215  * that having session_cache_cleanup() be a little bit slow isn't the
216  * end of the world, since it will still be faster than the network
217  * is. (NSS uses a linked list for its cache...)
218  */
219
220 G_LOCK_DEFINE_STATIC (session_cache_lock);
221 GHashTable *session_cache;
222
223 #define SESSION_CACHE_MAX_SIZE 50
224 #define SESSION_CACHE_MAX_AGE (60 * 60) /* one hour */
225
226 typedef struct {
227   gchar      *session_id;
228   GByteArray *session_data;
229   time_t      last_used;
230 } GTlsBackendGnutlsCacheData;
231
232 static void
233 session_cache_cleanup (void)
234 {
235   GHashTableIter iter;
236   gpointer key, value;
237   GTlsBackendGnutlsCacheData *cache_data;
238   time_t expired = time (NULL) - SESSION_CACHE_MAX_AGE;
239
240   g_hash_table_iter_init (&iter, session_cache);
241   while (g_hash_table_iter_next (&iter, &key, &value))
242     {
243       cache_data = value;
244       if (cache_data->last_used < expired)
245         g_hash_table_iter_remove (&iter);
246     }
247 }
248
249 static void
250 cache_data_free (gpointer data)
251 {
252   GTlsBackendGnutlsCacheData *cache_data = data;
253
254   g_free (cache_data->session_id);
255   g_byte_array_unref (cache_data->session_data);
256   g_slice_free (GTlsBackendGnutlsCacheData, cache_data);
257 }
258
259 void
260 g_tls_backend_gnutls_cache_session_data (const gchar *session_id,
261                                          guchar      *session_data,
262                                          gsize        session_data_length)
263 {
264   GTlsBackendGnutlsCacheData *cache_data;
265
266   G_LOCK (session_cache_lock);
267
268   if (!session_cache)
269     session_cache = g_hash_table_new_full (g_str_hash, g_str_equal,
270                                            NULL, cache_data_free);
271
272   cache_data = g_hash_table_lookup (session_cache, session_id);
273   if (cache_data)
274     {
275       if (cache_data->session_data->len == session_data_length &&
276           memcmp (cache_data->session_data->data,
277                   session_data, session_data_length) == 0)
278         {
279           cache_data->last_used = time (NULL);
280           G_UNLOCK (session_cache_lock);
281           return;
282         }
283
284       g_byte_array_set_size (cache_data->session_data, 0);
285     }
286   else
287     {
288       if (g_hash_table_size (session_cache) >= SESSION_CACHE_MAX_SIZE)
289         session_cache_cleanup ();
290
291       cache_data = g_slice_new (GTlsBackendGnutlsCacheData);
292       cache_data->session_id = g_strdup (session_id);
293       cache_data->session_data = g_byte_array_sized_new (session_data_length);
294
295       g_hash_table_insert (session_cache, cache_data->session_id, cache_data);
296     }
297
298   g_byte_array_append (cache_data->session_data,
299                        session_data, session_data_length);
300   cache_data->last_used = time (NULL);
301   G_UNLOCK (session_cache_lock);
302 }
303
304 void
305 g_tls_backend_gnutls_uncache_session_data (const gchar *session_id)
306 {
307   G_LOCK (session_cache_lock);
308   if (session_cache)
309     g_hash_table_remove (session_cache, session_id);
310   G_UNLOCK (session_cache_lock);
311 }
312
313 GByteArray *
314 g_tls_backend_gnutls_lookup_session_data (const gchar *session_id)
315 {
316   GTlsBackendGnutlsCacheData *cache_data;
317   GByteArray *session_data = NULL;
318
319   G_LOCK (session_cache_lock);
320   if (session_cache)
321     {
322       cache_data = g_hash_table_lookup (session_cache, session_id);
323       if (cache_data)
324         {
325           cache_data->last_used = time (NULL);
326           session_data = g_byte_array_ref (cache_data->session_data);
327         }
328     }
329   G_UNLOCK (session_cache_lock);
330
331   return session_data;
332 }
333
334 void
335 g_tls_backend_gnutls_register (GIOModule *module)
336 {
337   g_tls_backend_gnutls_register_type (G_TYPE_MODULE (module));
338   g_io_extension_point_implement (G_TLS_BACKEND_EXTENSION_POINT_NAME,
339                                   g_tls_backend_gnutls_get_type(),
340                                   "gnutls",
341                                   0);
342 }