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-certificate"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <time.h>
9 : :
10 : : #include <gio/gio.h>
11 : : #include <gnutls/gnutls.h>
12 : : #include <gnutls/abstract.h>
13 : : #include <gnutls/x509.h>
14 : :
15 : : #include <libvalent-core.h>
16 : : #include "valent-certificate.h"
17 : : #include "valent-device.h"
18 : :
19 : : #define DEFAULT_EXPIRATION (60L*60L*24L*10L*365L)
20 : : #define DEFAULT_KEY_SIZE 4096
21 : :
22 : : #define SHA256_HEX_LEN 64
23 : : #define SHA256_STR_LEN 96
24 : :
25 : :
26 : : /**
27 : : * valent_certificate_generate:
28 : : * @cert_path: (type filename): file path to the certificate
29 : : * @key_path: (type filename): file path to the private key
30 : : * @common_name: common name for the certificate
31 : : * @error: (nullable): a `GError`
32 : : *
33 : : * Generate a private key and certificate for @common_name, saving them at
34 : : * @key_path and @cert_path respectively.
35 : : *
36 : : * Returns: %TRUE if successful, or %FALSE with @error set
37 : : *
38 : : * Since: 1.0
39 : : */
40 : : static gboolean
41 : 24 : valent_certificate_generate (const char *cert_path,
42 : : const char *key_path,
43 : : const char *common_name,
44 : : GError **error)
45 : : {
46 : 48 : g_autofree char *dn = NULL;
47 : 24 : gnutls_x509_privkey_t privkey = NULL;
48 : 24 : gnutls_x509_crt_t crt = NULL;
49 : 24 : gnutls_datum_t out;
50 : 24 : time_t timestamp;
51 : 24 : unsigned int serial;
52 : 24 : int rc;
53 : 24 : gboolean ret = FALSE;
54 : :
55 : 24 : VALENT_ENTRY;
56 : :
57 : : /*
58 : : * Private Key
59 : : */
60 [ + - ]: 24 : if ((rc = gnutls_x509_privkey_init (&privkey)) != GNUTLS_E_SUCCESS ||
61 [ + - ]: 24 : (rc = gnutls_x509_privkey_generate (privkey,
62 : : GNUTLS_PK_RSA,
63 : : DEFAULT_KEY_SIZE,
64 : 24 : 0)) != GNUTLS_E_SUCCESS ||
65 [ - + ]: 24 : (rc = gnutls_x509_privkey_export2 (privkey,
66 : : GNUTLS_X509_FMT_PEM,
67 : : &out)) != GNUTLS_E_SUCCESS)
68 : : {
69 : 0 : g_set_error (error,
70 : : G_IO_ERROR,
71 : : G_IO_ERROR_FAILED,
72 : : "Generating private key: %s",
73 : : gnutls_strerror (rc));
74 : 0 : VALENT_GOTO (out);
75 : : }
76 : :
77 : : /* Output the private key PEM to file */
78 : 48 : ret = g_file_set_contents_full (key_path,
79 : 24 : (const char *)out.data,
80 : 24 : out.size,
81 : : G_FILE_SET_CONTENTS_DURABLE,
82 : : 0600,
83 : : error);
84 : 24 : gnutls_free (out.data);
85 : :
86 [ - + ]: 24 : if (!ret)
87 : 0 : VALENT_GOTO (out);
88 : :
89 : : /*
90 : : * TLS Certificate
91 : : */
92 [ + - ]: 24 : if ((rc = gnutls_x509_crt_init (&crt)) != GNUTLS_E_SUCCESS ||
93 [ + - ]: 24 : (rc = gnutls_x509_crt_set_key (crt, privkey)) != GNUTLS_E_SUCCESS ||
94 [ - + ]: 24 : (rc = gnutls_x509_crt_set_version (crt, 3)) != GNUTLS_E_SUCCESS)
95 : : {
96 : 0 : g_set_error (error,
97 : : G_IO_ERROR,
98 : : G_IO_ERROR_FAILED,
99 : : "Generating certificate: %s",
100 : : gnutls_strerror (rc));
101 : 0 : VALENT_GOTO (out);
102 : : }
103 : :
104 : : /* Expiry (10 years) */
105 : 24 : timestamp = time (NULL);
106 : :
107 [ + - ]: 24 : if ((rc = gnutls_x509_crt_set_activation_time (crt, timestamp)) != GNUTLS_E_SUCCESS ||
108 [ - + ]: 24 : (rc = gnutls_x509_crt_set_expiration_time (crt, timestamp + DEFAULT_EXPIRATION)) != GNUTLS_E_SUCCESS)
109 : : {
110 : 0 : g_set_error (error,
111 : : G_IO_ERROR,
112 : : G_IO_ERROR_FAILED,
113 : : "Generating certificate: %s",
114 : : gnutls_strerror (rc));
115 : 0 : VALENT_GOTO (out);
116 : : }
117 : :
118 : : /* Serial Number */
119 : 24 : serial = GUINT32_TO_BE (10);
120 : 24 : gnutls_x509_crt_set_serial (crt, &serial, sizeof (unsigned int));
121 : :
122 : : /* Distinguished Name (RFC4514) */
123 : 24 : dn = g_strdup_printf ("O=%s,OU=%s,CN=%s", "Valent", "Valent", common_name);
124 : :
125 [ - + ]: 24 : if ((rc = gnutls_x509_crt_set_dn (crt, dn, NULL)) != GNUTLS_E_SUCCESS)
126 : : {
127 : 0 : g_set_error (error,
128 : : G_IO_ERROR,
129 : : G_IO_ERROR_FAILED,
130 : : "Generating certificate: %s",
131 : : gnutls_strerror (rc));
132 : 0 : VALENT_GOTO (out);
133 : : }
134 : :
135 : : /* Sign and export the certificate */
136 [ + - ]: 24 : if ((rc = gnutls_x509_crt_sign2 (crt, crt, privkey, GNUTLS_DIG_SHA256, 0)) != GNUTLS_E_SUCCESS ||
137 [ - + ]: 24 : (rc = gnutls_x509_crt_export2 (crt, GNUTLS_X509_FMT_PEM, &out)) != GNUTLS_E_SUCCESS)
138 : : {
139 : 0 : g_set_error (error,
140 : : G_IO_ERROR,
141 : : G_IO_ERROR_FAILED,
142 : : "Signing certificate: %s",
143 : : gnutls_strerror (rc));
144 : 0 : VALENT_GOTO (out);
145 : : }
146 : :
147 : : /* Output the certificate PEM to file */
148 : 48 : ret = g_file_set_contents_full (cert_path,
149 : 24 : (const char *)out.data,
150 : 24 : out.size,
151 : : G_FILE_SET_CONTENTS_DURABLE,
152 : : 0600,
153 : : error);
154 : 24 : gnutls_free (out.data);
155 : :
156 : 24 : out:
157 : 24 : gnutls_x509_crt_deinit (crt);
158 : 24 : gnutls_x509_privkey_deinit (privkey);
159 : :
160 : 24 : VALENT_RETURN (ret);
161 : : }
162 : :
163 : : static void
164 : 1 : valent_certificate_new_task (GTask *task,
165 : : gpointer source_object,
166 : : gpointer task_data,
167 : : GCancellable *cancellable)
168 : : {
169 : 1 : g_autoptr (GTlsCertificate) certificate = NULL;
170 : 1 : const char *path = task_data;
171 : 1 : GError *error = NULL;
172 : :
173 [ - + ]: 1 : if ((certificate = valent_certificate_new_sync (path, &error)) == NULL)
174 : 0 : return g_task_return_error (task, error);
175 : :
176 : 1 : g_task_return_pointer (task, g_steal_pointer (&certificate), g_object_unref);
177 : : }
178 : :
179 : : /**
180 : : * valent_certificate_new:
181 : : * @path: (type filename): a directory path
182 : : * @cancellable: (nullable): `GCancellable`
183 : : * @callback: (scope async): a `GAsyncReadyCallback`
184 : : * @user_data: (closure): user supplied data
185 : : *
186 : : * Get a TLS certificate and private key pair.
187 : : *
188 : : * This ensures a TLS certificate with the filename `certificate.pem` and
189 : : * private key with filename `private.pem` exist in a directory at @path.
190 : : *
191 : : * If either one doesn't exist, a new certificate and private key pair will be
192 : : * generated. The common name will be set to a string returned by
193 : : * [func@GLib.uuid_string_random].
194 : : *
195 : : * Get the result with [func@Valent.certificate_new_finish].
196 : : *
197 : : * Since: 1.0
198 : : */
199 : : void
200 : 1 : valent_certificate_new (const char *path,
201 : : GCancellable *cancellable,
202 : : GAsyncReadyCallback callback,
203 : : gpointer user_data)
204 : : {
205 : 2 : g_autoptr (GTask) task = NULL;
206 : :
207 [ + - + - ]: 1 : g_return_if_fail (path != NULL && *path != '\0');
208 [ - + - - : 1 : g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
- - - - ]
209 : :
210 : 1 : task = g_task_new (NULL, cancellable, callback, user_data);
211 [ + - ]: 1 : g_task_set_source_tag (task, valent_certificate_new);
212 [ - + ]: 2 : g_task_set_task_data (task, g_strdup (path), g_free);
213 [ + - ]: 1 : g_task_run_in_thread (task, valent_certificate_new_task);
214 : : }
215 : :
216 : : /**
217 : : * valent_certificate_new_finish:
218 : : * @result: a `GAsyncResult` provided to callback
219 : : * @error: (nullable): a `GError`
220 : : *
221 : : * Finish an operation started by [func@Valent.certificate_new].
222 : : *
223 : : * If either generating or loading the certificate failed, %NULL will be
224 : : * returned with @error set.
225 : : *
226 : : * Returns: (transfer full) (nullable): a `GTlsCertificate`
227 : : *
228 : : * Since: 1.0
229 : : */
230 : : GTlsCertificate *
231 : 1 : valent_certificate_new_finish (GAsyncResult *result,
232 : : GError **error)
233 : : {
234 [ + - ]: 1 : g_return_val_if_fail (g_task_is_valid (result, NULL), NULL);
235 : :
236 : 1 : return g_task_propagate_pointer (G_TASK (result), error);
237 : : }
238 : :
239 : : /**
240 : : * valent_certificate_new_sync:
241 : : * @path: (type filename): a directory path
242 : : * @error: (nullable): a `GError`
243 : : *
244 : : * Get a TLS certificate and private key pair.
245 : : *
246 : : * This ensures a TLS certificate with the filename `certificate.pem` and
247 : : * private key with filename `private.pem` exist in a directory at @path.
248 : : *
249 : : * If either one doesn't exist, a new certificate and private key pair will be
250 : : * generated. The common name will be set to a string returned by
251 : : * [func@Valent.Device.generate_id].
252 : : *
253 : : * If either generating or loading the certificate fails, %NULL will be returned
254 : : * with @error set.
255 : : *
256 : : * Returns: (transfer full) (nullable): a `GTlsCertificate`
257 : : *
258 : : * Since: 1.0
259 : : */
260 : : GTlsCertificate *
261 : 24 : valent_certificate_new_sync (const char *path,
262 : : GError **error)
263 : : {
264 : 48 : g_autofree char *cert_path = NULL;
265 : 24 : g_autofree char *key_path = NULL;
266 : :
267 [ + - + - ]: 24 : g_return_val_if_fail (path != NULL && *path != '\0', NULL);
268 [ + + - + ]: 24 : g_return_val_if_fail (error == NULL || *error == NULL, NULL);
269 : :
270 : 24 : cert_path = g_build_filename (path, "certificate.pem", NULL);
271 : 24 : key_path = g_build_filename (path, "private.pem", NULL);
272 : :
273 [ - + - - ]: 24 : if (!g_file_test (cert_path, G_FILE_TEST_IS_REGULAR) ||
274 : 0 : !g_file_test (key_path, G_FILE_TEST_IS_REGULAR))
275 : : {
276 : 24 : g_autofree char *cn = NULL;
277 : :
278 : 24 : cn = valent_device_generate_id ();
279 : :
280 [ - + ]: 24 : if (!valent_certificate_generate (cert_path, key_path, cn, error))
281 : 0 : return FALSE;
282 : : }
283 : :
284 : 24 : return g_tls_certificate_new_from_files (cert_path, key_path, error);
285 : : }
286 : :
287 : : /**
288 : : * valent_certificate_get_common_name:
289 : : * @certificate: a `GTlsCertificate`
290 : : *
291 : : * Get the common name from @certificate, which by convention in KDE Connect is
292 : : * the single source of truth for a device's ID.
293 : : *
294 : : * Returns: (transfer none) (nullable): the certificate ID
295 : : *
296 : : * Since: 1.0
297 : : */
298 : : const char *
299 : 49 : valent_certificate_get_common_name (GTlsCertificate *certificate)
300 : : {
301 : 98 : g_autoptr (GByteArray) certificate_der = NULL;
302 : 49 : gnutls_x509_crt_t crt;
303 : 49 : gnutls_datum_t crt_der;
304 : 49 : char buf[64] = { 0, };
305 : 49 : size_t buf_size = 64;
306 : 49 : const char *device_id;
307 : 49 : int rc;
308 : :
309 [ + - + - : 49 : g_return_val_if_fail (G_IS_TLS_CERTIFICATE (certificate), NULL);
+ - - + ]
310 : :
311 : : /* Check */
312 : 49 : device_id = g_object_get_data (G_OBJECT (certificate),
313 : : "valent-certificate-cn");
314 : :
315 [ + + ]: 49 : if G_LIKELY (device_id != NULL)
316 : : return device_id;
317 : :
318 : : /* Extract the common name */
319 : 29 : g_object_get (certificate, "certificate", &certificate_der, NULL);
320 : 29 : crt_der.data = certificate_der->data;
321 : 29 : crt_der.size = certificate_der->len;
322 : :
323 [ + - ]: 29 : if ((rc = gnutls_x509_crt_init (&crt)) != GNUTLS_E_SUCCESS ||
324 [ + - ]: 29 : (rc = gnutls_x509_crt_import (crt, &crt_der, 0)) != GNUTLS_E_SUCCESS ||
325 [ - + ]: 29 : (rc = gnutls_x509_crt_get_dn_by_oid (crt,
326 : : GNUTLS_OID_X520_COMMON_NAME,
327 : : 0,
328 : : 0,
329 : : &buf,
330 : : &buf_size)) != GNUTLS_E_SUCCESS)
331 : : {
332 : 0 : g_warning ("%s(): %s", G_STRFUNC, gnutls_strerror (rc));
333 : 0 : gnutls_x509_crt_deinit (crt);
334 : :
335 : 0 : return NULL;
336 : : }
337 : :
338 : 29 : gnutls_x509_crt_deinit (crt);
339 : :
340 : : /* Intern the id as private data */
341 : 29 : g_object_set_data_full (G_OBJECT (certificate),
342 : : "valent-certificate-cn",
343 : 29 : g_strndup (buf, buf_size),
344 : : g_free);
345 : :
346 : 29 : return g_object_get_data (G_OBJECT (certificate), "valent-certificate-cn");
347 : : }
348 : :
349 : : /**
350 : : * valent_certificate_get_fingerprint:
351 : : * @certificate: a `GTlsCertificate`
352 : : *
353 : : * Get a SHA256 fingerprint hash of @certificate.
354 : : *
355 : : * Returns: (transfer none): a SHA256 hash
356 : : *
357 : : * Since: 1.0
358 : : */
359 : : const char *
360 : 1 : valent_certificate_get_fingerprint (GTlsCertificate *certificate)
361 : : {
362 : 2 : g_autoptr (GByteArray) certificate_der = NULL;
363 [ + - ]: 1 : g_autoptr (GChecksum) checksum = NULL;
364 : 1 : const char *check;
365 : 1 : const char *fingerprint;
366 : 1 : char buf[SHA256_STR_LEN] = { 0, };
367 : 1 : unsigned int i = 0;
368 : 1 : unsigned int o = 0;
369 : :
370 [ + - + - : 1 : g_return_val_if_fail (G_IS_TLS_CERTIFICATE (certificate), NULL);
+ - - + ]
371 : :
372 : 1 : fingerprint = g_object_get_data (G_OBJECT (certificate),
373 : : "valent-certificate-fp");
374 : :
375 [ + - ]: 1 : if G_LIKELY (fingerprint != NULL)
376 : : return fingerprint;
377 : :
378 : 1 : g_object_get (certificate, "certificate", &certificate_der, NULL);
379 : 1 : checksum = g_checksum_new (G_CHECKSUM_SHA256);
380 : 1 : g_checksum_update (checksum, certificate_der->data, certificate_der->len);
381 : :
382 : 1 : check = g_checksum_get_string (checksum);
383 : :
384 [ + + ]: 33 : while (i < SHA256_HEX_LEN)
385 : : {
386 : 32 : buf[o++] = check[i++];
387 : 32 : buf[o++] = check[i++];
388 : 32 : buf[o++] = ':';
389 : : }
390 : 1 : buf[SHA256_STR_LEN - 1] = '\0';
391 : :
392 : : /* Intern the hash as private data */
393 [ - + ]: 1 : g_object_set_data_full (G_OBJECT (certificate),
394 : : "valent-certificate-fp",
395 : 1 : g_strdup (buf),
396 : : g_free);
397 : :
398 : 1 : return g_object_get_data (G_OBJECT (certificate), "valent-certificate-fp");
399 : : }
400 : :
401 : : /**
402 : : * valent_certificate_get_public_key:
403 : : * @certificate: a `GTlsCertificate`
404 : : *
405 : : * Get the public key of @certificate.
406 : : *
407 : : * Returns: (transfer none): a DER-encoded publickey
408 : : *
409 : : * Since: 1.0
410 : : */
411 : : GByteArray *
412 : 5 : valent_certificate_get_public_key (GTlsCertificate *certificate)
413 : : {
414 : 10 : g_autoptr (GByteArray) certificate_der = NULL;
415 [ + - ]: 5 : g_autoptr (GByteArray) pubkey = NULL;
416 : 5 : size_t size;
417 : 5 : gnutls_x509_crt_t crt = NULL;
418 : 5 : gnutls_datum_t crt_der;
419 : 5 : gnutls_pubkey_t crt_pk = NULL;
420 : 5 : int rc;
421 : :
422 [ + - + - : 5 : g_return_val_if_fail (G_IS_TLS_CERTIFICATE (certificate), NULL);
+ - - + ]
423 : :
424 : 5 : pubkey = g_object_get_data (G_OBJECT (certificate),
425 : : "valent-certificate-pk");
426 : :
427 [ + - ]: 5 : if (pubkey != NULL)
428 : : return g_steal_pointer (&pubkey);
429 : :
430 : 5 : g_object_get (certificate, "certificate", &certificate_der, NULL);
431 : 5 : crt_der.data = certificate_der->data;
432 : 5 : crt_der.size = certificate_der->len;
433 : :
434 : : /* Load the certificate */
435 [ + - ]: 5 : if ((rc = gnutls_x509_crt_init (&crt)) != GNUTLS_E_SUCCESS ||
436 [ - + ]: 5 : (rc = gnutls_x509_crt_import (crt, &crt_der, GNUTLS_X509_FMT_DER)) != GNUTLS_E_SUCCESS)
437 : : {
438 : 0 : g_warning ("%s(): %s", G_STRFUNC, gnutls_strerror (rc));
439 : 0 : VALENT_GOTO (out);
440 : : }
441 : :
442 : : /* Load the public key */
443 [ + - ]: 5 : if ((rc = gnutls_pubkey_init (&crt_pk)) != GNUTLS_E_SUCCESS ||
444 [ - + ]: 5 : (rc = gnutls_pubkey_import_x509 (crt_pk, crt, 0)) != GNUTLS_E_SUCCESS)
445 : : {
446 : 0 : g_warning ("%s(): %s", G_STRFUNC, gnutls_strerror (rc));
447 : 0 : VALENT_GOTO (out);
448 : : }
449 : :
450 : : /* Read the public key */
451 : 5 : rc = gnutls_pubkey_export (crt_pk, GNUTLS_X509_FMT_DER, NULL, &size);
452 : :
453 [ + - ]: 5 : if (rc == GNUTLS_E_SUCCESS || rc == GNUTLS_E_SHORT_MEMORY_BUFFER)
454 : : {
455 : 5 : pubkey = g_byte_array_sized_new (size);
456 : 5 : pubkey->len = size;
457 : 10 : rc = gnutls_pubkey_export (crt_pk,
458 : : GNUTLS_X509_FMT_DER,
459 : 5 : pubkey->data, &size);
460 : :
461 : : /* Intern the DER as private data */
462 [ + - ]: 5 : if (rc == GNUTLS_E_SUCCESS)
463 : : {
464 : 5 : g_object_set_data_full (G_OBJECT (certificate),
465 : : "valent-certificate-pk",
466 : : g_steal_pointer (&pubkey),
467 : : (GDestroyNotify)g_byte_array_unref);
468 : : }
469 : : else
470 : 0 : g_warning ("%s(): %s", G_STRFUNC, gnutls_strerror (rc));
471 : : }
472 : : else
473 : 0 : g_warning ("%s(): %s", G_STRFUNC, gnutls_strerror (rc));
474 : :
475 : 5 : out:
476 : 5 : gnutls_x509_crt_deinit (crt);
477 : 5 : gnutls_pubkey_deinit (crt_pk);
478 : :
479 : 5 : return g_object_get_data (G_OBJECT (certificate), "valent-certificate-pk");
480 : : }
481 : :
|