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 : : /* KDE Connect uses self-signed certificates with a trust-on-first-use policy,
16 : : * and since GLib can not guarantee all certificate errors are set, the
17 : : * `accept-certificate` callback always returns %TRUE.
18 : : */
19 : : static gboolean
20 : 8 : valent_lan_connection_accept_certificate_cb (GTlsConnection *connection,
21 : : GTlsCertificate *peer_cert,
22 : : GTlsCertificateFlags errors,
23 : : gpointer user_data)
24 : : {
25 : 8 : return TRUE;
26 : : }
27 : :
28 : : /**
29 : : * valent_lan_connection_handshake:
30 : : * @connection: a `GSocketConnection`
31 : : * @certificate: a `GTlsCertificate`
32 : : * @trusted: (nullable): a `GTlsCertificate`
33 : : * @is_client: %TRUE for client connection, or %FALSE for a server connection
34 : : * @cancellable: (nullable): a `GCancellable`
35 : : * @error: (nullable): a `GError`
36 : : *
37 : : * Wrap @connection in a [class@Gio.TlsConnection] and perform the handshake.
38 : : *
39 : : * If @trusted is not %NULL, the remote device will be expected to handshake
40 : : * use the same certificate, otherwise trusted devices will be searched for
41 : : * a certificate. If the device is unknown, the certificate will be verified
42 : : * as self-signed and accepted on a trust-on-first-use basis.
43 : : *
44 : : * If @is_client is %TRUE, this will create a [class@Gio.TlsClientConnection],
45 : : * otherwise a [class@Gio.TlsServerConnection].
46 : : *
47 : : * Returns: (transfer full) (nullable): a TLS encrypted `GIOStream`
48 : : */
49 : : GIOStream *
50 : 8 : valent_lan_connection_handshake (GSocketConnection *connection,
51 : : GTlsCertificate *certificate,
52 : : GTlsCertificate *trusted,
53 : : gboolean is_client,
54 : : GCancellable *cancellable,
55 : : GError **error)
56 : : {
57 : 8 : GTlsBackend *backend;
58 : 16 : g_autoptr (GIOStream) ret = NULL;
59 [ - + ]: 8 : g_autoptr (GTlsCertificate) ca_certificate = NULL;
60 : 8 : GTlsCertificate *peer_certificate;
61 : 8 : const char *peer_common_name = NULL;
62 : 8 : GTlsCertificateFlags errors = 0;
63 : :
64 [ + - + - : 8 : g_assert (G_IS_SOCKET_CONNECTION (connection));
+ - - + ]
65 [ + - + - : 8 : g_assert (G_IS_TLS_CERTIFICATE (certificate));
+ - - + ]
66 [ + + + - : 8 : g_assert (trusted == NULL || G_IS_TLS_CERTIFICATE (trusted));
+ - - + ]
67 [ + + + - : 8 : g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
- + - - ]
68 [ + - - + ]: 8 : g_assert (error == NULL || *error == NULL);
69 : :
70 : 8 : g_socket_set_keepalive (g_socket_connection_get_socket (connection), TRUE);
71 : :
72 : : /* NOTE: When negotiating the primary connection, a KDE Connect device
73 : : * acts as the TLS server when opening TCP connections,
74 : : * and the TLS client when accepting TCP connections.
75 : : *
76 : : * When negotiating an auxiliary connection, a KDE Connect device
77 : : * acts as the TLS server when accepting TCP connections,
78 : : * and the TLS client when opening TCP connections.
79 : : *
80 : : * TODO: If a database returned by [ctor@Gio.TlsFileDatabase.new] were used
81 : : * for trusted certificates, this function could return immediately
82 : : * after a handshake with no errors.
83 : : *
84 : : * TODO: KDE Connect uses TLS/SNI to communicate the expected device ID, but
85 : : * but GnuTLS refuses server names that are not valid hostnames.
86 : : */
87 : 8 : backend = g_tls_backend_get_default ();
88 [ + + ]: 8 : if (is_client)
89 : : {
90 : 4 : ret = g_initable_new (g_tls_backend_get_client_connection_type (backend),
91 : : cancellable,
92 : : error,
93 : : "base-io-stream", connection,
94 : : "certificate", certificate,
95 : : // TODO: see note above about TLS/SNI
96 : : "server-identity", NULL,
97 : : NULL);
98 : : }
99 : : else
100 : : {
101 : 4 : ret = g_initable_new (g_tls_backend_get_server_connection_type (backend),
102 : : cancellable,
103 : : error,
104 : : "authentication-mode", G_TLS_AUTHENTICATION_REQUIRED,
105 : : "base-io-stream", connection,
106 : : "certificate", certificate,
107 : : NULL);
108 : : }
109 : :
110 [ + - ]: 8 : if (ret == NULL)
111 : : return NULL;
112 : :
113 : : /* Perform TLS handshake to get the peer certificate, which should only
114 : : * fail on cancellation.
115 : : */
116 : 8 : g_signal_connect (G_TLS_CONNECTION (ret),
117 : : "accept-certificate",
118 : : G_CALLBACK (valent_lan_connection_accept_certificate_cb),
119 : : NULL);
120 : :
121 [ + - ]: 8 : if (!g_tls_connection_handshake (G_TLS_CONNECTION (ret), cancellable, error))
122 : : return NULL;
123 : :
124 : : /* The certificate common name is validated as a device ID after the
125 : : * handshake, to sanitize queries to the filesystem for a trusted certificate.
126 : : */
127 : 8 : peer_certificate = g_tls_connection_get_peer_certificate (G_TLS_CONNECTION (ret));
128 : 8 : peer_common_name = valent_certificate_get_common_name (peer_certificate);
129 [ - + ]: 8 : if (!valent_device_validate_id (peer_common_name))
130 : : {
131 : 0 : g_set_error (error,
132 : : G_TLS_ERROR,
133 : : G_TLS_ERROR_HANDSHAKE,
134 : : "Certificate common name is not a valid device ID \"%s\"",
135 : : peer_common_name);
136 : 0 : return NULL;
137 : : }
138 : :
139 : : /* If @trusted is not provided, try to find a certificate in a device
140 : : * config directory. If neither are found, the certificate is verified
141 : : * as self-signed before being accepted per the trust-on-first-use policy.
142 : : *
143 : : * TODO: this should be handled by a managed cache object
144 : : */
145 [ + + ]: 8 : if (trusted != NULL)
146 : : {
147 : 2 : ca_certificate = g_object_ref (trusted);
148 : : }
149 : : else
150 : : {
151 : 6 : g_autofree char *trusted_path = NULL;
152 : :
153 : 6 : trusted_path = g_build_filename (g_get_user_config_dir(), PACKAGE_NAME,
154 : : "device", peer_common_name,
155 : : "certificate.pem",
156 : : NULL);
157 : 6 : ca_certificate = g_tls_certificate_new_from_file (trusted_path, NULL);
158 [ + - ]: 6 : if (ca_certificate == NULL)
159 : 6 : ca_certificate = g_object_ref (peer_certificate);
160 : : }
161 : :
162 : 8 : errors = g_tls_certificate_verify (peer_certificate, NULL, ca_certificate);
163 [ - + ]: 8 : if (errors != G_TLS_CERTIFICATE_NO_FLAGS)
164 : : {
165 : 0 : g_autofree char *errors_str = NULL;
166 : :
167 : 0 : errors_str = g_flags_to_string (G_TYPE_TLS_CERTIFICATE_FLAGS, errors);
168 : 0 : g_set_error (error,
169 : : G_TLS_ERROR,
170 : : G_TLS_ERROR_HANDSHAKE,
171 : : "Certificate for \"%s\" failed verification (%s)",
172 : : peer_common_name,
173 : : errors_str);
174 : 0 : return NULL;
175 : : }
176 : :
177 [ + + ]: 8 : g_debug (ca_certificate == peer_certificate
178 : : ? "Accepting certificate \"%s\" per trust-on-first-use policy"
179 : : : "Accepting certificate \"%s\" per successful verification",
180 : : peer_common_name);
181 : :
182 : 8 : return g_steal_pointer (&ret);
183 : : }
184 : :
|