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