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 : }
|