mirror of
https://gitlab.gnome.org/GNOME/libsecret.git
synced 2024-12-22 12:48:51 +00:00
28486191b2
This turns the `-Dgcrypt` build time option into a more generic `-Dcrypto` option, which enables user to choose which cryptographic library to link with. It currently supports libgcrypt (`libgcrypt`) and GnuTLS (`gnutls`); for the latter, GnuTLS 3.8.2 is the minimum required version. Signed-off-by: Daiki Ueno <dueno@src.gnome.org>
807 lines
20 KiB
C
807 lines
20 KiB
C
/* libsecret - GLib wrapper for Secret Service
|
|
*
|
|
* Copyright 2019 Red Hat, Inc.
|
|
*
|
|
* 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 licence or (at
|
|
* your option) any later version.
|
|
*
|
|
* See the included COPYING file for more information.
|
|
*
|
|
* Author: Daiki Ueno
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "secret-file-collection.h"
|
|
|
|
#include "egg/egg-keyring1.h"
|
|
#include "egg/egg-secure-memory.h"
|
|
|
|
EGG_SECURE_DECLARE (secret_file_collection);
|
|
|
|
#ifdef WITH_GCRYPT
|
|
#include "egg/egg-libgcrypt.h"
|
|
#endif
|
|
|
|
#define KEYRING_FILE_HEADER "GnomeKeyring\n\r\0\n"
|
|
#define KEYRING_FILE_HEADER_LEN 16
|
|
|
|
#define MAJOR_VERSION 1
|
|
#define MINOR_VERSION 0
|
|
|
|
struct _SecretFileCollection
|
|
{
|
|
GObject parent;
|
|
GFile *file;
|
|
gchar *etag;
|
|
SecretValue *password;
|
|
GBytes *salt;
|
|
guint32 iteration_count;
|
|
GDateTime *modified;
|
|
guint64 usage_count;
|
|
GBytes *key;
|
|
GVariant *items;
|
|
guint64 file_last_modified;
|
|
};
|
|
|
|
static void secret_file_collection_async_initable_iface (GAsyncInitableIface *iface);
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (SecretFileCollection, secret_file_collection, G_TYPE_OBJECT,
|
|
G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, secret_file_collection_async_initable_iface);
|
|
);
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_FILE,
|
|
PROP_PASSWORD
|
|
};
|
|
|
|
static guint64
|
|
get_file_last_modified (SecretFileCollection *self)
|
|
{
|
|
GFileInfo *info;
|
|
guint64 res;
|
|
|
|
info = g_file_query_info (self->file, G_FILE_ATTRIBUTE_TIME_MODIFIED, G_FILE_QUERY_INFO_NONE, NULL, NULL);
|
|
if (info == NULL)
|
|
return 0;
|
|
|
|
res = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
|
|
g_object_unref (info);
|
|
|
|
return res;
|
|
}
|
|
|
|
static void
|
|
secret_file_collection_init (SecretFileCollection *self)
|
|
{
|
|
}
|
|
|
|
static void
|
|
secret_file_collection_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
SecretFileCollection *self = SECRET_FILE_COLLECTION (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_FILE:
|
|
self->file = g_value_dup_object (value);
|
|
break;
|
|
case PROP_PASSWORD:
|
|
self->password = g_value_dup_boxed (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
secret_file_collection_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
switch (prop_id) {
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
secret_file_collection_finalize (GObject *object)
|
|
{
|
|
SecretFileCollection *self = SECRET_FILE_COLLECTION (object);
|
|
|
|
g_object_unref (self->file);
|
|
g_free (self->etag);
|
|
|
|
secret_value_unref (self->password);
|
|
|
|
g_clear_pointer (&self->salt, g_bytes_unref);
|
|
g_clear_pointer (&self->key, g_bytes_unref);
|
|
g_clear_pointer (&self->items, g_variant_unref);
|
|
g_clear_pointer (&self->modified, g_date_time_unref);
|
|
|
|
G_OBJECT_CLASS (secret_file_collection_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
secret_file_collection_class_init (SecretFileCollectionClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
object_class->set_property = secret_file_collection_set_property;
|
|
object_class->get_property = secret_file_collection_get_property;
|
|
object_class->finalize = secret_file_collection_finalize;
|
|
|
|
g_object_class_install_property (object_class, PROP_FILE,
|
|
g_param_spec_object ("file", "File", "File",
|
|
G_TYPE_FILE, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
|
|
g_object_class_install_property (object_class, PROP_PASSWORD,
|
|
g_param_spec_boxed ("password", "password", "Password",
|
|
SECRET_TYPE_VALUE,
|
|
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
|
|
#ifdef WITH_GCRYPT
|
|
egg_libgcrypt_initialize ();
|
|
#endif
|
|
}
|
|
|
|
static gboolean
|
|
load_contents (SecretFileCollection *self,
|
|
gchar *contents, /* takes ownership */
|
|
gsize length,
|
|
GError **error)
|
|
{
|
|
gchar *p;
|
|
GVariant *variant;
|
|
GVariant *salt_array;
|
|
guint32 salt_size;
|
|
guint32 iteration_count;
|
|
guint64 modified_time;
|
|
guint64 usage_count;
|
|
gconstpointer data;
|
|
gsize n_data;
|
|
const gchar *password;
|
|
gsize n_password;
|
|
|
|
p = contents;
|
|
if (length < KEYRING_FILE_HEADER_LEN ||
|
|
memcmp (p, KEYRING_FILE_HEADER, KEYRING_FILE_HEADER_LEN) != 0) {
|
|
g_set_error_literal (error,
|
|
SECRET_ERROR,
|
|
SECRET_ERROR_INVALID_FILE_FORMAT,
|
|
"file header mismatch");
|
|
return FALSE;
|
|
}
|
|
p += KEYRING_FILE_HEADER_LEN;
|
|
length -= KEYRING_FILE_HEADER_LEN;
|
|
|
|
if (length < 2 || *p != MAJOR_VERSION || *(p + 1) != MINOR_VERSION) {
|
|
g_set_error_literal (error,
|
|
SECRET_ERROR,
|
|
SECRET_ERROR_INVALID_FILE_FORMAT,
|
|
"version mismatch");
|
|
return FALSE;
|
|
}
|
|
p += 2;
|
|
length -= 2;
|
|
|
|
variant = g_variant_new_from_data (G_VARIANT_TYPE ("(uayutua(a{say}ay))"),
|
|
p,
|
|
length,
|
|
TRUE,
|
|
g_free,
|
|
contents);
|
|
g_variant_get (variant, "(u@ayutu@a(a{say}ay))",
|
|
&salt_size, &salt_array, &iteration_count,
|
|
&modified_time, &usage_count,
|
|
&self->items);
|
|
|
|
salt_size = GUINT32_FROM_LE(salt_size);
|
|
iteration_count = GUINT32_FROM_LE(iteration_count);
|
|
modified_time = GUINT64_FROM_LE(modified_time);
|
|
usage_count = GUINT32_FROM_LE(usage_count);
|
|
|
|
self->iteration_count = iteration_count;
|
|
self->modified = g_date_time_new_from_unix_utc (modified_time);
|
|
self->usage_count = usage_count;
|
|
|
|
data = g_variant_get_fixed_array (salt_array, &n_data, sizeof(guint8));
|
|
g_assert (n_data == salt_size);
|
|
|
|
self->salt = g_bytes_new (data, n_data);
|
|
|
|
g_variant_unref (salt_array);
|
|
g_variant_unref (variant);
|
|
|
|
password = secret_value_get (self->password, &n_password);
|
|
self->key = egg_keyring1_derive_key (password,
|
|
n_password,
|
|
self->salt,
|
|
self->iteration_count);
|
|
if (!self->key) {
|
|
g_set_error_literal (error,
|
|
SECRET_ERROR,
|
|
SECRET_ERROR_PROTOCOL,
|
|
"couldn't derive key");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
init_empty_file (SecretFileCollection *self,
|
|
GError **error)
|
|
{
|
|
GVariantBuilder builder;
|
|
const gchar *password;
|
|
gsize n_password;
|
|
guint8 salt[SALT_SIZE];
|
|
|
|
egg_keyring1_create_nonce (salt, sizeof(salt));
|
|
self->salt = g_bytes_new (salt, sizeof(salt));
|
|
self->iteration_count = ITERATION_COUNT;
|
|
self->modified = g_date_time_new_now_utc ();
|
|
self->usage_count = 0;
|
|
|
|
password = secret_value_get (self->password, &n_password);
|
|
self->key = egg_keyring1_derive_key (password,
|
|
n_password,
|
|
self->salt,
|
|
self->iteration_count);
|
|
if (!self->key) {
|
|
g_set_error_literal (error,
|
|
SECRET_ERROR,
|
|
SECRET_ERROR_PROTOCOL,
|
|
"couldn't derive key");
|
|
return FALSE;
|
|
}
|
|
|
|
g_variant_builder_init (&builder,
|
|
G_VARIANT_TYPE ("a(a{say}ay)"));
|
|
self->items = g_variant_builder_end (&builder);
|
|
g_variant_ref_sink (self->items);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
ensure_up_to_date (SecretFileCollection *self)
|
|
{
|
|
guint64 last_modified;
|
|
|
|
last_modified = get_file_last_modified (self);
|
|
if (last_modified != self->file_last_modified) {
|
|
gchar *contents = NULL;
|
|
gsize length = 0;
|
|
gboolean success;
|
|
GError *error = NULL;
|
|
gchar *etag = NULL;
|
|
|
|
self->file_last_modified = last_modified;
|
|
|
|
success = g_file_load_contents (self->file, NULL, &contents, &length, &etag, &error);
|
|
|
|
if (success) {
|
|
g_clear_pointer (&self->etag, g_free);
|
|
self->etag = g_steal_pointer (&etag);
|
|
} else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
|
|
g_clear_error (&error);
|
|
|
|
success = init_empty_file (self, &error);
|
|
}
|
|
|
|
if (success)
|
|
success = load_contents (self, contents, length, &error);
|
|
|
|
if (!success)
|
|
g_debug ("Failed to load file contents: %s", error ? error->message : "Unknown error");
|
|
|
|
g_clear_error (&error);
|
|
}
|
|
}
|
|
|
|
static void
|
|
on_load_contents (GObject *source_object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
GFile *file = G_FILE (source_object);
|
|
GTask *task = G_TASK (user_data);
|
|
SecretFileCollection *self = g_task_get_source_object (task);
|
|
gchar *contents;
|
|
gsize length;
|
|
GError *error = NULL;
|
|
gboolean ret;
|
|
gchar *etag = NULL;
|
|
|
|
self->file_last_modified = get_file_last_modified (self);
|
|
|
|
ret = g_file_load_contents_finish (file, result,
|
|
&contents, &length,
|
|
&etag,
|
|
&error);
|
|
|
|
if (!ret) {
|
|
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
|
|
g_clear_error (&error);
|
|
|
|
if (init_empty_file (self, &error)) {
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
}
|
|
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
g_clear_pointer (&self->etag, g_free);
|
|
self->etag = g_steal_pointer (&etag);
|
|
|
|
ret = load_contents (self, contents, length, &error);
|
|
if (ret)
|
|
g_task_return_boolean (task, ret);
|
|
else
|
|
g_task_return_error (task, error);
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
secret_file_collection_real_init_async (GAsyncInitable *initable,
|
|
int io_priority,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
SecretFileCollection *self = SECRET_FILE_COLLECTION (initable);
|
|
GTask *task;
|
|
|
|
task = g_task_new (initable, cancellable, callback, user_data);
|
|
|
|
g_file_load_contents_async (self->file, cancellable, on_load_contents, task);
|
|
}
|
|
|
|
static gboolean
|
|
secret_file_collection_real_init_finish (GAsyncInitable *initable,
|
|
GAsyncResult *result,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail (g_task_is_valid (result, initable), FALSE);
|
|
|
|
return g_task_propagate_boolean (G_TASK (result), error);
|
|
}
|
|
|
|
static void
|
|
secret_file_collection_async_initable_iface (GAsyncInitableIface *iface)
|
|
{
|
|
iface->init_async = secret_file_collection_real_init_async;
|
|
iface->init_finish = secret_file_collection_real_init_finish;
|
|
}
|
|
|
|
static GVariant *
|
|
hash_attributes (SecretFileCollection *self,
|
|
GHashTable *attributes)
|
|
{
|
|
GVariantBuilder builder;
|
|
guint8 buffer[MAC_SIZE];
|
|
GList *keys;
|
|
GList *l;
|
|
|
|
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{say}"));
|
|
|
|
keys = g_hash_table_get_keys (attributes);
|
|
keys = g_list_sort (keys, (GCompareFunc) g_strcmp0);
|
|
|
|
for (l = keys; l; l = g_list_next (l)) {
|
|
const gchar *value;
|
|
GVariant *variant;
|
|
|
|
value = g_hash_table_lookup (attributes, l->data);
|
|
if (!egg_keyring1_calculate_mac (self->key,
|
|
(const guint8 *)value,
|
|
strlen (value),
|
|
buffer)) {
|
|
g_list_free (keys);
|
|
return NULL;
|
|
}
|
|
|
|
variant = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
|
|
buffer,
|
|
MAC_SIZE,
|
|
sizeof(guint8));
|
|
g_variant_builder_add (&builder, "{s@ay}", l->data, variant);
|
|
}
|
|
g_list_free (keys);
|
|
|
|
return g_variant_builder_end (&builder);
|
|
}
|
|
|
|
static gboolean
|
|
hashed_attributes_match (SecretFileCollection *self,
|
|
GVariant *hashed_attributes,
|
|
GHashTable *attributes)
|
|
{
|
|
GHashTableIter iter;
|
|
GVariant *hashed_attribute = NULL;
|
|
gpointer key;
|
|
gpointer value;
|
|
|
|
g_hash_table_iter_init (&iter, attributes);
|
|
while (g_hash_table_iter_next (&iter, &key, &value)) {
|
|
const guint8 *data;
|
|
gsize n_data;
|
|
|
|
if (!g_variant_lookup (hashed_attributes, key,
|
|
"@ay", &hashed_attribute))
|
|
return FALSE;
|
|
|
|
data = g_variant_get_fixed_array (hashed_attribute,
|
|
&n_data, sizeof(guint8));
|
|
if (n_data != MAC_SIZE) {
|
|
g_variant_unref (hashed_attribute);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!egg_keyring1_verify_mac (self->key, value, strlen ((char *)value), data)) {
|
|
g_variant_unref (hashed_attribute);
|
|
return FALSE;
|
|
}
|
|
g_variant_unref (hashed_attribute);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
secret_file_collection_replace (SecretFileCollection *self,
|
|
GHashTable *attributes,
|
|
const gchar *label,
|
|
SecretValue *value,
|
|
GError **error)
|
|
{
|
|
GVariantBuilder builder;
|
|
GVariant *hashed_attributes;
|
|
GVariantIter iter;
|
|
GVariant *child;
|
|
SecretFileItem *item;
|
|
GVariant *serialized_item;
|
|
guint8 *data = NULL;
|
|
gsize n_data;
|
|
gsize n_padded;
|
|
GVariant *variant;
|
|
GDateTime *created = NULL;
|
|
GDateTime *modified;
|
|
|
|
ensure_up_to_date (self);
|
|
|
|
hashed_attributes = hash_attributes (self, attributes);
|
|
if (!hashed_attributes) {
|
|
g_set_error (error,
|
|
SECRET_ERROR,
|
|
SECRET_ERROR_PROTOCOL,
|
|
"couldn't calculate mac");
|
|
return FALSE;
|
|
}
|
|
|
|
/* Filter out the existing item */
|
|
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(a{say}ay)"));
|
|
g_variant_iter_init (&iter, self->items);
|
|
while ((child = g_variant_iter_next_value (&iter)) != NULL) {
|
|
GVariant *_hashed_attributes;
|
|
g_variant_get (child, "(@a{say}ay)", &_hashed_attributes, NULL);
|
|
if (g_variant_equal (hashed_attributes, _hashed_attributes)) {
|
|
SecretFileItem *existing =
|
|
_secret_file_item_decrypt (child, self, error);
|
|
guint64 created_time;
|
|
|
|
if (existing == NULL) {
|
|
g_variant_builder_clear (&builder);
|
|
g_variant_unref (child);
|
|
g_variant_unref (_hashed_attributes);
|
|
return FALSE;
|
|
}
|
|
g_object_get (existing, "created", &created_time, NULL);
|
|
g_object_unref (existing);
|
|
|
|
created = g_date_time_new_from_unix_utc (created_time);
|
|
} else {
|
|
g_variant_builder_add_value (&builder, child);
|
|
}
|
|
g_variant_unref (child);
|
|
g_variant_unref (_hashed_attributes);
|
|
}
|
|
|
|
modified = g_date_time_new_now_utc ();
|
|
if (created == NULL)
|
|
created = g_date_time_ref (modified);
|
|
|
|
/* Create a new item and append it */
|
|
item = g_object_new (SECRET_TYPE_FILE_ITEM,
|
|
"attributes", attributes,
|
|
"label", label,
|
|
"value", value,
|
|
"created", g_date_time_to_unix (created),
|
|
"modified", g_date_time_to_unix (modified),
|
|
NULL);
|
|
|
|
g_date_time_unref (created);
|
|
g_date_time_unref (modified);
|
|
|
|
serialized_item = secret_file_item_serialize (item);
|
|
g_object_unref (item);
|
|
|
|
/* Encrypt the item with PKCS #7 padding */
|
|
n_data = g_variant_get_size (serialized_item);
|
|
n_padded = ((n_data + CIPHER_BLOCK_SIZE) / CIPHER_BLOCK_SIZE) *
|
|
CIPHER_BLOCK_SIZE;
|
|
data = egg_secure_alloc (n_padded + IV_SIZE + MAC_SIZE);
|
|
g_variant_store (serialized_item, data);
|
|
g_variant_unref (serialized_item);
|
|
memset (data + n_data, n_padded - n_data, n_padded - n_data);
|
|
if (!egg_keyring1_encrypt (self->key, data, n_padded)) {
|
|
egg_secure_free (data);
|
|
g_set_error (error,
|
|
SECRET_ERROR,
|
|
SECRET_ERROR_PROTOCOL,
|
|
"couldn't encrypt item");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!egg_keyring1_calculate_mac (self->key, data, n_padded + IV_SIZE,
|
|
data + n_padded + IV_SIZE)) {
|
|
egg_secure_free (data);
|
|
g_set_error (error,
|
|
SECRET_ERROR,
|
|
SECRET_ERROR_PROTOCOL,
|
|
"couldn't calculate mac");
|
|
return FALSE;
|
|
}
|
|
|
|
self->usage_count++;
|
|
g_date_time_unref (self->modified);
|
|
self->modified = g_date_time_new_now_utc ();
|
|
|
|
variant = g_variant_new_from_data (G_VARIANT_TYPE ("ay"),
|
|
data,
|
|
n_padded + IV_SIZE + MAC_SIZE,
|
|
TRUE,
|
|
egg_secure_free,
|
|
data);
|
|
variant = g_variant_new ("(@a{say}@ay)", hashed_attributes, variant);
|
|
g_variant_builder_add_value (&builder, variant);
|
|
|
|
g_variant_unref (self->items);
|
|
self->items = g_variant_builder_end (&builder);
|
|
g_variant_ref_sink (self->items);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
GList *
|
|
secret_file_collection_search (SecretFileCollection *self,
|
|
GHashTable *attributes)
|
|
{
|
|
GVariantIter iter;
|
|
GVariant *child;
|
|
GList *result = NULL;
|
|
|
|
ensure_up_to_date (self);
|
|
|
|
g_variant_iter_init (&iter, self->items);
|
|
while ((child = g_variant_iter_next_value (&iter)) != NULL) {
|
|
GVariant *hashed_attributes;
|
|
gboolean matched;
|
|
|
|
g_variant_get (child, "(@a{say}ay)", &hashed_attributes, NULL);
|
|
matched = hashed_attributes_match (self,
|
|
hashed_attributes,
|
|
attributes);
|
|
g_variant_unref (hashed_attributes);
|
|
if (matched)
|
|
result = g_list_append (result, g_variant_ref (child));
|
|
g_variant_unref (child);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
SecretFileItem *
|
|
_secret_file_item_decrypt (GVariant *encrypted,
|
|
SecretFileCollection *collection,
|
|
GError **error)
|
|
{
|
|
GVariant *blob;
|
|
gconstpointer padded;
|
|
gsize n_data;
|
|
gsize n_padded;
|
|
guint8 *data;
|
|
SecretFileItem *item;
|
|
GVariant *serialized_item;
|
|
|
|
g_variant_get (encrypted, "(a{say}@ay)", NULL, &blob);
|
|
|
|
/* Decrypt the item */
|
|
padded = g_variant_get_fixed_array (blob, &n_padded, sizeof(guint8));
|
|
data = egg_secure_alloc (n_padded);
|
|
memcpy (data, padded, n_padded);
|
|
g_variant_unref (blob);
|
|
|
|
if (n_padded < IV_SIZE + MAC_SIZE) {
|
|
egg_secure_free (data);
|
|
g_set_error (error,
|
|
SECRET_ERROR,
|
|
SECRET_ERROR_PROTOCOL,
|
|
"couldn't calculate mac");
|
|
return FALSE;
|
|
}
|
|
|
|
n_padded -= MAC_SIZE;
|
|
if (!egg_keyring1_verify_mac (collection->key, data, n_padded, data + n_padded)) {
|
|
egg_secure_free (data);
|
|
g_set_error (error,
|
|
SECRET_ERROR,
|
|
SECRET_ERROR_PROTOCOL,
|
|
"couldn't calculate mac");
|
|
return FALSE;
|
|
}
|
|
|
|
n_padded -= IV_SIZE;
|
|
if (!egg_keyring1_decrypt (collection->key, data, n_padded)) {
|
|
egg_secure_free (data);
|
|
g_set_error (error,
|
|
SECRET_ERROR,
|
|
SECRET_ERROR_PROTOCOL,
|
|
"couldn't decrypt item");
|
|
return NULL;
|
|
}
|
|
|
|
/* Remove PKCS #7 padding */
|
|
n_data = n_padded - data[n_padded - 1];
|
|
|
|
serialized_item =
|
|
g_variant_new_from_data (G_VARIANT_TYPE ("(a{ss}sttay)"),
|
|
data,
|
|
n_data,
|
|
TRUE,
|
|
egg_secure_free,
|
|
data);
|
|
item = secret_file_item_deserialize (serialized_item);
|
|
g_variant_unref (serialized_item);
|
|
return item;
|
|
}
|
|
|
|
gboolean
|
|
secret_file_collection_clear (SecretFileCollection *self,
|
|
GHashTable *attributes,
|
|
GError **error)
|
|
{
|
|
GVariantBuilder builder;
|
|
GVariantIter items;
|
|
GVariant *child;
|
|
gboolean removed = FALSE;
|
|
|
|
ensure_up_to_date (self);
|
|
|
|
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(a{say}ay)"));
|
|
g_variant_iter_init (&items, self->items);
|
|
while ((child = g_variant_iter_next_value (&items)) != NULL) {
|
|
GVariant *hashed_attributes;
|
|
gboolean matched;
|
|
|
|
g_variant_get (child, "(@a{say}ay)", &hashed_attributes, NULL);
|
|
matched = hashed_attributes_match (self,
|
|
hashed_attributes,
|
|
attributes);
|
|
g_variant_unref (hashed_attributes);
|
|
if (matched)
|
|
removed = TRUE;
|
|
else
|
|
g_variant_builder_add_value (&builder, child);
|
|
g_variant_unref (child);
|
|
}
|
|
|
|
g_variant_unref (self->items);
|
|
self->items = g_variant_builder_end (&builder);
|
|
g_variant_ref_sink (self->items);
|
|
|
|
return removed;
|
|
}
|
|
|
|
static void
|
|
on_replace_contents (GObject *source_object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
GFile *file = G_FILE (source_object);
|
|
GTask *task = G_TASK (user_data);
|
|
SecretFileCollection *self = g_task_get_source_object (task);
|
|
GError *error = NULL;
|
|
gchar *etag = NULL;
|
|
|
|
if (!g_file_replace_contents_finish (file, result, &etag, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
self->file_last_modified = get_file_last_modified (self);
|
|
g_clear_pointer (&self->etag, g_free);
|
|
self->etag = g_steal_pointer (&etag);
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
void
|
|
secret_file_collection_write (SecretFileCollection *self,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
guint8 *contents;
|
|
gsize n_contents;
|
|
guint8 *p;
|
|
GVariant *salt_array;
|
|
GVariant *variant;
|
|
|
|
salt_array = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
|
|
g_bytes_get_data (self->salt, NULL),
|
|
g_bytes_get_size (self->salt),
|
|
sizeof(guint8));
|
|
variant = g_variant_new ("(u@ayutu@a(a{say}ay))",
|
|
GUINT32_TO_LE(g_bytes_get_size (self->salt)),
|
|
salt_array,
|
|
GUINT32_TO_LE(self->iteration_count),
|
|
GUINT64_TO_LE(g_date_time_to_unix (self->modified)),
|
|
GUINT32_TO_LE(self->usage_count),
|
|
self->items);
|
|
|
|
g_variant_get_data (variant); /* force serialize */
|
|
n_contents = KEYRING_FILE_HEADER_LEN + 2 + g_variant_get_size (variant);
|
|
contents = g_new (guint8, n_contents);
|
|
|
|
p = contents;
|
|
memcpy (p, KEYRING_FILE_HEADER, KEYRING_FILE_HEADER_LEN);
|
|
p += KEYRING_FILE_HEADER_LEN;
|
|
|
|
*p++ = MAJOR_VERSION;
|
|
*p++ = MINOR_VERSION;
|
|
|
|
g_variant_store (variant, p);
|
|
g_variant_unref (variant);
|
|
|
|
task = g_task_new (self, cancellable, callback, user_data);
|
|
g_task_set_task_data (task, contents, g_free);
|
|
g_file_replace_contents_async (self->file,
|
|
(gchar *) contents,
|
|
n_contents,
|
|
self->etag,
|
|
TRUE,
|
|
G_FILE_CREATE_PRIVATE |
|
|
G_FILE_CREATE_REPLACE_DESTINATION,
|
|
cancellable,
|
|
on_replace_contents,
|
|
task);
|
|
}
|
|
|
|
gboolean
|
|
secret_file_collection_write_finish (SecretFileCollection *self,
|
|
GAsyncResult *result,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail (g_task_is_valid (result, self), FALSE);
|
|
|
|
return g_task_propagate_boolean (G_TASK (result), error);
|
|
}
|