libgnunetchat

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

gnunet_chat_util.c (11964B)


      1 /*
      2    This file is part of GNUnet.
      3    Copyright (C) 2021--2024, 2026 GNUnet e.V.
      4 
      5    GNUnet is free software: you can redistribute it and/or modify it
      6    under the terms of the GNU Affero General Public License as published
      7    by the Free Software Foundation, either version 3 of the License,
      8    or (at your option) any later version.
      9 
     10    GNUnet is distributed in the hope that it will be useful, but
     11    WITHOUT ANY WARRANTY; without even the implied warranty of
     12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13    Affero General Public License for more details.
     14 
     15    You should have received a copy of the GNU Affero General Public License
     16    along with this program.  If not, see <http://www.gnu.org/licenses/>.
     17 
     18    SPDX-License-Identifier: AGPL3.0-or-later
     19  */
     20 /*
     21  * @author Tobias Frisch
     22  * @file gnunet_chat_util.c
     23  */
     24 
     25 #include "gnunet_chat_util.h"
     26 
     27 #include <gnunet/gnunet_common.h>
     28 #include <gnunet/gnunet_messenger_service.h>
     29 #include <gnunet/gnunet_util_lib.h>
     30 
     31 #define GNUNET_CHAT_SALT_FILE "gnunet-chat-salt-file-encryption-conLMESm@&lME~l"
     32 
     33 static const char label_prefix_of_contact [] = "contact";
     34 static const char label_prefix_of_group [] = "group";
     35 
     36 static const char identity_prefix_of_lobby [] = "_gnunet_chat_lobby";
     37 
     38 void
     39 util_shorthash_from_member (const struct GNUNET_MESSENGER_Contact *member,
     40 			                      struct GNUNET_ShortHashCode *shorthash)
     41 {
     42   GNUNET_assert(shorthash);
     43 
     44   const size_t id = GNUNET_MESSENGER_contact_get_id(member);
     45 
     46   memset(shorthash, 0, sizeof(*shorthash));
     47   GNUNET_memcpy(
     48     shorthash,
     49     &id,
     50     sizeof(id) < sizeof(*shorthash) ? sizeof(id) : sizeof(*shorthash)
     51   );
     52 }
     53 
     54 void
     55 util_shorthash_from_discourse_id (const struct GNUNET_CHAT_DiscourseId *id,
     56                                   struct GNUNET_ShortHashCode *shorthash)
     57 {
     58   GNUNET_assert(shorthash);
     59 
     60   memset(shorthash, 0, sizeof(*shorthash));
     61   GNUNET_memcpy(
     62     shorthash,
     63     id,
     64     sizeof(*id) < sizeof(*shorthash) ? sizeof(*id) : sizeof(*shorthash)
     65   );
     66 }
     67 
     68 void
     69 util_discourse_id_from_shorthash (const struct GNUNET_ShortHashCode *shorthash,
     70                                   struct GNUNET_CHAT_DiscourseId *id)
     71 {
     72   GNUNET_assert(id);
     73 
     74   memset(id, 0, sizeof(*id));
     75   GNUNET_memcpy(
     76     id,
     77     shorthash,
     78     sizeof(*id) < sizeof(*shorthash) ? sizeof(*id) : sizeof(*shorthash)
     79   );
     80 }
     81 
     82 void
     83 util_set_name_field (const char *name,
     84                      char **field)
     85 {
     86   GNUNET_assert(field);
     87 
     88   if (*field)
     89     GNUNET_free(*field);
     90 
     91   if (name)
     92     *field = GNUNET_strdup(name);
     93   else
     94     *field = NULL;
     95 }
     96 
     97 enum GNUNET_GenericReturnValue
     98 util_hash_file (const char *filename,
     99                 struct GNUNET_HashCode *hash)
    100 {
    101   GNUNET_assert((filename) && (hash));
    102 
    103   uint64_t size;
    104 
    105   if (GNUNET_OK != GNUNET_DISK_file_size(filename, &size, GNUNET_NO, GNUNET_YES))
    106     return GNUNET_SYSERR;
    107 
    108   struct GNUNET_DISK_FileHandle *file = GNUNET_DISK_file_open(
    109     filename, GNUNET_DISK_OPEN_READ, GNUNET_DISK_PERM_USER_READ
    110   );
    111 
    112   if (!file)
    113     return GNUNET_SYSERR;
    114 
    115   struct GNUNET_DISK_MapHandle *mapping;
    116   const void* data;
    117 
    118   if (size > 0)
    119   {
    120     data = GNUNET_DISK_file_map(
    121 	    file, &mapping, GNUNET_DISK_MAP_TYPE_READ, size
    122     );
    123 
    124     if ((!data) || (!mapping))
    125     {
    126       GNUNET_DISK_file_close(file);
    127       return GNUNET_SYSERR;
    128     }
    129   }
    130   else
    131   {
    132     mapping = NULL;
    133     data = NULL;
    134   }
    135 
    136   GNUNET_CRYPTO_hash(data, size, hash);
    137 
    138   if (mapping)
    139     GNUNET_DISK_file_unmap(mapping);
    140 
    141   GNUNET_DISK_file_close(file);
    142   return GNUNET_OK;
    143 }
    144 
    145 enum GNUNET_GenericReturnValue
    146 util_encrypt_file (const char *filename,
    147                    const struct GNUNET_HashCode *hash,
    148                    const struct GNUNET_CRYPTO_SymmetricSessionKey *key)
    149 {
    150   GNUNET_assert((filename) && (hash));
    151 
    152   uint64_t size;
    153 
    154   if (GNUNET_OK != GNUNET_DISK_file_size(filename, &size, GNUNET_NO, GNUNET_YES))
    155     return GNUNET_SYSERR;
    156 
    157   struct GNUNET_DISK_FileHandle *file = GNUNET_DISK_file_open(
    158     filename, GNUNET_DISK_OPEN_READWRITE,
    159     GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE
    160   );
    161 
    162   if (!file)
    163     return GNUNET_SYSERR;
    164 
    165   if (!size)
    166     return GNUNET_DISK_file_close(file);
    167 
    168   struct GNUNET_DISK_MapHandle *mapping;
    169   const void* data = GNUNET_DISK_file_map(
    170     file, &mapping, GNUNET_DISK_MAP_TYPE_READWRITE, size
    171   );
    172 
    173   if ((!data) || (!mapping))
    174   {
    175     GNUNET_DISK_file_close(file);
    176     return GNUNET_SYSERR;
    177   }
    178 
    179   struct GNUNET_CRYPTO_SymmetricInitializationVector iv;
    180   const uint64_t block_size = 1024*1024;
    181   ssize_t result = 0;
    182 
    183   const uint64_t blocks = ((size + block_size - 1) / block_size);
    184 
    185   if (!key)
    186     goto skip_encryption;
    187 
    188   if (GNUNET_YES != GNUNET_CRYPTO_hkdf_gnunet (
    189         &iv, sizeof (iv),
    190         GNUNET_CHAT_SALT_FILE,
    191         sizeof (GNUNET_CHAT_SALT_FILE),
    192         key,
    193         sizeof (*key),
    194         GNUNET_CRYPTO_kdf_arg_auto(hash)))
    195     return GNUNET_SYSERR;
    196 
    197   for (uint64_t i = 0; i < blocks; i++)
    198   {
    199     const uint64_t index = (blocks - i - 1);
    200     const uint64_t offset = block_size * index;
    201 
    202     const uint64_t remaining = (size - offset);
    203     void* location = ((uint8_t*) data) + offset;
    204 
    205     if (index > 0)
    206       memcpy(&iv, ((uint8_t*) data) + (block_size * (index - 1)), sizeof(iv));
    207 
    208     result = GNUNET_CRYPTO_symmetric_encrypt(
    209     	location,
    210     	remaining >= block_size? block_size : remaining,
    211     	key,
    212     	&iv,
    213     	location
    214     );
    215 
    216     if (result < 0)
    217       break;
    218   }
    219 
    220 skip_encryption:
    221   if (GNUNET_OK != GNUNET_DISK_file_unmap(mapping))
    222     result = -1;
    223 
    224   if (GNUNET_OK != GNUNET_DISK_file_sync(file))
    225     result = -1;
    226 
    227   if (GNUNET_OK != GNUNET_DISK_file_close(file))
    228     result = -1;
    229 
    230   if (result < 0)
    231     return GNUNET_SYSERR;
    232 
    233   return GNUNET_OK;
    234 }
    235 
    236 enum GNUNET_GenericReturnValue
    237 util_decrypt_file (const char *filename,
    238                    const struct GNUNET_HashCode *hash,
    239                    const struct GNUNET_CRYPTO_SymmetricSessionKey *key)
    240 {
    241   GNUNET_assert((filename) && (hash));
    242 
    243   uint64_t size;
    244 
    245   if (GNUNET_OK != GNUNET_DISK_file_size(filename, &size, GNUNET_NO, GNUNET_YES))
    246     return GNUNET_SYSERR;
    247 
    248   struct GNUNET_DISK_FileHandle *file = GNUNET_DISK_file_open(
    249     filename, GNUNET_DISK_OPEN_READWRITE,
    250     GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE
    251   );
    252 
    253   if (!file)
    254     return GNUNET_SYSERR;
    255 
    256   struct GNUNET_DISK_MapHandle *mapping = NULL;
    257   void* data = GNUNET_DISK_file_map(
    258     file, &mapping, GNUNET_DISK_MAP_TYPE_READWRITE, size
    259   );
    260 
    261   if ((!data) || (!mapping))
    262   {
    263     GNUNET_DISK_file_close(file);
    264     return GNUNET_SYSERR;
    265   }
    266 
    267   struct GNUNET_CRYPTO_SymmetricInitializationVector iv;
    268   const uint64_t block_size = 1024*1024;
    269   struct GNUNET_HashCode check;
    270   ssize_t result = 0;
    271 
    272   const uint64_t blocks = ((size + block_size - 1) / block_size);
    273 
    274   if (!key)
    275     goto skip_decryption;
    276 
    277   if (GNUNET_YES != GNUNET_CRYPTO_hkdf_gnunet (
    278         &iv, sizeof (iv),
    279         GNUNET_CHAT_SALT_FILE,
    280         sizeof (GNUNET_CHAT_SALT_FILE),
    281         key,
    282         sizeof (*key),
    283         GNUNET_CRYPTO_kdf_arg_auto(hash)))
    284     return GNUNET_SYSERR;
    285 
    286   for (uint64_t index = 0; index < blocks; index++)
    287   {
    288     const uint64_t offset = block_size * index;
    289 
    290     const uint64_t remaining = (size - offset);
    291     void* location = ((uint8_t*) data) + offset;
    292 
    293     if (index > 0)
    294       memcpy(&iv, ((uint8_t*) data) + (block_size * (index - 1)), sizeof(iv));
    295 
    296     result = GNUNET_CRYPTO_symmetric_decrypt(
    297       location,
    298       remaining >= block_size? block_size : remaining,
    299       key,
    300       &iv,
    301       location
    302     );
    303 
    304     if (result < 0)
    305       break;
    306   }
    307 
    308 skip_decryption:
    309   GNUNET_CRYPTO_hash(data, size, &check);
    310 
    311   if (0 != GNUNET_CRYPTO_hash_cmp(hash, &check))
    312     result = -1;
    313 
    314   if (GNUNET_OK != GNUNET_DISK_file_unmap(mapping))
    315     result = -1;
    316 
    317   if (GNUNET_OK != GNUNET_DISK_file_sync(file))
    318     result = -1;
    319 
    320   if (GNUNET_OK != GNUNET_DISK_file_close(file))
    321     result = -1;
    322 
    323   if (result < 0)
    324     return GNUNET_SYSERR;
    325 
    326   return GNUNET_OK;
    327 }
    328 
    329 int
    330 util_get_dirname (const char *directory,
    331                   const char *subdir,
    332                   char **filename)
    333 {
    334   GNUNET_assert(
    335     (filename) &&
    336     (directory) &&
    337     (subdir)
    338   );
    339 
    340   return GNUNET_asprintf (
    341     filename,
    342     "%s/%s",
    343     directory,
    344     subdir
    345   );
    346 }
    347 
    348 int
    349 util_get_filename (const char *directory,
    350                    const char *subdir,
    351                    const struct GNUNET_HashCode *hash,
    352                    char **filename)
    353 {
    354   GNUNET_assert(
    355     (filename) &&
    356 		(directory) &&
    357 		(subdir) &&
    358 		(hash)
    359   );
    360 
    361   char* dirname;
    362   util_get_dirname(directory, subdir, &dirname);
    363 
    364   int result = GNUNET_asprintf (
    365     filename,
    366     "%s/%s",
    367     dirname,
    368     GNUNET_h2s_full(hash)
    369   );
    370 
    371   GNUNET_free(dirname);
    372   return result;
    373 }
    374 
    375 char*
    376 util_get_lower(const char *name)
    377 {
    378   GNUNET_assert(name);
    379 
    380   char *lower = GNUNET_STRINGS_utf8_tolower(name);
    381   if (lower == NULL)
    382     return GNUNET_strdup(name);
    383 
    384   return lower;
    385 }
    386 
    387 int
    388 util_get_context_label (enum GNUNET_CHAT_ContextType type,
    389                         const struct GNUNET_HashCode *hash,
    390                         char **label)
    391 {
    392   GNUNET_assert((hash) && (label));
    393 
    394   const char *type_string = "chat";
    395 
    396   switch (type)
    397   {
    398     case GNUNET_CHAT_CONTEXT_TYPE_CONTACT:
    399       type_string = "contact";
    400       break;
    401     case GNUNET_CHAT_CONTEXT_TYPE_GROUP:
    402       type_string = "group";
    403       break;
    404     default:
    405       break;
    406   }
    407 
    408   char *low = util_get_lower(GNUNET_h2s(hash));
    409 
    410   int result = GNUNET_asprintf (
    411     label,
    412     "%s_%s",
    413     type_string,
    414     low
    415   );
    416 
    417   GNUNET_free(low);
    418   return result;
    419 }
    420 
    421 enum GNUNET_CHAT_ContextType
    422 util_get_context_label_type (const char *label,
    423 			                       const struct GNUNET_HashCode *hash)
    424 {
    425   GNUNET_assert((hash) && (label));
    426 
    427   enum GNUNET_CHAT_ContextType type = GNUNET_CHAT_CONTEXT_TYPE_UNKNOWN;
    428 
    429   char *low = util_get_lower(GNUNET_h2s(hash));
    430 
    431   const char *sub = strstr(label, low);
    432   if ((!sub) || (sub == label) || (sub[-1] != '_'))
    433     goto cleanup;
    434 
    435   const size_t len = (size_t) (sub - label - 1);
    436 
    437   if (0 == strncmp(label, label_prefix_of_group, len))
    438     type = GNUNET_CHAT_CONTEXT_TYPE_GROUP;
    439   else if (0 == strncmp(label, label_prefix_of_contact, len))
    440     type = GNUNET_CHAT_CONTEXT_TYPE_CONTACT;
    441 
    442 cleanup:
    443   GNUNET_free(low);
    444   return type;
    445 }
    446 
    447 int
    448 util_lobby_name (const struct GNUNET_HashCode *hash,
    449 		             char **name)
    450 {
    451   GNUNET_assert((hash) && (name));
    452 
    453   char *low = util_get_lower(GNUNET_h2s(hash));
    454 
    455   int result = GNUNET_asprintf (
    456     name,
    457     "%s_%s",
    458     identity_prefix_of_lobby,
    459     low
    460   );
    461 
    462   GNUNET_free(low);
    463   return result;
    464 }
    465 
    466 enum GNUNET_GenericReturnValue
    467 util_is_lobby_name(const char *name)
    468 {
    469   GNUNET_assert(name);
    470 
    471   const char *sub = strstr(name, identity_prefix_of_lobby);
    472   if ((!sub) || (sub != name))
    473     return GNUNET_NO;
    474 
    475   const size_t len = strlen(identity_prefix_of_lobby);
    476 
    477   if (name[len] != '_')
    478     return GNUNET_NO;
    479   else
    480     return GNUNET_YES;
    481 }
    482 
    483 enum GNUNET_CHAT_MessageKind
    484 util_message_kind_from_kind (enum GNUNET_MESSENGER_MessageKind kind)
    485 {
    486   switch (kind)
    487   {
    488     case GNUNET_MESSENGER_KIND_JOIN:
    489       return GNUNET_CHAT_KIND_JOIN;
    490     case GNUNET_MESSENGER_KIND_LEAVE:
    491       return GNUNET_CHAT_KIND_LEAVE;
    492     case GNUNET_MESSENGER_KIND_NAME:
    493     case GNUNET_MESSENGER_KIND_KEY:
    494     case GNUNET_MESSENGER_KIND_ID:
    495       return GNUNET_CHAT_KIND_CONTACT;
    496     case GNUNET_MESSENGER_KIND_INVITE:
    497       return GNUNET_CHAT_KIND_INVITATION;
    498     case GNUNET_MESSENGER_KIND_TEXT:
    499       return GNUNET_CHAT_KIND_TEXT;
    500     case GNUNET_MESSENGER_KIND_FILE:
    501       return GNUNET_CHAT_KIND_FILE;
    502     case GNUNET_MESSENGER_KIND_DELETION:
    503       return GNUNET_CHAT_KIND_DELETION;
    504     case GNUNET_MESSENGER_KIND_TICKET:
    505       return GNUNET_CHAT_KIND_SHARED_ATTRIBUTES;
    506     case GNUNET_MESSENGER_KIND_TAG:
    507       return GNUNET_CHAT_KIND_TAG;
    508     case GNUNET_MESSENGER_KIND_SUBSCRIBTION:
    509       return GNUNET_CHAT_KIND_DISCOURSE;
    510     case GNUNET_MESSENGER_KIND_TALK:
    511       return GNUNET_CHAT_KIND_DATA;
    512     default:
    513       return GNUNET_CHAT_KIND_UNKNOWN;
    514   }
    515 }