libgnunetchat

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

gnunet_chat_util.c (12234B)


      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   const uint64_t block_size = 1024*1024;
    180   ssize_t result = 0;
    181 
    182   const uint64_t blocks = ((size + block_size - 1) / block_size);
    183 
    184   if (!key)
    185     goto skip_encryption;
    186 
    187   struct GNUNET_CRYPTO_SymmetricInitializationVector first_iv;
    188 
    189   if (GNUNET_YES != GNUNET_CRYPTO_hkdf_gnunet (
    190         &first_iv, sizeof (first_iv),
    191         GNUNET_CHAT_SALT_FILE,
    192         sizeof (GNUNET_CHAT_SALT_FILE),
    193         key,
    194         sizeof (*key),
    195         GNUNET_CRYPTO_kdf_arg_auto(hash)))
    196     return GNUNET_SYSERR;
    197   
    198   struct GNUNET_CRYPTO_SymmetricInitializationVector iv;
    199 
    200   for (uint64_t i = 0; i < blocks; i++)
    201   {
    202     const uint64_t index = (blocks - i - 1);
    203     const uint64_t offset = block_size * index;
    204 
    205     const uint64_t remaining = (size - offset);
    206     void* location = ((uint8_t*) data) + offset;
    207 
    208     if (index > 0)
    209       memcpy(&iv, ((uint8_t*) data) + (block_size * (index - 1)), sizeof(iv));
    210 	else
    211       memcpy(&iv, &first_iv, sizeof(iv));
    212 
    213     result = GNUNET_CRYPTO_symmetric_encrypt(
    214     	location,
    215     	remaining >= block_size? block_size : remaining,
    216     	key,
    217     	&iv,
    218     	location
    219     );
    220 
    221     if (result < 0)
    222       break;
    223   }
    224 
    225 skip_encryption:
    226   if (GNUNET_OK != GNUNET_DISK_file_unmap(mapping))
    227     result = -1;
    228 
    229   if (GNUNET_OK != GNUNET_DISK_file_sync(file))
    230     result = -1;
    231 
    232   if (GNUNET_OK != GNUNET_DISK_file_close(file))
    233     result = -1;
    234 
    235   if (result < 0)
    236     return GNUNET_SYSERR;
    237 
    238   return GNUNET_OK;
    239 }
    240 
    241 enum GNUNET_GenericReturnValue
    242 util_decrypt_file (const char *filename,
    243                    const struct GNUNET_HashCode *hash,
    244                    const struct GNUNET_CRYPTO_SymmetricSessionKey *key)
    245 {
    246   GNUNET_assert((filename) && (hash));
    247 
    248   uint64_t size;
    249 
    250   if (GNUNET_OK != GNUNET_DISK_file_size(filename, &size, GNUNET_NO, GNUNET_YES))
    251     return GNUNET_SYSERR;
    252 
    253   struct GNUNET_DISK_FileHandle *file = GNUNET_DISK_file_open(
    254     filename, GNUNET_DISK_OPEN_READWRITE,
    255     GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE
    256   );
    257   
    258   if (!file)
    259     return GNUNET_SYSERR;
    260   
    261   struct GNUNET_DISK_MapHandle *mapping = NULL;
    262   void* data = GNUNET_DISK_file_map(
    263     file, &mapping, GNUNET_DISK_MAP_TYPE_READWRITE, size
    264   );
    265 
    266   if ((!data) || (!mapping))
    267   {
    268     GNUNET_DISK_file_close(file);
    269     return GNUNET_SYSERR;
    270   }
    271   
    272   const uint64_t block_size = 1024*1024;
    273   struct GNUNET_HashCode check;
    274   ssize_t result = 0;
    275 
    276   const uint64_t blocks = ((size + block_size - 1) / block_size);
    277 
    278   if (!key)
    279     goto skip_decryption;
    280   
    281   struct GNUNET_CRYPTO_SymmetricInitializationVector first_iv;
    282   
    283   if (GNUNET_YES != GNUNET_CRYPTO_hkdf_gnunet (
    284         &first_iv, sizeof (first_iv),
    285         GNUNET_CHAT_SALT_FILE,
    286         sizeof (GNUNET_CHAT_SALT_FILE),
    287         key,
    288         sizeof (*key),
    289         GNUNET_CRYPTO_kdf_arg_auto(hash)))
    290     return GNUNET_SYSERR;
    291   
    292   struct GNUNET_CRYPTO_SymmetricInitializationVector iv;
    293 
    294   for (uint64_t index = 0; index < blocks; index++)
    295   {
    296     const uint64_t offset = block_size * index;
    297 
    298     const uint64_t remaining = (size - offset);
    299     void* location = ((uint8_t*) data) + offset;
    300 
    301     if (index > 0)
    302       memcpy(&iv, ((uint8_t*) data) + (block_size * (index - 1)), sizeof(iv));
    303 	else
    304       memcpy(&iv, &first_iv, sizeof(iv));
    305 
    306     result = GNUNET_CRYPTO_symmetric_decrypt(
    307       location,
    308       remaining >= block_size? block_size : remaining,
    309       key,
    310       &iv,
    311       location
    312     );
    313 
    314     if (result < 0)
    315       break;
    316   }
    317   
    318 skip_decryption:
    319   GNUNET_CRYPTO_hash(data, size, &check);
    320 
    321   if (0 != GNUNET_CRYPTO_hash_cmp(hash, &check))
    322     result = -1;
    323 
    324   if (GNUNET_OK != GNUNET_DISK_file_unmap(mapping))
    325     result = -1;
    326 
    327   if (GNUNET_OK != GNUNET_DISK_file_sync(file))
    328     result = -1;
    329 
    330   if (GNUNET_OK != GNUNET_DISK_file_close(file))
    331     result = -1;
    332   
    333   if (result < 0)
    334     return GNUNET_SYSERR;
    335   
    336   return GNUNET_OK;
    337 }
    338 
    339 int
    340 util_get_dirname (const char *directory,
    341                   const char *subdir,
    342                   char **filename)
    343 {
    344   GNUNET_assert(
    345     (filename) &&
    346     (directory) &&
    347     (subdir)
    348   );
    349 
    350   return GNUNET_asprintf (
    351     filename,
    352     "%s/%s",
    353     directory,
    354     subdir
    355   );
    356 }
    357 
    358 int
    359 util_get_filename (const char *directory,
    360                    const char *subdir,
    361                    const struct GNUNET_HashCode *hash,
    362                    char **filename)
    363 {
    364   GNUNET_assert(
    365     (filename) &&
    366 		(directory) &&
    367 		(subdir) &&
    368 		(hash)
    369   );
    370 
    371   char* dirname;
    372   util_get_dirname(directory, subdir, &dirname);
    373 
    374   int result = GNUNET_asprintf (
    375     filename,
    376     "%s/%s",
    377     dirname,
    378     GNUNET_h2s_full(hash)
    379   );
    380 
    381   GNUNET_free(dirname);
    382   return result;
    383 }
    384 
    385 char*
    386 util_get_lower(const char *name)
    387 {
    388   GNUNET_assert(name);
    389 
    390   char *lower = GNUNET_STRINGS_utf8_tolower(name);
    391   if (lower == NULL)
    392     return GNUNET_strdup(name);
    393 
    394   return lower;
    395 }
    396 
    397 int
    398 util_get_context_label (enum GNUNET_CHAT_ContextType type,
    399                         const struct GNUNET_HashCode *hash,
    400                         char **label)
    401 {
    402   GNUNET_assert((hash) && (label));
    403 
    404   const char *type_string = "chat";
    405 
    406   switch (type)
    407   {
    408     case GNUNET_CHAT_CONTEXT_TYPE_CONTACT:
    409       type_string = "contact";
    410       break;
    411     case GNUNET_CHAT_CONTEXT_TYPE_GROUP:
    412       type_string = "group";
    413       break;
    414     default:
    415       break;
    416   }
    417 
    418   char *low = util_get_lower(GNUNET_h2s(hash));
    419 
    420   int result = GNUNET_asprintf (
    421     label,
    422     "%s_%s",
    423     type_string,
    424     low
    425   );
    426 
    427   GNUNET_free(low);
    428   return result;
    429 }
    430 
    431 enum GNUNET_CHAT_ContextType
    432 util_get_context_label_type (const char *label,
    433 			                       const struct GNUNET_HashCode *hash)
    434 {
    435   GNUNET_assert((hash) && (label));
    436 
    437   enum GNUNET_CHAT_ContextType type = GNUNET_CHAT_CONTEXT_TYPE_UNKNOWN;
    438 
    439   char *low = util_get_lower(GNUNET_h2s(hash));
    440 
    441   const char *sub = strstr(label, low);
    442   if ((!sub) || (sub == label) || (sub[-1] != '_'))
    443     goto cleanup;
    444 
    445   const size_t len = (size_t) (sub - label - 1);
    446 
    447   if (0 == strncmp(label, label_prefix_of_group, len))
    448     type = GNUNET_CHAT_CONTEXT_TYPE_GROUP;
    449   else if (0 == strncmp(label, label_prefix_of_contact, len))
    450     type = GNUNET_CHAT_CONTEXT_TYPE_CONTACT;
    451 
    452 cleanup:
    453   GNUNET_free(low);
    454   return type;
    455 }
    456 
    457 int
    458 util_lobby_name (const struct GNUNET_HashCode *hash,
    459 		             char **name)
    460 {
    461   GNUNET_assert((hash) && (name));
    462 
    463   char *low = util_get_lower(GNUNET_h2s(hash));
    464 
    465   int result = GNUNET_asprintf (
    466     name,
    467     "%s_%s",
    468     identity_prefix_of_lobby,
    469     low
    470   );
    471 
    472   GNUNET_free(low);
    473   return result;
    474 }
    475 
    476 enum GNUNET_GenericReturnValue
    477 util_is_lobby_name(const char *name)
    478 {
    479   GNUNET_assert(name);
    480 
    481   const char *sub = strstr(name, identity_prefix_of_lobby);
    482   if ((!sub) || (sub != name))
    483     return GNUNET_NO;
    484 
    485   const size_t len = strlen(identity_prefix_of_lobby);
    486 
    487   if (name[len] != '_')
    488     return GNUNET_NO;
    489   else
    490     return GNUNET_YES;
    491 }
    492 
    493 enum GNUNET_CHAT_MessageKind
    494 util_message_kind_from_kind (enum GNUNET_MESSENGER_MessageKind kind)
    495 {
    496   switch (kind)
    497   {
    498     case GNUNET_MESSENGER_KIND_JOIN:
    499       return GNUNET_CHAT_KIND_JOIN;
    500     case GNUNET_MESSENGER_KIND_LEAVE:
    501       return GNUNET_CHAT_KIND_LEAVE;
    502     case GNUNET_MESSENGER_KIND_NAME:
    503     case GNUNET_MESSENGER_KIND_KEY:
    504     case GNUNET_MESSENGER_KIND_ID:
    505       return GNUNET_CHAT_KIND_CONTACT;
    506     case GNUNET_MESSENGER_KIND_INVITE:
    507       return GNUNET_CHAT_KIND_INVITATION;
    508     case GNUNET_MESSENGER_KIND_TEXT:
    509       return GNUNET_CHAT_KIND_TEXT;
    510     case GNUNET_MESSENGER_KIND_FILE:
    511       return GNUNET_CHAT_KIND_FILE;
    512     case GNUNET_MESSENGER_KIND_DELETION:
    513       return GNUNET_CHAT_KIND_DELETION;
    514     case GNUNET_MESSENGER_KIND_TICKET:
    515       return GNUNET_CHAT_KIND_SHARED_ATTRIBUTES;
    516     case GNUNET_MESSENGER_KIND_TAG:
    517       return GNUNET_CHAT_KIND_TAG;
    518     case GNUNET_MESSENGER_KIND_SUBSCRIBTION:
    519       return GNUNET_CHAT_KIND_DISCOURSE;
    520     case GNUNET_MESSENGER_KIND_TALK:
    521       return GNUNET_CHAT_KIND_DATA;
    522     default:
    523       return GNUNET_CHAT_KIND_UNKNOWN;
    524   }
    525 }