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 }