1 /* GIO - GLib Input, Output and Streaming Library
3 * Copyright 2010 Red Hat, Inc
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.
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.
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/>.
24 #include <gnutls/gnutls.h>
25 #include <gnutls/x509.h>
28 #include "gtlsclientconnection-gnutls.h"
29 #include "gtlsbackend-gnutls.h"
30 #include "gtlscertificate-gnutls.h"
31 #include <glib/gi18n-lib.h>
36 PROP_VALIDATION_FLAGS,
42 static void g_tls_client_connection_gnutls_client_connection_interface_init (GTlsClientConnectionInterface *iface);
44 static int g_tls_client_connection_gnutls_retrieve_function (gnutls_session_t session,
45 const gnutls_datum_t *req_ca_rdn,
47 const gnutls_pk_algorithm_t *pk_algos,
51 G_DEFINE_TYPE_WITH_CODE (GTlsClientConnectionGnutls, g_tls_client_connection_gnutls, G_TYPE_TLS_CONNECTION_GNUTLS,
52 G_IMPLEMENT_INTERFACE (G_TYPE_TLS_CLIENT_CONNECTION,
53 g_tls_client_connection_gnutls_client_connection_interface_init));
55 struct _GTlsClientConnectionGnutlsPrivate
57 GTlsCertificateFlags validation_flags;
58 GSocketConnectable *server_identity;
63 gboolean cert_requested;
64 GPtrArray *accepted_cas;
69 g_tls_client_connection_gnutls_init (GTlsClientConnectionGnutls *gnutls)
71 gnutls_certificate_credentials_t creds;
73 gnutls->priv = G_TYPE_INSTANCE_GET_PRIVATE (gnutls, G_TYPE_TLS_CLIENT_CONNECTION_GNUTLS, GTlsClientConnectionGnutlsPrivate);
75 creds = g_tls_connection_gnutls_get_credentials (G_TLS_CONNECTION_GNUTLS (gnutls));
76 gnutls_certificate_set_retrieve_function (creds, g_tls_client_connection_gnutls_retrieve_function);
80 get_server_identity (GTlsClientConnectionGnutls *gnutls)
82 if (G_IS_NETWORK_ADDRESS (gnutls->priv->server_identity))
83 return g_network_address_get_hostname (G_NETWORK_ADDRESS (gnutls->priv->server_identity));
84 else if (G_IS_NETWORK_SERVICE (gnutls->priv->server_identity))
85 return g_network_service_get_domain (G_NETWORK_SERVICE (gnutls->priv->server_identity));
91 g_tls_client_connection_gnutls_constructed (GObject *object)
93 GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (object);
94 GSocketConnection *base_conn;
95 GSocketAddress *remote_addr;
99 /* Create a TLS session ID. We base it on the IP address since
100 * different hosts serving the same hostname/service will probably
101 * not share the same session cache. We base it on the
102 * server-identity because at least some servers will fail (rather
103 * than just failing to resume the session) if we don't.
104 * (https://bugs.launchpad.net/bugs/823325)
106 g_object_get (G_OBJECT (gnutls), "base-io-stream", &base_conn, NULL);
107 if (G_IS_SOCKET_CONNECTION (base_conn))
109 remote_addr = g_socket_connection_get_remote_address (base_conn, NULL);
110 if (G_IS_INET_SOCKET_ADDRESS (remote_addr))
112 GInetSocketAddress *isaddr = G_INET_SOCKET_ADDRESS (remote_addr);
113 const gchar *server_hostname;
114 gchar *addrstr, *session_id;
116 iaddr = g_inet_socket_address_get_address (isaddr);
117 port = g_inet_socket_address_get_port (isaddr);
119 addrstr = g_inet_address_to_string (iaddr);
120 server_hostname = get_server_identity (gnutls);
121 session_id = g_strdup_printf ("%s/%s/%d", addrstr,
122 server_hostname ? server_hostname : "",
124 gnutls->priv->session_id = g_bytes_new_take (session_id, strlen (session_id));
127 g_object_unref (remote_addr);
129 g_object_unref (base_conn);
131 if (G_OBJECT_CLASS (g_tls_client_connection_gnutls_parent_class)->constructed)
132 G_OBJECT_CLASS (g_tls_client_connection_gnutls_parent_class)->constructed (object);
136 g_tls_client_connection_gnutls_finalize (GObject *object)
138 GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (object);
140 if (gnutls->priv->server_identity)
141 g_object_unref (gnutls->priv->server_identity);
142 if (gnutls->priv->accepted_cas)
143 g_ptr_array_unref (gnutls->priv->accepted_cas);
144 if (gnutls->priv->session_id)
145 g_bytes_unref (gnutls->priv->session_id);
147 G_OBJECT_CLASS (g_tls_client_connection_gnutls_parent_class)->finalize (object);
151 g_tls_client_connection_gnutls_get_property (GObject *object,
156 GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (object);
162 case PROP_VALIDATION_FLAGS:
163 g_value_set_flags (value, gnutls->priv->validation_flags);
166 case PROP_SERVER_IDENTITY:
167 g_value_set_object (value, gnutls->priv->server_identity);
171 g_value_set_boolean (value, gnutls->priv->use_ssl3);
174 case PROP_ACCEPTED_CAS:
176 if (gnutls->priv->accepted_cas)
178 for (i = 0; i < gnutls->priv->accepted_cas->len; ++i)
180 accepted_cas = g_list_prepend (accepted_cas, g_byte_array_ref (
181 gnutls->priv->accepted_cas->pdata[i]));
183 accepted_cas = g_list_reverse (accepted_cas);
185 g_value_set_pointer (value, accepted_cas);
189 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
194 g_tls_client_connection_gnutls_set_property (GObject *object,
199 GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (object);
200 const char *hostname;
204 case PROP_VALIDATION_FLAGS:
205 gnutls->priv->validation_flags = g_value_get_flags (value);
208 case PROP_SERVER_IDENTITY:
209 if (gnutls->priv->server_identity)
210 g_object_unref (gnutls->priv->server_identity);
211 gnutls->priv->server_identity = g_value_dup_object (value);
213 hostname = get_server_identity (gnutls);
216 gnutls_session_t session = g_tls_connection_gnutls_get_session (G_TLS_CONNECTION_GNUTLS (gnutls));
218 gnutls_server_name_set (session, GNUTLS_NAME_DNS,
219 hostname, strlen (hostname));
224 gnutls->priv->use_ssl3 = g_value_get_boolean (value);
228 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
233 g_tls_client_connection_gnutls_retrieve_function (gnutls_session_t session,
234 const gnutls_datum_t *req_ca_rdn,
236 const gnutls_pk_algorithm_t *pk_algos,
240 GTlsClientConnectionGnutls *gnutls = gnutls_transport_get_ptr (session);
241 GPtrArray *accepted_cas;
245 gnutls->priv->cert_requested = TRUE;
247 accepted_cas = g_ptr_array_new_with_free_func ((GDestroyNotify)g_byte_array_unref);
248 for (i = 0; i < nreqs; i++)
250 dn = g_byte_array_new ();
251 g_byte_array_append (dn, req_ca_rdn[i].data, req_ca_rdn[i].size);
252 g_ptr_array_add (accepted_cas, dn);
255 if (gnutls->priv->accepted_cas)
256 g_ptr_array_unref (gnutls->priv->accepted_cas);
257 gnutls->priv->accepted_cas = accepted_cas;
258 g_object_notify (G_OBJECT (gnutls), "accepted-cas");
260 g_tls_connection_gnutls_get_certificate (G_TLS_CONNECTION_GNUTLS (gnutls), st);
265 g_tls_client_connection_gnutls_failed (GTlsConnectionGnutls *conn)
267 GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (conn);
269 if (gnutls->priv->session_id)
270 g_tls_backend_gnutls_remove_session (GNUTLS_CLIENT, gnutls->priv->session_id);
274 g_tls_client_connection_gnutls_begin_handshake (GTlsConnectionGnutls *conn)
276 GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (conn);
278 /* Try to get a cached session */
279 if (gnutls->priv->session_id)
281 GBytes *session_data;
283 session_data = g_tls_backend_gnutls_lookup_session (GNUTLS_CLIENT, gnutls->priv->session_id);
286 gnutls_session_set_data (g_tls_connection_gnutls_get_session (conn),
287 g_bytes_get_data (session_data, NULL),
288 g_bytes_get_size (session_data));
289 g_bytes_unref (session_data);
293 gnutls->priv->cert_requested = FALSE;
297 g_tls_client_connection_gnutls_finish_handshake (GTlsConnectionGnutls *conn,
298 GError **inout_error)
300 GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (conn);
302 g_assert (inout_error != NULL);
304 if (g_error_matches (*inout_error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS) &&
305 gnutls->priv->cert_requested)
307 g_clear_error (inout_error);
308 g_set_error_literal (inout_error, G_TLS_ERROR, G_TLS_ERROR_CERTIFICATE_REQUIRED,
309 _("Server required TLS certificate"));
312 if (gnutls->priv->session_id)
314 gnutls_datum session_datum;
317 gnutls_session_get_data2 (g_tls_connection_gnutls_get_session (conn),
318 &session_datum) == 0)
320 GBytes *session_data = g_bytes_new_with_free_func (session_datum.data, session_datum.size,
321 (GDestroyNotify)gnutls_free, session_datum.data);
323 g_tls_backend_gnutls_store_session (GNUTLS_CLIENT, gnutls->priv->session_id,
325 g_bytes_unref (session_data);
328 g_tls_backend_gnutls_remove_session (GNUTLS_CLIENT, gnutls->priv->session_id);
333 g_tls_client_connection_gnutls_class_init (GTlsClientConnectionGnutlsClass *klass)
335 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
336 GTlsConnectionGnutlsClass *connection_gnutls_class = G_TLS_CONNECTION_GNUTLS_CLASS (klass);
338 g_type_class_add_private (klass, sizeof (GTlsClientConnectionGnutlsPrivate));
340 gobject_class->get_property = g_tls_client_connection_gnutls_get_property;
341 gobject_class->set_property = g_tls_client_connection_gnutls_set_property;
342 gobject_class->constructed = g_tls_client_connection_gnutls_constructed;
343 gobject_class->finalize = g_tls_client_connection_gnutls_finalize;
345 connection_gnutls_class->failed = g_tls_client_connection_gnutls_failed;
346 connection_gnutls_class->begin_handshake = g_tls_client_connection_gnutls_begin_handshake;
347 connection_gnutls_class->finish_handshake = g_tls_client_connection_gnutls_finish_handshake;
349 g_object_class_override_property (gobject_class, PROP_VALIDATION_FLAGS, "validation-flags");
350 g_object_class_override_property (gobject_class, PROP_SERVER_IDENTITY, "server-identity");
351 g_object_class_override_property (gobject_class, PROP_USE_SSL3, "use-ssl3");
352 g_object_class_override_property (gobject_class, PROP_ACCEPTED_CAS, "accepted-cas");
356 g_tls_client_connection_gnutls_client_connection_interface_init (GTlsClientConnectionInterface *iface)