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 }