Branch data Line data Source code
1 : : /* libsecret - GLib wrapper for Secret Service
2 : : *
3 : : * Copyright 2019 Red Hat, Inc.
4 : : *
5 : : * This program is free software: you can redistribute it and/or modify
6 : : * it under the terms of the GNU Lesser General Public License as published
7 : : * by the Free Software Foundation; either version 2.1 of the licence or (at
8 : : * your option) any later version.
9 : : *
10 : : * See the included COPYING file for more information.
11 : : *
12 : : * Author: Daiki Ueno
13 : : */
14 : :
15 : : #include "config.h"
16 : :
17 : : #include "secret-file-collection.h"
18 : :
19 : : #include "egg/egg-keyring1.h"
20 : : #include "egg/egg-secure-memory.h"
21 : :
22 : 23 : EGG_SECURE_DECLARE (secret_file_collection);
23 : :
24 : : #ifdef WITH_GCRYPT
25 : : #include "egg/egg-libgcrypt.h"
26 : : #endif
27 : :
28 : : #define KEYRING_FILE_HEADER "GnomeKeyring\n\r\0\n"
29 : : #define KEYRING_FILE_HEADER_LEN 16
30 : :
31 : : #define MAJOR_VERSION 1
32 : : #define MINOR_VERSION 0
33 : :
34 : : struct _SecretFileCollection
35 : : {
36 : : GObject parent;
37 : : GFile *file;
38 : : gchar *etag;
39 : : SecretValue *password;
40 : : GBytes *salt;
41 : : guint32 iteration_count;
42 : : GDateTime *modified;
43 : : guint64 usage_count;
44 : : GBytes *key;
45 : : GVariant *items;
46 : : guint64 file_last_modified;
47 : : };
48 : :
49 : : static void secret_file_collection_async_initable_iface (GAsyncInitableIface *iface);
50 : :
51 [ + + + - : 135 : G_DEFINE_TYPE_WITH_CODE (SecretFileCollection, secret_file_collection, G_TYPE_OBJECT,
+ + ]
52 : : G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, secret_file_collection_async_initable_iface);
53 : : );
54 : :
55 : : enum {
56 : : PROP_0,
57 : : PROP_FILE,
58 : : PROP_PASSWORD
59 : : };
60 : :
61 : : static guint64
62 : 50 : get_file_last_modified (SecretFileCollection *self)
63 : : {
64 : : GFileInfo *info;
65 : : guint64 res;
66 : :
67 : 50 : info = g_file_query_info (self->file, G_FILE_ATTRIBUTE_TIME_MODIFIED, G_FILE_QUERY_INFO_NONE, NULL, NULL);
68 [ + + ]: 50 : if (info == NULL)
69 : 21 : return 0;
70 : :
71 : 29 : res = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
72 : 29 : g_object_unref (info);
73 : :
74 : 29 : return res;
75 : : }
76 : :
77 : : static void
78 : 19 : secret_file_collection_init (SecretFileCollection *self)
79 : : {
80 : 19 : }
81 : :
82 : : static void
83 : 38 : secret_file_collection_set_property (GObject *object,
84 : : guint prop_id,
85 : : const GValue *value,
86 : : GParamSpec *pspec)
87 : : {
88 : 38 : SecretFileCollection *self = SECRET_FILE_COLLECTION (object);
89 : :
90 [ + + - ]: 38 : switch (prop_id) {
91 : 19 : case PROP_FILE:
92 : 19 : self->file = g_value_dup_object (value);
93 : 19 : break;
94 : 19 : case PROP_PASSWORD:
95 : 19 : self->password = g_value_dup_boxed (value);
96 : 19 : break;
97 : 0 : default:
98 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
99 : 0 : break;
100 : : }
101 : 38 : }
102 : :
103 : : static void
104 : 0 : secret_file_collection_get_property (GObject *object,
105 : : guint prop_id,
106 : : GValue *value,
107 : : GParamSpec *pspec)
108 : : {
109 : : switch (prop_id) {
110 : : default:
111 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
112 : 0 : break;
113 : : }
114 : 0 : }
115 : :
116 : : static void
117 : 7 : secret_file_collection_finalize (GObject *object)
118 : : {
119 : 7 : SecretFileCollection *self = SECRET_FILE_COLLECTION (object);
120 : :
121 : 7 : g_object_unref (self->file);
122 : 7 : g_free (self->etag);
123 : :
124 : 7 : secret_value_unref (self->password);
125 : :
126 [ + - ]: 7 : g_clear_pointer (&self->salt, g_bytes_unref);
127 [ + - ]: 7 : g_clear_pointer (&self->key, g_bytes_unref);
128 [ + - ]: 7 : g_clear_pointer (&self->items, g_variant_unref);
129 [ + - ]: 7 : g_clear_pointer (&self->modified, g_date_time_unref);
130 : :
131 : 7 : G_OBJECT_CLASS (secret_file_collection_parent_class)->finalize (object);
132 : 7 : }
133 : :
134 : : static void
135 : 13 : secret_file_collection_class_init (SecretFileCollectionClass *klass)
136 : : {
137 : 13 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
138 : 13 : object_class->set_property = secret_file_collection_set_property;
139 : 13 : object_class->get_property = secret_file_collection_get_property;
140 : 13 : object_class->finalize = secret_file_collection_finalize;
141 : :
142 : 13 : g_object_class_install_property (object_class, PROP_FILE,
143 : : g_param_spec_object ("file", "File", "File",
144 : : G_TYPE_FILE, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
145 : 13 : g_object_class_install_property (object_class, PROP_PASSWORD,
146 : : g_param_spec_boxed ("password", "password", "Password",
147 : : SECRET_TYPE_VALUE,
148 : : G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
149 : : #ifdef WITH_GCRYPT
150 : 13 : egg_libgcrypt_initialize ();
151 : : #endif
152 : 13 : }
153 : :
154 : : static gboolean
155 : 11 : load_contents (SecretFileCollection *self,
156 : : gchar *contents, /* takes ownership */
157 : : gsize length,
158 : : GError **error)
159 : : {
160 : : gchar *p;
161 : : GVariant *variant;
162 : : GVariant *salt_array;
163 : : guint32 salt_size;
164 : : guint32 iteration_count;
165 : : guint64 modified_time;
166 : : guint64 usage_count;
167 : : gconstpointer data;
168 : : gsize n_data;
169 : : const gchar *password;
170 : : gsize n_password;
171 : :
172 : 11 : p = contents;
173 [ + - ]: 11 : if (length < KEYRING_FILE_HEADER_LEN ||
174 [ - + ]: 11 : memcmp (p, KEYRING_FILE_HEADER, KEYRING_FILE_HEADER_LEN) != 0) {
175 : 0 : g_set_error_literal (error,
176 : : SECRET_ERROR,
177 : : SECRET_ERROR_INVALID_FILE_FORMAT,
178 : : "file header mismatch");
179 : 0 : return FALSE;
180 : : }
181 : 11 : p += KEYRING_FILE_HEADER_LEN;
182 : 11 : length -= KEYRING_FILE_HEADER_LEN;
183 : :
184 [ + - + - : 11 : if (length < 2 || *p != MAJOR_VERSION || *(p + 1) != MINOR_VERSION) {
- + ]
185 : 0 : g_set_error_literal (error,
186 : : SECRET_ERROR,
187 : : SECRET_ERROR_INVALID_FILE_FORMAT,
188 : : "version mismatch");
189 : 0 : return FALSE;
190 : : }
191 : 11 : p += 2;
192 : 11 : length -= 2;
193 : :
194 : 11 : variant = g_variant_new_from_data (G_VARIANT_TYPE ("(uayutua(a{say}ay))"),
195 : : p,
196 : : length,
197 : : TRUE,
198 : : g_free,
199 : : contents);
200 : 11 : g_variant_get (variant, "(u@ayutu@a(a{say}ay))",
201 : : &salt_size, &salt_array, &iteration_count,
202 : : &modified_time, &usage_count,
203 : : &self->items);
204 : :
205 : 11 : salt_size = GUINT32_FROM_LE(salt_size);
206 : 11 : iteration_count = GUINT32_FROM_LE(iteration_count);
207 : 11 : modified_time = GUINT64_FROM_LE(modified_time);
208 : 11 : usage_count = GUINT32_FROM_LE(usage_count);
209 : :
210 : 11 : self->iteration_count = iteration_count;
211 : 11 : self->modified = g_date_time_new_from_unix_utc (modified_time);
212 : 11 : self->usage_count = usage_count;
213 : :
214 : 11 : data = g_variant_get_fixed_array (salt_array, &n_data, sizeof(guint8));
215 [ - + ]: 11 : g_assert (n_data == salt_size);
216 : :
217 : 11 : self->salt = g_bytes_new (data, n_data);
218 : :
219 : 11 : g_variant_unref (salt_array);
220 : 11 : g_variant_unref (variant);
221 : :
222 : 11 : password = secret_value_get (self->password, &n_password);
223 : 11 : self->key = egg_keyring1_derive_key (password,
224 : : n_password,
225 : : self->salt,
226 : : self->iteration_count);
227 [ - + ]: 11 : if (!self->key) {
228 : 0 : g_set_error_literal (error,
229 : : SECRET_ERROR,
230 : : SECRET_ERROR_PROTOCOL,
231 : : "couldn't derive key");
232 : 0 : return FALSE;
233 : : }
234 : :
235 : 11 : return TRUE;
236 : : }
237 : :
238 : : static gboolean
239 : 8 : init_empty_file (SecretFileCollection *self,
240 : : GError **error)
241 : : {
242 : : GVariantBuilder builder;
243 : : const gchar *password;
244 : : gsize n_password;
245 : : guint8 salt[SALT_SIZE];
246 : :
247 : 8 : egg_keyring1_create_nonce (salt, sizeof(salt));
248 : 8 : self->salt = g_bytes_new (salt, sizeof(salt));
249 : 8 : self->iteration_count = ITERATION_COUNT;
250 : 8 : self->modified = g_date_time_new_now_utc ();
251 : 8 : self->usage_count = 0;
252 : :
253 : 8 : password = secret_value_get (self->password, &n_password);
254 : 8 : self->key = egg_keyring1_derive_key (password,
255 : : n_password,
256 : : self->salt,
257 : : self->iteration_count);
258 [ - + ]: 8 : if (!self->key) {
259 : 0 : g_set_error_literal (error,
260 : : SECRET_ERROR,
261 : : SECRET_ERROR_PROTOCOL,
262 : : "couldn't derive key");
263 : 0 : return FALSE;
264 : : }
265 : :
266 : 8 : g_variant_builder_init (&builder,
267 : : G_VARIANT_TYPE ("a(a{say}ay)"));
268 : 8 : self->items = g_variant_builder_end (&builder);
269 : 8 : g_variant_ref_sink (self->items);
270 : :
271 : 8 : return TRUE;
272 : : }
273 : :
274 : : static void
275 : 24 : ensure_up_to_date (SecretFileCollection *self)
276 : : {
277 : : guint64 last_modified;
278 : :
279 : 24 : last_modified = get_file_last_modified (self);
280 [ - + ]: 24 : if (last_modified != self->file_last_modified) {
281 : 0 : gchar *contents = NULL;
282 : 0 : gsize length = 0;
283 : : gboolean success;
284 : 0 : GError *error = NULL;
285 : 0 : gchar *etag = NULL;
286 : :
287 : 0 : self->file_last_modified = last_modified;
288 : :
289 : 0 : success = g_file_load_contents (self->file, NULL, &contents, &length, &etag, &error);
290 : :
291 [ # # ]: 0 : if (success) {
292 [ # # ]: 0 : g_clear_pointer (&self->etag, g_free);
293 : 0 : self->etag = g_steal_pointer (&etag);
294 [ # # ]: 0 : } else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
295 : 0 : g_clear_error (&error);
296 : :
297 : 0 : success = init_empty_file (self, &error);
298 : : }
299 : :
300 [ # # ]: 0 : if (success)
301 : 0 : success = load_contents (self, contents, length, &error);
302 : :
303 [ # # ]: 0 : if (!success)
304 [ # # ]: 0 : g_debug ("Failed to load file contents: %s", error ? error->message : "Unknown error");
305 : :
306 : 0 : g_clear_error (&error);
307 : : }
308 : 24 : }
309 : :
310 : : static void
311 : 19 : on_load_contents (GObject *source_object,
312 : : GAsyncResult *result,
313 : : gpointer user_data)
314 : : {
315 : 19 : GFile *file = G_FILE (source_object);
316 : 19 : GTask *task = G_TASK (user_data);
317 : 19 : SecretFileCollection *self = g_task_get_source_object (task);
318 : : gchar *contents;
319 : : gsize length;
320 : 19 : GError *error = NULL;
321 : : gboolean ret;
322 : 19 : gchar *etag = NULL;
323 : :
324 : 19 : self->file_last_modified = get_file_last_modified (self);
325 : :
326 : 19 : ret = g_file_load_contents_finish (file, result,
327 : : &contents, &length,
328 : : &etag,
329 : : &error);
330 : :
331 [ + + ]: 19 : if (!ret) {
332 [ + - ]: 8 : if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
333 : 8 : g_clear_error (&error);
334 : :
335 [ + - ]: 8 : if (init_empty_file (self, &error)) {
336 : 8 : g_task_return_boolean (task, TRUE);
337 : 8 : g_object_unref (task);
338 : 8 : return;
339 : : }
340 : : }
341 : :
342 : 0 : g_task_return_error (task, error);
343 : 0 : g_object_unref (task);
344 : 0 : return;
345 : : }
346 : :
347 [ - + ]: 11 : g_clear_pointer (&self->etag, g_free);
348 : 11 : self->etag = g_steal_pointer (&etag);
349 : :
350 : 11 : ret = load_contents (self, contents, length, &error);
351 [ + - ]: 11 : if (ret)
352 : 11 : g_task_return_boolean (task, ret);
353 : : else
354 : 0 : g_task_return_error (task, error);
355 : :
356 : 11 : g_object_unref (task);
357 : : }
358 : :
359 : : static void
360 : 19 : secret_file_collection_real_init_async (GAsyncInitable *initable,
361 : : int io_priority,
362 : : GCancellable *cancellable,
363 : : GAsyncReadyCallback callback,
364 : : gpointer user_data)
365 : : {
366 : 19 : SecretFileCollection *self = SECRET_FILE_COLLECTION (initable);
367 : : GTask *task;
368 : :
369 : 19 : task = g_task_new (initable, cancellable, callback, user_data);
370 : :
371 : 19 : g_file_load_contents_async (self->file, cancellable, on_load_contents, task);
372 : 19 : }
373 : :
374 : : static gboolean
375 : 19 : secret_file_collection_real_init_finish (GAsyncInitable *initable,
376 : : GAsyncResult *result,
377 : : GError **error)
378 : : {
379 [ - + ]: 19 : g_return_val_if_fail (g_task_is_valid (result, initable), FALSE);
380 : :
381 : 19 : return g_task_propagate_boolean (G_TASK (result), error);
382 : : }
383 : :
384 : : static void
385 : 13 : secret_file_collection_async_initable_iface (GAsyncInitableIface *iface)
386 : : {
387 : 13 : iface->init_async = secret_file_collection_real_init_async;
388 : 13 : iface->init_finish = secret_file_collection_real_init_finish;
389 : 13 : }
390 : :
391 : : static GVariant *
392 : 12 : hash_attributes (SecretFileCollection *self,
393 : : GHashTable *attributes)
394 : : {
395 : : GVariantBuilder builder;
396 : : guint8 buffer[MAC_SIZE];
397 : : GList *keys;
398 : : GList *l;
399 : :
400 : 12 : g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{say}"));
401 : :
402 : 12 : keys = g_hash_table_get_keys (attributes);
403 : 12 : keys = g_list_sort (keys, (GCompareFunc) g_strcmp0);
404 : :
405 [ + - + + ]: 41 : for (l = keys; l; l = g_list_next (l)) {
406 : : const gchar *value;
407 : : GVariant *variant;
408 : :
409 : 29 : value = g_hash_table_lookup (attributes, l->data);
410 [ - + ]: 29 : if (!egg_keyring1_calculate_mac (self->key,
411 : : (const guint8 *)value,
412 : : strlen (value),
413 : : buffer)) {
414 : 0 : g_list_free (keys);
415 : 0 : return NULL;
416 : : }
417 : :
418 : 29 : variant = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
419 : : buffer,
420 : : MAC_SIZE,
421 : : sizeof(guint8));
422 : 29 : g_variant_builder_add (&builder, "{s@ay}", l->data, variant);
423 : : }
424 : 12 : g_list_free (keys);
425 : :
426 : 12 : return g_variant_builder_end (&builder);
427 : : }
428 : :
429 : : static gboolean
430 : 20 : hashed_attributes_match (SecretFileCollection *self,
431 : : GVariant *hashed_attributes,
432 : : GHashTable *attributes)
433 : : {
434 : : GHashTableIter iter;
435 : 20 : GVariant *hashed_attribute = NULL;
436 : : gpointer key;
437 : : gpointer value;
438 : :
439 : 20 : g_hash_table_iter_init (&iter, attributes);
440 [ + + ]: 43 : while (g_hash_table_iter_next (&iter, &key, &value)) {
441 : : const guint8 *data;
442 : : gsize n_data;
443 : :
444 [ + + ]: 26 : if (!g_variant_lookup (hashed_attributes, key,
445 : : "@ay", &hashed_attribute))
446 : 3 : return FALSE;
447 : :
448 : 23 : data = g_variant_get_fixed_array (hashed_attribute,
449 : : &n_data, sizeof(guint8));
450 [ - + ]: 23 : if (n_data != MAC_SIZE) {
451 : 0 : g_variant_unref (hashed_attribute);
452 : 0 : return FALSE;
453 : : }
454 : :
455 [ - + ]: 23 : if (!egg_keyring1_verify_mac (self->key, value, strlen ((char *)value), data)) {
456 : 0 : g_variant_unref (hashed_attribute);
457 : 0 : return FALSE;
458 : : }
459 : 23 : g_variant_unref (hashed_attribute);
460 : : }
461 : :
462 : 17 : return TRUE;
463 : : }
464 : :
465 : : gboolean
466 : 12 : secret_file_collection_replace (SecretFileCollection *self,
467 : : GHashTable *attributes,
468 : : const gchar *label,
469 : : SecretValue *value,
470 : : GError **error)
471 : : {
472 : : GVariantBuilder builder;
473 : : GVariant *hashed_attributes;
474 : : GVariantIter iter;
475 : : GVariant *child;
476 : : SecretFileItem *item;
477 : : GVariant *serialized_item;
478 : 12 : guint8 *data = NULL;
479 : : gsize n_data;
480 : : gsize n_padded;
481 : : GVariant *variant;
482 : 12 : GDateTime *created = NULL;
483 : : GDateTime *modified;
484 : :
485 : 12 : ensure_up_to_date (self);
486 : :
487 : 12 : hashed_attributes = hash_attributes (self, attributes);
488 [ - + ]: 12 : if (!hashed_attributes) {
489 : 0 : g_set_error (error,
490 : : SECRET_ERROR,
491 : : SECRET_ERROR_PROTOCOL,
492 : : "couldn't calculate mac");
493 : 0 : return FALSE;
494 : : }
495 : :
496 : : /* Filter out the existing item */
497 : 12 : g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(a{say}ay)"));
498 : 12 : g_variant_iter_init (&iter, self->items);
499 [ + + ]: 17 : while ((child = g_variant_iter_next_value (&iter)) != NULL) {
500 : : GVariant *_hashed_attributes;
501 : 5 : g_variant_get (child, "(@a{say}ay)", &_hashed_attributes, NULL);
502 [ + + ]: 5 : if (g_variant_equal (hashed_attributes, _hashed_attributes)) {
503 : : SecretFileItem *existing =
504 : 1 : _secret_file_item_decrypt (child, self, error);
505 : : guint64 created_time;
506 : :
507 [ - + ]: 1 : if (existing == NULL) {
508 : 0 : g_variant_builder_clear (&builder);
509 : 0 : g_variant_unref (child);
510 : 0 : g_variant_unref (_hashed_attributes);
511 : 0 : return FALSE;
512 : : }
513 : 1 : g_object_get (existing, "created", &created_time, NULL);
514 : 1 : g_object_unref (existing);
515 : :
516 : 1 : created = g_date_time_new_from_unix_utc (created_time);
517 : : } else {
518 : 4 : g_variant_builder_add_value (&builder, child);
519 : : }
520 : 5 : g_variant_unref (child);
521 : 5 : g_variant_unref (_hashed_attributes);
522 : : }
523 : :
524 : 12 : modified = g_date_time_new_now_utc ();
525 [ + + ]: 12 : if (created == NULL)
526 : 11 : created = g_date_time_ref (modified);
527 : :
528 : : /* Create a new item and append it */
529 : 12 : item = g_object_new (SECRET_TYPE_FILE_ITEM,
530 : : "attributes", attributes,
531 : : "label", label,
532 : : "value", value,
533 : : "created", g_date_time_to_unix (created),
534 : : "modified", g_date_time_to_unix (modified),
535 : : NULL);
536 : :
537 : 12 : g_date_time_unref (created);
538 : 12 : g_date_time_unref (modified);
539 : :
540 : 12 : serialized_item = secret_file_item_serialize (item);
541 : 12 : g_object_unref (item);
542 : :
543 : : /* Encrypt the item with PKCS #7 padding */
544 : 12 : n_data = g_variant_get_size (serialized_item);
545 : 12 : n_padded = ((n_data + CIPHER_BLOCK_SIZE) / CIPHER_BLOCK_SIZE) *
546 : : CIPHER_BLOCK_SIZE;
547 : 12 : data = egg_secure_alloc (n_padded + IV_SIZE + MAC_SIZE);
548 : 12 : g_variant_store (serialized_item, data);
549 : 12 : g_variant_unref (serialized_item);
550 : 12 : memset (data + n_data, n_padded - n_data, n_padded - n_data);
551 [ - + ]: 12 : if (!egg_keyring1_encrypt (self->key, data, n_padded)) {
552 : 0 : egg_secure_free (data);
553 : 0 : g_set_error (error,
554 : : SECRET_ERROR,
555 : : SECRET_ERROR_PROTOCOL,
556 : : "couldn't encrypt item");
557 : 0 : return FALSE;
558 : : }
559 : :
560 [ - + ]: 12 : if (!egg_keyring1_calculate_mac (self->key, data, n_padded + IV_SIZE,
561 : 12 : data + n_padded + IV_SIZE)) {
562 : 0 : egg_secure_free (data);
563 : 0 : g_set_error (error,
564 : : SECRET_ERROR,
565 : : SECRET_ERROR_PROTOCOL,
566 : : "couldn't calculate mac");
567 : 0 : return FALSE;
568 : : }
569 : :
570 : 12 : self->usage_count++;
571 : 12 : g_date_time_unref (self->modified);
572 : 12 : self->modified = g_date_time_new_now_utc ();
573 : :
574 : 12 : variant = g_variant_new_from_data (G_VARIANT_TYPE ("ay"),
575 : : data,
576 : : n_padded + IV_SIZE + MAC_SIZE,
577 : : TRUE,
578 : : egg_secure_free,
579 : : data);
580 : 12 : variant = g_variant_new ("(@a{say}@ay)", hashed_attributes, variant);
581 : 12 : g_variant_builder_add_value (&builder, variant);
582 : :
583 : 12 : g_variant_unref (self->items);
584 : 12 : self->items = g_variant_builder_end (&builder);
585 : 12 : g_variant_ref_sink (self->items);
586 : :
587 : 12 : return TRUE;
588 : : }
589 : :
590 : : GList *
591 : 9 : secret_file_collection_search (SecretFileCollection *self,
592 : : GHashTable *attributes)
593 : : {
594 : : GVariantIter iter;
595 : : GVariant *child;
596 : 9 : GList *result = NULL;
597 : :
598 : 9 : ensure_up_to_date (self);
599 : :
600 : 9 : g_variant_iter_init (&iter, self->items);
601 [ + + ]: 24 : while ((child = g_variant_iter_next_value (&iter)) != NULL) {
602 : : GVariant *hashed_attributes;
603 : : gboolean matched;
604 : :
605 : 15 : g_variant_get (child, "(@a{say}ay)", &hashed_attributes, NULL);
606 : 15 : matched = hashed_attributes_match (self,
607 : : hashed_attributes,
608 : : attributes);
609 : 15 : g_variant_unref (hashed_attributes);
610 [ + + ]: 15 : if (matched)
611 : 14 : result = g_list_append (result, g_variant_ref (child));
612 : 15 : g_variant_unref (child);
613 : : }
614 : :
615 : 9 : return result;
616 : : }
617 : :
618 : : SecretFileItem *
619 : 11 : _secret_file_item_decrypt (GVariant *encrypted,
620 : : SecretFileCollection *collection,
621 : : GError **error)
622 : : {
623 : : GVariant *blob;
624 : : gconstpointer padded;
625 : : gsize n_data;
626 : : gsize n_padded;
627 : : guint8 *data;
628 : : SecretFileItem *item;
629 : : GVariant *serialized_item;
630 : :
631 : 11 : g_variant_get (encrypted, "(a{say}@ay)", NULL, &blob);
632 : :
633 : : /* Decrypt the item */
634 : 11 : padded = g_variant_get_fixed_array (blob, &n_padded, sizeof(guint8));
635 : 11 : data = egg_secure_alloc (n_padded);
636 : 11 : memcpy (data, padded, n_padded);
637 : 11 : g_variant_unref (blob);
638 : :
639 [ - + ]: 11 : if (n_padded < IV_SIZE + MAC_SIZE) {
640 : 0 : egg_secure_free (data);
641 : 0 : g_set_error (error,
642 : : SECRET_ERROR,
643 : : SECRET_ERROR_PROTOCOL,
644 : : "couldn't calculate mac");
645 : 0 : return FALSE;
646 : : }
647 : :
648 : 11 : n_padded -= MAC_SIZE;
649 [ - + ]: 11 : if (!egg_keyring1_verify_mac (collection->key, data, n_padded, data + n_padded)) {
650 : 0 : egg_secure_free (data);
651 : 0 : g_set_error (error,
652 : : SECRET_ERROR,
653 : : SECRET_ERROR_PROTOCOL,
654 : : "couldn't calculate mac");
655 : 0 : return FALSE;
656 : : }
657 : :
658 : 11 : n_padded -= IV_SIZE;
659 [ - + ]: 11 : if (!egg_keyring1_decrypt (collection->key, data, n_padded)) {
660 : 0 : egg_secure_free (data);
661 : 0 : g_set_error (error,
662 : : SECRET_ERROR,
663 : : SECRET_ERROR_PROTOCOL,
664 : : "couldn't decrypt item");
665 : 0 : return NULL;
666 : : }
667 : :
668 : : /* Remove PKCS #7 padding */
669 : 11 : n_data = n_padded - data[n_padded - 1];
670 : :
671 : : serialized_item =
672 : 11 : g_variant_new_from_data (G_VARIANT_TYPE ("(a{ss}sttay)"),
673 : : data,
674 : : n_data,
675 : : TRUE,
676 : : egg_secure_free,
677 : : data);
678 : 11 : item = secret_file_item_deserialize (serialized_item);
679 : 11 : g_variant_unref (serialized_item);
680 : 11 : return item;
681 : : }
682 : :
683 : : gboolean
684 : 3 : secret_file_collection_clear (SecretFileCollection *self,
685 : : GHashTable *attributes,
686 : : GError **error)
687 : : {
688 : : GVariantBuilder builder;
689 : : GVariantIter items;
690 : : GVariant *child;
691 : 3 : gboolean removed = FALSE;
692 : :
693 : 3 : ensure_up_to_date (self);
694 : :
695 : 3 : g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(a{say}ay)"));
696 : 3 : g_variant_iter_init (&items, self->items);
697 [ + + ]: 8 : while ((child = g_variant_iter_next_value (&items)) != NULL) {
698 : : GVariant *hashed_attributes;
699 : : gboolean matched;
700 : :
701 : 5 : g_variant_get (child, "(@a{say}ay)", &hashed_attributes, NULL);
702 : 5 : matched = hashed_attributes_match (self,
703 : : hashed_attributes,
704 : : attributes);
705 : 5 : g_variant_unref (hashed_attributes);
706 [ + + ]: 5 : if (matched)
707 : 3 : removed = TRUE;
708 : : else
709 : 2 : g_variant_builder_add_value (&builder, child);
710 : 5 : g_variant_unref (child);
711 : : }
712 : :
713 : 3 : g_variant_unref (self->items);
714 : 3 : self->items = g_variant_builder_end (&builder);
715 : 3 : g_variant_ref_sink (self->items);
716 : :
717 : 3 : return removed;
718 : : }
719 : :
720 : : static void
721 : 7 : on_replace_contents (GObject *source_object,
722 : : GAsyncResult *result,
723 : : gpointer user_data)
724 : : {
725 : 7 : GFile *file = G_FILE (source_object);
726 : 7 : GTask *task = G_TASK (user_data);
727 : 7 : SecretFileCollection *self = g_task_get_source_object (task);
728 : 7 : GError *error = NULL;
729 : 7 : gchar *etag = NULL;
730 : :
731 [ - + ]: 7 : if (!g_file_replace_contents_finish (file, result, &etag, &error)) {
732 : 0 : g_task_return_error (task, error);
733 : 0 : g_object_unref (task);
734 : 0 : return;
735 : : }
736 : :
737 : 7 : self->file_last_modified = get_file_last_modified (self);
738 [ + + ]: 7 : g_clear_pointer (&self->etag, g_free);
739 : 7 : self->etag = g_steal_pointer (&etag);
740 : :
741 : 7 : g_task_return_boolean (task, TRUE);
742 : 7 : g_object_unref (task);
743 : : }
744 : :
745 : : void
746 : 7 : secret_file_collection_write (SecretFileCollection *self,
747 : : GCancellable *cancellable,
748 : : GAsyncReadyCallback callback,
749 : : gpointer user_data)
750 : : {
751 : : GTask *task;
752 : : guint8 *contents;
753 : : gsize n_contents;
754 : : guint8 *p;
755 : : GVariant *salt_array;
756 : : GVariant *variant;
757 : :
758 : 7 : salt_array = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
759 : : g_bytes_get_data (self->salt, NULL),
760 : : g_bytes_get_size (self->salt),
761 : : sizeof(guint8));
762 : 7 : variant = g_variant_new ("(u@ayutu@a(a{say}ay))",
763 : 7 : GUINT32_TO_LE(g_bytes_get_size (self->salt)),
764 : : salt_array,
765 : 7 : GUINT32_TO_LE(self->iteration_count),
766 : 7 : GUINT64_TO_LE(g_date_time_to_unix (self->modified)),
767 : 7 : GUINT32_TO_LE(self->usage_count),
768 : : self->items);
769 : :
770 : 7 : g_variant_get_data (variant); /* force serialize */
771 : 7 : n_contents = KEYRING_FILE_HEADER_LEN + 2 + g_variant_get_size (variant);
772 : 7 : contents = g_new (guint8, n_contents);
773 : :
774 : 7 : p = contents;
775 : 7 : memcpy (p, KEYRING_FILE_HEADER, KEYRING_FILE_HEADER_LEN);
776 : 7 : p += KEYRING_FILE_HEADER_LEN;
777 : :
778 : 7 : *p++ = MAJOR_VERSION;
779 : 7 : *p++ = MINOR_VERSION;
780 : :
781 : 7 : g_variant_store (variant, p);
782 : 7 : g_variant_unref (variant);
783 : :
784 : 7 : task = g_task_new (self, cancellable, callback, user_data);
785 : 7 : g_task_set_task_data (task, contents, g_free);
786 : 7 : g_file_replace_contents_async (self->file,
787 : : (gchar *) contents,
788 : : n_contents,
789 : 7 : self->etag,
790 : : TRUE,
791 : : G_FILE_CREATE_PRIVATE |
792 : : G_FILE_CREATE_REPLACE_DESTINATION,
793 : : cancellable,
794 : : on_replace_contents,
795 : : task);
796 : 7 : }
797 : :
798 : : gboolean
799 : 7 : secret_file_collection_write_finish (SecretFileCollection *self,
800 : : GAsyncResult *result,
801 : : GError **error)
802 : : {
803 [ - + ]: 7 : g_return_val_if_fail (g_task_is_valid (result, self), FALSE);
804 : :
805 : 7 : return g_task_propagate_boolean (G_TASK (result), error);
806 : : }
|