Line data Source code
1 : /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 : /* gkm-secret-binary.c - The binary encrypted format of a keyring
3 :
4 : Copyright (C) 2003 Red Hat, Inc
5 : Copyright (C) 2007, 2009 Stefan Walter
6 :
7 : Gnome keyring is free software; you can redistribute it and/or
8 : modify it under the terms of the GNU General Public License as
9 : published by the Free Software Foundation; either version 2 of the
10 : License, or (at your option) any later version.
11 :
12 : Gnome keyring is distributed in the hope that it will be useful,
13 : but WITHOUT ANY WARRANTY; without even the implied warranty of
14 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 : General Public License for more details.
16 :
17 : You should have received a copy of the GNU General Public License
18 : along with this program; if not, write to the Free Software
19 : Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 :
21 : Author: Alexander Larsson <alexl@redhat.com>
22 : Author: Stef Walter <stef@memberwebs.com>
23 : */
24 :
25 : #include "config.h"
26 :
27 : #include "gkm-secret-binary.h"
28 : #include "gkm-secret-collection.h"
29 : #include "gkm-secret-compat.h"
30 : #include "gkm-secret-data.h"
31 : #include "gkm-secret-fields.h"
32 : #include "gkm-secret-item.h"
33 :
34 : #include "egg/egg-buffer.h"
35 : #include "egg/egg-symkey.h"
36 : #include "egg/egg-secure-memory.h"
37 :
38 : #include "gkm/gkm-secret.h"
39 :
40 : #include <glib.h>
41 :
42 : #include <gcrypt.h>
43 :
44 : #include <sys/types.h>
45 : #include <sys/stat.h>
46 :
47 : #include <ctype.h>
48 : #include <errno.h>
49 : #include <fcntl.h>
50 : #include <stdlib.h>
51 : #include <stdio.h>
52 : #include <string.h>
53 : #include <unistd.h>
54 :
55 204 : EGG_SECURE_DECLARE (secret_binary);
56 :
57 : /* -----------------------------------------------------------------------------
58 : * DECLARATIONS
59 : */
60 :
61 : enum {
62 : LOCK_ON_IDLE_FLAG = 1 << 0,
63 : LOCK_AFTER_FLAG = 1 << 1
64 : };
65 :
66 : typedef struct {
67 : /* unencrypted: */
68 : guint32 id;
69 : gchar *identifier;
70 : guint32 type;
71 :
72 : /* encrypted: */
73 : char *display_name;
74 : const guchar *ptr_secret;
75 : gsize n_secret;
76 : time_t ctime;
77 : time_t mtime;
78 : GHashTable *attributes;
79 : GList *acl;
80 : } ItemInfo;
81 :
82 : #define KEYRING_FILE_HEADER "GnomeKeyring\n\r\0\n"
83 : #define KEYRING_FILE_HEADER_LEN 16
84 :
85 : /* -----------------------------------------------------------------------------
86 : * BUFFER UTILITY FUNCTIONS
87 : */
88 :
89 : static gboolean
90 86 : buffer_get_bytes (EggBuffer *buffer, gsize offset, gsize *next_offset,
91 : guchar *out, gsize n_bytes)
92 : {
93 86 : if (buffer->len < n_bytes || offset > buffer->len - n_bytes)
94 0 : return FALSE;
95 86 : memcpy (out, buffer->buf + offset, n_bytes);
96 86 : *next_offset = offset + n_bytes;
97 86 : return TRUE;
98 : }
99 :
100 : static gboolean
101 70 : buffer_add_time (EggBuffer *buffer, glong time)
102 : {
103 70 : guint64 val = time;
104 140 : return egg_buffer_add_uint32 (buffer, ((val >> 32) & 0xffffffff)) &&
105 70 : egg_buffer_add_uint32 (buffer, (val & 0xffffffff));
106 : }
107 :
108 : static gboolean
109 254 : buffer_get_time (EggBuffer *buffer, gsize offset, gsize *next_offset, time_t *time)
110 : {
111 : guint32 a, b;
112 : guint64 val;
113 :
114 508 : if (!egg_buffer_get_uint32 (buffer, offset, &offset, &a) ||
115 254 : !egg_buffer_get_uint32 (buffer, offset, &offset, &b))
116 0 : return FALSE;
117 :
118 254 : val = ((guint64)a) << 32 | b;
119 254 : *next_offset = offset;
120 254 : *time = (time_t) val;
121 254 : return TRUE;
122 : }
123 :
124 : static gboolean
125 92 : buffer_add_utf8_string (EggBuffer *buffer, const char *str)
126 : {
127 92 : if (str && !g_utf8_validate (str, -1, NULL))
128 0 : return FALSE;
129 92 : return egg_buffer_add_string (buffer, str);
130 : }
131 :
132 : static gboolean
133 663 : buffer_get_utf8_string (EggBuffer *buffer, gsize offset, gsize *next_offset,
134 : char **str_ret)
135 : {
136 : gsize len;
137 : char *str;
138 :
139 663 : if (!egg_buffer_get_string (buffer, offset, &offset, &str,
140 : (EggBufferAllocator)g_realloc))
141 0 : return FALSE;
142 663 : len = str ? strlen (str) : 0;
143 :
144 663 : if (str != NULL) {
145 608 : if (!g_utf8_validate (str, len, NULL)) {
146 0 : g_free (str);
147 0 : return FALSE;
148 : }
149 : }
150 :
151 663 : if (next_offset != NULL) {
152 663 : *next_offset = offset;
153 : }
154 663 : if (str_ret != NULL) {
155 663 : *str_ret = str;
156 : } else {
157 0 : g_free (str);
158 : }
159 663 : return TRUE;
160 : }
161 :
162 : static gboolean
163 19 : buffer_add_secret (EggBuffer *buffer, GkmSecret *secret)
164 : {
165 19 : const guchar *data = NULL;
166 19 : gsize n_data = 0;
167 19 : if (secret != NULL)
168 17 : data = gkm_secret_get (secret, &n_data);
169 19 : return egg_buffer_add_byte_array (buffer, data, n_data);
170 : }
171 :
172 : static void
173 11 : buffer_add_attribute (EggBuffer *buffer, GHashTable *attributes, const gchar *key)
174 : {
175 : guint32 number;
176 :
177 11 : buffer_add_utf8_string (buffer, key);
178 :
179 : /*
180 : * COMPATIBILITY:
181 : *
182 : * Our new Secrets API doesn't support integer attributes. However, to have
183 : * compatibility with old keyring code reading this file, we need to set
184 : * the uint32 type attribute appropriately where expected.
185 : *
186 : * If there's an extra compat-uint32 attribute and the name of this attribute
187 : * is contained in that list, then write as a uint32.
188 : */
189 :
190 : /* Determine if it's a uint32 compatible value, and store as such if it is */
191 11 : if (gkm_secret_fields_get_compat_uint32 (attributes, key, &number)) {
192 3 : egg_buffer_add_uint32 (buffer, 1);
193 3 : egg_buffer_add_uint32 (buffer, number);
194 :
195 : /* A normal string attribute */
196 : } else {
197 8 : egg_buffer_add_uint32 (buffer, 0);
198 8 : buffer_add_utf8_string (buffer, gkm_secret_fields_get (attributes, key));
199 : }
200 11 : }
201 :
202 : static void
203 11 : buffer_add_hashed_attribute (EggBuffer *buffer, GHashTable *attributes, const gchar *key)
204 : {
205 : guint32 number;
206 : gchar *value;
207 :
208 11 : buffer_add_utf8_string (buffer, key);
209 :
210 : /* See comments in buffer_add_attribute. */
211 :
212 : /* Determine if it's a uint32 compatible value, and store as such if it is */
213 11 : if (gkm_secret_fields_get_compat_hashed_uint32 (attributes, key, &number)) {
214 3 : egg_buffer_add_uint32 (buffer, 1);
215 3 : egg_buffer_add_uint32 (buffer, number);
216 :
217 : /* A standard string attribute */
218 : } else {
219 8 : if (!gkm_secret_fields_get_compat_hashed_string (attributes, key, &value))
220 0 : g_return_if_reached ();
221 8 : egg_buffer_add_uint32 (buffer, 0);
222 8 : buffer_add_utf8_string (buffer, value);
223 8 : g_free (value);
224 : }
225 : }
226 :
227 : static gboolean
228 38 : buffer_add_attributes (EggBuffer *buffer, GHashTable *attributes, gboolean hashed)
229 : {
230 : GList *names, *l;
231 :
232 38 : g_assert (buffer);
233 :
234 38 : if (attributes == NULL) {
235 0 : egg_buffer_add_uint32 (buffer, 0);
236 : } else {
237 38 : names = gkm_secret_fields_get_names (attributes);
238 38 : egg_buffer_add_uint32 (buffer, g_list_length (names));
239 60 : for (l = names; l; l = g_list_next (l)) {
240 22 : if (hashed)
241 11 : buffer_add_hashed_attribute (buffer, attributes, l->data);
242 : else
243 11 : buffer_add_attribute (buffer, attributes, l->data);
244 : }
245 38 : g_list_free (names);
246 : }
247 :
248 38 : return !egg_buffer_has_error (buffer);
249 : }
250 :
251 : static gboolean
252 166 : buffer_get_attributes (EggBuffer *buffer, gsize offset, gsize *next_offset,
253 : GHashTable **attributes_out, gboolean hashed)
254 : {
255 : guint32 list_size;
256 : GHashTable *attributes;
257 : char *name;
258 : guint32 type;
259 : char *str;
260 : guint32 val;
261 : int i;
262 :
263 166 : attributes = NULL;
264 :
265 166 : if (!egg_buffer_get_uint32 (buffer, offset, &offset, &list_size))
266 0 : goto bail;
267 :
268 166 : attributes = gkm_secret_fields_new ();
269 420 : for (i = 0; i < list_size; i++) {
270 254 : if (!buffer_get_utf8_string (buffer, offset, &offset, &name))
271 0 : goto bail;
272 254 : if (!egg_buffer_get_uint32 (buffer, offset, &offset, &type)) {
273 0 : g_free (name);
274 0 : goto bail;
275 : }
276 254 : switch (type) {
277 199 : case 0: /* A string */
278 199 : if (!buffer_get_utf8_string (buffer, offset, &offset, &str)) {
279 0 : g_free (name);
280 0 : goto bail;
281 : }
282 199 : if (hashed) {
283 152 : gkm_secret_fields_add_compat_hashed_string (attributes, name, str);
284 152 : g_free (name);
285 152 : g_free (str);
286 : } else {
287 47 : gkm_secret_fields_take (attributes, name, str);
288 : }
289 199 : break;
290 55 : case 1: /* A uint32 */
291 55 : if (!egg_buffer_get_uint32 (buffer, offset, &offset, &val)) {
292 0 : g_free (name);
293 0 : goto bail;
294 : }
295 55 : if (hashed)
296 42 : gkm_secret_fields_add_compat_hashed_uint32 (attributes, name, val);
297 : else
298 13 : gkm_secret_fields_add_compat_uint32 (attributes, name, val);
299 55 : g_free (name);
300 55 : break;
301 0 : default:
302 0 : g_free (name);
303 0 : goto bail;
304 : }
305 : }
306 :
307 166 : *attributes_out = attributes;
308 166 : *next_offset = offset;
309 :
310 166 : return TRUE;
311 :
312 0 : bail:
313 0 : g_hash_table_unref (attributes);
314 0 : return FALSE;
315 : }
316 :
317 : static gboolean
318 19 : convert_to_integer (const gchar *string, guint32 *result)
319 : {
320 : gchar *end;
321 19 : *result = strtoul (string, &end, 10);
322 19 : return *end == 0;
323 : }
324 :
325 : /* -----------------------------------------------------------------------------
326 : * BINARY ENCRYPTED FILE FORMAT
327 : */
328 :
329 : static gboolean
330 16 : encrypt_buffer (EggBuffer *buffer, GkmSecret *master,
331 : guchar salt[8], int iterations)
332 : {
333 : const gchar *password;
334 : gcry_cipher_hd_t cih;
335 : gcry_error_t gerr;
336 : guchar *key, *iv;
337 : gsize n_password;
338 : size_t pos;
339 :
340 16 : g_assert (buffer->len % 16 == 0);
341 16 : g_assert (16 == gcry_cipher_get_algo_blklen (GCRY_CIPHER_AES128));
342 16 : g_assert (16 == gcry_cipher_get_algo_keylen (GCRY_CIPHER_AES128));
343 :
344 16 : password = gkm_secret_get_password (master, &n_password);
345 16 : if (!egg_symkey_generate_simple (GCRY_CIPHER_AES128, GCRY_MD_SHA256,
346 : password, n_password, salt, 8, iterations, &key, &iv))
347 0 : return FALSE;
348 :
349 16 : gerr = gcry_cipher_open (&cih, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_CBC, 0);
350 16 : if (gerr) {
351 0 : g_warning ("couldn't create aes cipher context: %s",
352 : gcry_strerror (gerr));
353 0 : egg_secure_free (key);
354 0 : g_free (iv);
355 0 : return FALSE;
356 : }
357 :
358 : /* 16 = 128 bits */
359 16 : gerr = gcry_cipher_setkey (cih, key, 16);
360 16 : g_return_val_if_fail (!gerr, FALSE);
361 16 : egg_secure_free (key);
362 :
363 : /* 16 = 128 bits */
364 16 : gerr = gcry_cipher_setiv (cih, iv, 16);
365 16 : g_return_val_if_fail (!gerr, FALSE);
366 16 : g_free (iv);
367 :
368 135 : for (pos = 0; pos < buffer->len; pos += 16) {
369 : /* In place encryption */
370 119 : gerr = gcry_cipher_encrypt (cih, buffer->buf + pos, 16, NULL, 0);
371 119 : g_return_val_if_fail (!gerr, FALSE);
372 : }
373 :
374 16 : gcry_cipher_close (cih);
375 :
376 16 : return TRUE;
377 : }
378 :
379 : static gboolean
380 37 : decrypt_buffer (EggBuffer *buffer, GkmSecret *master,
381 : guchar salt[8], int iterations)
382 : {
383 37 : const gchar *password = NULL;
384 : gcry_cipher_hd_t cih;
385 : gcry_error_t gerr;
386 : guchar *key, *iv;
387 37 : gsize n_password = 0;
388 : size_t pos;
389 :
390 37 : g_assert (buffer->len % 16 == 0);
391 37 : g_assert (16 == gcry_cipher_get_algo_blklen (GCRY_CIPHER_AES128));
392 37 : g_assert (16 == gcry_cipher_get_algo_keylen (GCRY_CIPHER_AES128));
393 :
394 : /* No password is set, try an null password */
395 37 : if (master == NULL) {
396 1 : password = NULL;
397 1 : n_password = 0;
398 : } else {
399 36 : password = gkm_secret_get_password (master, &n_password);
400 : }
401 :
402 37 : if (!egg_symkey_generate_simple (GCRY_CIPHER_AES128, GCRY_MD_SHA256,
403 : password, n_password, salt, 8, iterations, &key, &iv))
404 0 : return FALSE;
405 :
406 37 : gerr = gcry_cipher_open (&cih, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_CBC, 0);
407 37 : if (gerr) {
408 0 : g_warning ("couldn't create aes cipher context: %s",
409 : gcry_strerror (gerr));
410 0 : egg_secure_free (key);
411 0 : g_free (iv);
412 0 : return FALSE;
413 : }
414 :
415 : /* 16 = 128 bits */
416 37 : gerr = gcry_cipher_setkey (cih, key, 16);
417 37 : g_return_val_if_fail (!gerr, FALSE);
418 37 : egg_secure_free (key);
419 :
420 : /* 16 = 128 bits */
421 37 : gerr = gcry_cipher_setiv (cih, iv, 16);
422 37 : g_return_val_if_fail (!gerr, FALSE);
423 37 : g_free (iv);
424 :
425 520 : for (pos = 0; pos < buffer->len; pos += 16) {
426 : /* In place encryption */
427 483 : gerr = gcry_cipher_decrypt (cih, buffer->buf + pos, 16, NULL, 0);
428 483 : g_return_val_if_fail (!gerr, FALSE);
429 : }
430 :
431 37 : gcry_cipher_close (cih);
432 :
433 37 : return TRUE;
434 : }
435 :
436 : static gboolean
437 37 : verify_decrypted_buffer (EggBuffer *buffer)
438 : {
439 : guchar digest[16];
440 :
441 : /* In case the world changes on us... */
442 37 : g_return_val_if_fail (gcry_md_get_algo_dlen (GCRY_MD_MD5) == sizeof (digest), 0);
443 :
444 37 : gcry_md_hash_buffer (GCRY_MD_MD5, (void*)digest,
445 37 : (guchar*)buffer->buf + 16, buffer->len - 16);
446 :
447 37 : return memcmp (buffer->buf, digest, 16) == 0;
448 : }
449 :
450 : static gboolean
451 19 : generate_acl_data (EggBuffer *buffer, GList *acl)
452 : {
453 : GList *l;
454 : GkmSecretAccess *ac;
455 :
456 19 : egg_buffer_add_uint32 (buffer, g_list_length (acl));
457 :
458 19 : for (l = acl; l != NULL; l = l->next) {
459 0 : ac = l->data;
460 :
461 0 : egg_buffer_add_uint32 (buffer, ac->types_allowed);
462 0 : if (!buffer_add_utf8_string (buffer, ac->display_name) ||
463 0 : !buffer_add_utf8_string (buffer, ac->pathname))
464 0 : return FALSE;
465 :
466 : /* Reserved: */
467 0 : if (!buffer_add_utf8_string (buffer, NULL))
468 0 : return FALSE;
469 0 : egg_buffer_add_uint32 (buffer, 0);
470 : }
471 :
472 19 : return TRUE;
473 : }
474 :
475 : static gboolean
476 16 : generate_encrypted_data (EggBuffer *buffer, GkmSecretCollection *collection,
477 : GkmSecretData *data)
478 : {
479 : GkmSecretObject *obj;
480 : GkmSecretItem *item;
481 : GList *items, *l;
482 : GHashTable *attributes;
483 : const gchar *label;
484 : GkmSecret *secret;
485 : GList *acl;
486 : int i;
487 :
488 16 : g_assert (buffer);
489 16 : g_assert (GKM_IS_SECRET_COLLECTION (collection));
490 16 : g_assert (GKM_IS_SECRET_DATA (data));
491 :
492 : /* Make sure we're using non-pageable memory */
493 16 : egg_buffer_set_allocator (buffer, egg_secure_realloc);
494 :
495 16 : items = gkm_secret_collection_get_items (collection);
496 35 : for (l = items; l && !egg_buffer_has_error(buffer); l = g_list_next (l)) {
497 19 : item = GKM_SECRET_ITEM (l->data);
498 19 : obj = GKM_SECRET_OBJECT (l->data);
499 :
500 19 : label = gkm_secret_object_get_label (obj);
501 19 : buffer_add_utf8_string (buffer, label);
502 :
503 19 : secret = gkm_secret_data_get_secret (data, gkm_secret_object_get_identifier (obj));
504 19 : buffer_add_secret (buffer, secret);
505 :
506 38 : if (!buffer_add_time (buffer, gkm_secret_object_get_created (obj)) ||
507 19 : !buffer_add_time (buffer, gkm_secret_object_get_modified (obj)))
508 : break;
509 :
510 : /* reserved: */
511 19 : if (!buffer_add_utf8_string (buffer, NULL))
512 0 : break;
513 95 : for (i = 0; i < 4; i++)
514 76 : egg_buffer_add_uint32 (buffer, 0);
515 :
516 19 : attributes = gkm_secret_item_get_fields (item);
517 19 : if (!buffer_add_attributes (buffer, attributes, FALSE))
518 0 : break;
519 :
520 19 : acl = g_object_get_data (G_OBJECT (item), "compat-acl");
521 19 : if (!generate_acl_data (buffer, acl))
522 0 : break;
523 : }
524 :
525 16 : g_list_free (items);
526 :
527 : /* Iteration completed prematurely == fail */
528 16 : return (l == NULL);
529 : }
530 :
531 : static gboolean
532 16 : generate_hashed_items (GkmSecretCollection *collection, EggBuffer *buffer)
533 : {
534 : GHashTable *attributes;
535 : const gchar *value;
536 : GList *items, *l;
537 : guint32 id, type;
538 :
539 16 : items = gkm_secret_collection_get_items (collection);
540 16 : egg_buffer_add_uint32 (buffer, g_list_length (items));
541 :
542 35 : for (l = items; l; l = g_list_next (l)) {
543 :
544 19 : value = gkm_secret_object_get_identifier (l->data);
545 19 : if (!convert_to_integer (value, &id)) {
546 0 : g_warning ("trying to save a non-numeric item identifier '%s' into "
547 : "the keyring file format which only supports numeric.", value);
548 0 : continue;
549 : }
550 19 : egg_buffer_add_uint32 (buffer, id);
551 :
552 19 : value = gkm_secret_item_get_schema (l->data);
553 19 : type = gkm_secret_compat_parse_item_type (value);
554 19 : egg_buffer_add_uint32 (buffer, type);
555 :
556 19 : attributes = gkm_secret_item_get_fields (l->data);
557 19 : buffer_add_attributes (buffer, attributes, TRUE);
558 : }
559 :
560 16 : g_list_free (items);
561 16 : return !egg_buffer_has_error (buffer);
562 : }
563 :
564 : GkmDataResult
565 16 : gkm_secret_binary_write (GkmSecretCollection *collection, GkmSecretData *sdata,
566 : gpointer *data, gsize *n_data)
567 : {
568 : GkmSecretObject *obj;
569 : EggBuffer to_encrypt;
570 : GkmSecret *master;
571 : guchar digest[16];
572 : EggBuffer buffer;
573 : gint hash_iterations;
574 : gint lock_timeout;
575 : guchar salt[8];
576 16 : guint flags = 0;
577 : int i;
578 :
579 16 : g_return_val_if_fail (GKM_IS_SECRET_COLLECTION (collection), GKM_DATA_FAILURE);
580 16 : g_return_val_if_fail (GKM_IS_SECRET_DATA (sdata), GKM_DATA_LOCKED);
581 16 : g_return_val_if_fail (data && n_data, GKM_DATA_FAILURE);
582 16 : g_return_val_if_fail (gcry_md_get_algo_dlen (GCRY_MD_MD5) == sizeof (digest), GKM_DATA_FAILURE);
583 :
584 16 : obj = GKM_SECRET_OBJECT (collection);
585 :
586 16 : egg_buffer_init_full (&buffer, 256, g_realloc);
587 :
588 : /* Prepare the keyring for encryption */
589 16 : hash_iterations = g_random_int_range (1000, 4096);
590 16 : gcry_create_nonce (salt, sizeof (salt));
591 :
592 16 : egg_buffer_append (&buffer, (guchar*)KEYRING_FILE_HEADER, KEYRING_FILE_HEADER_LEN);
593 16 : egg_buffer_add_byte (&buffer, 0); /* Major version */
594 16 : egg_buffer_add_byte (&buffer, 0); /* Minor version */
595 16 : egg_buffer_add_byte (&buffer, 0); /* crypto (0 == AES) */
596 16 : egg_buffer_add_byte (&buffer, 0); /* hash (0 == MD5) */
597 :
598 16 : buffer_add_utf8_string (&buffer, gkm_secret_object_get_label (obj));
599 16 : buffer_add_time (&buffer, gkm_secret_object_get_modified (obj));
600 16 : buffer_add_time (&buffer, gkm_secret_object_get_created (obj));
601 :
602 16 : lock_timeout = gkm_secret_collection_get_lock_idle (collection);
603 16 : if (lock_timeout) {
604 0 : flags |= LOCK_ON_IDLE_FLAG;
605 : } else {
606 16 : lock_timeout = gkm_secret_collection_get_lock_after (collection);
607 16 : if (lock_timeout)
608 0 : flags |= LOCK_AFTER_FLAG;
609 : }
610 :
611 16 : egg_buffer_add_uint32 (&buffer, flags);
612 :
613 16 : egg_buffer_add_uint32 (&buffer, lock_timeout);
614 16 : egg_buffer_add_uint32 (&buffer, hash_iterations);
615 16 : egg_buffer_append (&buffer, salt, 8);
616 :
617 : /* Reserved: */
618 80 : for (i = 0; i < 4; i++)
619 64 : egg_buffer_add_uint32 (&buffer, 0);
620 :
621 : /* Hashed items: */
622 16 : generate_hashed_items (collection, &buffer);
623 :
624 : /* Encrypted data. Use non-pageable memory */
625 16 : egg_buffer_init_full (&to_encrypt, 4096, egg_secure_realloc);
626 :
627 16 : egg_buffer_append (&to_encrypt, (guchar*)digest, 16); /* Space for hash */
628 :
629 16 : if (!generate_encrypted_data (&to_encrypt, collection, sdata)) {
630 0 : egg_buffer_uninit (&to_encrypt);
631 0 : egg_buffer_uninit (&buffer);
632 0 : return GKM_DATA_FAILURE;
633 : }
634 :
635 : /* Pad with zeros to multiple of 16 bytes */
636 70 : while (to_encrypt.len % 16 != 0)
637 54 : egg_buffer_add_byte (&to_encrypt, 0);
638 :
639 16 : gcry_md_hash_buffer (GCRY_MD_MD5, (void*)digest,
640 16 : (guchar*)to_encrypt.buf + 16, to_encrypt.len - 16);
641 16 : memcpy (to_encrypt.buf, digest, 16);
642 :
643 : /* If no master password is set, we shouldn't be writing binary... */
644 16 : master = gkm_secret_data_get_master (sdata);
645 16 : g_return_val_if_fail (master, GKM_DATA_FAILURE);
646 :
647 16 : if (!encrypt_buffer (&to_encrypt, master, salt, hash_iterations)) {
648 0 : egg_buffer_uninit (&buffer);
649 0 : egg_buffer_uninit (&to_encrypt);
650 0 : return GKM_DATA_FAILURE;
651 : }
652 :
653 16 : if (egg_buffer_has_error (&to_encrypt) || egg_buffer_has_error (&buffer)) {
654 0 : egg_buffer_uninit (&buffer);
655 0 : egg_buffer_uninit (&to_encrypt);
656 0 : return GKM_DATA_FAILURE;
657 : }
658 :
659 16 : egg_buffer_add_uint32 (&buffer, to_encrypt.len);
660 16 : egg_buffer_append (&buffer, to_encrypt.buf, to_encrypt.len);
661 16 : egg_buffer_uninit (&to_encrypt);
662 16 : *data = egg_buffer_uninit_steal (&buffer, n_data);
663 :
664 16 : return GKM_DATA_SUCCESS;
665 : }
666 :
667 : static gboolean
668 41 : decode_acl (EggBuffer *buffer, gsize offset, gsize *offset_out, GList **out)
669 : {
670 : GList *acl;
671 : guint32 num_acs;
672 : guint32 x, y;
673 : int i;
674 : GkmSecretAccess *ac;
675 : char *name, *path, *reserved;
676 :
677 41 : acl = NULL;
678 :
679 41 : if (!egg_buffer_get_uint32 (buffer, offset, &offset, &num_acs))
680 0 : return FALSE;
681 55 : for (i = 0; i < num_acs; i++) {
682 14 : if (!egg_buffer_get_uint32 (buffer, offset, &offset, &x)) {
683 0 : goto bail;
684 : }
685 14 : if (!buffer_get_utf8_string (buffer, offset, &offset, &name)) {
686 0 : goto bail;
687 : }
688 14 : if (!buffer_get_utf8_string (buffer, offset, &offset, &path)) {
689 0 : g_free (name);
690 0 : goto bail;
691 : }
692 14 : reserved = NULL;
693 14 : if (!buffer_get_utf8_string (buffer, offset, &offset, &reserved)) {
694 0 : g_free (name);
695 0 : g_free (path);
696 0 : goto bail;
697 : }
698 14 : g_free (reserved);
699 14 : if (!egg_buffer_get_uint32 (buffer, offset, &offset, &y)) {
700 0 : g_free (name);
701 0 : g_free (path);
702 0 : goto bail;
703 : }
704 :
705 14 : ac = g_new0 (GkmSecretAccess, 1);
706 14 : ac->display_name = name;
707 14 : ac->pathname = path;
708 14 : ac->types_allowed = x;
709 :
710 14 : acl = g_list_prepend (acl, ac);
711 : }
712 :
713 41 : *offset_out = offset;
714 41 : *out = g_list_reverse (acl);
715 41 : return TRUE;
716 :
717 0 : bail:
718 0 : gkm_secret_compat_acl_free (acl);
719 0 : return FALSE;
720 : }
721 :
722 : static void
723 3 : remove_unavailable_item (gpointer key, gpointer dummy, gpointer user_data)
724 : {
725 : /* Called to remove items from a keyring that no longer exist */
726 :
727 3 : GkmSecretCollection *collection = user_data;
728 : GkmSecretItem *item;
729 :
730 3 : g_assert (GKM_IS_SECRET_COLLECTION (collection));
731 :
732 3 : item = gkm_secret_collection_get_item (collection, key);
733 3 : if (item != NULL)
734 3 : gkm_secret_collection_remove_item (collection, item);
735 3 : }
736 :
737 : static void
738 116 : setup_item_from_info (GkmSecretItem *item, GkmSecretData *data, ItemInfo *info)
739 : {
740 116 : GkmSecretObject *obj = GKM_SECRET_OBJECT (item);
741 : const gchar *schema_name;
742 : GkmSecret *secret;
743 :
744 116 : gkm_secret_object_set_label (obj, info->display_name);
745 116 : gkm_secret_object_set_created (obj, info->ctime);
746 116 : gkm_secret_object_set_modified (obj, info->mtime);
747 :
748 116 : schema_name = g_hash_table_lookup (info->attributes, GKM_SECRET_FIELD_SCHEMA);
749 116 : if (schema_name == NULL)
750 115 : schema_name = gkm_secret_compat_format_item_type (info->type);
751 116 : gkm_secret_item_set_schema (item, schema_name);
752 :
753 116 : gkm_secret_item_set_fields (item, info->attributes);
754 :
755 : /* Collection is locked */
756 116 : if (!data) {
757 75 : g_object_set_data (G_OBJECT (item), "compat-acl", NULL);
758 :
759 : } else {
760 41 : secret = gkm_secret_new (info->ptr_secret, info->n_secret);
761 41 : gkm_secret_data_set_secret (data, gkm_secret_object_get_identifier (obj), secret);
762 41 : g_object_unref (secret);
763 41 : g_object_set_data_full (G_OBJECT (item), "compat-acl", info->acl, gkm_secret_compat_acl_free);
764 41 : info->acl = NULL;
765 : }
766 116 : }
767 :
768 : static gboolean
769 86 : read_hashed_item_info (EggBuffer *buffer, gsize *offset, ItemInfo *items, guint n_items)
770 : {
771 : gint i;
772 :
773 86 : g_assert (buffer);
774 86 : g_assert (offset);
775 86 : g_assert (items);
776 :
777 211 : for (i = 0; i < n_items; i++) {
778 250 : if (!egg_buffer_get_uint32 (buffer, *offset, offset, &items[i].id) ||
779 250 : !egg_buffer_get_uint32 (buffer, *offset, offset, &items[i].type) ||
780 125 : !buffer_get_attributes (buffer, *offset, offset, &items[i].attributes, TRUE))
781 0 : return FALSE;
782 125 : items[i].identifier = g_strdup_printf ("%u", items[i].id);
783 : }
784 :
785 86 : return TRUE;
786 : }
787 :
788 : static gboolean
789 31 : read_full_item_info (EggBuffer *buffer, gsize *offset, ItemInfo *items, guint n_items)
790 : {
791 : gchar *reserved;
792 : guint32 tmp;
793 : gint i, j;
794 :
795 31 : g_assert (buffer);
796 31 : g_assert (offset);
797 31 : g_assert (items);
798 :
799 72 : for (i = 0; i < n_items; i++) {
800 :
801 : /* The display name */
802 41 : if (!buffer_get_utf8_string (buffer, *offset, offset,
803 41 : &items[i].display_name))
804 0 : return FALSE;
805 :
806 : /* The secret */
807 41 : if (!egg_buffer_get_byte_array (buffer, *offset, offset,
808 41 : &items[i].ptr_secret, &items[i].n_secret))
809 0 : return FALSE;
810 :
811 : /* The item times */
812 82 : if (!buffer_get_time (buffer, *offset, offset, &items[i].ctime) ||
813 41 : !buffer_get_time (buffer, *offset, offset, &items[i].mtime))
814 0 : return FALSE;
815 :
816 : /* Reserved data */
817 41 : reserved = NULL;
818 41 : if (!buffer_get_utf8_string (buffer, *offset, offset, &reserved))
819 0 : return FALSE;
820 41 : g_free (reserved);
821 205 : for (j = 0; j < 4; j++) {
822 164 : if (!egg_buffer_get_uint32 (buffer, *offset, offset, &tmp))
823 0 : return FALSE;
824 : }
825 :
826 : /* The attributes */
827 41 : if (items[i].attributes)
828 41 : g_hash_table_unref (items[i].attributes);
829 41 : if (!buffer_get_attributes (buffer, *offset, offset, &items[i].attributes, FALSE))
830 0 : return FALSE;
831 :
832 : /* The ACLs */
833 41 : if (!decode_acl (buffer, *offset, offset, &items[i].acl))
834 0 : return FALSE;
835 : }
836 :
837 31 : return TRUE;
838 : }
839 :
840 : static void
841 125 : free_item_info (ItemInfo *info)
842 : {
843 125 : g_free (info->identifier);
844 125 : g_free (info->display_name);
845 125 : g_hash_table_unref (info->attributes);
846 125 : gkm_secret_compat_acl_free (info->acl);
847 125 : }
848 :
849 : GkmDataResult
850 107 : gkm_secret_binary_read (GkmSecretCollection *collection, GkmSecretData *sdata,
851 : gconstpointer data, gsize n_data)
852 : {
853 : gsize offset;
854 : guchar major, minor, crypto, hash;
855 : guint32 flags;
856 : guint32 lock_timeout;
857 : time_t mtime, ctime;
858 : char *display_name;
859 : guint32 tmp;
860 : guint32 num_items;
861 : guint32 crypto_size;
862 : guint32 hash_iterations;
863 : guchar salt[8];
864 : ItemInfo *items;
865 : GkmSecret* master;
866 : GkmSecretObject *obj;
867 107 : EggBuffer to_decrypt = EGG_BUFFER_EMPTY;
868 107 : GkmDataResult res = GKM_DATA_FAILURE;
869 107 : GHashTable *checks = NULL;
870 : GkmSecretItem *item;
871 : EggBuffer buffer;
872 : GList *l, *iteml;
873 : int i;
874 :
875 107 : display_name = NULL;
876 107 : items = 0;
877 107 : obj = GKM_SECRET_OBJECT (collection);
878 :
879 : /* The buffer we read from */
880 107 : egg_buffer_init_static (&buffer, data, n_data);
881 :
882 107 : if (buffer.len < KEYRING_FILE_HEADER_LEN ||
883 107 : memcmp (buffer.buf, KEYRING_FILE_HEADER, KEYRING_FILE_HEADER_LEN) != 0) {
884 21 : egg_buffer_uninit (&buffer);
885 21 : return GKM_DATA_UNRECOGNIZED;
886 : }
887 :
888 86 : offset = KEYRING_FILE_HEADER_LEN;
889 86 : major = buffer.buf[offset++];
890 86 : minor = buffer.buf[offset++];
891 86 : crypto = buffer.buf[offset++];
892 86 : hash = buffer.buf[offset++];
893 :
894 86 : if (major != 0 || minor != 0 || crypto != 0 || hash != 0) {
895 0 : egg_buffer_uninit (&buffer);
896 0 : return GKM_DATA_UNRECOGNIZED;
897 : }
898 :
899 172 : if (!buffer_get_utf8_string (&buffer, offset, &offset, &display_name) ||
900 172 : !buffer_get_time (&buffer, offset, &offset, &ctime) ||
901 172 : !buffer_get_time (&buffer, offset, &offset, &mtime) ||
902 172 : !egg_buffer_get_uint32 (&buffer, offset, &offset, &flags) ||
903 172 : !egg_buffer_get_uint32 (&buffer, offset, &offset, &lock_timeout) ||
904 172 : !egg_buffer_get_uint32 (&buffer, offset, &offset, &hash_iterations) ||
905 86 : !buffer_get_bytes (&buffer, offset, &offset, salt, 8))
906 0 : goto bail;
907 :
908 430 : for (i = 0; i < 4; i++) {
909 344 : if (!egg_buffer_get_uint32 (&buffer, offset, &offset, &tmp))
910 0 : goto bail;
911 : }
912 :
913 86 : if (!egg_buffer_get_uint32 (&buffer, offset, &offset, &num_items))
914 0 : goto bail;
915 :
916 86 : items = g_new0 (ItemInfo, num_items + 1);
917 :
918 : /* Hashed data, without secrets */
919 86 : if (!read_hashed_item_info (&buffer, &offset, items, num_items))
920 0 : goto bail;
921 :
922 86 : if (!egg_buffer_get_uint32 (&buffer, offset, &offset, &crypto_size))
923 0 : goto bail;
924 :
925 : /* Make the crypted part is the right size */
926 86 : if (crypto_size % 16 != 0)
927 0 : goto bail;
928 :
929 : /* Ensure the file is large enough to hold all the data (in case it got truncated) */
930 86 : if (buffer.len < offset + crypto_size)
931 0 : goto bail;
932 :
933 : /* Copy the data into to_decrypt into non-pageable memory */
934 86 : egg_buffer_set_allocator (&to_decrypt, egg_secure_realloc);
935 86 : egg_buffer_reserve (&to_decrypt, crypto_size);
936 86 : memcpy (to_decrypt.buf, buffer.buf + offset, crypto_size);
937 86 : to_decrypt.len = crypto_size;
938 :
939 86 : if (sdata != NULL) {
940 37 : master = gkm_secret_data_get_master (sdata);
941 37 : if (!decrypt_buffer (&to_decrypt, master, salt, hash_iterations))
942 0 : goto bail;
943 37 : if (!verify_decrypted_buffer (&to_decrypt)) {
944 6 : res = GKM_DATA_LOCKED;
945 6 : goto bail;
946 : } else {
947 31 : offset = 16; /* Skip hash */
948 31 : if (!read_full_item_info (&to_decrypt, &offset, items, num_items))
949 0 : goto bail;
950 : }
951 : }
952 :
953 : /* Correctly read all data, possibly including the decrypted data.
954 : * Now update the keyring and items: */
955 :
956 80 : gkm_secret_object_set_label (obj, display_name);
957 80 : gkm_secret_object_set_modified (obj, mtime);
958 80 : gkm_secret_object_set_created (obj, ctime);
959 80 : if (flags & LOCK_ON_IDLE_FLAG)
960 0 : gkm_secret_collection_set_lock_idle (collection, lock_timeout);
961 80 : else if (flags & LOCK_AFTER_FLAG)
962 0 : gkm_secret_collection_set_lock_after (collection, lock_timeout);
963 :
964 : /* Build a Hash table where we can track ids we haven't yet seen */
965 80 : checks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
966 80 : iteml = gkm_secret_collection_get_items (collection);
967 113 : for (l = iteml; l; l = g_list_next (l))
968 66 : g_hash_table_insert (checks, g_strdup (gkm_secret_object_get_identifier (l->data)), "unused");
969 80 : g_list_free (iteml);
970 :
971 196 : for (i = 0; i < num_items; i++) {
972 :
973 : /* We've seen this id */
974 116 : g_hash_table_remove (checks, items[i].identifier);
975 :
976 116 : item = gkm_secret_collection_get_item (collection, items[i].identifier);
977 116 : if (item == NULL)
978 86 : item = gkm_secret_collection_new_item (collection, items[i].identifier);
979 :
980 116 : setup_item_from_info (item, sdata, &items[i]);
981 : }
982 :
983 80 : g_hash_table_foreach (checks, remove_unavailable_item, collection);
984 80 : res = GKM_DATA_SUCCESS;
985 :
986 86 : bail:
987 86 : egg_buffer_uninit (&to_decrypt);
988 86 : if (checks)
989 80 : g_hash_table_destroy (checks);
990 86 : g_free (display_name);
991 :
992 211 : for (i = 0; items && i < num_items; i++)
993 125 : free_item_info (&items[i]);
994 86 : g_free (items);
995 :
996 86 : return res;
997 : }
|