/* * gnome-keyring * * Copyright (C) 2009 Stefan Walter * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see * . */ #include "config.h" #include "gkm-secret-fields.h" #include "egg/egg-hex.h" #include "gkm/gkm-attributes.h" #include #include static gboolean begins_with (const gchar *string, const gchar *prefix) { gsize len = strlen (prefix); return (strncmp (string, prefix, len) == 0); } static gboolean is_compat_name (const gchar *name) { g_assert (name); return begins_with (name, "gkr:compat:"); } static gchar* make_compat_hashed_name (const gchar *name) { g_assert (!is_compat_name (name)); return g_strdup_printf ("gkr:compat:hashed:%s", name); } static gchar* make_compat_uint32_name (const gchar *name) { g_assert (!is_compat_name (name)); return g_strdup_printf ("gkr:compat:uint32:%s", name); } static gboolean string_ptr_equal (const gchar *one, const gchar *two) { if (one == two) return TRUE; if (!one || !two) return FALSE; return g_str_equal (one, two); } static gint string_ptr_compare (gconstpointer one, gconstpointer two) { if (one == two) return 0; if (!one || !two) return one < two; return strcmp (one, two); } static gboolean parse_uint32 (const gchar *value, guint32 *result) { gchar *end; g_assert (value); g_assert (result); *result = strtoul(value, &end, 10); return (*end == '\0'); } static gchar* format_uint32 (guint32 value) { return g_strdup_printf ("%u", value); } static gboolean compat_hash_value_as_uint32 (const gchar *value, guint32 *hash) { guint32 x; if (!value || !parse_uint32 (value, &x)) return FALSE; /* The same algorithm as the old keyring code used */ *hash = 0x18273645 ^ x ^ (x << 16 | x >> 16); return TRUE; } static gchar* compat_hash_value_as_string (const gchar *value) { guchar digest[16]; if (!value) return NULL; g_assert (gcry_md_get_algo_dlen (GCRY_MD_MD5) == sizeof (digest)); gcry_md_hash_buffer (GCRY_MD_MD5, (void*)digest, value, strlen (value)); /* The old keyring code used lower case hex */ return egg_hex_encode_full (digest, sizeof (digest), FALSE, '\0', 0); } GType gkm_secret_fields_boxed_type (void) { static gsize type_inited = 0; static GType type = 0; if (g_once_init_enter (&type_inited)) { type = g_boxed_type_register_static ("GHashTable_Fields", (GBoxedCopyFunc)g_hash_table_ref, (GBoxedFreeFunc)g_hash_table_unref); g_once_init_leave (&type_inited, 1); } return type; } GHashTable* gkm_secret_fields_new (void) { return g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); } CK_RV gkm_secret_fields_parse (CK_ATTRIBUTE_PTR attr, GHashTable **fields, gchar **schema_name) { GHashTable *result; const gchar *name; gsize n_name; const gchar *value; gsize n_value; const gchar *ptr; const gchar *last; g_assert (attr); g_assert (fields); ptr = attr->pValue; last = ptr + attr->ulValueLen; if (!ptr && last != ptr) return CKR_ATTRIBUTE_VALUE_INVALID; result = gkm_secret_fields_new (); while (ptr && ptr != last) { g_assert (ptr < last); name = ptr; ptr = memchr (ptr, 0, last - ptr); /* No value is present? */ if (!ptr) { g_hash_table_unref (result); return CKR_ATTRIBUTE_VALUE_INVALID; } n_name = ptr - name; value = ++ptr; ptr = memchr (ptr, 0, last - ptr); /* Missing null terminator on value */ if (ptr == NULL) { g_hash_table_unref (result); return CKR_ATTRIBUTE_VALUE_INVALID; } n_value = ptr - value; ++ptr; /* Validate the name and value*/ if (!g_utf8_validate (name, n_name, NULL) || !g_utf8_validate (value, n_value, NULL)) { g_hash_table_unref (result); return CKR_ATTRIBUTE_VALUE_INVALID; } g_hash_table_replace (result, g_strndup (name, n_name), g_strndup (value, n_value)); } if (schema_name) *schema_name = g_strdup (g_hash_table_lookup (result, GKM_SECRET_FIELD_SCHEMA)); *fields = result; return CKR_OK; } CK_RV gkm_secret_fields_serialize (CK_ATTRIBUTE_PTR attr, GHashTable *fields, const gchar *schema_name) { GList *l, *keys; gboolean saw_schema = FALSE; const gchar *key; gpointer value; GString *result; CK_RV rv; g_assert (attr != NULL); g_assert (fields != NULL); keys = g_hash_table_get_keys (fields); keys = g_list_sort (keys, (GCompareFunc) g_strcmp0); if (!attr->pValue) { attr->ulValueLen = 0; for (l = keys; l != NULL; l = l->next) { key = (const gchar *) l->data; value = g_hash_table_lookup (fields, key); if (g_str_equal (key, GKM_SECRET_FIELD_SCHEMA)) saw_schema = TRUE; attr->ulValueLen += strlen (key); attr->ulValueLen += strlen (value); attr->ulValueLen += 2; } if (schema_name && !saw_schema) { attr->ulValueLen += strlen (GKM_SECRET_FIELD_SCHEMA); attr->ulValueLen += strlen (schema_name); attr->ulValueLen += 2; } g_list_free (keys); return CKR_OK; } result = g_string_sized_new (256); for (l = keys; l != NULL; l = l->next) { key = (const gchar *) l->data; value = g_hash_table_lookup (fields, key); if (g_str_equal (key, GKM_SECRET_FIELD_SCHEMA)) saw_schema = TRUE; g_string_append (result, key); g_string_append_c (result, '\0'); g_string_append (result, value); g_string_append_c (result, '\0'); } if (schema_name && !saw_schema) { g_string_append (result, GKM_SECRET_FIELD_SCHEMA); g_string_append_c (result, '\0'); g_string_append (result, schema_name); g_string_append_c (result, '\0'); } rv = gkm_attribute_set_data (attr, result->str, result->len); g_string_free (result, TRUE); g_list_free (keys); return rv; } gboolean gkm_secret_fields_match_one (GHashTable *haystack, const gchar *needle_key, const gchar *needle_value) { const gchar *hay; gchar *other_key, *hashed; guint32 number; gboolean match; g_return_val_if_fail (haystack != NULL, FALSE); g_return_val_if_fail (needle_key != NULL, FALSE); g_return_val_if_fail (needle_value != NULL, FALSE); /* Compat attributes in the needle make no difference */ if (is_compat_name (needle_key)) return TRUE; /* A direct match? */ if (g_hash_table_lookup_extended (haystack, needle_key, NULL, (gpointer*)&hay)) return string_ptr_equal (hay, needle_value); /* Try to find a hashed value? */ other_key = make_compat_hashed_name (needle_key); match = g_hash_table_lookup_extended (haystack, other_key, NULL, (gpointer*)&hay); g_free (other_key); if (!match) return FALSE; /* * Now since the old keyring code would hash in two different * ways depending on whether it was a uint32 or string, * we need to do the same here. */ other_key = make_compat_uint32_name (needle_key); if (g_hash_table_lookup (haystack, other_key)) { hashed = NULL; if (compat_hash_value_as_uint32 (needle_value, &number)) hashed = format_uint32 (number); } else { hashed = compat_hash_value_as_string (needle_value); } g_free (other_key); /* Does the incoming hashed value match our hashed value? */ match = string_ptr_equal (hay, hashed); g_free (hashed); return match; } gboolean gkm_secret_fields_match (GHashTable *haystack, GHashTable *needle) { GHashTableIter iter; const gchar *key, *value; g_return_val_if_fail (haystack, FALSE); g_return_val_if_fail (needle, FALSE); g_hash_table_iter_init (&iter, needle); while (g_hash_table_iter_next (&iter, (gpointer*)&key, (gpointer*)&value)) { g_assert (key && value); if (!gkm_secret_fields_match_one (haystack, key, value)) return FALSE; } return TRUE; } void gkm_secret_fields_take (GHashTable *fields, gchar *name, gchar *value) { g_return_if_fail (fields); g_return_if_fail (name); if (value == NULL) value = g_strdup (""); g_hash_table_replace (fields, name, value); } void gkm_secret_fields_add (GHashTable *fields, const gchar *name, const gchar *value) { g_return_if_fail (fields); g_return_if_fail (name); gkm_secret_fields_take (fields, g_strdup (name), g_strdup (value)); } const gchar* gkm_secret_fields_get (GHashTable *fields, const gchar *name) { g_return_val_if_fail (fields, NULL); g_return_val_if_fail (name, NULL); g_return_val_if_fail (!is_compat_name (name), NULL); return g_hash_table_lookup (fields, name); } GList* gkm_secret_fields_get_names (GHashTable *fields) { const gchar *prefix = "gkr:compat:hashed:"; GList *keys, *l, *next; gsize len = strlen (prefix); gchar *last = NULL; g_return_val_if_fail (fields, NULL); keys = g_hash_table_get_keys (fields); /* Include hashed compat attributes as their base name */ for (l = keys; l; l = g_list_next (l)) { if (strncmp (prefix, l->data, len) == 0) l->data = (gchar*)(l->data) + len; } /* Sort the list nicely */ keys = g_list_sort (keys, string_ptr_compare); /* Remove all compat attributes, duplicates */ for (l = keys; l; l = next) { next = g_list_next (l); if (is_compat_name (l->data) || string_ptr_equal (last, l->data)) keys = g_list_delete_link (keys, l); else last = l->data; } return keys; } void gkm_secret_fields_add_compat_uint32 (GHashTable *fields, const gchar *name, guint32 value) { g_return_if_fail (fields); g_return_if_fail (name); g_return_if_fail (!is_compat_name (name)); g_hash_table_replace (fields, g_strdup (name), format_uint32 (value)); g_hash_table_replace (fields, make_compat_uint32_name (name), g_strdup ("")); } gboolean gkm_secret_fields_get_compat_uint32 (GHashTable *fields, const gchar *name, guint32 *value) { gchar *other_key; gboolean ret; g_return_val_if_fail (fields, FALSE); g_return_val_if_fail (name, FALSE); g_return_val_if_fail (value, FALSE); g_return_val_if_fail (!is_compat_name (name), FALSE); other_key = make_compat_uint32_name (name); ret = g_hash_table_lookup (fields, other_key) != NULL; g_free (other_key); if (ret) ret = parse_uint32 (g_hash_table_lookup (fields, name), value); return ret; } void gkm_secret_fields_add_compat_hashed_string (GHashTable *fields, const gchar *name, const gchar *value) { g_return_if_fail (fields); g_return_if_fail (name); g_return_if_fail (!is_compat_name (name)); g_hash_table_replace (fields, make_compat_hashed_name (name), g_strdup (value)); } gboolean gkm_secret_fields_get_compat_hashed_string (GHashTable *fields, const gchar *name, gchar **value) { gchar *other_key; gboolean ret; const gchar *val; g_return_val_if_fail (fields, FALSE); g_return_val_if_fail (name, FALSE); g_return_val_if_fail (value, FALSE); g_return_val_if_fail (!is_compat_name (name), FALSE); /* Even though this is more expensive, it's far more common */ if (g_hash_table_lookup_extended (fields, name, NULL, (gpointer*)&val)) { *value = compat_hash_value_as_string (val); return TRUE; } /* See if we already have it hashed */ other_key = make_compat_hashed_name (name); ret = g_hash_table_lookup_extended (fields, other_key, NULL, (gpointer*)&val); g_free (other_key); if (ret) *value = g_strdup (val); return ret; } void gkm_secret_fields_add_compat_hashed_uint32 (GHashTable *fields, const gchar *name, guint32 value) { g_return_if_fail (fields); g_return_if_fail (name); g_return_if_fail (!is_compat_name (name)); g_hash_table_replace (fields, make_compat_hashed_name (name), format_uint32 (value)); g_hash_table_replace (fields, make_compat_uint32_name (name), g_strdup (name)); } gboolean gkm_secret_fields_get_compat_hashed_uint32 (GHashTable *fields, const gchar *name, guint32 *value) { const gchar *val; gchar *other_key; gboolean ret; g_return_val_if_fail (fields, FALSE); g_return_val_if_fail (name, FALSE); g_return_val_if_fail (value, FALSE); g_return_val_if_fail (!is_compat_name (name), FALSE); /* Even though this is more expensive, it's far more common */ /* Check if it's a uint32 */ other_key = make_compat_uint32_name (name); ret = g_hash_table_lookup_extended (fields, other_key, NULL, NULL); g_free (other_key); /* It is a uint32 */ if (ret == TRUE) { val = g_hash_table_lookup (fields, name); if (val && compat_hash_value_as_uint32 (val, value)) return TRUE; } /* See if we already have it hashed */ other_key = make_compat_hashed_name (name); ret = g_hash_table_lookup_extended (fields, other_key, NULL, (gpointer*)&val); g_free (other_key); if (ret) ret = parse_uint32 (val, value); return ret; }