LCOV - code coverage report
Current view: top level - src/libvalent/device - valent-certificate.c (source / functions) Coverage Total Hit
Test: Code Coverage Lines: 83.8 % 142 119
Test Date: 2024-11-16 20:34:14 Functions: 100.0 % 7 7
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 48.0 % 100 48

             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                 :          49 : valent_certificate_get_common_name (GTlsCertificate *certificate)
     306                 :             : {
     307                 :          98 :   g_autoptr (GByteArray) certificate_der = NULL;
     308                 :          49 :   gnutls_x509_crt_t crt;
     309                 :          49 :   gnutls_datum_t crt_der;
     310                 :          49 :   char buf[64] = { 0, };
     311                 :          49 :   size_t buf_size = 64;
     312                 :          49 :   const char *device_id;
     313                 :          49 :   int rc;
     314                 :             : 
     315   [ +  -  +  -  :          49 :   g_return_val_if_fail (G_IS_TLS_CERTIFICATE (certificate), NULL);
             +  -  -  + ]
     316                 :             : 
     317                 :             :   /* Check */
     318                 :          49 :   device_id = g_object_get_data (G_OBJECT (certificate),
     319                 :             :                                  "valent-certificate-cn");
     320                 :             : 
     321         [ +  + ]:          49 :   if G_LIKELY (device_id != NULL)
     322                 :             :     return device_id;
     323                 :             : 
     324                 :             :   /* Extract the common name */
     325                 :          29 :   g_object_get (certificate, "certificate", &certificate_der, NULL);
     326                 :          29 :   crt_der.data = certificate_der->data;
     327                 :          29 :   crt_der.size = certificate_der->len;
     328                 :             : 
     329         [ +  - ]:          29 :   if ((rc = gnutls_x509_crt_init (&crt)) != GNUTLS_E_SUCCESS ||
     330         [ +  - ]:          29 :       (rc = gnutls_x509_crt_import (crt, &crt_der, 0)) != GNUTLS_E_SUCCESS ||
     331         [ -  + ]:          29 :       (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                 :          29 :   gnutls_x509_crt_deinit (crt);
     345                 :             : 
     346                 :             :   /* Intern the id as private data */
     347                 :          29 :   g_object_set_data_full (G_OBJECT (certificate),
     348                 :             :                           "valent-certificate-cn",
     349                 :          29 :                           g_strndup (buf, buf_size),
     350                 :             :                           g_free);
     351                 :             : 
     352                 :          29 :   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                 :             : 
        

Generated by: LCOV version 2.0-1