1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
6 * Ian Peters <itp@ximian.com>
8 * Copyright (C) 2003, Ximian, Inc.
22 #include <gnutls/gnutls.h>
25 #include "soup-misc.h"
27 gboolean soup_ssl_supported = TRUE;
32 gnutls_certificate_credentials cred;
33 gboolean have_ca_file;
39 GIOChannel *real_sock;
40 gnutls_session session;
48 verify_certificate (gnutls_session session, const char *hostname)
52 status = gnutls_certificate_verify_peers (session);
54 if (status == GNUTLS_E_NO_CERTIFICATE_FOUND) {
55 g_warning ("No certificate was sent.");
59 if (status & GNUTLS_CERT_INVALID ||
60 status & GNUTLS_CERT_NOT_TRUSTED ||
61 status & GNUTLS_CERT_REVOKED)
63 g_warning ("The certificate is not trusted.");
67 if (gnutls_certificate_expiration_time_peers (session) < time (0)) {
68 g_warning ("The certificate has expired.");
72 if (gnutls_certificate_activation_time_peers (session) > time (0)) {
73 g_warning ("The certificate is not yet activated.");
77 if (gnutls_certificate_type_get (session) == GNUTLS_CRT_X509) {
78 const gnutls_datum* cert_list;
82 cert_list = gnutls_certificate_get_peers (
83 session, &cert_list_size);
85 if (cert_list == NULL) {
86 g_warning ("No certificate was found.");
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.");
96 if (!gnutls_x509_crt_check_hostname (cert, hostname)) {
97 g_warning ("The certificate does not match hostname.");
106 do_handshake (SoupGNUTLSChannel *chan, GError **err)
110 result = gnutls_handshake (chan->session);
112 if (result == GNUTLS_E_AGAIN ||
113 result == GNUTLS_E_INTERRUPTED)
114 return G_IO_STATUS_AGAIN;
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;
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;
133 return G_IO_STATUS_NORMAL;
137 soup_gnutls_read (GIOChannel *channel,
143 SoupGNUTLSChannel *chan = (SoupGNUTLSChannel *) channel;
148 if (!chan->established) {
149 result = do_handshake (chan, err);
151 if (result == G_IO_STATUS_AGAIN ||
152 result == G_IO_STATUS_ERROR)
155 chan->established = TRUE;
158 result = gnutls_record_recv (chan->session, buf, count);
160 if (result == GNUTLS_E_REHANDSHAKE) {
161 chan->established = FALSE;
162 return G_IO_STATUS_AGAIN;
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;
174 *bytes_read = result;
176 return G_IO_STATUS_NORMAL;
181 soup_gnutls_write (GIOChannel *channel,
184 gsize *bytes_written,
187 SoupGNUTLSChannel *chan = (SoupGNUTLSChannel *) channel;
192 if (!chan->established) {
193 result = do_handshake (chan, err);
195 if (result == G_IO_STATUS_AGAIN ||
196 result == G_IO_STATUS_ERROR)
199 chan->established = TRUE;
202 result = gnutls_record_send (chan->session, buf, count);
204 if (result == GNUTLS_E_REHANDSHAKE) {
205 chan->established = FALSE;
206 return G_IO_STATUS_AGAIN;
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;
218 *bytes_written = result;
220 return (result > 0) ? G_IO_STATUS_NORMAL : G_IO_STATUS_EOF;
225 soup_gnutls_seek (GIOChannel *channel,
230 SoupGNUTLSChannel *chan = (SoupGNUTLSChannel *) channel;
232 return chan->real_sock->funcs->io_seek (channel, offset, type, err);
236 soup_gnutls_close (GIOChannel *channel,
239 SoupGNUTLSChannel *chan = (SoupGNUTLSChannel *) channel;
241 if (chan->established) {
245 ret = gnutls_bye (chan->session, GNUTLS_SHUT_RDWR);
246 } while (ret == GNUTLS_E_INTERRUPTED ||
247 ret == GNUTLS_E_AGAIN);
250 return chan->real_sock->funcs->io_close (channel, err);
254 soup_gnutls_create_watch (GIOChannel *channel,
255 GIOCondition condition)
257 SoupGNUTLSChannel *chan = (SoupGNUTLSChannel *) channel;
259 return chan->real_sock->funcs->io_create_watch (channel,
264 soup_gnutls_free (GIOChannel *channel)
266 SoupGNUTLSChannel *chan = (SoupGNUTLSChannel *) channel;
267 g_io_channel_unref (chan->real_sock);
268 gnutls_deinit (chan->session);
273 soup_gnutls_set_flags (GIOChannel *channel,
277 SoupGNUTLSChannel *chan = (SoupGNUTLSChannel *) channel;
279 return chan->real_sock->funcs->io_set_flags (channel, flags, err);
283 soup_gnutls_get_flags (GIOChannel *channel)
285 SoupGNUTLSChannel *chan = (SoupGNUTLSChannel *) channel;
287 return chan->real_sock->funcs->io_get_flags (channel);
290 GIOFuncs soup_gnutls_channel_funcs = {
295 soup_gnutls_create_watch,
297 soup_gnutls_set_flags,
298 soup_gnutls_get_flags
301 static gnutls_dh_params dh_params = NULL;
304 init_dh_params (void)
306 if (gnutls_dh_params_init (&dh_params) != 0)
307 goto THROW_CREATE_ERROR;
309 if (gnutls_dh_params_generate2 (dh_params, DH_BITS) != 0)
310 goto THROW_CREATE_ERROR;
316 gnutls_dh_params_deinit (dh_params);
324 soup_ssl_wrap_iochannel (GIOChannel *sock, SoupSSLType type,
325 const char *hostname, gpointer cred_pointer)
327 SoupGNUTLSChannel *chan = NULL;
328 GIOChannel *gchan = NULL;
329 gnutls_session session = NULL;
330 SoupGNUTLSCred *cred = cred_pointer;
334 g_return_val_if_fail (sock != NULL, NULL);
335 g_return_val_if_fail (cred_pointer != NULL, NULL);
337 sockfd = g_io_channel_unix_get_fd (sock);
339 g_warning ("Failed to get channel fd.");
340 goto THROW_CREATE_ERROR;
343 chan = g_new0 (SoupGNUTLSChannel, 1);
345 ret = gnutls_init (&session,
346 (type == SOUP_SSL_TYPE_CLIENT) ? GNUTLS_CLIENT : GNUTLS_SERVER);
348 goto THROW_CREATE_ERROR;
350 if (gnutls_set_default_priority (session) != 0)
351 goto THROW_CREATE_ERROR;
353 if (gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE,
355 goto THROW_CREATE_ERROR;
357 if (type == SOUP_SSL_TYPE_SERVER)
358 gnutls_dh_set_prime_bits (session, DH_BITS);
360 gnutls_transport_set_ptr (session, GINT_TO_POINTER (sockfd));
363 chan->real_sock = sock;
364 chan->session = session;
366 chan->hostname = g_strdup (hostname);
368 g_io_channel_ref (sock);
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;
381 gnutls_deinit (session);
386 soup_ssl_get_client_credentials (const char *ca_file)
388 SoupGNUTLSCred *cred;
391 gnutls_global_init ();
393 cred = g_new0 (SoupGNUTLSCred, 1);
394 gnutls_certificate_allocate_credentials (&cred->cred);
397 cred->have_ca_file = TRUE;
398 status = gnutls_certificate_set_x509_trust_file (
399 cred->cred, ca_file, GNUTLS_X509_FMT_PEM);
401 g_warning ("Failed to set SSL trust file (%s).",
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.
415 soup_ssl_free_client_credentials (gpointer client_creds)
417 SoupGNUTLSCred *cred = client_creds;
419 gnutls_certificate_free_credentials (cred->cred);
424 soup_ssl_get_server_credentials (const char *cert_file, const char *key_file)
426 SoupGNUTLSCred *cred;
428 gnutls_global_init ();
430 if (!init_dh_params ())
434 cred = g_new0 (SoupGNUTLSCred, 1);
435 gnutls_certificate_allocate_credentials (&cred->cred);
437 if (gnutls_certificate_set_x509_key_file (cred->cred,
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);
446 gnutls_certificate_set_dh_params (cred->cred, dh_params);
451 soup_ssl_free_server_credentials (gpointer server_creds)
453 SoupGNUTLSCred *cred = server_creds;
455 gnutls_certificate_free_credentials (cred->cred);
459 #endif /* HAVE_SSL */