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 : : *
170 : : * TODO: this should be handled by centralized manager object
171 : : */
172 : 6 : file = g_file_new_build_filename (g_get_user_config_dir(), PACKAGE_NAME,
173 : : "device", peer_id,
174 : : "certificate.pem",
175 : : NULL);
176 : :
177 [ - + ]: 6 : if (!g_file_query_exists (file, NULL))
178 : : return TRUE;
179 : :
180 : 0 : peer_trusted = g_tls_certificate_new_from_file (g_file_peek_path (file),
181 : : error);
182 : :
183 : : // TODO: handle the case of a corrupted certificate
184 [ # # ]: 0 : if (peer_trusted == NULL)
185 : : return FALSE;
186 : :
187 [ # # ]: 0 : if (!g_tls_certificate_is_same (peer_trusted, peer_certificate))
188 : : {
189 : 0 : g_set_error (error,
190 : : G_TLS_ERROR,
191 : : G_TLS_ERROR_HANDSHAKE,
192 : : "Invalid certificate for \"%s\"",
193 : : peer_id);
194 : 0 : return FALSE;
195 : : }
196 : :
197 : : return TRUE;
198 : : }
199 : :
200 : : /**
201 : : * valent_lan_encrypt_client_connection:
202 : : * @connection: a `GSocketConnection`
203 : : * @certificate: a `GTlsCertificate`
204 : : * @cancellable: (nullable): a `GCancellable`
205 : : * @error: (nullable): a `GError`
206 : : *
207 : : * Authenticate and encrypt a client connection.
208 : : *
209 : : * This function sets the standard KDE Connect socket options on @connection,
210 : : * wraps it in a [class@Gio.TlsConnection] and returns the result.
211 : : *
212 : : * The common name is extracted from the peer's TLS certificate and used as the
213 : : * device ID to check for a trusted certificate. For auxiliary connections
214 : : * created from an existing channel, use [func@Valent.lan_encrypt_client].
215 : : *
216 : : * Returns: (transfer full) (nullable): a TLS encrypted `GIOStream`
217 : : */
218 : : GIOStream *
219 : 3 : valent_lan_encrypt_client_connection (GSocketConnection *connection,
220 : : GTlsCertificate *certificate,
221 : : GCancellable *cancellable,
222 : : GError **error)
223 : : {
224 : 6 : g_autoptr (GSocketAddress) address = NULL;
225 [ + - ]: 3 : g_autoptr (GIOStream) tls_stream = NULL;
226 : :
227 [ + - + - : 3 : g_assert (G_IS_SOCKET_CONNECTION (connection));
+ - - + ]
228 [ + - + - : 3 : g_assert (G_IS_TLS_CERTIFICATE (certificate));
+ - - + ]
229 [ + + + - : 3 : g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
- + - - ]
230 [ + - - + ]: 3 : g_assert (error == NULL || *error == NULL);
231 : :
232 : 3 : valent_lan_configure_socket (connection);
233 : :
234 : : /* We're the client when accepting incoming connections */
235 : 3 : address = g_socket_connection_get_remote_address (connection, error);
236 : :
237 [ - + ]: 3 : if (address == NULL)
238 : : return NULL;
239 : :
240 : 3 : tls_stream = g_tls_client_connection_new (G_IO_STREAM (connection),
241 : : G_SOCKET_CONNECTABLE (address),
242 : : error);
243 : :
244 [ - + ]: 3 : if (tls_stream == NULL)
245 : : return NULL;
246 : :
247 : 3 : g_tls_connection_set_certificate (G_TLS_CONNECTION (tls_stream), certificate);
248 : :
249 [ - + ]: 3 : if (!valent_lan_handshake_peer (G_TLS_CONNECTION (tls_stream),
250 : : cancellable,
251 : : error))
252 : : {
253 : 0 : g_io_stream_close (tls_stream, NULL, NULL);
254 : 0 : return NULL;
255 : : }
256 : :
257 : : return g_steal_pointer (&tls_stream);
258 : : }
259 : :
260 : : /**
261 : : * valent_lan_encrypt_client:
262 : : * @connection: a `GSocketConnection`
263 : : * @certificate: a `GTlsCertificate`
264 : : * @peer_certificate: a `GTlsCertificate`
265 : : * @cancellable: (nullable): a `GCancellable`
266 : : * @error: (nullable): a `GError`
267 : : *
268 : : * Authenticate and encrypt an auxiliary client connection.
269 : : *
270 : : * This function sets the standard KDE Connect socket options on @connection,
271 : : * wraps it in a [class@Gio.TlsConnection] and returns the result.
272 : : *
273 : : * Returns: (transfer full) (nullable): a TLS encrypted `GIOStream`
274 : : */
275 : : GIOStream *
276 : 1 : valent_lan_encrypt_client (GSocketConnection *connection,
277 : : GTlsCertificate *certificate,
278 : : GTlsCertificate *peer_certificate,
279 : : GCancellable *cancellable,
280 : : GError **error)
281 : : {
282 : 2 : g_autoptr (GSocketAddress) address = NULL;
283 [ + - ]: 1 : g_autoptr (GIOStream) tls_stream = NULL;
284 : :
285 [ + - + - : 1 : g_assert (G_IS_SOCKET_CONNECTION (connection));
+ - - + ]
286 [ + - + - : 1 : g_assert (G_IS_TLS_CERTIFICATE (certificate));
+ - - + ]
287 : : //g_assert (G_IS_TLS_CERTIFICATE (peer_certificate));
288 [ - + - - : 1 : g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
- - - - ]
289 [ + - - + ]: 1 : g_assert (error == NULL || *error == NULL);
290 : :
291 : : /* TODO: Occasionally we are not passed a certificate. This could mean the
292 : : * parent connection is unauthorized, but more likely there is a logic error
293 : : * elsewhere where we're making a false assumption. */
294 [ + - + - : 1 : if G_UNLIKELY (!G_IS_TLS_CERTIFICATE (peer_certificate))
+ - + - ]
295 : : {
296 : 0 : g_set_error (error,
297 : : G_TLS_ERROR,
298 : : G_TLS_ERROR_CERTIFICATE_REQUIRED,
299 : : "No peer certificate");
300 : 0 : return NULL;
301 : : }
302 : :
303 : 1 : valent_lan_configure_socket (connection);
304 : :
305 : 1 : address = g_socket_connection_get_remote_address (connection, error);
306 : :
307 [ + - ]: 1 : if (address == NULL)
308 : : return NULL;
309 : :
310 : : /* We're the client when opening auxiliary connections */
311 : 1 : tls_stream = g_tls_client_connection_new (G_IO_STREAM (connection),
312 : : G_SOCKET_CONNECTABLE (address),
313 : : error);
314 : :
315 [ + - ]: 1 : if (tls_stream == NULL)
316 : : return NULL;
317 : :
318 : 1 : g_tls_connection_set_certificate (G_TLS_CONNECTION (tls_stream), certificate);
319 : :
320 [ - + ]: 1 : if (!valent_lan_handshake_certificate (G_TLS_CONNECTION (tls_stream),
321 : : peer_certificate,
322 : : cancellable,
323 : : error))
324 : : {
325 : 0 : g_io_stream_close (tls_stream, NULL, NULL);
326 : 0 : return NULL;
327 : : }
328 : :
329 : : return g_steal_pointer (&tls_stream);
330 : : }
331 : :
332 : : /**
333 : : * valent_lan_encrypt_server_connection:
334 : : * @connection: a `GSocketConnection`
335 : : * @certificate: a `GTlsConnection`
336 : : * @cancellable: (nullable): a `GCancellable`
337 : : * @error: (nullable): a `GError`
338 : : *
339 : : * Authenticate and encrypt a server connection.
340 : : *
341 : : * This function sets the standard KDE Connect socket options on @connection,
342 : : * wraps it in a [class@Gio.TlsConnection] and returns the result.
343 : : *
344 : : * The common name is extracted from the peer's TLS certificate and used as the
345 : : * device ID to check for a trusted certificate. For auxiliary connections
346 : : * created from an existing channel, use [func@Valent.lan_encrypt_server].
347 : : *
348 : : * Returns: (transfer full) (nullable): a TLS encrypted `GIOStream`
349 : : */
350 : : GIOStream *
351 : 3 : valent_lan_encrypt_server_connection (GSocketConnection *connection,
352 : : GTlsCertificate *certificate,
353 : : GCancellable *cancellable,
354 : : GError **error)
355 : : {
356 : 6 : g_autoptr (GIOStream) tls_stream = NULL;
357 : :
358 [ + - + - : 3 : g_assert (G_IS_SOCKET_CONNECTION (connection));
+ - - + ]
359 [ + - + - : 3 : g_assert (G_IS_TLS_CERTIFICATE (certificate));
+ - - + ]
360 [ + + + - : 3 : g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
- + - - ]
361 [ + - - + ]: 3 : g_assert (error == NULL || *error == NULL);
362 : :
363 : 3 : valent_lan_configure_socket (connection);
364 : :
365 : : /* We're the server when opening outgoing connections */
366 : 3 : tls_stream = g_tls_server_connection_new (G_IO_STREAM (connection),
367 : : certificate,
368 : : error);
369 : :
370 [ - + ]: 3 : if (tls_stream == NULL)
371 : : return NULL;
372 : :
373 : 3 : g_object_set (G_TLS_SERVER_CONNECTION (tls_stream),
374 : : "authentication-mode", G_TLS_AUTHENTICATION_REQUIRED,
375 : : NULL);
376 : :
377 [ - + ]: 3 : if (!valent_lan_handshake_peer (G_TLS_CONNECTION (tls_stream),
378 : : cancellable,
379 : : error))
380 : : {
381 : 0 : g_io_stream_close (tls_stream, NULL, NULL);
382 : 0 : return NULL;
383 : : }
384 : :
385 : : return g_steal_pointer (&tls_stream);
386 : : }
387 : :
388 : : /**
389 : : * valent_lan_encrypt_server:
390 : : * @connection: a `GSocketConnection`
391 : : * @certificate: a `GTlsCertificate`
392 : : * @peer_certificate: a `GTlsCertificate`
393 : : * @cancellable: (nullable): a `GCancellable`
394 : : * @error: (nullable): a `GError`
395 : : *
396 : : * Authenticate and encrypt an auxiliary server connection.
397 : : *
398 : : * This function sets the standard KDE Connect socket options on @connection,
399 : : * wraps it in a [class@Gio.TlsConnection] and returns the result.
400 : : *
401 : : * Returns: (transfer full) (nullable): a TLS encrypted `GIOStream`
402 : : */
403 : : GIOStream *
404 : 1 : valent_lan_encrypt_server (GSocketConnection *connection,
405 : : GTlsCertificate *certificate,
406 : : GTlsCertificate *peer_certificate,
407 : : GCancellable *cancellable,
408 : : GError **error)
409 : : {
410 : 2 : g_autoptr (GIOStream) tls_stream = NULL;
411 : :
412 [ + - + - : 1 : g_assert (G_IS_SOCKET_CONNECTION (connection));
+ - - + ]
413 [ + - + - : 1 : g_assert (G_IS_TLS_CERTIFICATE (certificate));
+ - - + ]
414 [ + - + - : 1 : g_assert (G_IS_TLS_CERTIFICATE (peer_certificate));
+ - - + ]
415 [ - + - - : 1 : g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
- - - - ]
416 [ + - - + ]: 1 : g_assert (error == NULL || *error == NULL);
417 : :
418 : 1 : valent_lan_configure_socket (connection);
419 : :
420 : : /* We're the server when accepting auxiliary connections */
421 : 1 : tls_stream = g_tls_server_connection_new (G_IO_STREAM (connection),
422 : : certificate,
423 : : error);
424 : :
425 [ - + ]: 1 : if (tls_stream == NULL)
426 : : return NULL;
427 : :
428 : 1 : g_object_set (G_TLS_SERVER_CONNECTION (tls_stream),
429 : : "authentication-mode", G_TLS_AUTHENTICATION_REQUIRED,
430 : : NULL);
431 : :
432 [ - + ]: 1 : if (!valent_lan_handshake_certificate (G_TLS_CONNECTION (tls_stream),
433 : : peer_certificate,
434 : : cancellable,
435 : : error))
436 : : {
437 : 0 : g_io_stream_close (tls_stream, NULL, NULL);
438 : 0 : return NULL;
439 : : }
440 : :
441 : : return g_steal_pointer (&tls_stream);
442 : : }
443 : :
|