commit df190f5ec4aac08ff18916aa74e4d0bcb75ab2a6
parent 5ea1755fa0079066803fc0df1df307a10cce4b0f
Author: Jacki <jacki@thejackimonster.de>
Date: Sun, 11 Jan 2026 05:29:36 +0100
Manage account secrets for local key storage via libsecret
Signed-off-by: Jacki <jacki@thejackimonster.de>
Diffstat:
10 files changed, 420 insertions(+), 18 deletions(-)
diff --git a/Doxyfile b/Doxyfile
@@ -48,7 +48,7 @@ PROJECT_NAME = libgnunetchat
# could be handy for archiving the generated documentation or if some version
# control system is used.
-PROJECT_NUMBER = 0.6.0
+PROJECT_NUMBER = 0.7.0
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
diff --git a/include/gnunet/gnunet_chat_lib.h b/include/gnunet/gnunet_chat_lib.h
@@ -1,6 +1,6 @@
/*
This file is part of GNUnet.
- Copyright (C) 2021--2025 GNUnet e.V.
+ Copyright (C) 2021--2026 GNUnet e.V.
GNUnet is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
@@ -55,7 +55,7 @@ struct GNUNET_CONFIGURATION_Handle;
* the #GNUNET_MESSENGER_VERSION of the GNUnet Messenger
* service while the patch version is independent.
*/
-#define GNUNET_CHAT_VERSION 0x000000060001L
+#define GNUNET_CHAT_VERSION 0x000000070000L
#define GNUNET_CHAT_VERSION_MAJOR ((GNUNET_CHAT_VERSION >> 32L) & 0xFFFFL)
#define GNUNET_CHAT_VERSION_MINOR ((GNUNET_CHAT_VERSION >> 16L) & 0xFFFFL)
diff --git a/meson.build b/meson.build
@@ -1,6 +1,6 @@
#
# This file is part of GNUnet.
-# Copyright (C) 2023--2025 GNUnet e.V.
+# Copyright (C) 2023--2026 GNUnet e.V.
#
# GNUnet is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
@@ -45,6 +45,9 @@ gnunetchat_deps = [
dependency('gnunetreclaim'),
dependency('gnunetregex'),
dependency('gnunetutil'),
+
+ dependency('glib-2.0'),
+ dependency('libsecret-1'),
]
subdir('include')
diff --git a/src/gnunet_chat_account.c b/src/gnunet_chat_account.c
@@ -1,6 +1,6 @@
/*
This file is part of GNUnet.
- Copyright (C) 2022--2025 GNUnet e.V.
+ Copyright (C) 2022--2026 GNUnet e.V.
GNUnet is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
@@ -30,13 +30,21 @@
#include <gnunet/gnunet_identity_service.h>
#include <gnunet/gnunet_messenger_service.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <libsecret/secret.h>
+
+#include "gnunet_chat_account_intern.c"
+
struct GNUNET_CHAT_Account*
-account_create (const char *name)
+account_create (struct GNUNET_CHAT_Handle *handle,
+ const char *name)
{
GNUNET_assert(name);
struct GNUNET_CHAT_Account *account = GNUNET_new(struct GNUNET_CHAT_Account);
+ account->handle = handle;
+
account->ego = NULL;
account->created = GNUNET_NO;
@@ -44,18 +52,33 @@ account_create (const char *name)
util_set_name_field(name, &(account->name));
+ memset(&(account->secret), 0, sizeof(account->secret));
+ account->cancellable = NULL;
+
account->user_pointer = NULL;
+ account->cancellable = g_cancellable_new();
+ secret_password_lookup(
+ CHAT_ACCOUNT_SECRET_SCHEMA,
+ account->cancellable,
+ on_account_secret_lookup,
+ account,
+ "name", name,
+ "app_id", CHAT_ACCOUNT_APP_ID,
+ NULL
+ );
+
return account;
}
struct GNUNET_CHAT_Account*
-account_create_from_ego (struct GNUNET_IDENTITY_Ego *ego,
+account_create_from_ego (struct GNUNET_CHAT_Handle *handle,
+ struct GNUNET_IDENTITY_Ego *ego,
const char *name)
{
GNUNET_assert((ego) && (name));
- struct GNUNET_CHAT_Account *account = account_create(name);
+ struct GNUNET_CHAT_Account *account = account_create(handle, name);
account->ego = ego;
account->created = GNUNET_YES;
@@ -84,6 +107,82 @@ account_get_name (const struct GNUNET_CHAT_Account *account)
return account->name;
}
+const struct GNUNET_HashCode*
+account_get_secret (struct GNUNET_CHAT_Account *account)
+{
+ GNUNET_assert(account);
+
+ if (!(account->cancellable))
+ return &(account->secret);
+
+ g_cancellable_cancel(account->cancellable);
+ g_object_unref(account->cancellable);
+ account->cancellable = NULL;
+
+ GError *error = NULL;
+ const char *name = account_get_name(account);
+
+ gchar *password = secret_password_lookup_sync(
+ CHAT_ACCOUNT_SECRET_SCHEMA,
+ account->cancellable,
+ &error,
+ "name", name,
+ "app_id", CHAT_ACCOUNT_APP_ID,
+ NULL
+ );
+
+ if (error)
+ handle_account_secret_error(account, error);
+ else if (password)
+ {
+ GNUNET_CRYPTO_hash(
+ password,
+ g_utf8_strlen(password, -1),
+ &(account->secret)
+ );
+
+ secret_password_free(password);
+ }
+ else
+ {
+ struct GNUNET_HashCode raw_secret;
+
+ GNUNET_CRYPTO_random_block(
+ GNUNET_CRYPTO_QUALITY_STRONG,
+ &raw_secret, sizeof(raw_secret)
+ );
+
+ const char *secret_value = GNUNET_h2s_full(&raw_secret);
+
+ GNUNET_CRYPTO_hash_from_string(
+ secret_value,
+ &(account->secret)
+ );
+
+ char *desc = CHAT_ACCOUNT_DESC(name);
+
+ secret_password_store_sync(
+ CHAT_ACCOUNT_SECRET_SCHEMA,
+ SECRET_COLLECTION_DEFAULT,
+ desc,
+ secret_value,
+ account->cancellable,
+ &error,
+ "name", name,
+ "app_id", CHAT_ACCOUNT_APP_ID,
+ NULL
+ );
+
+ if (desc)
+ GNUNET_free(desc);
+
+ if (error)
+ handle_account_secret_error(account, error);
+ }
+
+ return &(account->secret);
+}
+
void
account_update_ego (struct GNUNET_CHAT_Account *account,
struct GNUNET_CHAT_Handle *handle,
@@ -128,6 +227,54 @@ account_delete (struct GNUNET_CHAT_Account *account)
GNUNET_assert(account);
// TODO: clear namestore entries
+
+ drop_account_secret(account);
+}
+
+void
+account_rename (struct GNUNET_CHAT_Account *account,
+ const char *new_name)
+{
+ GNUNET_assert(account);
+
+ GError *error = NULL;
+ const char *name = account_get_name(account);
+
+ gchar *password = secret_password_lookup_sync(
+ CHAT_ACCOUNT_SECRET_SCHEMA,
+ account->cancellable,
+ &error,
+ "name", name,
+ "app_id", CHAT_ACCOUNT_APP_ID,
+ NULL
+ );
+
+ if (error)
+ handle_account_secret_error(account, error);
+ else if (password)
+ {
+ char *desc = CHAT_ACCOUNT_DESC(new_name);
+
+ secret_password_store_sync(
+ CHAT_ACCOUNT_SECRET_SCHEMA,
+ SECRET_COLLECTION_DEFAULT,
+ desc,
+ password,
+ account->cancellable,
+ &error,
+ "name", new_name,
+ "app_id", CHAT_ACCOUNT_APP_ID,
+ NULL
+ );
+
+ if (desc)
+ GNUNET_free(desc);
+
+ secret_password_free(password);
+ handle_account_secret_error(account, error);
+ }
+
+ drop_account_secret(account);
}
void
diff --git a/src/gnunet_chat_account.h b/src/gnunet_chat_account.h
@@ -1,6 +1,6 @@
/*
This file is part of GNUnet.
- Copyright (C) 2022--2025 GNUnet e.V.
+ Copyright (C) 2022--2026 GNUnet e.V.
GNUnet is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
@@ -29,14 +29,21 @@
#include <gnunet/gnunet_identity_service.h>
#include <gnunet/gnunet_util_lib.h>
+#include <glib-2.0/gio/gio.h>
+#include <libsecret/secret.h>
+
struct GNUNET_CHAT_Handle;
struct GNUNET_CHAT_Account
{
+ struct GNUNET_CHAT_Handle *handle;
+
struct GNUNET_IDENTITY_Ego *ego;
enum GNUNET_GenericReturnValue created;
char *name;
+ struct GNUNET_HashCode secret;
+ GCancellable *cancellable;
void *user_pointer;
};
@@ -44,22 +51,26 @@ struct GNUNET_CHAT_Account
/**
* Creates a chat account using a given <i>name</i>.
*
+ * @param[in,out] handle Handle
* @param[in] name Name
* @return New chat account
*/
struct GNUNET_CHAT_Account*
-account_create (const char *name);
+account_create (struct GNUNET_CHAT_Handle *handle,
+ const char *name);
/**
* Creates a chat account using a given <i>ego</i> and
* a matching <i>name</i>.
*
+ * @param[in,out] handle Handle
* @param[in] ego EGO
* @param[in] name Name
* @return New chat account
*/
struct GNUNET_CHAT_Account*
-account_create_from_ego (struct GNUNET_IDENTITY_Ego *ego,
+account_create_from_ego (struct GNUNET_CHAT_Handle *handle,
+ struct GNUNET_IDENTITY_Ego *ego,
const char *name);
/**
@@ -82,6 +93,16 @@ const char*
account_get_name (const struct GNUNET_CHAT_Account *account);
/**
+ * Returns the secret for local key storage from a given
+ * chat <i>account</i>.
+ *
+ * @param[in] account Chat account
+ * @return Secret or NULL
+ */
+const struct GNUNET_HashCode*
+account_get_secret (struct GNUNET_CHAT_Account *account);
+
+/**
* Updates the key from a given chat <i>account</i> using
* the chat <i>handle</i> and a specific <i>ego</i> matching
* the accounts name.
@@ -104,6 +125,16 @@ void
account_delete (struct GNUNET_CHAT_Account *account);
/**
+ * Updates a chat <i>account</i> name related metadata.
+ *
+ * @param[in,out] account Chat account
+ * @param[in] new_name New name
+ */
+void
+account_rename (struct GNUNET_CHAT_Account *account,
+ const char *new_name);
+
+/**
* Destroys a chat <i>account</i> and frees its memory.
*
* @param[in,out] account Chat account
diff --git a/src/gnunet_chat_account_intern.c b/src/gnunet_chat_account_intern.c
@@ -0,0 +1,190 @@
+/*
+ This file is part of GNUnet.
+ Copyright (C) 2026 GNUnet e.V.
+
+ GNUnet is free software: you can redistribute it and/or modify it
+ under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ GNUnet 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
+ Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ SPDX-License-Identifier: AGPL3.0-or-later
+ */
+/*
+ * @author Tobias Frisch
+ * @file gnunet_chat_account_intern.c
+ */
+
+#include <gnunet/gnunet_util_lib.h>
+#include <libsecret/secret.h>
+
+const SecretSchema * account_secret_schema (void) G_GNUC_CONST;
+
+#define CHAT_ACCOUNT_SECRET_SCHEMA account_secret_schema()
+#define CHAT_ACCOUNT_DESC(name) account_secret_description(name)
+#define CHAT_ACCOUNT_APP_ID "org.gnunet.Messenger"
+
+const SecretSchema *
+account_secret_schema(void)
+{
+ static const SecretSchema the_schema = {
+ "org.gnunet.chat.AccountSecret", SECRET_SCHEMA_NONE,
+ {
+ { "name", SECRET_SCHEMA_ATTRIBUTE_STRING },
+ { "app_id", SECRET_SCHEMA_ATTRIBUTE_STRING },
+ { "NULL", 0 },
+ }
+ };
+ return &the_schema;
+}
+
+char*
+account_secret_description(const char *name)
+{
+ char *desc;
+
+ GNUNET_asprintf(
+ &desc,
+ "GNUnet Messenger account secret for identity %s",
+ name
+ );
+
+ return desc;
+}
+
+static void
+handle_account_secret_error(struct GNUNET_CHAT_Account *account,
+ GError *error)
+{
+ GNUNET_assert(account);
+
+ if (!error)
+ return;
+
+ if (error->message)
+ handle_send_internal_message(
+ account->handle,
+ account,
+ NULL,
+ GNUNET_CHAT_KIND_WARNING,
+ error->message,
+ GNUNET_YES
+ );
+
+ g_error_free(error);
+}
+
+static void
+on_account_secret_stored (GNUNET_UNUSED GObject *source,
+ GAsyncResult *result,
+ gpointer cls)
+{
+ GNUNET_assert((result) && (cls));
+
+ struct GNUNET_CHAT_Account *account = cls;
+ GError *error = NULL;
+
+ secret_password_store_finish(result, &error);
+
+ if (account->cancellable)
+ {
+ g_object_unref(account->cancellable);
+ account->cancellable = NULL;
+ }
+
+ handle_account_secret_error(account, error);
+}
+
+static void
+on_account_secret_lookup (GNUNET_UNUSED GObject *source,
+ GAsyncResult *result,
+ gpointer cls)
+{
+ GNUNET_assert((result) && (cls));
+
+ struct GNUNET_CHAT_Account *account = cls;
+ GError *error = NULL;
+
+ gchar *password = secret_password_lookup_finish(result, &error);
+
+ if (account->cancellable)
+ {
+ g_object_unref(account->cancellable);
+ account->cancellable = NULL;
+ }
+
+ if (error)
+ handle_account_secret_error(account, error);
+ else if (password)
+ {
+ GNUNET_CRYPTO_hash(
+ password,
+ g_utf8_strlen(password, -1),
+ &(account->secret)
+ );
+
+ secret_password_free(password);
+ }
+ else
+ {
+ struct GNUNET_HashCode raw_secret;
+
+ GNUNET_CRYPTO_random_block(
+ GNUNET_CRYPTO_QUALITY_STRONG,
+ &raw_secret, sizeof(raw_secret)
+ );
+
+ const char *secret_value = GNUNET_h2s_full(&raw_secret);
+ const char *name = account_get_name(account);
+
+ GNUNET_CRYPTO_hash_from_string(
+ secret_value,
+ &(account->secret)
+ );
+
+ char *desc = CHAT_ACCOUNT_DESC(name);
+
+ secret_password_store(
+ CHAT_ACCOUNT_SECRET_SCHEMA,
+ SECRET_COLLECTION_DEFAULT,
+ desc,
+ secret_value,
+ account->cancellable,
+ on_account_secret_stored,
+ account,
+ "name", name,
+ "app_id", CHAT_ACCOUNT_APP_ID,
+ NULL
+ );
+
+ if (desc)
+ GNUNET_free(desc);
+ }
+}
+
+static void
+drop_account_secret(struct GNUNET_CHAT_Account *account)
+{
+ GNUNET_assert(account);
+
+ GError *error = NULL;
+ const char *name = account_get_name(account);
+
+ secret_password_clear_sync(
+ CHAT_ACCOUNT_SECRET_SCHEMA,
+ account->cancellable,
+ &error,
+ "name", name,
+ "app_id", CHAT_ACCOUNT_APP_ID,
+ NULL
+ );
+
+ handle_account_secret_error(account, error);
+}
diff --git a/src/gnunet_chat_handle.c b/src/gnunet_chat_handle.c
@@ -1,6 +1,6 @@
/*
This file is part of GNUnet.
- Copyright (C) 2021--2025 GNUnet e.V.
+ Copyright (C) 2021--2026 GNUnet e.V.
GNUnet is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
@@ -365,8 +365,11 @@ handle_connect (struct GNUNET_CHAT_Handle *handle,
const char *name = account_get_name(account);
+ const struct GNUNET_HashCode *secret;
+ secret = account_get_secret(account);
+
handle->messenger = GNUNET_MESSENGER_connect(
- handle->cfg, name, key,
+ handle->cfg, name, key, secret,
on_handle_message,
handle
);
@@ -705,6 +708,8 @@ handle_rename_account (struct GNUNET_CHAT_Handle *handle,
if (GNUNET_OK != result)
return result;
+ account_rename(accounts->account, new_name);
+
accounts->op = GNUNET_IDENTITY_rename(
handle->identity,
old_name,
diff --git a/src/gnunet_chat_handle_intern.c b/src/gnunet_chat_handle_intern.c
@@ -1,6 +1,6 @@
/*
This file is part of GNUnet.
- Copyright (C) 2021--2025 GNUnet e.V.
+ Copyright (C) 2021--2026 GNUnet e.V.
GNUnet is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
@@ -371,7 +371,7 @@ skip_account:
accounts = internal_accounts_create(
handle,
- account_create_from_ego(ego, name)
+ account_create_from_ego(handle, ego, name)
);
send_refresh:
@@ -401,7 +401,7 @@ cb_account_creation (void *cls,
if ((!(accounts->account)) && (accounts->identifier))
accounts->account = account_create(
- accounts->identifier
+ accounts->handle, accounts->identifier
);
internal_accounts_stop_method(accounts);
diff --git a/tools/gnunet_messenger_ping.c b/tools/gnunet_messenger_ping.c
@@ -1,6 +1,6 @@
/*
This file is part of GNUnet.
- Copyright (C) 2025 GNUnet e.V.
+ Copyright (C) 2025--2026 GNUnet e.V.
GNUnet is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
@@ -61,6 +61,7 @@ struct GNUNET_MESSENGER_PingTool
char *ego_name;
char *room_name;
+ char *secret_value;
uint count;
uint timeout;
uint delay;
@@ -477,10 +478,15 @@ ego_lookup (void *cls,
const struct GNUNET_CRYPTO_BlindablePrivateKey *key;
key = ego? GNUNET_IDENTITY_ego_get_private_key(ego) : NULL;
+ struct GNUNET_HashCode secret;
+ if (tool->secret_value)
+ GNUNET_CRYPTO_hash_from_string (tool->secret_value, &secret);
+
tool->handle = GNUNET_MESSENGER_connect(
tool->cfg,
tool->ego_name,
key,
+ tool->secret_value? &secret : NULL,
message_callback,
tool
);
@@ -633,6 +639,13 @@ main (int argc,
"name of room to read messages from",
&(tool.room_name)
),
+ GNUNET_GETOPT_option_string(
+ 'S',
+ "secret",
+ "SECRET",
+ "secret for local key storage",
+ &(tool.secret_value)
+ ),
GNUNET_GETOPT_option_uint(
'c',
"count",
diff --git a/tools/gnunet_messenger_uml.c b/tools/gnunet_messenger_uml.c
@@ -1,6 +1,6 @@
/*
This file is part of GNUnet.
- Copyright (C) 2024--2025 GNUnet e.V.
+ Copyright (C) 2024--2026 GNUnet e.V.
GNUnet is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
@@ -62,6 +62,7 @@ struct GNUNET_MESSENGER_Tool
char *ego_name;
char *room_name;
+ char *secret_value;
int public_room;
int ignore_targets;
int ignore_epochs;
@@ -401,10 +402,15 @@ ego_lookup (void *cls,
const struct GNUNET_CRYPTO_BlindablePrivateKey *key;
key = ego? GNUNET_IDENTITY_ego_get_private_key(ego) : NULL;
+ struct GNUNET_HashCode secret;
+ if (tool->secret_value)
+ GNUNET_CRYPTO_hash_from_string (tool->secret_value, &secret);
+
tool->handle = GNUNET_MESSENGER_connect(
tool->cfg,
tool->ego_name,
key,
+ tool->secret_value? &secret : NULL,
message_callback,
tool
);
@@ -491,6 +497,13 @@ main (int argc,
"name of room to read messages from",
&(tool.room_name)
),
+ GNUNET_GETOPT_option_string(
+ 'S',
+ "secret",
+ "SECRET",
+ "secret for local key storage",
+ &(tool.secret_value)
+ ),
GNUNET_GETOPT_option_flag(
'P',
"public",