libgnunetchat

library for GNUnet Messenger
Log | Files | Refs | README | LICENSE

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:
MDoxyfile | 2+-
Minclude/gnunet/gnunet_chat_lib.h | 4++--
Mmeson.build | 5++++-
Msrc/gnunet_chat_account.c | 155++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/gnunet_chat_account.h | 37++++++++++++++++++++++++++++++++++---
Asrc/gnunet_chat_account_intern.c | 190+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/gnunet_chat_handle.c | 9+++++++--
Msrc/gnunet_chat_handle_intern.c | 6+++---
Mtools/gnunet_messenger_ping.c | 15++++++++++++++-
Mtools/gnunet_messenger_uml.c | 15++++++++++++++-
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",