GCC Code Coverage Report


Directory: ./
File: panels/system/remote-desktop/cc-tls-certificate.c
Date: 2024-05-04 07:58:27
Exec Total Coverage
Lines: 0 204 0.0%
Functions: 0 22 0.0%
Branches: 0 254 0.0%

Line Branch Exec Source
1 /* cc-tls-certificate.c
2 *
3 * Copyright 2018 Christian Hergert <chergert@redhat.com>
4 *
5 * This file is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU Lesser General Public License as
7 * published by the Free Software Foundation; either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This file is distributed in the hope that it will be useful, but
11 * 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 Public
16 * License along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: LGPL-3.0-or-later
19 */
20
21 #include "config.h"
22
23 #include <errno.h>
24 #include <string.h>
25 #include <glib/gstdio.h>
26 #include <gnutls/x509.h>
27
28 #include "cc-tls-certificate.h"
29
30 #define DEFAULT_KEY_SIZE 4096
31 #define DEFAULT_EXPIRATION (60L*60L*24L*2L*365L)
32
33 static void
34 _gnutls_datum_clear (gnutls_datum_t *datum)
35 {
36 if (datum->data != NULL)
37 gnutls_free (datum->data);
38 }
39
40 static void
41 _gnutls_crt_free (gnutls_x509_crt_t *cert)
42 {
43 if (cert != NULL)
44 gnutls_x509_crt_deinit (*cert);
45 }
46
47 static void
48 _gnutls_privkey_free (gnutls_x509_privkey_t *privkey)
49 {
50 if (privkey != NULL)
51 gnutls_x509_privkey_deinit (*privkey);
52 }
53
54 G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (gnutls_datum_t, _gnutls_datum_clear)
55 G_DEFINE_AUTOPTR_CLEANUP_FUNC (gnutls_x509_crt_t, _gnutls_crt_free)
56 G_DEFINE_AUTOPTR_CLEANUP_FUNC (gnutls_x509_privkey_t, _gnutls_privkey_free)
57
58 typedef struct
59 {
60 gchar *public_key_path;
61 gchar *private_key_path;
62 gchar *c;
63 gchar *cn;
64 } GenerateData;
65
66 static void
67 generate_data_free (GenerateData *data)
68 {
69 g_clear_pointer (&data->public_key_path, g_free);
70 g_clear_pointer (&data->private_key_path, g_free);
71 g_clear_pointer (&data->c, g_free);
72 g_clear_pointer (&data->cn, g_free);
73 g_slice_free (GenerateData, data);
74 }
75
76 static gboolean
77 make_directory_parent (const gchar *path,
78 GError **error)
79 {
80 g_autofree gchar *dir = NULL;
81
82 g_assert (path != NULL);
83 g_assert (error != NULL);
84
85 dir = g_path_get_dirname (path);
86
87 if (g_mkdir_with_parents (dir, 0750) == -1)
88 {
89 g_set_error_literal (error,
90 G_IO_ERROR,
91 g_io_error_from_errno (errno),
92 g_strerror (errno));
93 return FALSE;
94 }
95
96 return TRUE;
97 }
98
99 static void
100 bonsai_tls_certificate_generate_worker (GTask *task,
101 gpointer source_object,
102 gpointer task_data,
103 GCancellable *cancellable)
104 {
105 GenerateData *data = task_data;
106 g_autoptr(GTlsCertificate) certificate = NULL;
107 g_autoptr(GError) error = NULL;
108 g_autoptr(gnutls_x509_crt_t) certptr = NULL;
109 g_autoptr(gnutls_x509_privkey_t) privkeyptr = NULL;
110 g_auto(gnutls_datum_t) pubkey_data = {0};
111 g_auto(gnutls_datum_t) privkey_data = {0};
112 g_autofree char *dn = NULL;
113 gnutls_x509_privkey_t privkey;
114 gnutls_x509_crt_t cert;
115 guint32 serial = 1;
116 int gtlsret = 0;
117
118 g_assert (G_IS_TASK (task));
119 g_assert (source_object == NULL);
120 g_assert (data != NULL);
121 g_assert (data->public_key_path != NULL);
122 g_assert (data->private_key_path != NULL);
123 g_assert (data->c != NULL);
124 g_assert (data->cn != NULL);
125
126 if (!make_directory_parent (data->public_key_path, &error) ||
127 !make_directory_parent (data->private_key_path, &error))
128 {
129 g_task_return_error (task, g_steal_pointer (&error));
130 return;
131 }
132
133 /*
134 * From the GnuTLS documentation:
135 *
136 * To be consistent with the X.509/PKIX specifications the provided serial
137 * should be a big-endian positive number (i.e. it's leftmost bit should be
138 * zero).
139 */
140 serial = GUINT32_TO_BE (serial);
141
142 #define HANDLE_FAILURE(x) \
143 G_STMT_START { \
144 gtlsret = x; \
145 if (gtlsret != GNUTLS_E_SUCCESS) \
146 goto failure; \
147 } G_STMT_END
148
149 HANDLE_FAILURE (gnutls_x509_crt_init (&cert));
150 certptr = &cert;
151 HANDLE_FAILURE (gnutls_x509_crt_set_version (cert, 3));
152 HANDLE_FAILURE (gnutls_x509_crt_set_activation_time (cert, time (NULL)));
153 dn = g_strdup_printf ("C=%s,CN=%s", data->c, data->cn);
154 HANDLE_FAILURE (gnutls_x509_crt_set_dn (cert, dn, NULL));
155 HANDLE_FAILURE (gnutls_x509_crt_set_serial (cert, &serial, sizeof serial));
156 /* 5 years. We'll figure out key rotation in that time... */
157 HANDLE_FAILURE (gnutls_x509_crt_set_expiration_time (cert, time (NULL) + DEFAULT_EXPIRATION));
158 HANDLE_FAILURE (gnutls_x509_privkey_init (&privkey));
159 privkeyptr = &privkey;
160 HANDLE_FAILURE (gnutls_x509_privkey_generate (privkey, GNUTLS_PK_RSA, DEFAULT_KEY_SIZE, 0));
161 HANDLE_FAILURE (gnutls_x509_crt_set_key (cert, privkey));
162 HANDLE_FAILURE (gnutls_x509_crt_sign (cert, cert, privkey));
163 HANDLE_FAILURE (gnutls_x509_crt_export2 (cert, GNUTLS_X509_FMT_PEM, &pubkey_data));
164 if (!g_file_set_contents (data->public_key_path, (char *)pubkey_data.data, pubkey_data.size, &error))
165 goto failure;
166
167 HANDLE_FAILURE (gnutls_x509_privkey_export2 (privkey, GNUTLS_X509_FMT_PEM, &privkey_data));
168 if (!g_file_set_contents (data->private_key_path, (char*)privkey_data.data, privkey_data.size, &error))
169 goto failure;
170
171 #undef HANDLE_FAILURE
172
173 if ((certificate = g_tls_certificate_new_from_files (data->public_key_path, data->private_key_path, &error)))
174 {
175 g_task_return_pointer (task, g_steal_pointer (&certificate), g_object_unref);
176 return;
177 }
178
179 failure:
180
181 if (error != NULL)
182 g_task_return_error (task, g_steal_pointer (&error));
183 else if (gtlsret != 0)
184 g_task_return_new_error (task,
185 G_TLS_ERROR,
186 G_TLS_ERROR_MISC,
187 "GnuTLS Error: %s",
188 gnutls_strerror (gtlsret));
189 else
190 g_task_return_new_error (task,
191 G_IO_ERROR,
192 G_IO_ERROR_FAILED,
193 "Failed to generate TLS certificate pair");
194 }
195
196 void
197 bonsai_tls_certificate_new_generate_async (const gchar *public_key_path,
198 const gchar *private_key_path,
199 const gchar *c,
200 const gchar *cn,
201 GCancellable *cancellable,
202 GAsyncReadyCallback callback,
203 gpointer user_data)
204 {
205 g_autoptr(GTask) task = NULL;
206 GenerateData *data;
207
208 g_return_if_fail (public_key_path != NULL);
209 g_return_if_fail (private_key_path != NULL);
210 g_return_if_fail (c != NULL);
211 g_return_if_fail (cn != NULL);
212 g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
213
214 task = g_task_new (NULL, cancellable, callback, user_data);
215 g_task_set_source_tag (task, bonsai_tls_certificate_new_generate_async);
216
217 data = g_slice_new0 (GenerateData);
218 data->public_key_path = g_strdup (public_key_path);
219 data->private_key_path = g_strdup (private_key_path);
220 data->c = g_strdup (c);
221 data->cn = g_strdup (cn);
222 g_task_set_task_data (task, data, (GDestroyNotify)generate_data_free);
223
224 g_task_run_in_thread (task, bonsai_tls_certificate_generate_worker);
225 }
226
227 GTlsCertificate *
228 bonsai_tls_certificate_new_generate_finish (GAsyncResult *result,
229 GError **error)
230 {
231 g_return_val_if_fail (G_IS_TASK (result), NULL);
232
233 return g_task_propagate_pointer (G_TASK (result), error);
234 }
235
236 GTlsCertificate *
237 bonsai_tls_certificate_new_generate (const gchar *public_key_path,
238 const gchar *private_key_path,
239 const gchar *c,
240 const gchar *cn,
241 GCancellable *cancellable,
242 GError **error)
243 {
244 g_autoptr(GTask) task = NULL;
245 GenerateData *data;
246
247 g_return_val_if_fail (public_key_path != NULL, NULL);
248 g_return_val_if_fail (private_key_path != NULL, NULL);
249 g_return_val_if_fail (c != NULL, NULL);
250 g_return_val_if_fail (cn != NULL, NULL);
251 g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), NULL);
252
253 task = g_task_new (NULL, cancellable, NULL, NULL);
254 g_task_set_source_tag (task, bonsai_tls_certificate_new_generate);
255
256 data = g_slice_new0 (GenerateData);
257 data->public_key_path = g_strdup (public_key_path);
258 data->private_key_path = g_strdup (private_key_path);
259 data->c = g_strdup (c);
260 data->cn = g_strdup (cn);
261 g_task_set_task_data (task, data, (GDestroyNotify)generate_data_free);
262
263 bonsai_tls_certificate_generate_worker (task, NULL, data, cancellable);
264
265 return g_task_propagate_pointer (task, error);
266 }
267
268 gchar *
269 bonsai_tls_certificate_get_hash (GTlsCertificate *cert)
270 {
271 g_autoptr(GByteArray) bytesarray = NULL;
272 g_autoptr(GChecksum) checksum = NULL;
273
274 g_return_val_if_fail (G_IS_TLS_CERTIFICATE (cert), NULL);
275
276 g_object_get (cert, "certificate", &bytesarray, NULL);
277
278 checksum = g_checksum_new (G_CHECKSUM_SHA256);
279 g_checksum_update (checksum, bytesarray->data, bytesarray->len);
280
281 return g_ascii_strdown (g_checksum_get_string (checksum), -1);
282 }
283
284 typedef struct
285 {
286 gchar *public_key_path;
287 gchar *private_key_path;
288 gchar *c;
289 gchar *cn;
290 } NewFromFilesOrGenerate;
291
292 static void
293 new_from_files_or_generate_free (gpointer data)
294 {
295 NewFromFilesOrGenerate *state = data;
296
297 g_clear_pointer (&state->public_key_path, g_free);
298 g_clear_pointer (&state->private_key_path, g_free);
299 g_clear_pointer (&state->c, g_free);
300 g_clear_pointer (&state->cn, g_free);
301 g_free (state);
302 }
303
304 static void
305 bonsai_tls_certificate_new_from_files_or_generate_worker (GTask *task,
306 gpointer source_object,
307 gpointer task_data,
308 GCancellable *cancellable)
309 {
310 NewFromFilesOrGenerate *state = task_data;
311 g_autoptr(GTlsCertificate) certificate = NULL;
312 g_autoptr(GError) error = NULL;
313
314 g_assert (G_IS_TASK (task));
315 g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
316 g_assert (state != NULL);
317 g_assert (state->public_key_path != NULL);
318 g_assert (state->private_key_path != NULL);
319
320 /* Generate new public/private key for server if we need one.
321 * Ideally, we would generate something signed by a real CA
322 * for the user. But since this is "private cloud" oriented,
323 * we should be fine for now.
324 */
325 if (!g_file_test (state->public_key_path, G_FILE_TEST_EXISTS) ||
326 !g_file_test (state->private_key_path, G_FILE_TEST_EXISTS))
327 certificate = bonsai_tls_certificate_new_generate (state->public_key_path,
328 state->private_key_path,
329 state->c,
330 state->cn,
331 cancellable,
332 &error);
333 else
334 certificate = g_tls_certificate_new_from_files (state->public_key_path,
335 state->private_key_path,
336 &error);
337
338 if (certificate == NULL)
339 g_task_return_error (task, g_steal_pointer (&error));
340 else
341 g_task_return_pointer (task, g_steal_pointer (&certificate), g_object_unref);
342 }
343
344 /**
345 * bonsai_tls_certificate_new_from_files_or_generate_async:
346 * @public_key_path: the path to the public key file
347 * @private_key_path: the path to the private key file
348 * @cancellable: (nullable): a #GCancellable or %NULL
349 * @callback: a callback to execute upon completion
350 * @user_data: closure data for @callback
351 *
352 * Asynchronously requests that a certificate is loaded, or generate one if it
353 * does not yet exist. The generated certificate is a self-signed certificate.
354 *
355 * Since: 0.2
356 */
357 void
358 bonsai_tls_certificate_new_from_files_or_generate_async (const gchar *public_key_path,
359 const gchar *private_key_path,
360 const gchar *c,
361 const gchar *cn,
362 GCancellable *cancellable,
363 GAsyncReadyCallback callback,
364 gpointer user_data)
365 {
366 g_autoptr(GTask) task = NULL;
367 NewFromFilesOrGenerate state;
368
369 g_assert (public_key_path != NULL);
370 g_assert (private_key_path != NULL);
371 g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
372
373 state.public_key_path = g_strdup (public_key_path);
374 state.private_key_path = g_strdup (private_key_path);
375 state.c = g_strdup (c);
376 state.cn = g_strdup (cn);
377
378 task = g_task_new (NULL, cancellable, callback, user_data);
379 g_task_set_source_tag (task, bonsai_tls_certificate_new_from_files_or_generate_async);
380 g_task_set_task_data (task, g_memdup2 (&state, sizeof state), new_from_files_or_generate_free);
381 g_task_run_in_thread (task, bonsai_tls_certificate_new_from_files_or_generate_worker);
382 }
383
384 /**
385 * bonsai_tls_certificate_new_from_files_or_generate_finish:
386 * @result: a #GAsyncResult provided to callback
387 * @error: a location for a #GError, or %NULL
388 *
389 * Completes a request to
390 * bonsai_tls_certificate_new_from_files_or_generate_async() which will
391 * either load a #GTlsCertificate for the files if they exist, or generate
392 * a new self-signed certificate in their place.
393 *
394 * Returns: (transfer none): a #GTlsCertificate or %NULL and @error is set.
395 *
396 * Since: 0.2
397 */
398 GTlsCertificate *
399 bonsai_tls_certificate_new_from_files_or_generate_finish (GAsyncResult *result,
400 GError **error)
401 {
402 g_assert (G_IS_TASK (result));
403
404 return g_task_propagate_pointer (G_TASK (result), error);
405 }
406
407 /**
408 * bonsai_tls_certificate_new_from_files_or_generate:
409 * @public_key_path: the path to the public key
410 * @private_key_path: the path to the private key
411 * @c: the C for the certificate
412 * @cn: the CN for the certificate
413 * @cancellable: (nullable): a #GCancellable or %NULL
414 * @error: the location for the error
415 *
416 * Loads a certificate or generates a new self-signed certificate in
417 * it's place.
418 *
419 * Returns: (transfer full): a #GTlsCertificate or %NULL and @error is set
420 *
421 * Since: 0.2
422 */
423 GTlsCertificate *
424 bonsai_tls_certificate_new_from_files_or_generate (const gchar *public_key_path,
425 const gchar *private_key_path,
426 const gchar *c,
427 const gchar *cn,
428 GCancellable *cancellable,
429 GError **error)
430 {
431 GTlsCertificate *ret;
432
433 g_return_val_if_fail (public_key_path != NULL, NULL);
434 g_return_val_if_fail (private_key_path != NULL, NULL);
435 g_return_val_if_fail (c != NULL, NULL);
436 g_return_val_if_fail (cn != NULL, NULL);
437 g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), NULL);
438
439 if (!(ret = g_tls_certificate_new_from_files (public_key_path, private_key_path, NULL)))
440 ret = bonsai_tls_certificate_new_generate (public_key_path,
441 private_key_path,
442 c,
443 cn,
444 cancellable,
445 error);
446
447 return g_steal_pointer (&ret);
448 }
449
450 /**
451 * bonsai_tls_certificate_new_for_user:
452 * @public_key_path: the path to the public key
453 * @private_key_path: the path to the private key
454 *
455 * This is a simplified form to create a new certificate or load a previously
456 * created certificate for the current user.
457 *
458 * Returns: (transfer none): a #GTlsCertificate or %NULL and @error is set.
459 *
460 * Since: 0.2
461 */
462 GTlsCertificate *
463 bonsai_tls_certificate_new_for_user (GCancellable *cancellable,
464 GError **error)
465 {
466 g_autofree gchar *public_key_path = NULL;
467 g_autofree gchar *private_key_path = NULL;
468
469 g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), NULL);
470
471 public_key_path = g_build_filename (g_get_user_config_dir (), "bonsai", "public.key", NULL);
472 private_key_path = g_build_filename (g_get_user_config_dir (), "bonsai", "private.key", NULL);
473
474 return bonsai_tls_certificate_new_from_files_or_generate (public_key_path,
475 private_key_path,
476 "US",
477 "GNOME",
478 cancellable,
479 error);
480 }
481
482 gboolean
483 bonsai_is_tls_hash (const gchar *hash)
484 {
485 guint len = 0;
486
487 if (hash == NULL)
488 return FALSE;
489
490 for (; *hash; hash++)
491 {
492 if (len == 64)
493 return FALSE;
494
495 switch (*hash)
496 {
497 case '0': case '1': case '2': case '3': case '4':
498 case '5': case '6': case '7': case '8': case '9':
499 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
500 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
501 len++;
502 break;
503
504 default:
505 return FALSE;
506 }
507 }
508
509 return len == 64;
510 }
511
512