9327097305699e7d8d0494caf81da1ad49610cc3
[platform/upstream/libsoup.git] / libsoup / soup-gnutls.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * soup-gnutls.c
4  *
5  * Authors:
6  *      Ian Peters <itp@ximian.com>
7  *
8  * Copyright (C) 2003, Ximian, Inc.
9  */
10
11 #ifdef HAVE_CONFIG_H
12 #include <config.h>
13 #endif
14
15 #ifdef HAVE_SSL
16
17 #include <stdlib.h>
18 #include <string.h>
19
20 #include <glib.h>
21
22 #include <gnutls/gnutls.h>
23
24 #include "soup-ssl.h"
25 #include "soup-misc.h"
26
27 gboolean soup_ssl_supported = TRUE;
28
29 #define DH_BITS 1024
30
31 typedef struct {
32         gnutls_certificate_credentials cred;
33         gboolean have_ca_file;
34 } SoupGNUTLSCred;
35
36 typedef struct {
37         GIOChannel channel;
38         int fd;
39         GIOChannel *real_sock;
40         gnutls_session session;
41         SoupGNUTLSCred *cred;
42         char *hostname;
43         gboolean established;
44         SoupSSLType type;
45 } SoupGNUTLSChannel;
46
47 static gboolean
48 verify_certificate (gnutls_session session, const char *hostname)
49 {
50         int status;
51
52         status = gnutls_certificate_verify_peers (session);
53
54         if (status == GNUTLS_E_NO_CERTIFICATE_FOUND) {
55                 g_warning ("No certificate was sent.");
56                 return FALSE;
57         }
58
59         if (status & GNUTLS_CERT_INVALID ||
60             status & GNUTLS_CERT_NOT_TRUSTED ||
61             status & GNUTLS_CERT_REVOKED)
62         {
63                 g_warning ("The certificate is not trusted.");
64                 return FALSE;
65         }
66
67         if (gnutls_certificate_expiration_time_peers (session) < time (0)) {
68                 g_warning ("The certificate has expired.");
69                 return FALSE;
70         }
71
72         if (gnutls_certificate_activation_time_peers (session) > time (0)) {
73                 g_warning ("The certificate is not yet activated.");
74                 return FALSE;
75         }
76
77         if (gnutls_certificate_type_get (session) == GNUTLS_CRT_X509) {
78                 const gnutls_datum* cert_list;
79                 int cert_list_size;
80                 gnutls_x509_crt cert;
81       
82                 cert_list = gnutls_certificate_get_peers (
83                         session, &cert_list_size);
84
85                 if (cert_list == NULL) {
86                         g_warning ("No certificate was found.");
87                         return FALSE;
88                 }
89
90                 if (gnutls_x509_crt_import (cert, &cert_list[0],
91                                             GNUTLS_X509_FMT_DER) < 0) {
92                         g_warning ("The certificate could not be parsed.");
93                         return FALSE;
94                 }
95
96                 if (!gnutls_x509_crt_check_hostname (cert, hostname)) {
97                         g_warning ("The certificate does not match hostname.");
98                         return FALSE;
99                 }
100         }
101    
102         return TRUE;
103 }
104
105 static GIOStatus
106 do_handshake (SoupGNUTLSChannel *chan, GError **err)
107 {
108         int result;
109
110         result = gnutls_handshake (chan->session);
111
112         if (result == GNUTLS_E_AGAIN ||
113             result == GNUTLS_E_INTERRUPTED)
114                 return G_IO_STATUS_AGAIN;
115
116         if (result < 0) {
117                 g_set_error (err, G_IO_CHANNEL_ERROR,
118                              G_IO_CHANNEL_ERROR_FAILED,
119                              "Unable to handshake");
120                 return G_IO_STATUS_ERROR;
121         }
122
123         if (chan->type == SOUP_SSL_TYPE_CLIENT &&
124             chan->cred->have_ca_file) {
125                 if (!verify_certificate (chan->session, chan->hostname)) {
126                         g_set_error (err, G_IO_CHANNEL_ERROR,
127                                      G_IO_CHANNEL_ERROR_FAILED,
128                                      "Unable to verify certificate");
129                         return G_IO_STATUS_ERROR;
130                 }
131         }
132
133         return G_IO_STATUS_NORMAL;
134 }
135
136 static GIOStatus
137 soup_gnutls_read (GIOChannel   *channel,
138                   gchar        *buf,
139                   gsize         count,
140                   gsize        *bytes_read,
141                   GError      **err)
142 {
143         SoupGNUTLSChannel *chan = (SoupGNUTLSChannel *) channel;
144         gint result;
145
146         *bytes_read = 0;
147
148         if (!chan->established) {
149                 result = do_handshake (chan, err);
150
151                 if (result == G_IO_STATUS_AGAIN ||
152                     result == G_IO_STATUS_ERROR)
153                         return result;
154
155                 chan->established = TRUE;
156         }
157
158         result = gnutls_record_recv (chan->session, buf, count);
159
160         if (result == GNUTLS_E_REHANDSHAKE) {
161                 chan->established = FALSE;
162                 return G_IO_STATUS_AGAIN;
163         }
164
165         if (result < 0) {
166                 if ((result == GNUTLS_E_INTERRUPTED) ||
167                     (result == GNUTLS_E_AGAIN))
168                         return G_IO_STATUS_AGAIN;
169                 g_set_error (err, G_IO_CHANNEL_ERROR,
170                              G_IO_CHANNEL_ERROR_FAILED,
171                              "Received corrupted data");
172                 return G_IO_STATUS_ERROR;
173         } else {
174                 *bytes_read = result;
175
176                 return G_IO_STATUS_NORMAL;
177         }
178 }
179
180 static GIOStatus
181 soup_gnutls_write (GIOChannel   *channel,
182                    const gchar  *buf,
183                    gsize         count,
184                    gsize        *bytes_written,
185                    GError      **err)
186 {
187         SoupGNUTLSChannel *chan = (SoupGNUTLSChannel *) channel;
188         gint result;
189
190         *bytes_written = 0;
191
192         if (!chan->established) {
193                 result = do_handshake (chan, err);
194
195                 if (result == G_IO_STATUS_AGAIN ||
196                     result == G_IO_STATUS_ERROR)
197                         return result;
198
199                 chan->established = TRUE;
200         }
201
202         result = gnutls_record_send (chan->session, buf, count);
203
204         if (result == GNUTLS_E_REHANDSHAKE) {
205                 chan->established = FALSE;
206                 return G_IO_STATUS_AGAIN;
207         }
208
209         if (result < 0) {
210                 if ((result == GNUTLS_E_INTERRUPTED) ||
211                     (result == GNUTLS_E_AGAIN))
212                         return G_IO_STATUS_AGAIN;
213                 g_set_error (err, G_IO_CHANNEL_ERROR,
214                              G_IO_CHANNEL_ERROR_FAILED,
215                              "Received corrupted data");
216                 return G_IO_STATUS_ERROR;
217         } else {
218                 *bytes_written = result;
219
220                 return (result > 0) ? G_IO_STATUS_NORMAL : G_IO_STATUS_EOF;
221         }
222 }
223
224 static GIOStatus
225 soup_gnutls_seek (GIOChannel  *channel,
226                   gint64       offset,
227                   GSeekType    type,
228                   GError     **err)
229 {
230         SoupGNUTLSChannel *chan = (SoupGNUTLSChannel *) channel;
231
232         return chan->real_sock->funcs->io_seek (channel, offset, type, err);
233 }
234
235 static GIOStatus
236 soup_gnutls_close (GIOChannel  *channel,
237                    GError     **err)
238 {
239         SoupGNUTLSChannel *chan = (SoupGNUTLSChannel *) channel;
240
241         if (chan->established) {
242                 int ret;
243
244                 do {
245                         ret = gnutls_bye (chan->session, GNUTLS_SHUT_RDWR);
246                 } while (ret == GNUTLS_E_INTERRUPTED ||
247                          ret == GNUTLS_E_AGAIN);
248         }
249
250         return chan->real_sock->funcs->io_close (channel, err);
251 }
252
253 static GSource *
254 soup_gnutls_create_watch (GIOChannel   *channel,
255                           GIOCondition  condition)
256 {
257         SoupGNUTLSChannel *chan = (SoupGNUTLSChannel *) channel;
258
259         return chan->real_sock->funcs->io_create_watch (channel,
260                                                         condition);
261 }
262
263 static void
264 soup_gnutls_free (GIOChannel *channel)
265 {
266         SoupGNUTLSChannel *chan = (SoupGNUTLSChannel *) channel;
267         g_io_channel_unref (chan->real_sock);
268         gnutls_deinit (chan->session);
269         g_free (chan);
270 }
271
272 static GIOStatus
273 soup_gnutls_set_flags (GIOChannel  *channel,
274                        GIOFlags     flags,
275                        GError     **err)
276 {
277         SoupGNUTLSChannel *chan = (SoupGNUTLSChannel *) channel;
278
279         return chan->real_sock->funcs->io_set_flags (channel, flags, err);
280 }
281
282 static GIOFlags
283 soup_gnutls_get_flags (GIOChannel *channel)
284 {
285         SoupGNUTLSChannel *chan = (SoupGNUTLSChannel *) channel;
286
287         return chan->real_sock->funcs->io_get_flags (channel);
288 }
289
290 GIOFuncs soup_gnutls_channel_funcs = {
291         soup_gnutls_read,
292         soup_gnutls_write,
293         soup_gnutls_seek,
294         soup_gnutls_close,
295         soup_gnutls_create_watch,
296         soup_gnutls_free,
297         soup_gnutls_set_flags,
298         soup_gnutls_get_flags
299 };
300
301 static gnutls_dh_params dh_params = NULL;
302
303 static gboolean
304 init_dh_params (void)
305 {
306         if (gnutls_dh_params_init (&dh_params) != 0)
307                 goto THROW_CREATE_ERROR;
308
309         if (gnutls_dh_params_generate2 (dh_params, DH_BITS) != 0)
310                 goto THROW_CREATE_ERROR;
311
312         return TRUE;
313
314 THROW_CREATE_ERROR:
315         if (dh_params) {
316                 gnutls_dh_params_deinit (dh_params);
317                 dh_params = NULL;
318         }
319
320         return FALSE;
321 }
322
323 GIOChannel *
324 soup_ssl_wrap_iochannel (GIOChannel *sock, SoupSSLType type,
325                          const char *hostname, gpointer cred_pointer)
326 {
327         SoupGNUTLSChannel *chan = NULL;
328         GIOChannel *gchan = NULL;
329         gnutls_session session = NULL;
330         SoupGNUTLSCred *cred = cred_pointer;
331         int sockfd;
332         int ret;
333
334         g_return_val_if_fail (sock != NULL, NULL);
335         g_return_val_if_fail (cred_pointer != NULL, NULL);
336
337         sockfd = g_io_channel_unix_get_fd (sock);
338         if (!sockfd) {
339                 g_warning ("Failed to get channel fd.");
340                 goto THROW_CREATE_ERROR;
341         }
342
343         chan = g_new0 (SoupGNUTLSChannel, 1);
344
345         ret = gnutls_init (&session,
346                            (type == SOUP_SSL_TYPE_CLIENT) ? GNUTLS_CLIENT : GNUTLS_SERVER);
347         if (ret)
348                 goto THROW_CREATE_ERROR;
349
350         if (gnutls_set_default_priority (session) != 0)
351                 goto THROW_CREATE_ERROR;
352
353         if (gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE,
354                                     cred->cred) != 0)
355                 goto THROW_CREATE_ERROR;
356
357         if (type == SOUP_SSL_TYPE_SERVER)
358                 gnutls_dh_set_prime_bits (session, DH_BITS);
359
360         gnutls_transport_set_ptr (session, GINT_TO_POINTER (sockfd));
361
362         chan->fd = sockfd;
363         chan->real_sock = sock;
364         chan->session = session;
365         chan->cred = cred;
366         chan->hostname = g_strdup (hostname);
367         chan->type = type;
368         g_io_channel_ref (sock);
369
370         gchan = (GIOChannel *) chan;
371         gchan->funcs = &soup_gnutls_channel_funcs;
372         g_io_channel_init (gchan);
373         g_io_channel_set_close_on_unref (gchan, TRUE);
374         gchan->is_readable = gchan->is_writeable = TRUE;
375         gchan->use_buffer = FALSE;
376
377         return gchan;
378
379  THROW_CREATE_ERROR:
380         if (session)
381                 gnutls_deinit (session);
382         return NULL;
383 }
384
385 gpointer
386 soup_ssl_get_client_credentials (const char *ca_file)
387 {
388         SoupGNUTLSCred *cred;
389         int status;
390
391         gnutls_global_init ();
392
393         cred = g_new0 (SoupGNUTLSCred, 1);
394         gnutls_certificate_allocate_credentials (&cred->cred);
395
396         if (ca_file) {
397                 cred->have_ca_file = TRUE;
398                 status = gnutls_certificate_set_x509_trust_file (
399                         cred->cred, ca_file, GNUTLS_X509_FMT_PEM);
400                 if (status < 0) {
401                         g_warning ("Failed to set SSL trust file (%s).",
402                                    ca_file);
403                         /* Since we set have_ca_file though, this just
404                          * means that no certs will validate, so we're
405                          * ok securitywise if we just return these
406                          * creds to the caller.
407                          */
408                 }
409         }
410
411         return cred;
412 }
413
414 void
415 soup_ssl_free_client_credentials (gpointer client_creds)
416 {
417         SoupGNUTLSCred *cred = client_creds;
418
419         gnutls_certificate_free_credentials (cred->cred);
420         g_free (cred);
421 }
422
423 gpointer
424 soup_ssl_get_server_credentials (const char *cert_file, const char *key_file)
425 {
426         SoupGNUTLSCred *cred;
427
428         gnutls_global_init ();
429         if (!dh_params) {
430                 if (!init_dh_params ())
431                         return NULL;
432         }
433
434         cred = g_new0 (SoupGNUTLSCred, 1);
435         gnutls_certificate_allocate_credentials (&cred->cred);
436
437         if (gnutls_certificate_set_x509_key_file (cred->cred,
438                                                   cert_file, key_file,
439                                                   GNUTLS_X509_FMT_PEM) != 0) {
440                 g_warning ("Failed to set SSL certificate and key files "
441                            "(%s, %s).", cert_file, key_file);
442                 soup_ssl_free_server_credentials (cred);
443                 return NULL;
444         }
445
446         gnutls_certificate_set_dh_params (cred->cred, dh_params);
447         return cred;
448 }
449
450 void
451 soup_ssl_free_server_credentials (gpointer server_creds)
452 {
453         SoupGNUTLSCred *cred = server_creds;
454
455         gnutls_certificate_free_credentials (cred->cred);
456         g_free (cred);
457 }
458
459 #endif /* HAVE_SSL */