Imported Upstream version 2.34.0
[platform/upstream/glib-networking.git] / tls / gnutls / gtlsclientconnection-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 <gnutls/gnutls.h>
25 #include <gnutls/x509.h>
26 #include <string.h>
27
28 #include "gtlsclientconnection-gnutls.h"
29 #include "gtlsbackend-gnutls.h"
30 #include "gtlscertificate-gnutls.h"
31 #include <glib/gi18n-lib.h>
32
33 enum
34 {
35   PROP_0,
36   PROP_VALIDATION_FLAGS,
37   PROP_SERVER_IDENTITY,
38   PROP_USE_SSL3,
39   PROP_ACCEPTED_CAS
40 };
41
42 static void g_tls_client_connection_gnutls_client_connection_interface_init (GTlsClientConnectionInterface *iface);
43
44 static int g_tls_client_connection_gnutls_retrieve_function (gnutls_session_t             session,
45                                                              const gnutls_datum_t        *req_ca_rdn,
46                                                              int                          nreqs,
47                                                              const gnutls_pk_algorithm_t *pk_algos,
48                                                              int                          pk_algos_length,
49                                                              gnutls_retr2_st             *st);
50
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));
54
55 struct _GTlsClientConnectionGnutlsPrivate
56 {
57   GTlsCertificateFlags validation_flags;
58   GSocketConnectable *server_identity;
59   gboolean use_ssl3;
60
61   GBytes *session_id;
62
63   gboolean cert_requested;
64   GPtrArray *accepted_cas;
65 };
66
67
68 static void
69 g_tls_client_connection_gnutls_init (GTlsClientConnectionGnutls *gnutls)
70 {
71   gnutls_certificate_credentials_t creds;
72
73   gnutls->priv = G_TYPE_INSTANCE_GET_PRIVATE (gnutls, G_TYPE_TLS_CLIENT_CONNECTION_GNUTLS, GTlsClientConnectionGnutlsPrivate);
74
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);
77 }
78
79 static const gchar *
80 get_server_identity (GTlsClientConnectionGnutls *gnutls)
81 {
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));
86   else
87     return NULL;
88 }
89
90 static void
91 g_tls_client_connection_gnutls_constructed (GObject *object)
92 {
93   GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (object);
94   GSocketConnection *base_conn;
95   GSocketAddress *remote_addr;
96   GInetAddress *iaddr;
97   guint port;
98
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)
105    */
106   g_object_get (G_OBJECT (gnutls), "base-io-stream", &base_conn, NULL);
107   if (G_IS_SOCKET_CONNECTION (base_conn))
108     {
109       remote_addr = g_socket_connection_get_remote_address (base_conn, NULL);
110       if (G_IS_INET_SOCKET_ADDRESS (remote_addr))
111         {
112           GInetSocketAddress *isaddr = G_INET_SOCKET_ADDRESS (remote_addr);
113           const gchar *server_hostname;
114           gchar *addrstr, *session_id;
115
116           iaddr = g_inet_socket_address_get_address (isaddr);
117           port = g_inet_socket_address_get_port (isaddr);
118
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 : "",
123                                         port);
124           gnutls->priv->session_id = g_bytes_new_take (session_id, strlen (session_id));
125           g_free (addrstr);
126         }
127       g_object_unref (remote_addr);
128     }
129   g_object_unref (base_conn);
130
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);
133 }
134
135 static void
136 g_tls_client_connection_gnutls_finalize (GObject *object)
137 {
138   GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (object);
139
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);
146
147   G_OBJECT_CLASS (g_tls_client_connection_gnutls_parent_class)->finalize (object);
148 }
149
150 static void
151 g_tls_client_connection_gnutls_get_property (GObject    *object,
152                                              guint       prop_id,
153                                              GValue     *value,
154                                              GParamSpec *pspec)
155 {
156   GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (object);
157   GList *accepted_cas;
158   gint i;
159
160   switch (prop_id)
161     {
162     case PROP_VALIDATION_FLAGS:
163       g_value_set_flags (value, gnutls->priv->validation_flags);
164       break;
165
166     case PROP_SERVER_IDENTITY:
167       g_value_set_object (value, gnutls->priv->server_identity);
168       break;
169
170     case PROP_USE_SSL3:
171       g_value_set_boolean (value, gnutls->priv->use_ssl3);
172       break;
173
174     case PROP_ACCEPTED_CAS:
175       accepted_cas = NULL;
176       if (gnutls->priv->accepted_cas)
177         {
178           for (i = 0; i < gnutls->priv->accepted_cas->len; ++i)
179             {
180               accepted_cas = g_list_prepend (accepted_cas, g_byte_array_ref (
181                                              gnutls->priv->accepted_cas->pdata[i]));
182             }
183           accepted_cas = g_list_reverse (accepted_cas);
184         }
185       g_value_set_pointer (value, accepted_cas);
186       break;
187
188     default:
189       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
190     }
191 }
192
193 static void
194 g_tls_client_connection_gnutls_set_property (GObject      *object,
195                                              guint         prop_id,
196                                              const GValue *value,
197                                              GParamSpec   *pspec)
198 {
199   GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (object);
200   const char *hostname;
201
202   switch (prop_id)
203     {
204     case PROP_VALIDATION_FLAGS:
205       gnutls->priv->validation_flags = g_value_get_flags (value);
206       break;
207
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);
212
213       hostname = get_server_identity (gnutls);
214       if (hostname)
215         {
216           gnutls_session_t session = g_tls_connection_gnutls_get_session (G_TLS_CONNECTION_GNUTLS (gnutls));
217
218           gnutls_server_name_set (session, GNUTLS_NAME_DNS,
219                                   hostname, strlen (hostname));
220         }
221       break;
222
223     case PROP_USE_SSL3:
224       gnutls->priv->use_ssl3 = g_value_get_boolean (value);
225       break;
226
227     default:
228       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
229     }
230 }
231
232 static int
233 g_tls_client_connection_gnutls_retrieve_function (gnutls_session_t             session,
234                                                   const gnutls_datum_t        *req_ca_rdn,
235                                                   int                          nreqs,
236                                                   const gnutls_pk_algorithm_t *pk_algos,
237                                                   int                          pk_algos_length,
238                                                   gnutls_retr2_st             *st)
239 {
240   GTlsClientConnectionGnutls *gnutls = gnutls_transport_get_ptr (session);
241   GPtrArray *accepted_cas;
242   GByteArray *dn;
243   int i;
244
245   gnutls->priv->cert_requested = TRUE;
246
247   accepted_cas = g_ptr_array_new_with_free_func ((GDestroyNotify)g_byte_array_unref);
248   for (i = 0; i < nreqs; i++)
249     {
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);
253     }
254
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");
259
260   g_tls_connection_gnutls_get_certificate (G_TLS_CONNECTION_GNUTLS (gnutls), st);
261   return 0;
262 }
263
264 static void
265 g_tls_client_connection_gnutls_failed (GTlsConnectionGnutls *conn)
266 {
267   GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (conn);
268
269   if (gnutls->priv->session_id)
270     g_tls_backend_gnutls_remove_session (GNUTLS_CLIENT, gnutls->priv->session_id);
271 }
272
273 static void
274 g_tls_client_connection_gnutls_begin_handshake (GTlsConnectionGnutls *conn)
275 {
276   GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (conn);
277
278   /* Try to get a cached session */
279   if (gnutls->priv->session_id)
280     {
281       GBytes *session_data;
282
283       session_data = g_tls_backend_gnutls_lookup_session (GNUTLS_CLIENT, gnutls->priv->session_id);
284       if (session_data)
285         {
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);
290         }
291     }
292
293   gnutls->priv->cert_requested = FALSE;
294 }
295
296 static void
297 g_tls_client_connection_gnutls_finish_handshake (GTlsConnectionGnutls  *conn,
298                                                  GError               **inout_error)
299 {
300   GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (conn);
301
302   g_assert (inout_error != NULL);
303
304   if (g_error_matches (*inout_error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS) &&
305       gnutls->priv->cert_requested)
306     {
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"));
310     }
311
312   if (gnutls->priv->session_id)
313     {
314       gnutls_datum session_datum;
315
316       if (!*inout_error &&
317           gnutls_session_get_data2 (g_tls_connection_gnutls_get_session (conn),
318                                     &session_datum) == 0)
319         {
320           GBytes *session_data = g_bytes_new_with_free_func (session_datum.data, session_datum.size,
321                                                              (GDestroyNotify)gnutls_free, session_datum.data);
322
323           g_tls_backend_gnutls_store_session (GNUTLS_CLIENT, gnutls->priv->session_id,
324                                               session_data);
325           g_bytes_unref (session_data);
326         }
327       else
328         g_tls_backend_gnutls_remove_session (GNUTLS_CLIENT, gnutls->priv->session_id);
329     }
330 }
331
332 static void
333 g_tls_client_connection_gnutls_class_init (GTlsClientConnectionGnutlsClass *klass)
334 {
335   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
336   GTlsConnectionGnutlsClass *connection_gnutls_class = G_TLS_CONNECTION_GNUTLS_CLASS (klass);
337
338   g_type_class_add_private (klass, sizeof (GTlsClientConnectionGnutlsPrivate));
339
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;
344
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;
348
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");
353 }
354
355 static void
356 g_tls_client_connection_gnutls_client_connection_interface_init (GTlsClientConnectionInterface *iface)
357 {
358 }