Line data Source code
1 : /*
2 : * gnome-keyring
3 : *
4 : * Copyright (C) 2010 Stefan Walter
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 "gkm-xdg-assertion.h"
24 : #include "gkm-xdg-module.h"
25 : #include "gkm-xdg-store.h"
26 : #include "gkm-xdg-trust.h"
27 :
28 : #include "egg/egg-asn1x.h"
29 : #include "egg/egg-asn1-defs.h"
30 : #include "egg/egg-dn.h"
31 : #include "egg/egg-error.h"
32 : #include "egg/egg-file-tracker.h"
33 : #include "egg/egg-hex.h"
34 :
35 : #include "gkm/gkm-assertion.h"
36 : #include "gkm/gkm-certificate.h"
37 : #define DEBUG_FLAG GKM_DEBUG_STORAGE
38 : #include "gkm/gkm-debug.h"
39 : #include "gkm/gkm-serializable.h"
40 : #include "gkm/gkm-transaction.h"
41 : #include "gkm/gkm-util.h"
42 :
43 : #include "pkcs11x.h"
44 :
45 : #include <string.h>
46 :
47 : struct _GkmXdgModule {
48 : GkmModule parent;
49 : gchar *directory;
50 : GHashTable *objects_by_path;
51 : EggFileTracker *tracker;
52 : CK_TOKEN_INFO token_info;
53 : };
54 :
55 : static const CK_SLOT_INFO user_module_slot_info = {
56 : "User Key Storage",
57 : "Gnome Keyring",
58 : CKF_TOKEN_PRESENT,
59 : { 0, 0 },
60 : { 0, 0 }
61 : };
62 :
63 : static const CK_TOKEN_INFO user_module_token_info = {
64 : "User Key Storage",
65 : "Gnome Keyring",
66 : "1.0",
67 : "1:XDG:DEFAULT", /* Unique serial number for manufacturer */
68 : CKF_TOKEN_INITIALIZED,
69 : CK_EFFECTIVELY_INFINITE,
70 : CK_EFFECTIVELY_INFINITE,
71 : CK_EFFECTIVELY_INFINITE,
72 : CK_EFFECTIVELY_INFINITE,
73 : 1024,
74 : 1,
75 : CK_UNAVAILABLE_INFORMATION,
76 : CK_UNAVAILABLE_INFORMATION,
77 : CK_UNAVAILABLE_INFORMATION,
78 : CK_UNAVAILABLE_INFORMATION,
79 : { 0, 0 },
80 : { 0, 0 },
81 : ""
82 : };
83 :
84 : #define UNUSED_VALUE (GUINT_TO_POINTER (1))
85 :
86 : #define UNWANTED_FILENAME_CHARS ":/\\<>|\t\n\r\v "
87 :
88 513 : G_DEFINE_TYPE (GkmXdgModule, gkm_xdg_module, GKM_TYPE_MODULE);
89 :
90 : GkmModule* _gkm_xdg_store_get_module_for_testing (void);
91 :
92 : /* Forward declarations */
93 : static void remove_object_from_module (GkmXdgModule *self, GkmObject *object,
94 : const gchar *filename, GkmTransaction *transaction);
95 : static void add_object_to_module (GkmXdgModule *self, GkmObject *object,
96 : const gchar *filename, GkmTransaction *transaction);
97 :
98 : /* -----------------------------------------------------------------------------
99 : * ACTUAL PKCS#11 Module Implementation
100 : */
101 :
102 : /* Include all the module entry points */
103 : #include "gkm/gkm-module-ep.h"
104 59 : GKM_DEFINE_MODULE (gkm_xdg_module, GKM_TYPE_XDG_MODULE);
105 :
106 : /* -----------------------------------------------------------------------------
107 : * INTERNAL
108 : */
109 :
110 : static GType
111 52 : type_from_path (const gchar *path)
112 : {
113 : const gchar *ext;
114 :
115 52 : ext = strrchr (path, '.');
116 :
117 : /* The file tracker doesn't match files without exts */
118 52 : g_return_val_if_fail (ext, 0);
119 :
120 52 : if (g_str_equal (ext, ".trust"))
121 24 : return GKM_XDG_TYPE_TRUST;
122 28 : else if (strcmp (ext, ".cer") == 0)
123 12 : return GKM_TYPE_CERTIFICATE;
124 :
125 16 : return 0;
126 : }
127 :
128 : static const gchar*
129 36 : lookup_filename_for_object (GkmObject *object)
130 : {
131 36 : return g_object_get_data (G_OBJECT (object), "xdg-module-filename");
132 : }
133 :
134 : static gboolean
135 2 : complete_add_object (GkmTransaction *transaction, GObject *module, gpointer user_data)
136 : {
137 2 : GkmXdgModule *self = GKM_XDG_MODULE (module);
138 2 : GkmObject *object = GKM_OBJECT (user_data);
139 : const gchar *filename;
140 :
141 : /* If the transaction failed, revert it */
142 2 : if (gkm_transaction_get_failed (transaction)) {
143 0 : filename = g_object_get_data (G_OBJECT (object), "xdg-module-filename");
144 0 : g_return_val_if_fail (filename, FALSE);
145 0 : remove_object_from_module (self, object, filename, NULL);
146 : }
147 :
148 2 : g_object_unref (object);
149 2 : return TRUE;
150 : }
151 :
152 : static void
153 26 : add_object_to_module (GkmXdgModule *self, GkmObject *object,
154 : const gchar *filename, GkmTransaction *transaction)
155 : {
156 26 : g_assert (!g_hash_table_lookup (self->objects_by_path, filename));
157 52 : g_hash_table_insert (self->objects_by_path, g_strdup (filename), g_object_ref (object));
158 :
159 26 : g_assert (!lookup_filename_for_object (object));
160 26 : g_object_set_data_full (G_OBJECT (object), "xdg-module-filename",
161 26 : g_strdup (filename), g_free);
162 :
163 26 : gkm_object_expose (object, TRUE);
164 :
165 26 : if (transaction != NULL)
166 2 : gkm_transaction_add (transaction, self, complete_add_object, g_object_ref (object));
167 26 : }
168 :
169 : static gboolean
170 2 : complete_remove_object (GkmTransaction *transaction, GObject *module, gpointer user_data)
171 : {
172 2 : GkmXdgModule *self = GKM_XDG_MODULE (module);
173 2 : GkmObject *object = GKM_OBJECT (user_data);
174 : const gchar *filename;
175 :
176 : /* If the transaction failed, revert it */
177 2 : if (gkm_transaction_get_failed (transaction)) {
178 0 : filename = g_object_get_data (G_OBJECT (object), "xdg-module-filename");
179 0 : g_return_val_if_fail (filename, FALSE);
180 0 : add_object_to_module (self, object, filename, NULL);
181 : }
182 :
183 2 : g_object_unref (object);
184 2 : return TRUE;
185 : }
186 :
187 : static void
188 4 : remove_object_from_module (GkmXdgModule *self, GkmObject *object,
189 : const gchar *filename, GkmTransaction *transaction)
190 : {
191 4 : gkm_object_expose (object, FALSE);
192 :
193 4 : if (transaction != NULL)
194 2 : gkm_transaction_add (transaction, self, complete_remove_object, g_object_ref (object));
195 :
196 4 : g_assert (g_hash_table_lookup (self->objects_by_path, filename) == object);
197 4 : g_hash_table_remove (self->objects_by_path, filename);
198 4 : }
199 :
200 : static void
201 54 : file_load (EggFileTracker *tracker,
202 : const gchar *path,
203 : GkmXdgModule *self)
204 : {
205 : GkmObject *object;
206 : GkmManager *manager;
207 54 : gboolean added = FALSE;
208 54 : GError *error = NULL;
209 : GBytes *bytes;
210 : GType type;
211 : guchar *data;
212 : gsize n_data;
213 :
214 70 : g_return_if_fail (path);
215 54 : g_return_if_fail (GKM_IS_XDG_MODULE (self));
216 :
217 54 : manager = gkm_module_get_manager (GKM_MODULE (self));
218 :
219 : /* Already have this object? */
220 54 : object = g_hash_table_lookup (self->objects_by_path, path);
221 54 : if (object == NULL) {
222 :
223 : /* Figure out what type of object we're dealing with */
224 52 : type = type_from_path (path);
225 52 : if (type == 0) {
226 16 : gkm_debug ("don't know how to load file in key store: %s", path);
227 16 : return;
228 : }
229 :
230 : /* Create a new object for this identifier */
231 36 : object = g_object_new (type,
232 36 : "module", GKM_MODULE (self),
233 : "manager", manager, NULL);
234 36 : g_return_if_fail (GKM_IS_SERIALIZABLE (object));
235 36 : g_return_if_fail (GKM_SERIALIZABLE_GET_INTERFACE (object)->extension);
236 :
237 36 : added = TRUE;
238 :
239 : } else {
240 2 : g_object_ref (object);
241 : }
242 :
243 : /* Read the file in */
244 38 : if (!g_file_get_contents (path, (gchar**)&data, &n_data, &error)) {
245 0 : g_warning ("couldn't read file in key store: %s: %s", path,
246 : egg_error_message (error));
247 0 : g_object_unref (object);
248 0 : g_clear_error (&error);
249 0 : return;
250 : }
251 :
252 38 : bytes = g_bytes_new_take (data, n_data);
253 :
254 : /* And load the data into it */
255 38 : if (gkm_serializable_load (GKM_SERIALIZABLE (object), NULL, bytes)) {
256 25 : if (added)
257 24 : add_object_to_module (self, object, path, NULL);
258 25 : gkm_object_expose (object, TRUE);
259 :
260 : } else {
261 13 : g_message ("failed to load file in user store: %s", path);
262 13 : if (!added) {
263 1 : gkm_object_expose (object, FALSE);
264 1 : remove_object_from_module (self, object, path, NULL);
265 : }
266 : }
267 :
268 38 : g_bytes_unref (bytes);
269 38 : g_object_unref (object);
270 : }
271 :
272 : static void
273 2 : file_remove (EggFileTracker *tracker,
274 : const gchar *path,
275 : GkmXdgModule *self)
276 : {
277 : GkmObject *object;
278 :
279 2 : g_return_if_fail (path);
280 2 : g_return_if_fail (GKM_IS_XDG_MODULE (self));
281 :
282 2 : object = g_hash_table_lookup (self->objects_by_path, path);
283 2 : if (object != NULL)
284 1 : remove_object_from_module (self, object, path, NULL);
285 : }
286 :
287 : static gchar*
288 1 : name_for_subject (gconstpointer data,
289 : gsize n_data)
290 : {
291 : GBytes *subject;
292 : GNode *asn;
293 : gchar *name;
294 :
295 1 : g_assert (data != NULL);
296 :
297 1 : subject = g_bytes_new (data, n_data);
298 1 : asn = egg_asn1x_create_and_decode (pkix_asn1_tab, "Name", subject);
299 1 : g_return_val_if_fail (asn != NULL, NULL);
300 1 : g_bytes_unref (subject);
301 :
302 1 : name = egg_dn_read_part (egg_asn1x_node (asn, "rdnSequence", NULL), "CN");
303 1 : egg_asn1x_destroy (asn);
304 :
305 1 : return name;
306 : }
307 :
308 : static gchar*
309 2 : guess_basename_for_object (GkmObject *object)
310 : {
311 : GkmSerializableIface *serial;
312 : const gchar *ext;
313 : gchar *filename;
314 2 : gchar *name = NULL;
315 : guchar *data;
316 : gsize n_data;
317 :
318 2 : g_assert (GKM_IS_OBJECT (object));
319 2 : g_assert (GKM_IS_SERIALIZABLE (object));
320 :
321 : /* Figure out the extension and prefix */
322 2 : serial = GKM_SERIALIZABLE_GET_INTERFACE (object);
323 2 : ext = serial->extension;
324 2 : g_return_val_if_fail (ext, NULL);
325 :
326 : /* First we try to use the CN of a subject */
327 2 : data = gkm_object_get_attribute_data (object, NULL, CKA_SUBJECT, &n_data);
328 2 : if (data && n_data)
329 1 : name = name_for_subject (data, n_data);
330 2 : g_free (data);
331 :
332 : /* Next we try and see if it has a peer (is a trust assertion) */
333 2 : if (name == NULL) {
334 1 : data = gkm_object_get_attribute_data (object, NULL, CKA_X_PEER, &n_data);
335 1 : if (data && n_data)
336 0 : name = g_strndup ((gchar *)data, n_data);
337 1 : g_free (data);
338 : }
339 :
340 : /* Next we try hex encoding the ID */
341 2 : if (name == NULL) {
342 1 : data = gkm_object_get_attribute_data (object, NULL, CKA_ID, &n_data);
343 1 : if (data && n_data)
344 0 : name = egg_hex_encode (data, n_data);
345 1 : g_free (data);
346 : }
347 :
348 2 : if (name == NULL)
349 1 : name = g_strdup_printf ("object-%08x", ABS (g_random_int ()));
350 :
351 : /* Build up the identifier */
352 2 : filename = g_strconcat (name, ext, NULL);
353 2 : g_strdelimit (filename, UNWANTED_FILENAME_CHARS, '_');
354 :
355 2 : g_free (name);
356 2 : return filename;
357 : }
358 :
359 : /* -----------------------------------------------------------------------------
360 : * OBJECT
361 : */
362 :
363 : static const CK_SLOT_INFO*
364 67 : gkm_xdg_module_real_get_slot_info (GkmModule *base)
365 : {
366 67 : return &user_module_slot_info;
367 : }
368 :
369 : static const CK_TOKEN_INFO*
370 9 : gkm_xdg_module_real_get_token_info (GkmModule *base)
371 : {
372 9 : GkmXdgModule *self = GKM_XDG_MODULE (base);
373 :
374 : /* TODO: Update the info with current info */
375 9 : return &self->token_info;
376 : }
377 :
378 : static void
379 59 : gkm_xdg_module_real_parse_argument (GkmModule *base, const gchar *name, const gchar *value)
380 : {
381 59 : GkmXdgModule *self = GKM_XDG_MODULE (base);
382 59 : if (g_str_equal (name, "directory")) {
383 59 : g_free (self->directory);
384 59 : self->directory = g_strdup (value);
385 : }
386 59 : }
387 :
388 : static CK_RV
389 22 : gkm_xdg_module_real_refresh_token (GkmModule *base)
390 : {
391 22 : GkmXdgModule *self = GKM_XDG_MODULE (base);
392 22 : egg_file_tracker_refresh (self->tracker, FALSE);
393 22 : return CKR_OK;
394 : }
395 :
396 : static void
397 4 : gkm_xdg_module_real_add_token_object (GkmModule *module, GkmTransaction *transaction,
398 : GkmObject *object)
399 : {
400 4 : gchar *filename = NULL;
401 : GkmXdgModule *self;
402 : GkmTrust *trust;
403 : gchar *basename;
404 : gchar *actual;
405 :
406 4 : self = GKM_XDG_MODULE (module);
407 :
408 : /* Always serialize the trust object for each assertion */
409 4 : if (GKM_XDG_IS_ASSERTION (object)) {
410 2 : trust = gkm_assertion_get_trust_object (GKM_ASSERTION (object));
411 2 : object = GKM_OBJECT (trust);
412 :
413 : /* If this trust object has already been added, then ignore */
414 2 : if (lookup_filename_for_object (object))
415 2 : return;
416 : }
417 :
418 : /* Double check that the object is in fact serializable */
419 2 : if (!GKM_IS_SERIALIZABLE (object)) {
420 0 : g_message ("can't store object of type '%s' on token", G_OBJECT_TYPE_NAME (object));
421 0 : gkm_transaction_fail (transaction, CKR_TEMPLATE_INCONSISTENT);
422 0 : return;
423 : }
424 :
425 2 : g_return_if_fail (lookup_filename_for_object (object) == NULL);
426 :
427 2 : basename = guess_basename_for_object (object);
428 2 : g_return_if_fail (basename);
429 :
430 2 : actual = gkm_transaction_unique_file (transaction, self->directory, basename);
431 2 : if (!gkm_transaction_get_failed (transaction)) {
432 2 : filename = g_build_filename (self->directory, actual, NULL);
433 2 : add_object_to_module (self, object, filename, transaction);
434 2 : g_free (filename);
435 : }
436 :
437 2 : g_free (actual);
438 2 : g_free (basename);
439 : }
440 :
441 : static void
442 4 : gkm_xdg_module_real_store_token_object (GkmModule *module, GkmTransaction *transaction,
443 : GkmObject *object)
444 : {
445 4 : GkmXdgModule *self = GKM_XDG_MODULE (module);
446 : const gchar *filename;
447 : GBytes *bytes;
448 : GkmTrust *trust;
449 :
450 : /* Always serialize the trust object for each assertion */
451 4 : if (GKM_XDG_IS_ASSERTION (object)) {
452 2 : trust = gkm_assertion_get_trust_object (GKM_ASSERTION (object));
453 2 : object = GKM_OBJECT (trust);
454 : }
455 :
456 : /* Double check that the object is in fact serializable */
457 4 : if (!GKM_IS_SERIALIZABLE (object)) {
458 0 : g_message ("can't store object of type '%s' on token", G_OBJECT_TYPE_NAME (object));
459 0 : gkm_transaction_fail (transaction, CKR_TEMPLATE_INCONSISTENT);
460 0 : return;
461 : }
462 :
463 : /* Serialize the object in question */
464 4 : bytes = gkm_serializable_save (GKM_SERIALIZABLE (object), NULL);
465 :
466 4 : if (bytes == NULL) {
467 0 : gkm_transaction_fail (transaction, CKR_FUNCTION_FAILED);
468 0 : g_return_if_reached ();
469 : }
470 :
471 4 : filename = lookup_filename_for_object (object);
472 4 : g_return_if_fail (filename != NULL);
473 4 : g_return_if_fail (g_hash_table_lookup (self->objects_by_path, filename) == object);
474 :
475 4 : gkm_transaction_write_file (transaction, filename,
476 : g_bytes_get_data (bytes, NULL),
477 : g_bytes_get_size (bytes));
478 4 : g_bytes_unref (bytes);
479 : }
480 :
481 : static void
482 2 : gkm_xdg_module_real_remove_token_object (GkmModule *module, GkmTransaction *transaction,
483 : GkmObject *object)
484 : {
485 2 : GkmXdgModule *self = GKM_XDG_MODULE (module);
486 : const gchar *filename;
487 : GkmXdgTrust *trust;
488 :
489 : /* Always serialize the trust object for each assertion */
490 2 : if (GKM_XDG_IS_ASSERTION (object)) {
491 1 : trust = GKM_XDG_TRUST (gkm_assertion_get_trust_object (GKM_ASSERTION (object)));
492 1 : gkm_xdg_trust_remove_assertion (trust, GKM_ASSERTION (object), transaction);
493 :
494 : /* Remove the trust object if it has no assertions */
495 1 : if (!gkm_xdg_trust_have_assertion (trust))
496 1 : object = GKM_OBJECT (trust);
497 : else
498 0 : object = NULL;
499 : }
500 :
501 2 : if (object && !gkm_transaction_get_failed (transaction)) {
502 2 : filename = lookup_filename_for_object (object);
503 2 : g_return_if_fail (filename != NULL);
504 2 : g_return_if_fail (g_hash_table_lookup (self->objects_by_path, filename) == object);
505 :
506 2 : gkm_transaction_remove_file (transaction, filename);
507 2 : remove_object_from_module (self, object, filename, transaction);
508 : }
509 : }
510 :
511 : static GObject*
512 59 : gkm_xdg_module_constructor (GType type, guint n_props, GObjectConstructParam *props)
513 : {
514 59 : GkmXdgModule *self = GKM_XDG_MODULE (G_OBJECT_CLASS (gkm_xdg_module_parent_class)->constructor(type, n_props, props));
515 59 : g_return_val_if_fail (self, NULL);
516 :
517 59 : if (!self->directory)
518 0 : self->directory = g_build_filename (g_get_user_data_dir (), "keystore", NULL);
519 :
520 59 : self->tracker = egg_file_tracker_new (self->directory, "*.*", NULL);
521 59 : g_signal_connect (self->tracker, "file-added", G_CALLBACK (file_load), self);
522 59 : g_signal_connect (self->tracker, "file-changed", G_CALLBACK (file_load), self);
523 59 : g_signal_connect (self->tracker, "file-removed", G_CALLBACK (file_remove), self);
524 :
525 59 : return G_OBJECT (self);
526 : }
527 :
528 : static void
529 59 : gkm_xdg_module_init (GkmXdgModule *self)
530 : {
531 59 : self->objects_by_path = g_hash_table_new_full (g_str_hash, g_str_equal,
532 : g_free, gkm_util_dispose_unref);
533 :
534 : /* Our default token info, updated as module runs */
535 59 : memcpy (&self->token_info, &user_module_token_info, sizeof (CK_TOKEN_INFO));
536 :
537 : /* For creating stored objects */
538 59 : gkm_module_register_factory (GKM_MODULE (self), GKM_XDG_FACTORY_ASSERTION);
539 59 : gkm_module_register_factory (GKM_MODULE (self), GKM_FACTORY_CERTIFICATE);
540 59 : }
541 :
542 : static void
543 118 : gkm_xdg_module_dispose (GObject *obj)
544 : {
545 118 : GkmXdgModule *self = GKM_XDG_MODULE (obj);
546 :
547 118 : if (self->tracker)
548 59 : g_object_unref (self->tracker);
549 118 : self->tracker = NULL;
550 :
551 118 : g_hash_table_remove_all (self->objects_by_path);
552 :
553 118 : G_OBJECT_CLASS (gkm_xdg_module_parent_class)->dispose (obj);
554 118 : }
555 :
556 : static void
557 59 : gkm_xdg_module_finalize (GObject *obj)
558 : {
559 59 : GkmXdgModule *self = GKM_XDG_MODULE (obj);
560 :
561 59 : g_assert (self->tracker == NULL);
562 :
563 59 : g_hash_table_destroy (self->objects_by_path);
564 59 : self->objects_by_path = NULL;
565 :
566 59 : g_free (self->directory);
567 59 : self->directory = NULL;
568 :
569 59 : G_OBJECT_CLASS (gkm_xdg_module_parent_class)->finalize (obj);
570 59 : }
571 :
572 : static void
573 29 : gkm_xdg_module_class_init (GkmXdgModuleClass *klass)
574 : {
575 29 : GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
576 29 : GkmModuleClass *module_class = GKM_MODULE_CLASS (klass);
577 :
578 29 : gobject_class->constructor = gkm_xdg_module_constructor;
579 29 : gobject_class->dispose = gkm_xdg_module_dispose;
580 29 : gobject_class->finalize = gkm_xdg_module_finalize;
581 :
582 29 : module_class->get_slot_info = gkm_xdg_module_real_get_slot_info;
583 29 : module_class->get_token_info = gkm_xdg_module_real_get_token_info;
584 29 : module_class->parse_argument = gkm_xdg_module_real_parse_argument;
585 29 : module_class->refresh_token = gkm_xdg_module_real_refresh_token;
586 29 : module_class->add_token_object = gkm_xdg_module_real_add_token_object;
587 29 : module_class->store_token_object = gkm_xdg_module_real_store_token_object;
588 29 : module_class->remove_token_object = gkm_xdg_module_real_remove_token_object;
589 29 : }
590 :
591 : /* ----------------------------------------------------------------------------
592 : * PUBLIC
593 : */
594 :
595 : CK_FUNCTION_LIST_PTR
596 91 : gkm_xdg_store_get_functions (void)
597 : {
598 91 : gkm_crypto_initialize ();
599 91 : return gkm_xdg_module_function_list;
600 : }
601 :
602 : GkmModule*
603 64 : _gkm_xdg_store_get_module_for_testing (void)
604 : {
605 64 : return pkcs11_module;
606 : }
|