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