Line data Source code
1 : /*
2 : * gnome-keyring
3 : *
4 : * Copyright (C) 2009 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-secret-fields.h"
24 :
25 : #include "egg/egg-hex.h"
26 :
27 : #include "gkm/gkm-attributes.h"
28 :
29 : #include <ctype.h>
30 : #include <string.h>
31 :
32 : static gboolean
33 2815 : begins_with (const gchar *string, const gchar *prefix)
34 : {
35 2815 : gsize len = strlen (prefix);
36 2815 : return (strncmp (string, prefix, len) == 0);
37 : }
38 :
39 : static gboolean
40 2815 : is_compat_name (const gchar *name)
41 : {
42 2815 : g_assert (name);
43 2815 : return begins_with (name, "gkr:compat:");
44 : }
45 :
46 : static gchar*
47 238 : make_compat_hashed_name (const gchar *name)
48 : {
49 238 : g_assert (!is_compat_name (name));
50 238 : return g_strdup_printf ("gkr:compat:hashed:%s", name);
51 : }
52 :
53 : static gchar*
54 145 : make_compat_uint32_name (const gchar *name)
55 : {
56 145 : g_assert (!is_compat_name (name));
57 145 : return g_strdup_printf ("gkr:compat:uint32:%s", name);
58 : }
59 :
60 : static gboolean
61 2047 : string_ptr_equal (const gchar *one, const gchar *two)
62 : {
63 2047 : if (one == two)
64 0 : return TRUE;
65 2047 : if (!one || !two)
66 14 : return FALSE;
67 2033 : return g_str_equal (one, two);
68 : }
69 :
70 : static gint
71 47 : string_ptr_compare (gconstpointer one, gconstpointer two)
72 : {
73 47 : if (one == two)
74 0 : return 0;
75 47 : if (!one || !two)
76 0 : return one < two;
77 47 : return strcmp (one, two);
78 : }
79 :
80 : static gboolean
81 21 : parse_uint32 (const gchar *value, guint32 *result)
82 : {
83 : gchar *end;
84 21 : g_assert (value);
85 21 : g_assert (result);
86 21 : *result = strtoul(value, &end, 10);
87 21 : return (*end == '\0');
88 : }
89 :
90 : static gchar*
91 102 : format_uint32 (guint32 value)
92 : {
93 102 : return g_strdup_printf ("%u", value);
94 : }
95 :
96 : static gboolean
97 6 : compat_hash_value_as_uint32 (const gchar *value, guint32 *hash)
98 : {
99 : guint32 x;
100 :
101 6 : if (!value || !parse_uint32 (value, &x))
102 0 : return FALSE;
103 :
104 : /* The same algorithm as the old keyring code used */
105 6 : *hash = 0x18273645 ^ x ^ (x << 16 | x >> 16);
106 6 : return TRUE;
107 : }
108 :
109 : static gchar*
110 10 : compat_hash_value_as_string (const gchar *value)
111 : {
112 : guchar digest[16];
113 :
114 10 : if (!value)
115 0 : return NULL;
116 :
117 10 : g_assert (gcry_md_get_algo_dlen (GCRY_MD_MD5) == sizeof (digest));
118 10 : gcry_md_hash_buffer (GCRY_MD_MD5, (void*)digest, value, strlen (value));
119 :
120 : /* The old keyring code used lower case hex */
121 10 : return egg_hex_encode_full (digest, sizeof (digest), FALSE, '\0', 0);
122 : }
123 :
124 : GType
125 37 : gkm_secret_fields_boxed_type (void)
126 : {
127 : static gsize type_inited = 0;
128 : static GType type = 0;
129 :
130 37 : if (g_once_init_enter (&type_inited)) {
131 30 : type = g_boxed_type_register_static ("GHashTable_Fields",
132 : (GBoxedCopyFunc)g_hash_table_ref,
133 : (GBoxedFreeFunc)g_hash_table_unref);
134 30 : g_once_init_leave (&type_inited, 1);
135 : }
136 :
137 37 : return type;
138 : }
139 :
140 : GHashTable*
141 2301 : gkm_secret_fields_new (void)
142 : {
143 2301 : return g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
144 : }
145 :
146 : CK_RV
147 32 : gkm_secret_fields_parse (CK_ATTRIBUTE_PTR attr,
148 : GHashTable **fields,
149 : gchar **schema_name)
150 : {
151 : GHashTable *result;
152 : const gchar *name;
153 : gsize n_name;
154 : const gchar *value;
155 : gsize n_value;
156 : const gchar *ptr;
157 : const gchar *last;
158 :
159 32 : g_assert (attr);
160 32 : g_assert (fields);
161 :
162 32 : ptr = attr->pValue;
163 32 : last = ptr + attr->ulValueLen;
164 :
165 32 : if (!ptr && last != ptr)
166 1 : return CKR_ATTRIBUTE_VALUE_INVALID;
167 :
168 31 : result = gkm_secret_fields_new ();
169 :
170 62 : while (ptr && ptr != last) {
171 35 : g_assert (ptr < last);
172 :
173 35 : name = ptr;
174 35 : ptr = memchr (ptr, 0, last - ptr);
175 :
176 : /* No value is present? */
177 35 : if (!ptr) {
178 2 : g_hash_table_unref (result);
179 2 : return CKR_ATTRIBUTE_VALUE_INVALID;
180 : }
181 :
182 33 : n_name = ptr - name;
183 33 : value = ++ptr;
184 33 : ptr = memchr (ptr, 0, last - ptr);
185 :
186 : /* Missing null terminator on value */
187 33 : if (ptr == NULL) {
188 1 : g_hash_table_unref (result);
189 1 : return CKR_ATTRIBUTE_VALUE_INVALID;
190 : }
191 :
192 32 : n_value = ptr - value;
193 32 : ++ptr;
194 :
195 : /* Validate the name and value*/
196 64 : if (!g_utf8_validate (name, n_name, NULL) ||
197 32 : !g_utf8_validate (value, n_value, NULL)) {
198 1 : g_hash_table_unref (result);
199 1 : return CKR_ATTRIBUTE_VALUE_INVALID;
200 : }
201 :
202 31 : g_hash_table_replace (result, g_strndup (name, n_name), g_strndup (value, n_value));
203 : }
204 :
205 27 : if (schema_name)
206 52 : *schema_name = g_strdup (g_hash_table_lookup (result, GKM_SECRET_FIELD_SCHEMA));
207 :
208 27 : *fields = result;
209 27 : return CKR_OK;
210 : }
211 :
212 : CK_RV
213 13 : gkm_secret_fields_serialize (CK_ATTRIBUTE_PTR attr,
214 : GHashTable *fields,
215 : const gchar *schema_name)
216 : {
217 : GList *l, *keys;
218 13 : gboolean saw_schema = FALSE;
219 : const gchar *key;
220 : gpointer value;
221 : GString *result;
222 : CK_RV rv;
223 :
224 13 : g_assert (attr != NULL);
225 13 : g_assert (fields != NULL);
226 :
227 13 : keys = g_hash_table_get_keys (fields);
228 13 : keys = g_list_sort (keys, (GCompareFunc) g_strcmp0);
229 :
230 13 : if (!attr->pValue) {
231 3 : attr->ulValueLen = 0;
232 6 : for (l = keys; l != NULL; l = l->next) {
233 3 : key = (const gchar *) l->data;
234 3 : value = g_hash_table_lookup (fields, key);
235 :
236 3 : if (g_str_equal (key, GKM_SECRET_FIELD_SCHEMA))
237 0 : saw_schema = TRUE;
238 3 : attr->ulValueLen += strlen (key);
239 3 : attr->ulValueLen += strlen (value);
240 3 : attr->ulValueLen += 2;
241 : }
242 3 : if (schema_name && !saw_schema) {
243 0 : attr->ulValueLen += strlen (GKM_SECRET_FIELD_SCHEMA);
244 0 : attr->ulValueLen += strlen (schema_name);
245 0 : attr->ulValueLen += 2;
246 : }
247 3 : g_list_free (keys);
248 3 : return CKR_OK;
249 : }
250 :
251 10 : result = g_string_sized_new (256);
252 21 : for (l = keys; l != NULL; l = l->next) {
253 11 : key = (const gchar *) l->data;
254 11 : value = g_hash_table_lookup (fields, key);
255 11 : if (g_str_equal (key, GKM_SECRET_FIELD_SCHEMA))
256 1 : saw_schema = TRUE;
257 : g_string_append (result, key);
258 : g_string_append_c (result, '\0');
259 : g_string_append (result, value);
260 : g_string_append_c (result, '\0');
261 : }
262 10 : if (schema_name && !saw_schema) {
263 2 : g_string_append (result, GKM_SECRET_FIELD_SCHEMA);
264 : g_string_append_c (result, '\0');
265 : g_string_append (result, schema_name);
266 : g_string_append_c (result, '\0');
267 : }
268 :
269 10 : rv = gkm_attribute_set_data (attr, result->str, result->len);
270 10 : g_string_free (result, TRUE);
271 10 : g_list_free (keys);
272 :
273 10 : return rv;
274 : }
275 :
276 : gboolean
277 2038 : gkm_secret_fields_match_one (GHashTable *haystack,
278 : const gchar *needle_key,
279 : const gchar *needle_value)
280 : {
281 : const gchar *hay;
282 : gchar *other_key, *hashed;
283 : guint32 number;
284 : gboolean match;
285 :
286 2038 : g_return_val_if_fail (haystack != NULL, FALSE);
287 2038 : g_return_val_if_fail (needle_key != NULL, FALSE);
288 2038 : g_return_val_if_fail (needle_value != NULL, FALSE);
289 :
290 : /* Compat attributes in the needle make no difference */
291 2038 : if (is_compat_name (needle_key))
292 0 : return TRUE;
293 :
294 : /* A direct match? */
295 2038 : if (g_hash_table_lookup_extended (haystack, needle_key, NULL, (gpointer*)&hay))
296 2010 : return string_ptr_equal (hay, needle_value);
297 :
298 : /* Try to find a hashed value? */
299 28 : other_key = make_compat_hashed_name (needle_key);
300 28 : match = g_hash_table_lookup_extended (haystack, other_key, NULL, (gpointer*)&hay);
301 28 : g_free (other_key);
302 :
303 28 : if (!match)
304 25 : return FALSE;
305 :
306 : /*
307 : * Now since the old keyring code would hash in two different
308 : * ways depending on whether it was a uint32 or string,
309 : * we need to do the same here.
310 : */
311 :
312 3 : other_key = make_compat_uint32_name (needle_key);
313 3 : if (g_hash_table_lookup (haystack, other_key)) {
314 2 : hashed = NULL;
315 2 : if (compat_hash_value_as_uint32 (needle_value, &number))
316 2 : hashed = format_uint32 (number);
317 : } else {
318 1 : hashed = compat_hash_value_as_string (needle_value);
319 : }
320 3 : g_free (other_key);
321 :
322 : /* Does the incoming hashed value match our hashed value? */
323 3 : match = string_ptr_equal (hay, hashed);
324 3 : g_free (hashed);
325 :
326 3 : return match;
327 : }
328 :
329 : gboolean
330 2040 : gkm_secret_fields_match (GHashTable *haystack,
331 : GHashTable *needle)
332 : {
333 : GHashTableIter iter;
334 : const gchar *key, *value;
335 :
336 2040 : g_return_val_if_fail (haystack, FALSE);
337 2040 : g_return_val_if_fail (needle, FALSE);
338 :
339 2040 : g_hash_table_iter_init (&iter, needle);
340 4051 : while (g_hash_table_iter_next (&iter, (gpointer*)&key, (gpointer*)&value)) {
341 2038 : g_assert (key && value);
342 :
343 2038 : if (!gkm_secret_fields_match_one (haystack, key, value))
344 27 : return FALSE;
345 : }
346 :
347 2013 : return TRUE;
348 : }
349 :
350 : void
351 2193 : gkm_secret_fields_take (GHashTable *fields, gchar *name, gchar *value)
352 : {
353 2193 : g_return_if_fail (fields);
354 2193 : g_return_if_fail (name);
355 2193 : if (value == NULL)
356 5 : value = g_strdup ("");
357 2193 : g_hash_table_replace (fields, name, value);
358 : }
359 :
360 : void
361 2068 : gkm_secret_fields_add (GHashTable *fields, const gchar *name,
362 : const gchar *value)
363 : {
364 2068 : g_return_if_fail (fields);
365 2068 : g_return_if_fail (name);
366 2068 : gkm_secret_fields_take (fields, g_strdup (name), g_strdup (value));
367 : }
368 :
369 : const gchar*
370 43 : gkm_secret_fields_get (GHashTable *fields, const gchar *name)
371 : {
372 43 : g_return_val_if_fail (fields, NULL);
373 43 : g_return_val_if_fail (name, NULL);
374 43 : g_return_val_if_fail (!is_compat_name (name), NULL);
375 43 : return g_hash_table_lookup (fields, name);
376 : }
377 :
378 : GList*
379 44 : gkm_secret_fields_get_names (GHashTable *fields)
380 : {
381 44 : const gchar *prefix = "gkr:compat:hashed:";
382 : GList *keys, *l, *next;
383 44 : gsize len = strlen (prefix);
384 44 : gchar *last = NULL;
385 :
386 44 : g_return_val_if_fail (fields, NULL);
387 :
388 44 : keys = g_hash_table_get_keys (fields);
389 :
390 : /* Include hashed compat attributes as their base name */
391 88 : for (l = keys; l; l = g_list_next (l)) {
392 44 : if (strncmp (prefix, l->data, len) == 0)
393 1 : l->data = (gchar*)(l->data) + len;
394 : }
395 :
396 : /* Sort the list nicely */
397 44 : keys = g_list_sort (keys, string_ptr_compare);
398 :
399 : /* Remove all compat attributes, duplicates */
400 88 : for (l = keys; l; l = next) {
401 44 : next = g_list_next (l);
402 44 : if (is_compat_name (l->data) || string_ptr_equal (last, l->data))
403 10 : keys = g_list_delete_link (keys, l);
404 : else
405 34 : last = l->data;
406 : }
407 :
408 44 : return keys;
409 : }
410 :
411 : void
412 55 : gkm_secret_fields_add_compat_uint32 (GHashTable *fields, const gchar *name,
413 : guint32 value)
414 : {
415 55 : g_return_if_fail (fields);
416 55 : g_return_if_fail (name);
417 55 : g_return_if_fail (!is_compat_name (name));
418 110 : g_hash_table_replace (fields, g_strdup (name), format_uint32 (value));
419 55 : g_hash_table_replace (fields, make_compat_uint32_name (name), g_strdup (""));
420 : }
421 :
422 : gboolean
423 29 : gkm_secret_fields_get_compat_uint32 (GHashTable *fields, const gchar *name,
424 : guint32 *value)
425 : {
426 : gchar *other_key;
427 : gboolean ret;
428 :
429 29 : g_return_val_if_fail (fields, FALSE);
430 29 : g_return_val_if_fail (name, FALSE);
431 29 : g_return_val_if_fail (value, FALSE);
432 29 : g_return_val_if_fail (!is_compat_name (name), FALSE);
433 :
434 29 : other_key = make_compat_uint32_name (name);
435 29 : ret = g_hash_table_lookup (fields, other_key) != NULL;
436 29 : g_free (other_key);
437 :
438 29 : if (ret)
439 14 : ret = parse_uint32 (g_hash_table_lookup (fields, name), value);
440 :
441 29 : return ret;
442 : }
443 :
444 : void
445 155 : gkm_secret_fields_add_compat_hashed_string (GHashTable *fields, const gchar *name,
446 : const gchar *value)
447 : {
448 155 : g_return_if_fail (fields);
449 155 : g_return_if_fail (name);
450 155 : g_return_if_fail (!is_compat_name (name));
451 155 : g_hash_table_replace (fields, make_compat_hashed_name (name), g_strdup (value));
452 : }
453 :
454 : gboolean
455 10 : gkm_secret_fields_get_compat_hashed_string (GHashTable *fields, const gchar *name,
456 : gchar **value)
457 : {
458 : gchar *other_key;
459 : gboolean ret;
460 : const gchar *val;
461 :
462 10 : g_return_val_if_fail (fields, FALSE);
463 10 : g_return_val_if_fail (name, FALSE);
464 10 : g_return_val_if_fail (value, FALSE);
465 10 : g_return_val_if_fail (!is_compat_name (name), FALSE);
466 :
467 : /* Even though this is more expensive, it's far more common */
468 10 : if (g_hash_table_lookup_extended (fields, name, NULL, (gpointer*)&val)) {
469 9 : *value = compat_hash_value_as_string (val);
470 9 : return TRUE;
471 : }
472 :
473 : /* See if we already have it hashed */
474 1 : other_key = make_compat_hashed_name (name);
475 1 : ret = g_hash_table_lookup_extended (fields, other_key, NULL, (gpointer*)&val);
476 1 : g_free (other_key);
477 :
478 1 : if (ret)
479 2 : *value = g_strdup (val);
480 1 : return ret;
481 : }
482 :
483 : void
484 45 : gkm_secret_fields_add_compat_hashed_uint32 (GHashTable *fields, const gchar *name,
485 : guint32 value)
486 : {
487 45 : g_return_if_fail (fields);
488 45 : g_return_if_fail (name);
489 45 : g_return_if_fail (!is_compat_name (name));
490 45 : g_hash_table_replace (fields, make_compat_hashed_name (name), format_uint32 (value));
491 45 : g_hash_table_replace (fields, make_compat_uint32_name (name), g_strdup (name));
492 : }
493 :
494 : gboolean
495 13 : gkm_secret_fields_get_compat_hashed_uint32 (GHashTable *fields, const gchar *name,
496 : guint32 *value)
497 : {
498 : const gchar *val;
499 : gchar *other_key;
500 : gboolean ret;
501 :
502 13 : g_return_val_if_fail (fields, FALSE);
503 13 : g_return_val_if_fail (name, FALSE);
504 13 : g_return_val_if_fail (value, FALSE);
505 13 : g_return_val_if_fail (!is_compat_name (name), FALSE);
506 :
507 : /* Even though this is more expensive, it's far more common */
508 :
509 : /* Check if it's a uint32 */
510 13 : other_key = make_compat_uint32_name (name);
511 13 : ret = g_hash_table_lookup_extended (fields, other_key, NULL, NULL);
512 13 : g_free (other_key);
513 :
514 : /* It is a uint32 */
515 13 : if (ret == TRUE) {
516 5 : val = g_hash_table_lookup (fields, name);
517 5 : if (val && compat_hash_value_as_uint32 (val, value))
518 4 : return TRUE;
519 : }
520 :
521 : /* See if we already have it hashed */
522 9 : other_key = make_compat_hashed_name (name);
523 9 : ret = g_hash_table_lookup_extended (fields, other_key, NULL, (gpointer*)&val);
524 9 : g_free (other_key);
525 9 : if (ret)
526 1 : ret = parse_uint32 (val, value);
527 9 : return ret;
528 : }
|