LCOV - code coverage report
Current view: top level - src/plugins/lan - valent-lan-utils.c (source / functions) Coverage Total Hit
Test: Code Coverage Lines: 85.7 % 42 36
Test Date: 2025-04-18 23:22:27 Functions: 100.0 % 2 2
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 57.4 % 54 31

             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-lan-utils"
       5                 :             : 
       6                 :             : #include "config.h"
       7                 :             : 
       8                 :             : #include <gio/gio.h>
       9                 :             : #include <gio/gnetworking.h>
      10                 :             : #include <valent.h>
      11                 :             : 
      12                 :             : #include "valent-lan-utils.h"
      13                 :             : 
      14                 :             : 
      15                 :             : /* KDE Connect uses self-signed certificates with a trust-on-first-use policy,
      16                 :             :  * and since GLib can not guarantee all certificate errors are set, the
      17                 :             :  * `accept-certificate` callback always returns %TRUE.
      18                 :             :  */
      19                 :             : static gboolean
      20                 :           8 : valent_lan_connection_accept_certificate_cb (GTlsConnection       *connection,
      21                 :             :                                              GTlsCertificate      *peer_cert,
      22                 :             :                                              GTlsCertificateFlags  errors,
      23                 :             :                                              gpointer              user_data)
      24                 :             : {
      25                 :           8 :   return TRUE;
      26                 :             : }
      27                 :             : 
      28                 :             : /**
      29                 :             :  * valent_lan_connection_handshake:
      30                 :             :  * @connection: a `GSocketConnection`
      31                 :             :  * @certificate: a `GTlsCertificate`
      32                 :             :  * @trusted: (nullable): a `GTlsCertificate`
      33                 :             :  * @is_client: %TRUE for client connection, or %FALSE for a server connection
      34                 :             :  * @cancellable: (nullable): a `GCancellable`
      35                 :             :  * @error: (nullable): a `GError`
      36                 :             :  *
      37                 :             :  * Wrap @connection in a [class@Gio.TlsConnection] and perform the handshake.
      38                 :             :  *
      39                 :             :  * If @trusted is not %NULL, the remote device will be expected to handshake
      40                 :             :  * use the same certificate, otherwise trusted devices will be searched for
      41                 :             :  * a certificate. If the device is unknown, the certificate will be verified
      42                 :             :  * as self-signed and accepted on a trust-on-first-use basis.
      43                 :             :  *
      44                 :             :  * If @is_client is %TRUE, this will create a [class@Gio.TlsClientConnection],
      45                 :             :  * otherwise a [class@Gio.TlsServerConnection].
      46                 :             :  *
      47                 :             :  * Returns: (transfer full) (nullable): a TLS encrypted `GIOStream`
      48                 :             :  */
      49                 :             : GIOStream *
      50                 :           8 : valent_lan_connection_handshake (GSocketConnection  *connection,
      51                 :             :                                  GTlsCertificate    *certificate,
      52                 :             :                                  GTlsCertificate    *trusted,
      53                 :             :                                  gboolean            is_client,
      54                 :             :                                  GCancellable       *cancellable,
      55                 :             :                                  GError            **error)
      56                 :             : {
      57                 :           8 :   GTlsBackend *backend;
      58                 :          16 :   g_autoptr (GIOStream) ret = NULL;
      59         [ -  + ]:           8 :   g_autoptr (GTlsCertificate) ca_certificate = NULL;
      60                 :           8 :   GTlsCertificate *peer_certificate;
      61                 :           8 :   const char *peer_common_name = NULL;
      62                 :           8 :   GTlsCertificateFlags errors = 0;
      63                 :             : 
      64   [ +  -  +  -  :           8 :   g_assert (G_IS_SOCKET_CONNECTION (connection));
             +  -  -  + ]
      65   [ +  -  +  -  :           8 :   g_assert (G_IS_TLS_CERTIFICATE (certificate));
             +  -  -  + ]
      66   [ +  +  +  -  :           8 :   g_assert (trusted == NULL || G_IS_TLS_CERTIFICATE (trusted));
             +  -  -  + ]
      67   [ +  +  +  -  :           8 :   g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
             -  +  -  - ]
      68   [ +  -  -  + ]:           8 :   g_assert (error == NULL || *error == NULL);
      69                 :             : 
      70                 :           8 :   g_socket_set_keepalive (g_socket_connection_get_socket (connection), TRUE);
      71                 :             : 
      72                 :             :   /* NOTE: When negotiating the primary connection, a KDE Connect device
      73                 :             :    *       acts as the TLS server when opening TCP connections,
      74                 :             :    *       and the TLS client when accepting TCP connections.
      75                 :             :    *
      76                 :             :    *       When negotiating an auxiliary connection, a KDE Connect device
      77                 :             :    *       acts as the TLS server when accepting TCP connections,
      78                 :             :    *       and the TLS client when opening TCP connections.
      79                 :             :    *
      80                 :             :    * TODO: If a database returned by [ctor@Gio.TlsFileDatabase.new] were used
      81                 :             :    *       for trusted certificates, this function could return immediately
      82                 :             :    *       after a handshake with no errors.
      83                 :             :    *
      84                 :             :    * TODO: KDE Connect uses TLS/SNI to communicate the expected device ID, but
      85                 :             :    *       but GnuTLS refuses server names that are not valid hostnames.
      86                 :             :    */
      87                 :           8 :   backend = g_tls_backend_get_default ();
      88         [ +  + ]:           8 :   if (is_client)
      89                 :             :     {
      90                 :           4 :       ret = g_initable_new (g_tls_backend_get_client_connection_type (backend),
      91                 :             :                             cancellable,
      92                 :             :                             error,
      93                 :             :                             "base-io-stream",  connection,
      94                 :             :                             "certificate",     certificate,
      95                 :             :                             // TODO: see note above about TLS/SNI
      96                 :             :                             "server-identity", NULL,
      97                 :             :                             NULL);
      98                 :             :     }
      99                 :             :   else
     100                 :             :     {
     101                 :           4 :       ret = g_initable_new (g_tls_backend_get_server_connection_type (backend),
     102                 :             :                             cancellable,
     103                 :             :                             error,
     104                 :             :                             "authentication-mode", G_TLS_AUTHENTICATION_REQUIRED,
     105                 :             :                             "base-io-stream",      connection,
     106                 :             :                             "certificate",         certificate,
     107                 :             :                             NULL);
     108                 :             :     }
     109                 :             : 
     110         [ +  - ]:           8 :   if (ret == NULL)
     111                 :             :     return NULL;
     112                 :             : 
     113                 :             :   /* Perform TLS handshake to get the peer certificate, which should only
     114                 :             :    * fail on cancellation.
     115                 :             :    */
     116                 :           8 :   g_signal_connect (G_TLS_CONNECTION (ret),
     117                 :             :                     "accept-certificate",
     118                 :             :                     G_CALLBACK (valent_lan_connection_accept_certificate_cb),
     119                 :             :                     NULL);
     120                 :             : 
     121         [ +  - ]:           8 :   if (!g_tls_connection_handshake (G_TLS_CONNECTION (ret), cancellable, error))
     122                 :             :     return NULL;
     123                 :             : 
     124                 :             :   /* The certificate common name is validated as a device ID after the
     125                 :             :    * handshake, to sanitize queries to the filesystem for a trusted certificate.
     126                 :             :    */
     127                 :           8 :   peer_certificate = g_tls_connection_get_peer_certificate (G_TLS_CONNECTION (ret));
     128                 :           8 :   peer_common_name = valent_certificate_get_common_name (peer_certificate);
     129         [ -  + ]:           8 :   if (!valent_device_validate_id (peer_common_name))
     130                 :             :     {
     131                 :           0 :       g_set_error (error,
     132                 :             :                    G_TLS_ERROR,
     133                 :             :                    G_TLS_ERROR_HANDSHAKE,
     134                 :             :                    "Certificate common name is not a valid device ID \"%s\"",
     135                 :             :                    peer_common_name);
     136                 :           0 :       return NULL;
     137                 :             :     }
     138                 :             : 
     139                 :             :   /* If @trusted is not provided, try to find a certificate in a device
     140                 :             :    * config directory. If neither are found, the certificate is verified
     141                 :             :    * as self-signed before being accepted per the trust-on-first-use policy.
     142                 :             :    *
     143                 :             :    * TODO: this should be handled by a managed cache object
     144                 :             :    */
     145         [ +  + ]:           8 :   if (trusted != NULL)
     146                 :             :     {
     147                 :           2 :       ca_certificate = g_object_ref (trusted);
     148                 :             :     }
     149                 :             :   else
     150                 :             :     {
     151                 :           6 :       g_autofree char *trusted_path = NULL;
     152                 :             : 
     153                 :           6 :       trusted_path = g_build_filename (g_get_user_config_dir(), PACKAGE_NAME,
     154                 :             :                                        "device", peer_common_name,
     155                 :             :                                        "certificate.pem",
     156                 :             :                                        NULL);
     157                 :           6 :       ca_certificate = g_tls_certificate_new_from_file (trusted_path, NULL);
     158         [ +  - ]:           6 :       if (ca_certificate == NULL)
     159                 :           6 :         ca_certificate = g_object_ref (peer_certificate);
     160                 :             :     }
     161                 :             : 
     162                 :           8 :   errors = g_tls_certificate_verify (peer_certificate, NULL, ca_certificate);
     163         [ -  + ]:           8 :   if (errors != G_TLS_CERTIFICATE_NO_FLAGS)
     164                 :             :     {
     165                 :           0 :       g_autofree char *errors_str = NULL;
     166                 :             : 
     167                 :           0 :       errors_str = g_flags_to_string (G_TYPE_TLS_CERTIFICATE_FLAGS, errors);
     168                 :           0 :       g_set_error (error,
     169                 :             :                    G_TLS_ERROR,
     170                 :             :                    G_TLS_ERROR_HANDSHAKE,
     171                 :             :                    "Certificate for \"%s\" failed verification (%s)",
     172                 :             :                    peer_common_name,
     173                 :             :                    errors_str);
     174                 :           0 :       return NULL;
     175                 :             :     }
     176                 :             : 
     177         [ +  + ]:           8 :   g_debug (ca_certificate == peer_certificate
     178                 :             :              ? "Accepting certificate \"%s\" per trust-on-first-use policy"
     179                 :             :              : "Accepting certificate \"%s\" per successful verification",
     180                 :             :            peer_common_name);
     181                 :             : 
     182                 :           8 :   return g_steal_pointer (&ret);
     183                 :             : }
     184                 :             : 
        

Generated by: LCOV version 2.0-1