LCOV - code coverage report
Current view: top level - daemon/dbus - gkd-secret-portal.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 73.7 % 186 137
Test Date: 2024-04-08 13:24:42 Functions: 88.2 % 17 15

            Line data    Source code
       1              : /*
       2              :  * gnome-keyring
       3              :  *
       4              :  * Copyright (C) 2019 Red Hat, Inc.
       5              :  *
       6              :  * This program is free software; you can redistribute it and/or modify
       7              :  * it under the terms of the GNU Lesser General Public License as
       8              :  * published by the Free Software Foundation; either version 2.1 of
       9              :  * the License, or (at your option) any later version.
      10              :  *
      11              :  * This program is distributed in the hope that it will be useful, but
      12              :  * WITHOUT ANY WARRANTY; without even the implied warranty of
      13              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      14              :  * Lesser General Public License for more details.
      15              :  *
      16              :  * You should have received a copy of the GNU Lesser General Public
      17              :  * License along with this program; if not, see
      18              :  * <http://www.gnu.org/licenses/>.
      19              :  */
      20              : 
      21              : #include "config.h"
      22              : 
      23              : #include "gkd-secret-portal.h"
      24              : 
      25              : #include <gck/gck.h>
      26              : #include "pkcs11/pkcs11i.h"
      27              : #include <gcrypt.h>
      28              : 
      29              : #include "gkd-portal-generated.h"
      30              : #include "gkd-portal-request-generated.h"
      31              : #include "gkd-secret-property.h"
      32              : #include "gkd-secret-service.h"
      33              : #include <gio/gunixfdlist.h>
      34              : #include <gio/gunixoutputstream.h>
      35              : 
      36              : #define PORTAL_DEFAULT_KEY_SIZE 64
      37              : 
      38              : static gboolean
      39              : portal_method_retrieve_secret (GkdExportedPortal *skeleton,
      40              :                                GDBusMethodInvocation *invocation,
      41              :                                GUnixFDList *fd_list,
      42              :                                const gchar *arg_handle,
      43              :                                const gchar *arg_app_id,
      44              :                                GVariant *arg_fd,
      45              :                                GVariant *arg_options,
      46              :                                GkdSecretPortal *self);
      47              : 
      48              : struct _GkdSecretPortal {
      49              :         GObject parent;
      50              :         GkdSecretService *service;
      51              :         GkdExportedPortal *skeleton;
      52              :         GkdExportedPortalRequest *request_skeleton;
      53              :         gchar *collection;
      54              :         GCancellable *cancellable;
      55              : };
      56              : 
      57          150 : G_DEFINE_TYPE (GkdSecretPortal, gkd_secret_portal, G_TYPE_OBJECT);
      58              : 
      59              : enum {
      60              :         PROP_0,
      61              :         PROP_SERVICE
      62              : };
      63              : 
      64              : static char *
      65            9 : get_default_collection ()
      66              : {
      67            9 :         char *default_path = NULL;
      68            9 :         char *contents = NULL;
      69              : 
      70            9 :         default_path = g_build_filename (g_get_user_data_dir (),
      71              :                                          "keyrings",
      72              :                                          "default",
      73              :                                          NULL);
      74            9 :         if (g_file_get_contents (default_path, &contents, NULL, NULL)) {
      75            0 :                 g_strstrip (contents);
      76            0 :                 if (!contents[0]) {
      77            0 :                         g_free (contents);
      78            0 :                         contents = NULL;
      79              :                 }
      80              :         }
      81              : 
      82            9 :         g_free (default_path);
      83              : 
      84           18 :         return (contents != NULL)? contents : g_strdup ("login");
      85              : }
      86              : 
      87              : static void
      88           25 : gkd_secret_portal_init (GkdSecretPortal *self)
      89              : {
      90              : #if WITH_DEBUG
      91           25 :         const gchar *collection = g_getenv ("GNOME_KEYRING_TEST_LOGIN");
      92           25 :         if (collection && collection[0])
      93           16 :                 self->collection = g_strdup (collection);
      94              :         else
      95              : #endif
      96            9 :                 self->collection = get_default_collection ();
      97           25 :         self->cancellable = g_cancellable_new ();
      98           25 : }
      99              : 
     100              : static void
     101           25 : gkd_secret_portal_constructed (GObject *object)
     102              : {
     103           25 :         GkdSecretPortal *self = GKD_SECRET_PORTAL (object);
     104           25 :         GDBusConnection *connection = gkd_secret_service_get_connection (self->service);
     105           25 :         GError *error = NULL;
     106              : 
     107           25 :         self->skeleton = gkd_exported_portal_skeleton_new ();
     108           25 :         g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self->skeleton),
     109              :                                           connection,
     110              :                                           PORTAL_SERVICE_PATH,
     111              :                                           &error);
     112              : 
     113           25 :         if (error != NULL) {
     114            0 :                 g_warning ("could not register portal interface service on session bus: %s", error->message);
     115            0 :                 g_clear_error (&error);
     116              :         }
     117              : 
     118           25 :         g_signal_connect (self->skeleton, "handle-retrieve-secret",
     119              :                           G_CALLBACK (portal_method_retrieve_secret), self);
     120              : 
     121           25 :         G_OBJECT_CLASS (gkd_secret_portal_parent_class)->constructed (object);
     122           25 : }
     123              : 
     124              : static void
     125           25 : gkd_secret_portal_set_property (GObject      *object,
     126              :                                 guint         prop_id,
     127              :                                 const GValue *value,
     128              :                                 GParamSpec   *pspec)
     129              : {
     130           25 :         GkdSecretPortal *self = GKD_SECRET_PORTAL (object);
     131              : 
     132           25 :         switch (prop_id) {
     133           25 :         case PROP_SERVICE:
     134           25 :                 self->service = g_value_dup_object (value);
     135           25 :                 break;
     136            0 :         default:
     137            0 :                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
     138            0 :                 break;
     139              :         }
     140           25 : }
     141              : 
     142              : static void
     143            0 : gkd_secret_portal_get_property (GObject    *object,
     144              :                                 guint       prop_id,
     145              :                                 GValue     *value,
     146              :                                 GParamSpec *pspec)
     147              : {
     148            0 :         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
     149            0 : }
     150              : 
     151              : static void
     152           25 : gkd_secret_portal_finalize (GObject *object)
     153              : {
     154           25 :         GkdSecretPortal *self = GKD_SECRET_PORTAL (object);
     155              : 
     156           25 :         g_clear_object (&self->skeleton);
     157           25 :         g_clear_object (&self->request_skeleton);
     158           25 :         g_free (self->collection);
     159           25 :         g_object_unref (self->cancellable);
     160              : 
     161           25 :         G_OBJECT_CLASS (gkd_secret_portal_parent_class)->finalize (object);
     162           25 : }
     163              : 
     164              : static void
     165           25 : gkd_secret_portal_class_init (GkdSecretPortalClass *klass)
     166              : {
     167           25 :         GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
     168           25 :         gobject_class->constructed = gkd_secret_portal_constructed;
     169           25 :         gobject_class->set_property = gkd_secret_portal_set_property;
     170           25 :         gobject_class->get_property = gkd_secret_portal_get_property;
     171           25 :         gobject_class->finalize = gkd_secret_portal_finalize;
     172              : 
     173           25 :         g_object_class_install_property (gobject_class, PROP_SERVICE,
     174              :                 g_param_spec_object ("service", "Service", "Secret Service",
     175              :                                      GKD_SECRET_TYPE_SERVICE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
     176           25 : }
     177              : 
     178              : static gboolean
     179            0 : request_method_close (GkdExportedPortalRequest *skeleton,
     180              :                       GDBusMethodInvocation *invocation,
     181              :                       GkdSecretPortal *self)
     182              : {
     183            0 :         g_cancellable_cancel (self->cancellable);
     184            0 :         g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (skeleton));
     185            0 :         return TRUE;
     186              : }
     187              : 
     188              : static gboolean
     189            6 : create_application_attributes (const char *app_id,
     190              :                                GckBuilder *builder)
     191              : {
     192              :         GVariantBuilder attributes;
     193              : 
     194            6 :         g_variant_builder_init (&attributes, G_VARIANT_TYPE ("a{ss}"));
     195            6 :         g_variant_builder_add (&attributes, "{ss}", "app_id", app_id);
     196              : 
     197            6 :         return gkd_secret_property_parse_fields (g_variant_builder_end (&attributes),
     198              :                                                  builder);
     199              : }
     200              : 
     201              : static gboolean
     202            1 : unlock_collection (GkdSecretPortal *self,
     203              :                    GckObject *collection,
     204              :                    GError **error)
     205              : {
     206            1 :         GckBuilder builder = GCK_BUILDER_INIT;
     207              :         GckSession *session;
     208              :         GckObject *object;
     209              : 
     210            1 :         session = gkd_secret_service_internal_pkcs11_session (self->service);
     211            1 :         gck_builder_add_ulong (&builder, CKA_CLASS, CKO_G_CREDENTIAL);
     212            1 :         gck_builder_add_ulong (&builder, CKA_G_OBJECT,
     213              :                                gck_object_get_handle (collection));
     214            1 :         gck_builder_add_boolean (&builder, CKA_GNOME_TRANSIENT, TRUE);
     215            1 :         gck_builder_add_data (&builder, CKA_VALUE, NULL, 0);
     216              : 
     217            1 :         object = gck_session_create_object (session, gck_builder_end (&builder),
     218              :                                             self->cancellable, error);
     219            1 :         if (object == NULL)
     220            0 :                 return FALSE;
     221            1 :         g_object_unref (object);
     222              : 
     223            1 :         return TRUE;
     224              : }
     225              : 
     226              : static gboolean
     227            4 : ensure_collection (GkdSecretPortal *self,
     228              :                    GError **error)
     229              : {
     230            4 :         GckBuilder builder = GCK_BUILDER_INIT;
     231              :         GckSession *session;
     232              :         GList *objects;
     233              :         gpointer data;
     234              :         gsize n_data;
     235            4 :         gboolean retval = TRUE;
     236              : 
     237              :         /* Find login collection */
     238            4 :         session = gkd_secret_service_internal_pkcs11_session (self->service);
     239            4 :         gck_builder_add_ulong (&builder, CKA_CLASS, CKO_G_COLLECTION);
     240            4 :         gck_builder_add_string (&builder, CKA_ID, self->collection);
     241            4 :         objects = gck_session_find_objects (session, gck_builder_end (&builder),
     242              :                                             NULL, error);
     243            4 :         if (*error != NULL)
     244            0 :                 return FALSE;
     245            4 :         if (objects == NULL) {
     246            0 :                 g_set_error (error,
     247              :                              G_DBUS_ERROR,
     248              :                              G_DBUS_ERROR_FAILED,
     249              :                              "Collection %s doesn't exist",
     250              :                              self->collection);
     251            0 :                 return FALSE;
     252              :         }
     253              : 
     254              :         /* Check if it is locked */
     255            4 :         data = gck_object_get_data (objects->data, CKA_G_LOCKED,
     256              :                                     self->cancellable, &n_data, error);
     257            4 :         if (data == NULL)
     258            0 :                 return FALSE;
     259            4 :         if (n_data != 1) {
     260            0 :                 g_set_error (error,
     261              :                              G_DBUS_ERROR,
     262              :                              G_DBUS_ERROR_FAILED,
     263              :                              "couldn't check if %s is locked",
     264              :                              self->collection);
     265            0 :                 return FALSE;
     266              :         }
     267              : 
     268              :         /* Unlock the collection if it is locked */
     269            4 :         if (*((CK_BBOOL*)data) == CK_TRUE)
     270            1 :                 retval = unlock_collection (self, objects->data, error);
     271            4 :         gck_list_unref_free (objects);
     272              : 
     273            4 :         return retval;
     274              : }
     275              : 
     276              : static guint8 *
     277            4 : lookup_secret_value (GkdSecretPortal *self,
     278              :                      const char *app_id,
     279              :                      gsize *n_value,
     280              :                      GError **error)
     281              : {
     282            4 :         GckBuilder builder = GCK_BUILDER_INIT;
     283              :         GckObject *search;
     284              :         GckSession *session;
     285              :         gpointer data;
     286              :         gsize n_data;
     287              : 
     288            4 :         if (!create_application_attributes (app_id, &builder)) {
     289            0 :                 gck_builder_clear (&builder);
     290            0 :                 g_set_error (error,
     291              :                              G_DBUS_ERROR,
     292              :                              G_DBUS_ERROR_FAILED,
     293              :                              "Invalid data in attributes argument");
     294            0 :                 return NULL;
     295              :         }
     296              : 
     297              :         /* Find items matching the collection and fields */
     298            4 :         gck_builder_add_ulong (&builder, CKA_CLASS, CKO_G_SEARCH);
     299            4 :         gck_builder_add_boolean (&builder, CKA_TOKEN, FALSE);
     300            4 :         gck_builder_add_string (&builder, CKA_G_COLLECTION, self->collection);
     301              : 
     302              :         /* Create the search object */
     303            4 :         session = gkd_secret_service_internal_pkcs11_session (self->service);
     304            4 :         search = gck_session_create_object (session,
     305              :                                             gck_builder_end (&builder),
     306              :                                             NULL, error);
     307            4 :         if (search == NULL)
     308            0 :                 return NULL;
     309              : 
     310              :         /* Get the matched item handles, and delete the search object */
     311            4 :         data = gck_object_get_data (search, CKA_G_MATCHED, NULL, &n_data, error);
     312            4 :         gck_object_destroy (search, NULL, NULL);
     313            4 :         g_object_unref (search);
     314              : 
     315            4 :         if (data == NULL)
     316            0 :                 return NULL;
     317              : 
     318            4 :         if (n_data > 0) {
     319              :                 /* Return the first matching item if any */
     320              :                 GList *items;
     321              :                 guint8 *value;
     322              : 
     323              :                 /* Build a list of object handles */
     324            2 :                 items = gck_objects_from_handle_array (session,
     325              :                                                        data,
     326              :                                                        n_data / sizeof (CK_OBJECT_HANDLE));
     327            2 :                 g_free (data);
     328              : 
     329            2 :                 value = gck_object_get_data (GCK_OBJECT (items->data),
     330              :                                              CKA_VALUE,
     331              :                                              NULL,
     332              :                                              n_value,
     333              :                                              error);
     334            2 :                 gck_list_unref_free (items);
     335            2 :                 return value;
     336              :         }
     337              : 
     338            2 :         return NULL;
     339              : }
     340              : 
     341              : static guint8 *
     342            2 : create_secret_value (GkdSecretPortal *self,
     343              :                      const char *app_id,
     344              :                      gsize *n_value,
     345              :                      GError **error)
     346              : {
     347            2 :         GckBuilder builder = GCK_BUILDER_INIT;
     348              :         GckObject *item;
     349              :         GckSession *session;
     350              :         guint8 *value;
     351              : 
     352            2 :         value = g_new0 (guint8, PORTAL_DEFAULT_KEY_SIZE);
     353            2 :         *n_value = PORTAL_DEFAULT_KEY_SIZE;
     354              : 
     355            2 :         gcry_randomize (value, *n_value, GCRY_STRONG_RANDOM);
     356              : 
     357              :         /* Create a new item */
     358            2 :         if (!create_application_attributes (app_id, &builder)) {
     359            0 :                 gck_builder_clear (&builder);
     360            0 :                 g_free (value);
     361            0 :                 g_set_error (error,
     362              :                              G_DBUS_ERROR,
     363              :                              G_DBUS_ERROR_FAILED,
     364              :                              "Invalid data in attributes argument");
     365            0 :                 return NULL;
     366              :         }
     367              : 
     368            2 :         gck_builder_add_string (&builder, CKA_G_COLLECTION, self->collection);
     369            2 :         gck_builder_add_ulong (&builder, CKA_CLASS, CKO_SECRET_KEY);
     370            2 :         gck_builder_add_boolean (&builder, CKA_TOKEN, TRUE);
     371            2 :         gck_builder_add_data (&builder, CKA_VALUE, value, *n_value);
     372              : 
     373            2 :         session = gkd_secret_service_internal_pkcs11_session (self->service);
     374            2 :         item = gck_session_create_object (session,
     375              :                                           gck_builder_end (&builder),
     376              :                                           self->cancellable,
     377              :                                           error);
     378            2 :         if (item == NULL) {
     379            0 :                 g_free (value);
     380            0 :                 return NULL;
     381              :         }
     382            2 :         g_object_unref (item);
     383              : 
     384            2 :         return value;
     385              : }
     386              : 
     387              : static gboolean
     388            4 : portal_method_retrieve_secret (GkdExportedPortal *skeleton,
     389              :                                GDBusMethodInvocation *invocation,
     390              :                                GUnixFDList *fd_list,
     391              :                                const gchar *arg_handle,
     392              :                                const gchar *arg_app_id,
     393              :                                GVariant *arg_fd,
     394              :                                GVariant *arg_options,
     395              :                                GkdSecretPortal *self)
     396              : {
     397              :         int idx, fd;
     398            4 :         GError *error = NULL;
     399            4 :         guint8 *value = NULL;
     400            4 :         gsize n_value = 0;
     401              :         GOutputStream *stream;
     402              :         GVariantBuilder builder;
     403              : 
     404            4 :         g_variant_get (arg_fd, "h", &idx);
     405            4 :         fd = g_unix_fd_list_get (fd_list, idx, NULL);
     406              : 
     407            4 :         g_clear_object (&self->request_skeleton);
     408            4 :         self->request_skeleton = gkd_exported_portal_request_skeleton_new ();
     409            4 :         if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self->request_skeleton),
     410              :                                                g_dbus_method_invocation_get_connection (invocation),
     411              :                                                arg_handle, &error)) {
     412            0 :                 g_warning ("error exporting request: %s\n", error->message);
     413            0 :                 g_clear_error (&error);
     414              :         } else {
     415            4 :                 g_signal_connect (self->request_skeleton, "handle-close",
     416              :                                   G_CALLBACK (request_method_close), self);
     417              :         }
     418              : 
     419            4 :         if (!ensure_collection (self, &error)) {
     420            0 :                 g_clear_object (&self->request_skeleton);
     421            0 :                 g_dbus_method_invocation_take_error (invocation, error);
     422            0 :                 return TRUE;
     423              :         }
     424              : 
     425            4 :         value = lookup_secret_value (self, arg_app_id, &n_value, &error);
     426            4 :         if (error != NULL) {
     427            0 :                 g_clear_object (&self->request_skeleton);
     428            0 :                 g_dbus_method_invocation_take_error (invocation, error);
     429            0 :                 return TRUE;
     430              :         }
     431              : 
     432              :         /* If secret is not found, create a new random key */
     433            4 :         if (value == NULL) {
     434            2 :                 value = create_secret_value (self, arg_app_id, &n_value, &error);
     435            2 :                 if (value == NULL) {
     436            0 :                         g_clear_object (&self->request_skeleton);
     437            0 :                         g_dbus_method_invocation_take_error (invocation, error);
     438            0 :                         return TRUE;
     439              :                 }
     440              :         }
     441              : 
     442              :         /* Write the secret value to the file descriptor */
     443            4 :         stream = g_unix_output_stream_new (fd, TRUE);
     444            4 :         if (!g_output_stream_write_all (stream, value, n_value, NULL, NULL, &error)) {
     445            0 :                 g_free (value);
     446            0 :                 g_object_unref (stream);
     447            0 :                 g_dbus_method_invocation_take_error (invocation, error);
     448            0 :                 return TRUE;
     449              :         }
     450            4 :         g_free (value);
     451            4 :         g_object_unref (stream);
     452              : 
     453            4 :         g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
     454            4 :         gkd_exported_portal_complete_retrieve_secret (skeleton,
     455              :                                                       invocation,
     456              :                                                       NULL,
     457              :                                                       0,
     458              :                                                       g_variant_builder_end (&builder));
     459              : 
     460            4 :         return TRUE;
     461              : }
        

Generated by: LCOV version 2.0-1