/*
* 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;
}