Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-3.0-or-later
2 : : // SPDX-FileCopyrightText: Andy Holmes <andrew.g.r.holmes@gmail.com>
3 : :
4 : : #define G_LOG_DOMAIN "valent-lan-utils"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <gio/gio.h>
9 : : #include <gio/gnetworking.h>
10 : : #include <valent.h>
11 : :
12 : : #include "valent-lan-utils.h"
13 : :
14 : :
15 : : /* < private >
16 : : * valent_lan_configure_socket:
17 : : * @connection: a #GSocketConnection
18 : : *
19 : : * Configure TCP socket options as they are set in kdeconnect-kde.
20 : : *
21 : : * Unlike kdeconnect-kde keepalive is not enabled if the required socket options
22 : : * are not defined, otherwise connections may hang indefinitely.
23 : : *
24 : : * See: https://invent.kde.org/network/kdeconnect-kde/blob/master/core/backends/lan/lanlinkprovider.cpp
25 : : */
26 : : static inline void
27 : 8 : valent_lan_configure_socket (GSocketConnection *connection)
28 : : {
29 : : #if defined(TCP_KEEPIDLE) && defined(TCP_KEEPINTVL) && defined(TCP_KEEPCNT)
30 : 8 : GSocket *socket;
31 : 8 : GError *error = NULL;
32 : :
33 [ + - + - : 8 : g_assert (G_IS_TCP_CONNECTION (connection));
- + - - ]
34 : :
35 : 8 : socket = g_socket_connection_get_socket (connection);
36 : 8 : g_socket_set_keepalive (socket, TRUE);
37 : :
38 [ - + ]: 8 : if (!g_socket_set_option (socket, IPPROTO_TCP, TCP_KEEPIDLE, 10, &error))
39 : : {
40 : 0 : g_warning ("%s(): TCP_KEEPIDLE: %s", G_STRFUNC, error->message);
41 : 0 : g_clear_error (&error);
42 : : }
43 : :
44 [ - + ]: 8 : if (!g_socket_set_option (socket, IPPROTO_TCP, TCP_KEEPINTVL, 5, &error))
45 : : {
46 : 0 : g_warning ("%s(): TCP_KEEPINTVL: %s", G_STRFUNC, error->message);
47 : 0 : g_clear_error (&error);
48 : : }
49 : :
50 [ - + ]: 8 : if (!g_socket_set_option (socket, IPPROTO_TCP, TCP_KEEPCNT, 3, &error))
51 : : {
52 : 0 : g_warning ("%s(): TCP_KEEPCNT: %s", G_STRFUNC, error->message);
53 : 0 : g_clear_error (&error);
54 : : }
55 : : #endif /* TCP_KEEPIDLE && TCP_KEEPINTVL && TCP_KEEPCNT */
56 : 8 : }
57 : :
58 : :
59 : : /*
60 : : * The KDE Connect protocol follows a trust-on-first-use approach to TLS, so we
61 : : * use a dummy callback for #GTlsConnection::accept-certificate that always
62 : : * returns %TRUE.
63 : : */
64 : : static gboolean
65 : 8 : valent_lan_accept_certificate_cb (GTlsConnection *connection,
66 : : GTlsCertificate *peer_cert,
67 : : GTlsCertificateFlags errors,
68 : : gpointer user_data)
69 : : {
70 : 8 : return TRUE;
71 : : }
72 : :
73 : : static gboolean
74 : 8 : valent_lan_accept_certificate (GTlsConnection *connection,
75 : : GCancellable *cancellable,
76 : : GError **error)
77 : : {
78 : 8 : unsigned long accept_id;
79 : 8 : gboolean ret;
80 : :
81 : 8 : accept_id = g_signal_connect (G_OBJECT (connection),
82 : : "accept-certificate",
83 : : G_CALLBACK (valent_lan_accept_certificate_cb),
84 : : NULL);
85 : :
86 : 8 : ret = g_tls_connection_handshake (connection, cancellable, error);
87 [ + - ]: 8 : g_clear_signal_handler (&accept_id, connection);
88 : :
89 : 8 : return ret;
90 : : }
91 : :
92 : : /* < private >
93 : : * valent_lan_handshake_certificate:
94 : : * @connection: a #GTlsConnection
95 : : * @trusted: a #GTlsCertificate
96 : : * @cancellable: (nullable): a #GCancellable
97 : : * @error: (nullable): a #GError
98 : : *
99 : : * Authenticate a connection for a known peer.
100 : : *
101 : : * This function is used to authenticate a TLS connection against a known and
102 : : * trusted TLS certificate. This should be used to authenticate auxiliary
103 : : * connections for authenticated channels.
104 : : *
105 : : * Returns: %TRUE, or %FALSE with @error set
106 : : */
107 : : static gboolean
108 : 2 : valent_lan_handshake_certificate (GTlsConnection *connection,
109 : : GTlsCertificate *trusted,
110 : : GCancellable *cancellable,
111 : : GError **error)
112 : : {
113 : 2 : GTlsCertificate *peer_cert;
114 : :
115 [ - + ]: 2 : if (!valent_lan_accept_certificate (connection, cancellable, error))
116 : : return FALSE;
117 : :
118 : 2 : peer_cert = g_tls_connection_get_peer_certificate (connection);
119 : :
120 [ - + ]: 2 : if (!g_tls_certificate_is_same (trusted, peer_cert))
121 : : {
122 : 0 : g_set_error (error,
123 : : G_TLS_ERROR,
124 : : G_TLS_ERROR_HANDSHAKE,
125 : : "Invalid certificate");
126 : 0 : return FALSE;
127 : : }
128 : :
129 : : return TRUE;
130 : : }
131 : :
132 : : /* < private >
133 : : * valent_lan_handshake_peer:
134 : : * @connection: a #GTlsConnection
135 : : * @cancellable: (nullable): a #GCancellable
136 : : * @error: (nullable): a #GError
137 : : *
138 : : * Authenticate a connection for an unknown peer.
139 : : *
140 : : * This function is used to authenticate a TLS connection whether the remote
141 : : * device is paired or not. This should be used to authenticate new connections
142 : : * when negotiating a [class@Valent.LanChannel].
143 : : *
144 : : * If the TLS certificate is not known (i.e. previously authenticated), the
145 : : * device is assumed to be unpaired and %TRUE will be returned to
146 : : * trust-on-first-use. The certificate will become "known" when if and when the
147 : : * device is successfully paired.
148 : : *
149 : : * Returns: %TRUE, or %FALSE with @error set
150 : : */
151 : : static gboolean
152 : 6 : valent_lan_handshake_peer (GTlsConnection *connection,
153 : : GCancellable *cancellable,
154 : : GError **error)
155 : : {
156 : 12 : g_autoptr (GFile) file = NULL;
157 [ + - ]: 6 : g_autoptr (GTlsCertificate) peer_trusted = NULL;
158 : 6 : GTlsCertificate *peer_certificate;
159 : 6 : const char *peer_id;
160 : :
161 [ + - ]: 6 : if (!valent_lan_accept_certificate (connection, cancellable, error))
162 : : return FALSE;
163 : :
164 : 6 : peer_certificate = g_tls_connection_get_peer_certificate (connection);
165 : 6 : peer_id = valent_certificate_get_common_name (peer_certificate);
166 : :
167 : : /* If the certificate can not be found, assume that's because the device is
168 : : * unpaired and the certificate will be verified with user interaction */
169 : 6 : file = g_file_new_build_filename (g_get_user_config_dir(), PACKAGE_NAME,
170 : : peer_id, "certificate.pem",
171 : : NULL);
172 : :
173 [ - + ]: 6 : if (!g_file_query_exists (file, NULL))
174 : : return TRUE;
175 : :
176 : 0 : peer_trusted = g_tls_certificate_new_from_file (g_file_peek_path (file),
177 : : error);
178 : :
179 : : // TODO: handle the case of a corrupted certificate
180 [ # # ]: 0 : if (peer_trusted == NULL)
181 : : return FALSE;
182 : :
183 [ # # ]: 0 : if (!g_tls_certificate_is_same (peer_trusted, peer_certificate))
184 : : {
185 : 0 : g_set_error (error,
186 : : G_TLS_ERROR,
187 : : G_TLS_ERROR_HANDSHAKE,
188 : : "Invalid certificate for \"%s\"",
189 : : peer_id);
190 : 0 : return FALSE;
191 : : }
192 : :
193 : : return TRUE;
194 : : }
195 : :
196 : : /**
197 : : * valent_lan_encrypt_client_connection:
198 : : * @connection: a #GSocketConnection
199 : : * @certificate: a #GTlsCertificate
200 : : * @cancellable: (nullable): a #GCancellable
201 : : * @error: (nullable): a #GError
202 : : *
203 : : * Authenticate and encrypt a client connection.
204 : : *
205 : : * This function sets the standard KDE Connect socket options on @connection,
206 : : * wraps it in a [class@Gio.TlsConnection] and returns the result.
207 : : *
208 : : * The common name is extracted from the peer's TLS certificate and used as the
209 : : * device ID to check for a trusted certificate. For auxiliary connections
210 : : * created from an existing channel, use [func@Valent.lan_encrypt_client].
211 : : *
212 : : * Returns: (transfer full) (nullable): a TLS encrypted #GIOStream
213 : : */
214 : : GIOStream *
215 : 3 : valent_lan_encrypt_client_connection (GSocketConnection *connection,
216 : : GTlsCertificate *certificate,
217 : : GCancellable *cancellable,
218 : : GError **error)
219 : : {
220 : 6 : g_autoptr (GSocketAddress) address = NULL;
221 [ + - ]: 3 : g_autoptr (GIOStream) tls_stream = NULL;
222 : :
223 [ + - + - : 3 : g_assert (G_IS_SOCKET_CONNECTION (connection));
+ - - + ]
224 [ + - + - : 3 : g_assert (G_IS_TLS_CERTIFICATE (certificate));
+ - - + ]
225 [ + + + - : 3 : g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
- + - - ]
226 [ + - - + ]: 3 : g_assert (error == NULL || *error == NULL);
227 : :
228 : 3 : valent_lan_configure_socket (connection);
229 : :
230 : : /* We're the client when accepting incoming connections */
231 : 3 : address = g_socket_connection_get_remote_address (connection, error);
232 : :
233 [ - + ]: 3 : if (address == NULL)
234 : : return NULL;
235 : :
236 : 3 : tls_stream = g_tls_client_connection_new (G_IO_STREAM (connection),
237 : : G_SOCKET_CONNECTABLE (address),
238 : : error);
239 : :
240 [ - + ]: 3 : if (tls_stream == NULL)
241 : : return NULL;
242 : :
243 : 3 : g_tls_connection_set_certificate (G_TLS_CONNECTION (tls_stream), certificate);
244 : :
245 [ - + ]: 3 : if (!valent_lan_handshake_peer (G_TLS_CONNECTION (tls_stream),
246 : : cancellable,
247 : : error))
248 : : {
249 : 0 : g_io_stream_close (tls_stream, NULL, NULL);
250 : 0 : return NULL;
251 : : }
252 : :
253 : : return g_steal_pointer (&tls_stream);
254 : : }
255 : :
256 : : /**
257 : : * valent_lan_encrypt_client:
258 : : * @connection: a #GSocketConnection
259 : : * @certificate: a #GTlsCertificate
260 : : * @peer_certificate: a #GTlsCertificate
261 : : * @cancellable: (nullable): a #GCancellable
262 : : * @error: (nullable): a #GError
263 : : *
264 : : * Authenticate and encrypt an auxiliary client connection.
265 : : *
266 : : * This function sets the standard KDE Connect socket options on @connection,
267 : : * wraps it in a [class@Gio.TlsConnection] and returns the result.
268 : : *
269 : : * Returns: (transfer full) (nullable): a TLS encrypted #GIOStream
270 : : */
271 : : GIOStream *
272 : 1 : valent_lan_encrypt_client (GSocketConnection *connection,
273 : : GTlsCertificate *certificate,
274 : : GTlsCertificate *peer_certificate,
275 : : GCancellable *cancellable,
276 : : GError **error)
277 : : {
278 : 2 : g_autoptr (GSocketAddress) address = NULL;
279 [ + - ]: 1 : g_autoptr (GIOStream) tls_stream = NULL;
280 : :
281 [ + - + - : 1 : g_assert (G_IS_SOCKET_CONNECTION (connection));
+ - - + ]
282 [ + - + - : 1 : g_assert (G_IS_TLS_CERTIFICATE (certificate));
+ - - + ]
283 : : //g_assert (G_IS_TLS_CERTIFICATE (peer_certificate));
284 [ - + - - : 1 : g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
- - - - ]
285 [ + - - + ]: 1 : g_assert (error == NULL || *error == NULL);
286 : :
287 : : /* TODO: Occasionally we are not passed a certificate. This could mean the
288 : : * parent connection is unauthorized, but more likely there is a logic error
289 : : * elsewhere where we're making a false assumption. */
290 [ + - + - : 1 : if G_UNLIKELY (!G_IS_TLS_CERTIFICATE (peer_certificate))
+ - + - ]
291 : : {
292 : 0 : g_set_error (error,
293 : : G_TLS_ERROR,
294 : : G_TLS_ERROR_CERTIFICATE_REQUIRED,
295 : : "No peer certificate");
296 : 0 : return NULL;
297 : : }
298 : :
299 : 1 : valent_lan_configure_socket (connection);
300 : :
301 : 1 : address = g_socket_connection_get_remote_address (connection, error);
302 : :
303 [ - + ]: 1 : if (address == NULL)
304 : : return NULL;
305 : :
306 : : /* We're the client when opening auxiliary connections */
307 : 1 : tls_stream = g_tls_client_connection_new (G_IO_STREAM (connection),
308 : : G_SOCKET_CONNECTABLE (address),
309 : : error);
310 : :
311 [ + - ]: 1 : if (tls_stream == NULL)
312 : : return NULL;
313 : :
314 : 1 : g_tls_connection_set_certificate (G_TLS_CONNECTION (tls_stream), certificate);
315 : :
316 [ - + ]: 1 : if (!valent_lan_handshake_certificate (G_TLS_CONNECTION (tls_stream),
317 : : peer_certificate,
318 : : cancellable,
319 : : error))
320 : : {
321 : 0 : g_io_stream_close (tls_stream, NULL, NULL);
322 : 0 : return NULL;
323 : : }
324 : :
325 : : return g_steal_pointer (&tls_stream);
326 : : }
327 : :
328 : : /**
329 : : * valent_lan_encrypt_server_connection:
330 : : * @connection: a #GSocketConnection
331 : : * @certificate: a #GTlsConnection
332 : : * @cancellable: (nullable): a #GCancellable
333 : : * @error: (nullable): a #GError
334 : : *
335 : : * Authenticate and encrypt a server connection.
336 : : *
337 : : * This function sets the standard KDE Connect socket options on @connection,
338 : : * wraps it in a [class@Gio.TlsConnection] and returns the result.
339 : : *
340 : : * The common name is extracted from the peer's TLS certificate and used as the
341 : : * device ID to check for a trusted certificate. For auxiliary connections
342 : : * created from an existing channel, use [func@Valent.lan_encrypt_server].
343 : : *
344 : : * Returns: (transfer full) (nullable): a TLS encrypted #GIOStream
345 : : */
346 : : GIOStream *
347 : 3 : valent_lan_encrypt_server_connection (GSocketConnection *connection,
348 : : GTlsCertificate *certificate,
349 : : GCancellable *cancellable,
350 : : GError **error)
351 : : {
352 : 6 : g_autoptr (GIOStream) tls_stream = NULL;
353 : :
354 [ + - + - : 3 : g_assert (G_IS_SOCKET_CONNECTION (connection));
+ - - + ]
355 [ + - + - : 3 : g_assert (G_IS_TLS_CERTIFICATE (certificate));
+ - - + ]
356 [ + + + - : 3 : g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
- + - - ]
357 [ + - - + ]: 3 : g_assert (error == NULL || *error == NULL);
358 : :
359 : 3 : valent_lan_configure_socket (connection);
360 : :
361 : : /* We're the server when opening outgoing connections */
362 : 3 : tls_stream = g_tls_server_connection_new (G_IO_STREAM (connection),
363 : : certificate,
364 : : error);
365 : :
366 [ - + ]: 3 : if (tls_stream == NULL)
367 : : return NULL;
368 : :
369 : 3 : g_object_set (G_TLS_SERVER_CONNECTION (tls_stream),
370 : : "authentication-mode", G_TLS_AUTHENTICATION_REQUIRED,
371 : : NULL);
372 : :
373 [ - + ]: 3 : if (!valent_lan_handshake_peer (G_TLS_CONNECTION (tls_stream),
374 : : cancellable,
375 : : error))
376 : : {
377 : 0 : g_io_stream_close (tls_stream, NULL, NULL);
378 : 0 : return NULL;
379 : : }
380 : :
381 : : return g_steal_pointer (&tls_stream);
382 : : }
383 : :
384 : : /**
385 : : * valent_lan_encrypt_server:
386 : : * @connection: a #GSocketConnection
387 : : * @certificate: a #GTlsCertificate
388 : : * @peer_certificate: a #GTlsCertificate
389 : : * @cancellable: (nullable): a #GCancellable
390 : : * @error: (nullable): a #GError
391 : : *
392 : : * Authenticate and encrypt an auxiliary server connection.
393 : : *
394 : : * This function sets the standard KDE Connect socket options on @connection,
395 : : * wraps it in a [class@Gio.TlsConnection] and returns the result.
396 : : *
397 : : * Returns: (transfer full) (nullable): a TLS encrypted #GIOStream
398 : : */
399 : : GIOStream *
400 : 1 : valent_lan_encrypt_server (GSocketConnection *connection,
401 : : GTlsCertificate *certificate,
402 : : GTlsCertificate *peer_certificate,
403 : : GCancellable *cancellable,
404 : : GError **error)
405 : : {
406 : 2 : g_autoptr (GIOStream) tls_stream = NULL;
407 : :
408 [ + - + - : 1 : g_assert (G_IS_SOCKET_CONNECTION (connection));
+ - - + ]
409 [ + - + - : 1 : g_assert (G_IS_TLS_CERTIFICATE (certificate));
+ - - + ]
410 [ + - + - : 1 : g_assert (G_IS_TLS_CERTIFICATE (peer_certificate));
+ - - + ]
411 [ - + - - : 1 : g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
- - - - ]
412 [ + - - + ]: 1 : g_assert (error == NULL || *error == NULL);
413 : :
414 : 1 : valent_lan_configure_socket (connection);
415 : :
416 : : /* We're the server when accepting auxiliary connections */
417 : 1 : tls_stream = g_tls_server_connection_new (G_IO_STREAM (connection),
418 : : certificate,
419 : : error);
420 : :
421 [ - + ]: 1 : if (tls_stream == NULL)
422 : : return NULL;
423 : :
424 : 1 : g_object_set (G_TLS_SERVER_CONNECTION (tls_stream),
425 : : "authentication-mode", G_TLS_AUTHENTICATION_REQUIRED,
426 : : NULL);
427 : :
428 [ - + ]: 1 : if (!valent_lan_handshake_certificate (G_TLS_CONNECTION (tls_stream),
429 : : peer_certificate,
430 : : cancellable,
431 : : error))
432 : : {
433 : 0 : g_io_stream_close (tls_stream, NULL, NULL);
434 : 0 : return NULL;
435 : : }
436 : :
437 : : return g_steal_pointer (&tls_stream);
438 : : }
439 : :
|