unrealircd- supernets unrealircd source & configuration |
git clone git://git.acid.vegas/unrealircd.git |
Log | Files | Refs | Archive | README | LICENSE |
extjwt.c (31694B)
1 /* 2 * IRC - Internet Relay Chat, src/modules/extjwt.c 3 * (C) 2021 The UnrealIRCd Team 4 * 5 * See file AUTHORS in IRC package for additional names of 6 * the programmers. 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License as published by 10 * the Free Software Foundation; either version 1, or (at your option) 11 * any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public License 19 * along with this program; if not, write to the Free Software 20 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 21 */ 22 23 #include "unrealircd.h" 24 25 #if defined(__GNUC__) 26 /* Temporarily ignore these for this entire file. FIXME later when updating the code for OpenSSL 3: */ 27 #pragma GCC diagnostic ignored "-Wdeprecated-declarations" 28 #endif 29 30 /* internal definitions */ 31 32 #define MSG_EXTJWT "EXTJWT" 33 #define MYCONF "extjwt" 34 35 #undef NEW_ISUPPORT /* enable this for https://github.com/ircv3/ircv3-specifications/pull/341#issuecomment-617038799 */ 36 37 #define EXTJWT_METHOD_NOT_SET 0 38 #define EXTJWT_METHOD_HS256 1 39 #define EXTJWT_METHOD_HS384 2 40 #define EXTJWT_METHOD_HS512 3 41 #define EXTJWT_METHOD_RS256 4 42 #define EXTJWT_METHOD_RS384 5 43 #define EXTJWT_METHOD_RS512 6 44 #define EXTJWT_METHOD_ES256 7 45 #define EXTJWT_METHOD_ES384 8 46 #define EXTJWT_METHOD_ES512 9 47 #define EXTJWT_METHOD_NONE 10 48 49 #define NEEDS_KEY(x) (x>=EXTJWT_METHOD_RS256 && x<=EXTJWT_METHOD_ES512) 50 51 #define URL_LENGTH 4096 52 #define MODES_SIZE 41 /* about 10 mode chars */ 53 #define TS_LENGTH 19 /* 64-bit integer */ 54 #define MAX_TOKEN_CHUNK (510-sizeof(extjwt_message_pattern)-HOSTLEN-CHANNELLEN) 55 56 /* OpenSSL 1.0.x compatibility */ 57 58 #if (OPENSSL_VERSION_NUMBER < 0x10100000L) 59 void ECDSA_SIG_get0(const ECDSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps) 60 { 61 if (pr != NULL) 62 *pr = sig->r; 63 if (ps != NULL) 64 *ps = sig->s; 65 } 66 #endif 67 68 /* struct definitions */ 69 70 struct extjwt_config { 71 time_t exp_delay; 72 char *secret; 73 int method; 74 char *vfy; 75 }; 76 77 struct jwt_service { 78 char *name; 79 struct extjwt_config *cfg; 80 struct jwt_service *next; 81 }; 82 83 /* function declarations */ 84 85 CMD_FUNC(cmd_extjwt); 86 char *extjwt_make_payload(Client *client, Channel *channel, struct extjwt_config *config); 87 char *extjwt_generate_token(const char *payload, struct extjwt_config *config); 88 void b64url(char *b64); 89 unsigned char *extjwt_hmac_extjwt_hash(int method, const void *key, int keylen, const unsigned char *data, int datalen, unsigned int* resultlen); 90 unsigned char *extjwt_sha_pem_extjwt_hash(int method, const void *key, int keylen, const unsigned char *data, int datalen, unsigned int* resultlen); 91 unsigned char *extjwt_hash(int method, const void *key, int keylen, const unsigned char *data, int datalen, unsigned int* resultlen); 92 char *extjwt_gen_header(int method); 93 int extjwt_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs); 94 int extjwt_configrun(ConfigFile *cf, ConfigEntry *ce, int type); 95 int extjwt_configposttest(int *errs); 96 void extjwt_free_services(struct jwt_service **services); 97 struct jwt_service *find_jwt_service(struct jwt_service *services, const char *name); 98 int extjwt_valid_integer_string(const char *in, int min, int max); 99 char *extjwt_test_key(const char *file, int method); 100 char *extjwt_read_file_contents(const char *file, int absolute, int *size); 101 int EXTJWT_METHOD_from_string(const char *in); 102 #ifdef NEW_ISUPPORT 103 char *extjwt_isupport_param(void); 104 #endif 105 106 /* string constants */ 107 108 const char extjwt_message_pattern[] = ":%s EXTJWT %s %s %s%s"; 109 110 /* global structs */ 111 112 ModuleHeader MOD_HEADER = { 113 "extjwt", 114 "6.0", 115 "Command /EXTJWT (web service authorization)", 116 "UnrealIRCd Team", 117 "unrealircd-6" 118 }; 119 120 struct { 121 int have_secret; 122 int have_key; 123 int have_method; 124 int have_expire; 125 int have_vfy; 126 char *key_filename; 127 } cfg_state; 128 129 struct extjwt_config cfg; 130 struct jwt_service *jwt_services; 131 132 MOD_TEST() 133 { 134 memset(&cfg_state, 0, sizeof(cfg_state)); 135 HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, extjwt_configtest); 136 HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, extjwt_configposttest); 137 return MOD_SUCCESS; 138 } 139 140 MOD_INIT() 141 { 142 CommandAdd(modinfo->handle, MSG_EXTJWT, cmd_extjwt, 2, CMD_USER); 143 HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, extjwt_configrun); 144 return MOD_SUCCESS; 145 } 146 147 MOD_LOAD() 148 { 149 struct jwt_service *service = jwt_services; 150 #ifdef NEW_ISUPPORT 151 ISupportAdd(modinfo->handle, "EXTJWT", extjwt_isupport_param()); 152 #else 153 ISupportAdd(modinfo->handle, "EXTJWT", "1"); 154 #endif 155 while (service) 156 { /* copy default exp to all services not having one specified */ 157 if (service->cfg->exp_delay == 0) 158 service->cfg->exp_delay = cfg.exp_delay; 159 service = service->next; 160 } 161 return MOD_SUCCESS; 162 } 163 164 MOD_UNLOAD() 165 { 166 extjwt_free_services(&jwt_services); 167 return MOD_SUCCESS; 168 } 169 170 #ifdef NEW_ISUPPORT 171 char *extjwt_isupport_param(void) 172 { 173 struct jwt_service *services = jwt_services; 174 int count = 0; 175 static char buf[500]; 176 strlcpy(buf, "V:1", sizeof(buf)); 177 while (services) 178 { 179 strlcat(buf, count?",":"&S:", sizeof(buf)); 180 strlcat(buf, services->name, sizeof(buf)); 181 count++; 182 services = services->next; 183 } 184 return buf; 185 } 186 #endif 187 188 void extjwt_free_services(struct jwt_service **services){ 189 struct jwt_service *ss, *next; 190 ss = *services; 191 while (ss) 192 { 193 next = ss->next; 194 safe_free(ss->name); 195 if (ss->cfg) 196 safe_free(ss->cfg->secret); 197 safe_free(ss->cfg); 198 safe_free(ss); 199 ss = next; 200 } 201 *services = NULL; 202 } 203 204 struct jwt_service *find_jwt_service(struct jwt_service *services, const char *name) 205 { 206 if (!name) 207 return NULL; 208 while (services) 209 { 210 if (services->name && !strcmp(services->name, name)) 211 return services; 212 services = services->next; 213 } 214 return NULL; 215 } 216 217 int extjwt_valid_integer_string(const char *in, int min, int max) 218 { 219 int i, val; 220 if (BadPtr(in)) 221 return 0; 222 for (i=0; in[i]; i++){ 223 if (!isdigit(in[i])) 224 return 0; 225 } 226 val = atoi(in); 227 if (val < min || val > max) 228 return 0; 229 return 1; 230 } 231 232 int vfy_url_is_valid(const char *string) 233 { 234 if (strstr(string, "http://") == string || strstr(string, "https://") == string) 235 { 236 if (strstr(string, "%s")) 237 return 1; 238 } 239 return 0; 240 } 241 242 char *extjwt_test_key(const char *file, int method) 243 { /* returns NULL when valid */ 244 int fsize; 245 char *fcontent = NULL; 246 char *retval = NULL; 247 BIO *bufkey = NULL; 248 EVP_PKEY *pkey = NULL; 249 int type, pkey_type; 250 do { 251 switch (method) 252 { 253 case EXTJWT_METHOD_RS256: case EXTJWT_METHOD_RS384: case EXTJWT_METHOD_RS512: 254 type = EVP_PKEY_RSA; 255 break; 256 case EXTJWT_METHOD_ES256: case EXTJWT_METHOD_ES384: case EXTJWT_METHOD_ES512: 257 type = EVP_PKEY_EC; 258 break; 259 default: 260 retval = "Internal error (invalid type)"; 261 return retval; 262 } 263 fcontent = extjwt_read_file_contents(file, 0, &fsize); 264 if (!fcontent) 265 { 266 retval = "Cannot open file"; 267 break; 268 } 269 if (fsize == 0) 270 { 271 retval = "File is empty"; 272 break; 273 } 274 if (!(bufkey = BIO_new_mem_buf(fcontent, fsize))) 275 { 276 retval = "Unknown error"; 277 break; 278 } 279 if (!(pkey = PEM_read_bio_PrivateKey(bufkey, NULL, NULL, NULL))) 280 { 281 retval = "Key is invalid"; 282 break; 283 } 284 pkey_type = EVP_PKEY_id(pkey); 285 if (type != pkey_type) 286 { 287 retval = "Key does not match method"; 288 break; 289 } 290 } while (0); 291 safe_free(fcontent); 292 if (bufkey) 293 BIO_free(bufkey); 294 if (pkey) 295 EVP_PKEY_free(pkey); 296 return retval; 297 } 298 299 int EXTJWT_METHOD_from_string(const char *in) 300 { 301 if (!strcmp(in, "HS256")) 302 return EXTJWT_METHOD_HS256; 303 if (!strcmp(in, "HS384")) 304 return EXTJWT_METHOD_HS384; 305 if (!strcmp(in, "HS512")) 306 return EXTJWT_METHOD_HS512; 307 if (!strcmp(in, "RS256")) 308 return EXTJWT_METHOD_RS256; 309 if (!strcmp(in, "RS384")) 310 return EXTJWT_METHOD_RS384; 311 if (!strcmp(in, "RS512")) 312 return EXTJWT_METHOD_RS512; 313 if (!strcmp(in, "ES256")) 314 return EXTJWT_METHOD_ES256; 315 if (!strcmp(in, "ES384")) 316 return EXTJWT_METHOD_ES384; 317 if (!strcmp(in, "ES512")) 318 return EXTJWT_METHOD_ES512; 319 if (!strcmp(in, "NONE")) 320 return EXTJWT_METHOD_NONE; 321 return EXTJWT_METHOD_NOT_SET; 322 } 323 324 /* Configuration is described in conf/modules.optional.conf */ 325 326 int extjwt_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs) 327 { 328 int errors = 0; 329 ConfigEntry *cep, *cep2; 330 int i; 331 struct jwt_service *services = NULL; 332 struct jwt_service **ss = &services; /* list for checking whether service names repeat */ 333 int have_ssecret, have_smethod, have_svfy, have_scert; 334 unsigned int sfilename_line_number = 0; 335 char *sfilename = NULL; 336 337 if (type != CONFIG_MAIN) 338 return 0; 339 340 if (!ce || strcmp(ce->name, MYCONF)) 341 return 0; 342 343 for (cep = ce->items; cep; cep = cep->next) 344 { 345 if (!cep->value) 346 { 347 config_error("%s:%i: blank %s::%s without value", cep->file->filename, cep->line_number, MYCONF, cep->name); 348 errors++; 349 continue; 350 } 351 if (!strcmp(cep->name, "method")) 352 { 353 if (cfg_state.have_method) 354 { 355 config_error("%s:%i: duplicate %s::%s item", cep->file->filename, cep->line_number, MYCONF, cep->name); 356 errors++; 357 continue; 358 } 359 cfg_state.have_method = EXTJWT_METHOD_from_string(cep->value); 360 if (cfg_state.have_method == EXTJWT_METHOD_NOT_SET) 361 { 362 config_error("%s:%i: invalid value %s::%s \"%s\" (check docs for allowed options)", cep->file->filename, cep->line_number, MYCONF, cep->name, cep->value); 363 errors++; 364 } 365 continue; 366 } 367 if (!strcmp(cep->name, "expire-after")) 368 { 369 if (!extjwt_valid_integer_string(cep->value, 1, 9999)) 370 { 371 config_error("%s:%i: %s::%s must be an integer between 1 and 9999 (seconds)", cep->file->filename, cep->line_number, MYCONF, cep->name); 372 errors++; 373 } 374 continue; 375 } 376 if (!strcmp(cep->name, "secret")) 377 { 378 if (cfg_state.have_secret) 379 { 380 config_error("%s:%i: duplicate %s::%s item", cep->file->filename, cep->line_number, MYCONF, cep->name); 381 errors++; 382 continue; 383 } 384 cfg_state.have_secret = 1; 385 if (strlen(cep->value) < 4) 386 { 387 config_error("%s:%i: Secret specified in %s::%s is too short!", cep->file->filename, cep->line_number, MYCONF, cep->name); 388 errors++; 389 } 390 continue; 391 } 392 if (!strcmp(cep->name, "key")) 393 { 394 if (cfg_state.have_key) 395 { 396 config_error("%s:%i: duplicate %s::%s item", cep->file->filename, cep->line_number, MYCONF, cep->name); 397 errors++; 398 continue; 399 } 400 if (!is_file_readable(cep->value, CONFDIR)) 401 { 402 config_error("%s:%i: Cannot open file \"%s\" specified in %s::%s for reading", cep->file->filename, cep->line_number, cep->value, MYCONF, cep->name); 403 errors++; 404 } 405 safe_strdup(cfg_state.key_filename, cep->value); 406 cfg_state.have_key = 1; 407 continue; 408 } 409 if (!strcmp(cep->name, "verify-url")) 410 { 411 if (cfg_state.have_vfy) 412 { 413 config_error("%s:%i: duplicate %s:%s item", cep->file->filename, cep->line_number, MYCONF, cep->name); 414 errors++; 415 continue; 416 } 417 cfg_state.have_vfy = 1; 418 if (!vfy_url_is_valid(cep->value)) 419 { 420 config_error("%s:%i: Optional URL specified in %s::%s is invalid!", cep->file->filename, cep->line_number, MYCONF, cep->name); 421 errors++; 422 continue; 423 } 424 if (strlen(cep->value) > URL_LENGTH) 425 { 426 config_error("%s:%i: Optional URL specified in %s::%s is too long!", cep->file->filename, cep->line_number, MYCONF, cep->name); 427 errors++; 428 } 429 continue; 430 } 431 if (!strcmp(cep->name, "service")) 432 { 433 have_ssecret = 0; 434 have_smethod = 0; 435 have_svfy = 0; 436 have_scert = 0; 437 if (strchr(cep->value, ' ') || strchr(cep->value, ',')) 438 { 439 config_error("%s:%i: Invalid %s::%s name (contains spaces or commas)", cep->file->filename, cep->line_number, MYCONF, cep->name); 440 errors++; 441 continue; 442 } 443 if (find_jwt_service(services, cep->value)) 444 { 445 config_error("%s:%i: Duplicate %s::%s name \"%s\"", cep->file->filename, cep->line_number, MYCONF, cep->name, cep->value); 446 errors++; 447 continue; 448 } 449 *ss = safe_alloc(sizeof(struct jwt_service)); /* store the new name for further checking */ 450 safe_strdup((*ss)->name, cep->value); 451 ss = &(*ss)->next; 452 for (cep2 = cep->items; cep2; cep2 = cep2->next) 453 { 454 if (!cep2->name || !cep2->value || !cep2->value[0]) 455 { 456 config_error("%s:%i: blank/incomplete %s::service entry", cep2->file->filename, cep2->line_number, MYCONF); 457 errors++; 458 continue; 459 } 460 461 if (!strcmp(cep2->name, "method")) 462 { 463 if (have_smethod) 464 { 465 config_error("%s:%i: duplicate %s::service::%s item", cep2->file->filename, cep2->line_number, MYCONF, cep2->name); 466 errors++; 467 continue; 468 } 469 have_smethod = EXTJWT_METHOD_from_string(cep2->value); 470 if (have_smethod == EXTJWT_METHOD_NOT_SET || have_smethod == EXTJWT_METHOD_NONE) 471 { 472 config_error("%s:%i: invalid value of optional %s::service::%s \"%s\" (check docs for allowed options)", cep2->file->filename, cep2->line_number, MYCONF, cep2->name, cep2->value); 473 errors++; 474 } 475 continue; 476 } 477 478 if (!strcmp(cep2->name, "secret")) 479 { 480 if (have_ssecret) 481 { 482 config_error("%s:%i: duplicate %s::service::%s item", cep2->file->filename, cep2->line_number, MYCONF, cep2->name); 483 errors++; 484 continue; 485 } 486 have_ssecret = 1; 487 if (strlen(cep2->value) < 4) /* TODO maybe a better check? */ 488 { 489 config_error("%s:%i: Secret specified in %s::service::%s is too short!", cep2->file->filename, cep2->line_number, MYCONF, cep2->name); 490 errors++; 491 } 492 continue; 493 } 494 495 if (!strcmp(cep2->name, "key")) 496 { 497 if (have_scert) 498 { 499 config_error("%s:%i: duplicate %s::service::%s item", cep2->file->filename, cep2->line_number, MYCONF, cep2->name); 500 errors++; 501 continue; 502 } 503 if (!is_file_readable(cep2->value, CONFDIR)) 504 { 505 config_error("%s:%i: Cannot open file \"%s\" specified in %s::service::%s for reading", cep2->file->filename, cep2->line_number, cep2->value, MYCONF, cep2->name); 506 errors++; 507 } 508 have_scert = 1; 509 safe_strdup(sfilename, cep2->value); 510 sfilename_line_number = cep2->line_number; 511 continue; 512 } 513 514 if (!strcmp(cep2->name, "expire-after")) 515 { 516 if (!extjwt_valid_integer_string(cep2->value, 1, 9999)) 517 { 518 config_error("%s:%i: %s::%s must be an integer between 1 and 9999 (seconds)", cep2->file->filename, cep2->line_number, MYCONF, cep2->name); 519 errors++; 520 } 521 continue; 522 } 523 524 if (!strcmp(cep2->name, "verify-url")) 525 { 526 if (have_svfy) 527 { 528 config_error("%s:%i: duplicate %s::service::%s item", cep2->file->filename, cep2->line_number, MYCONF, cep2->name); 529 errors++; 530 continue; 531 } 532 have_svfy = 1; 533 if (!vfy_url_is_valid(cep2->value)) 534 { 535 config_error("%s:%i: Optional URL specified in %s::service::%s is invalid!", cep2->file->filename, cep2->line_number, MYCONF, cep2->name); 536 errors++; 537 continue; 538 } 539 if (strlen(cep2->value) > URL_LENGTH) 540 { 541 config_error("%s:%i: Optional URL specified in %s::service::%s is too long!", cep2->file->filename, cep2->line_number, MYCONF, cep2->name); 542 errors++; 543 } 544 continue; 545 } 546 547 config_error("%s:%i: invalid %s::service attribute %s (must be one of: name, secret, expire-after)", cep2->file->filename, cep2->line_number, MYCONF, cep2->name); 548 errors++; 549 } 550 if (!have_smethod) 551 { 552 config_error("%s:%i: invalid %s::service entry (no %s::service::method specfied)", cep->file->filename, cep->line_number, MYCONF, MYCONF); 553 errors++; 554 continue; 555 } 556 if (have_ssecret && NEEDS_KEY(have_smethod)) 557 { 558 config_error("%s:%i: invalid %s::service entry (this method needs %s::service::key and not %s::service::secret option)", cep->file->filename, cep->line_number, MYCONF, MYCONF, MYCONF); 559 errors++; 560 continue; 561 } 562 if (have_scert && !NEEDS_KEY(have_smethod)) 563 { 564 config_error("%s:%i: invalid %s::service entry (this method needs %s::service::secret and not %s::service::key option)", cep->file->filename, cep->line_number, MYCONF, MYCONF, MYCONF); 565 errors++; 566 continue; 567 } 568 if (!have_ssecret && !NEEDS_KEY(have_smethod)) 569 { 570 config_error("%s:%i: invalid %s::service entry (must contain %s::service::secret option)", cep->file->filename, cep->line_number, MYCONF, MYCONF); 571 errors++; 572 continue; 573 } 574 if (!have_scert && NEEDS_KEY(have_smethod)) { 575 config_error("%s:%i: invalid %s::service entry (must contain %s::service::key option)", cep->file->filename, cep->line_number, MYCONF, MYCONF); 576 errors++; 577 continue; 578 } 579 if (NEEDS_KEY(have_smethod) && have_scert) 580 { 581 char *keyerr; 582 keyerr = extjwt_test_key(sfilename, have_smethod); 583 if (keyerr) 584 { 585 config_error("%s:%i: Invalid key file specified for %s::key: %s", cep->file->filename, sfilename_line_number, MYCONF, keyerr); 586 errors++; 587 } 588 } 589 continue; 590 } 591 config_error("%s:%i: unknown directive %s::%s", cep->file->filename, cep->line_number, MYCONF, cep->name); 592 errors++; 593 } 594 *errs = errors; 595 extjwt_free_services(&services); 596 if (errors) 597 safe_free(cfg_state.key_filename); 598 safe_free(sfilename); 599 return errors ? -1 : 1; 600 } 601 602 int extjwt_configposttest(int *errs) 603 { 604 int errors = 0; 605 if (cfg_state.have_method == EXTJWT_METHOD_NOT_SET) 606 { 607 config_error("No %s::method specfied!", MYCONF); 608 errors++; 609 } else 610 { 611 if (cfg_state.have_method != EXTJWT_METHOD_NONE && !NEEDS_KEY(cfg_state.have_method) && !cfg_state.have_secret) 612 { 613 config_error("No %s::secret specfied as required by requested method!", MYCONF); 614 errors++; 615 } 616 if ((cfg_state.have_method == EXTJWT_METHOD_NONE || NEEDS_KEY(cfg_state.have_method)) && cfg_state.have_secret) 617 { 618 config_error("A %s::secret specfied but it should not be when using requested method!", MYCONF); 619 errors++; 620 } 621 if (NEEDS_KEY(cfg_state.have_method) && !cfg_state.have_key) 622 { 623 config_error("No %s::key specfied as required by requested method!", MYCONF); 624 errors++; 625 } 626 if (!NEEDS_KEY(cfg_state.have_method) && cfg_state.have_key) 627 { 628 config_error("A %s::key specfied but it should not be when using requested method!", MYCONF); 629 errors++; 630 } 631 if (NEEDS_KEY(cfg_state.have_method) && cfg_state.have_key && cfg_state.key_filename) 632 { 633 char *keyerr; 634 635 keyerr = extjwt_test_key(cfg_state.key_filename, cfg_state.have_method); 636 if (keyerr) 637 { 638 config_error("Invalid key file specified for %s::key: %s", MYCONF, keyerr); 639 errors++; 640 } 641 } 642 } 643 safe_free(cfg_state.key_filename); 644 if (errors) 645 { 646 *errs = errors; 647 return -1; 648 } 649 /* setting defaults, FIXME this may behave incorrectly if there's another module failing POSTTEST */ 650 if (!cfg_state.have_expire) 651 cfg.exp_delay = 30; 652 /* prepare service list to load new data */ 653 extjwt_free_services(&jwt_services); 654 return 1; 655 } 656 657 int extjwt_configrun(ConfigFile *cf, ConfigEntry *ce, int type) 658 { /* actually use the new configuration data */ 659 ConfigEntry *cep, *cep2; 660 struct jwt_service **ss = &jwt_services; 661 if (*ss) 662 ss = &((*ss)->next); 663 664 if (type != CONFIG_MAIN) 665 return 0; 666 667 if (!ce || strcmp(ce->name, MYCONF)) 668 return 0; 669 670 for (cep = ce->items; cep; cep = cep->next) 671 { 672 if (!strcmp(cep->name, "method")) 673 { 674 cfg.method = EXTJWT_METHOD_from_string(cep->value); 675 continue; 676 } 677 if (!strcmp(cep->name, "expire-after")) 678 { 679 cfg.exp_delay = atoi(cep->value); 680 continue; 681 } 682 if (!strcmp(cep->name, "secret")) 683 { 684 cfg.secret = strdup(cep->value); 685 continue; 686 } 687 if (!strcmp(cep->name, "key")) 688 { 689 cfg.secret = extjwt_read_file_contents(cep->value, 0, NULL); 690 continue; 691 } 692 if (!strcmp(cep->name, "verify-url")) 693 { 694 cfg.vfy = strdup(cep->value); 695 continue; 696 } 697 if (!strcmp(cep->name, "service")) 698 { /* nested block */ 699 *ss = safe_alloc(sizeof(struct jwt_service)); 700 (*ss)->cfg = safe_alloc(sizeof(struct extjwt_config)); 701 safe_strdup((*ss)->name, cep->value); /* copy the service name */ 702 for (cep2 = cep->items; cep2; cep2 = cep2->next) 703 { 704 if (!strcmp(cep2->name, "method")) 705 { 706 (*ss)->cfg->method = EXTJWT_METHOD_from_string(cep2->value); 707 continue; 708 } 709 if (!strcmp(cep2->name, "expire-after")) 710 { 711 (*ss)->cfg->exp_delay = atoi(cep2->value); 712 continue; 713 } 714 if (!strcmp(cep2->name, "secret")) 715 { 716 (*ss)->cfg->secret = strdup(cep2->value); 717 continue; 718 } 719 if (!strcmp(cep2->name, "key")) 720 { 721 (*ss)->cfg->secret = extjwt_read_file_contents(cep2->value, 0, NULL); 722 continue; 723 } 724 if (!strcmp(cep2->name, "verify-url")) 725 { 726 (*ss)->cfg->vfy = strdup(cep2->value); 727 continue; 728 } 729 } 730 ss = &((*ss)->next); 731 } 732 } 733 return 1; 734 } 735 736 char *extjwt_read_file_contents(const char *file, int absolute, int *size) 737 { 738 FILE *f = NULL; 739 int fsize; 740 char *filename = NULL; 741 char *buf = NULL; 742 do 743 { 744 safe_strdup(filename, file); 745 if (!absolute) 746 convert_to_absolute_path(&filename, CONFDIR); 747 f = fopen(filename, "rb"); 748 if (!f) 749 break; 750 fseek(f, 0, SEEK_END); 751 fsize = ftell(f); 752 fseek(f, 0, SEEK_SET); 753 buf = safe_alloc(fsize + 1); 754 fsize = fread(buf, 1, fsize, f); 755 buf[fsize] = '\0'; 756 if (size) 757 *size = fsize; 758 fclose(f); 759 } while (0); 760 safe_free(filename); 761 if (!buf && size) 762 *size = 0; 763 return buf; 764 } 765 766 CMD_FUNC(cmd_extjwt) 767 { 768 Channel *channel; 769 char *payload; 770 char *token, *full_token; 771 struct jwt_service *service = NULL; 772 struct extjwt_config *config; 773 int last = 0; 774 char message[MAX_TOKEN_CHUNK+1]; 775 if (parc < 2 || BadPtr(parv[1])) 776 { 777 sendnumeric(client, ERR_NEEDMOREPARAMS, MSG_EXTJWT); 778 return; 779 } 780 if (parv[1][0] == '*' && parv[1][1] == '\0') 781 { 782 channel = NULL; /* not linked to a channel */ 783 } else 784 { 785 channel = find_channel(parv[1]); 786 if (!channel) 787 { 788 sendnumeric(client, ERR_NOSUCHNICK, parv[1]); 789 return; 790 } 791 } 792 if (parc > 2 && !BadPtr(parv[2])) 793 { 794 service = find_jwt_service(jwt_services, parv[2]); 795 if (!service) 796 { 797 sendto_one(client, NULL, ":%s FAIL %s NO_SUCH_SERVICE :No such service", me.name, MSG_EXTJWT); 798 return; 799 } 800 } 801 if (service){ 802 config = service->cfg; /* service config */ 803 } else { 804 config = &cfg; /* default config */ 805 } 806 if (!(payload = extjwt_make_payload(client, channel, config)) || !(full_token = extjwt_generate_token(payload, config))) 807 { 808 sendto_one(client, NULL, ":%s FAIL %s UNKNOWN_ERROR :Failed to generate token", me.name, MSG_EXTJWT); 809 return; 810 } 811 safe_free(payload); 812 token = full_token; 813 do 814 { 815 if (strlen(token) <= MAX_TOKEN_CHUNK) 816 { /* the remaining data (or whole token) will fit a single irc message */ 817 last = 1; 818 strcpy(message, token); 819 } else 820 { /* send a chunk and shift buffer */ 821 strlcpy(message, token, MAX_TOKEN_CHUNK+1); 822 token += MAX_TOKEN_CHUNK; 823 } 824 sendto_one(client, NULL, extjwt_message_pattern, me.name, parv[1], "*", last?"":"* ", message); 825 } while (!last); 826 safe_free(full_token); 827 } 828 829 char *extjwt_make_payload(Client *client, Channel *channel, struct extjwt_config *config) 830 { 831 Membership *lp; 832 json_t *payload = NULL; 833 json_t *modes = NULL; 834 json_t *umodes = NULL; 835 char *modestring; 836 char singlemode[2] = { '\0' }; 837 char *result; 838 839 if (!IsUser(client)) 840 return NULL; 841 842 payload = json_object(); 843 modes = json_array(); 844 umodes = json_array(); 845 846 json_object_set_new(payload, "exp", json_integer(TStime()+config->exp_delay)); 847 json_object_set_new(payload, "iss", json_string_unreal(me.name)); 848 json_object_set_new(payload, "sub", json_string_unreal(client->name)); 849 json_object_set_new(payload, "account", json_string_unreal(IsLoggedIn(client)?client->user->account:"")); 850 851 if (config->vfy) /* also add the URL */ 852 json_object_set_new(payload, "vfy", json_string_unreal(config->vfy)); 853 854 if (IsOper(client)) /* add "o" ircop flag */ 855 json_array_append_new(umodes, json_string("o")); 856 json_object_set_new(payload, "umodes", umodes); 857 858 if (channel) 859 { /* fill in channel information and user flags */ 860 lp = find_membership_link(client->user->channel, channel); 861 if (lp) 862 { 863 modestring = lp->member_modes; 864 while (*modestring) 865 { 866 singlemode[0] = *modestring; 867 json_array_append_new(modes, json_string(singlemode)); 868 modestring++; 869 } 870 } 871 json_object_set_new(payload, "channel", json_string_unreal(channel->name)); 872 json_object_set_new(payload, "joined", json_integer(lp?1:0)); 873 json_object_set_new(payload, "cmodes", modes); 874 } 875 result = json_dumps(payload, JSON_COMPACT); 876 json_decref(modes); 877 json_decref(umodes); 878 json_decref(payload); 879 return result; 880 } 881 882 void b64url(char *b64) 883 { /* convert base64 to base64-url */ 884 while (*b64) 885 { 886 if (*b64 == '+') 887 *b64 = '-'; 888 if (*b64 == '/') 889 *b64 = '_'; 890 if (*b64 == '=') 891 { 892 *b64 = '\0'; 893 return; 894 } 895 b64++; 896 } 897 } 898 899 unsigned char *extjwt_hash(int method, const void *key, int keylen, const unsigned char *data, int datalen, unsigned int* resultlen) 900 { 901 switch(method) 902 { 903 case EXTJWT_METHOD_HS256: case EXTJWT_METHOD_HS384: case EXTJWT_METHOD_HS512: 904 return extjwt_hmac_extjwt_hash(method, key, keylen, data, datalen, resultlen); 905 case EXTJWT_METHOD_RS256: case EXTJWT_METHOD_RS384: case EXTJWT_METHOD_RS512: case EXTJWT_METHOD_ES256: case EXTJWT_METHOD_ES384: case EXTJWT_METHOD_ES512: 906 return extjwt_sha_pem_extjwt_hash(method, key, keylen, data, datalen, resultlen); 907 } 908 return NULL; 909 } 910 911 unsigned char* extjwt_sha_pem_extjwt_hash(int method, const void *key, int keylen, const unsigned char *data, int datalen, unsigned int* resultlen) 912 { 913 EVP_MD_CTX *mdctx = NULL; 914 ECDSA_SIG *ec_sig = NULL; 915 const BIGNUM *ec_sig_r = NULL; 916 const BIGNUM *ec_sig_s = NULL; 917 BIO *bufkey = NULL; 918 const EVP_MD *alg; 919 int type; 920 EVP_PKEY *pkey = NULL; 921 int pkey_type; 922 unsigned char *sig = NULL; 923 int ret = 0; 924 size_t slen; 925 char *retval = NULL; 926 char *output = NULL; 927 char *sig_ptr; 928 929 do 930 { 931 switch (method) 932 { 933 case EXTJWT_METHOD_RS256: 934 alg = EVP_sha256(); 935 type = EVP_PKEY_RSA; 936 break; 937 case EXTJWT_METHOD_RS384: 938 alg = EVP_sha384(); 939 type = EVP_PKEY_RSA; 940 break; 941 case EXTJWT_METHOD_RS512: 942 alg = EVP_sha512(); 943 type = EVP_PKEY_RSA; 944 break; 945 case EXTJWT_METHOD_ES256: 946 alg = EVP_sha256(); 947 type = EVP_PKEY_EC; 948 break; 949 case EXTJWT_METHOD_ES384: 950 alg = EVP_sha384(); 951 type = EVP_PKEY_EC; 952 break; 953 case EXTJWT_METHOD_ES512: 954 alg = EVP_sha512(); 955 type = EVP_PKEY_EC; 956 break; 957 default: 958 return NULL; 959 } 960 961 #if (OPENSSL_VERSION_NUMBER < 0x10100003L) /* https://github.com/openssl/openssl/commit/8ab31975bacb9c907261088937d3aa4102e3af84 */ 962 if (!(bufkey = BIO_new_mem_buf((void *)key, keylen))) 963 break; /* out of memory */ 964 #else 965 if (!(bufkey = BIO_new_mem_buf(key, keylen))) 966 break; /* out of memory */ 967 #endif 968 if (!(pkey = PEM_read_bio_PrivateKey(bufkey, NULL, NULL, NULL))) 969 break; /* invalid key? */ 970 pkey_type = EVP_PKEY_id(pkey); 971 if (type != pkey_type) 972 break; /* invalid key type */ 973 if (!(mdctx = EVP_MD_CTX_create())) 974 break; /* out of memory */ 975 if (EVP_DigestSignInit(mdctx, NULL, alg, NULL, pkey) != 1) 976 break; /* initialize error */ 977 if (EVP_DigestSignUpdate(mdctx, data, datalen) != 1) 978 break; /* signing error */ 979 if (EVP_DigestSignFinal(mdctx, NULL, &slen) != 1) /* get required buffer length */ 980 break; 981 sig = safe_alloc(slen); 982 if (EVP_DigestSignFinal(mdctx, sig, &slen) != 1) 983 break; 984 if (pkey_type != EVP_PKEY_EC) 985 { 986 *resultlen = slen; 987 output = safe_alloc(slen); 988 memcpy(output, sig, slen); 989 retval = output; 990 } else 991 { 992 unsigned int degree, bn_len, r_len, s_len, buf_len; 993 unsigned char *raw_buf = NULL; 994 EC_KEY *ec_key; 995 if (!(ec_key = EVP_PKEY_get1_EC_KEY(pkey))) 996 break; /* out of memory */ 997 degree = EC_GROUP_get_degree(EC_KEY_get0_group(ec_key)); 998 EC_KEY_free(ec_key); 999 sig_ptr = sig; 1000 if (!(ec_sig = d2i_ECDSA_SIG(NULL, (const unsigned char **)&sig_ptr, slen))) 1001 break; /* out of memory */ 1002 ECDSA_SIG_get0(ec_sig, &ec_sig_r, &ec_sig_s); 1003 r_len = BN_num_bytes(ec_sig_r); 1004 s_len = BN_num_bytes(ec_sig_s); 1005 bn_len = (degree+7)/8; 1006 if (r_len>bn_len || s_len > bn_len) 1007 break; 1008 buf_len = bn_len*2; 1009 raw_buf = safe_alloc(buf_len); 1010 BN_bn2bin(ec_sig_r, raw_buf+bn_len-r_len); 1011 BN_bn2bin(ec_sig_s, raw_buf+buf_len-s_len); 1012 output = safe_alloc(buf_len); 1013 *resultlen = buf_len; 1014 memcpy(output, raw_buf, buf_len); 1015 retval = output; 1016 safe_free(raw_buf); 1017 } 1018 } while (0); 1019 1020 if (bufkey) 1021 BIO_free(bufkey); 1022 if (pkey) 1023 EVP_PKEY_free(pkey); 1024 if (mdctx) 1025 EVP_MD_CTX_destroy(mdctx); 1026 if (ec_sig) 1027 ECDSA_SIG_free(ec_sig); 1028 safe_free(sig); 1029 return retval; 1030 } 1031 1032 unsigned char* extjwt_hmac_extjwt_hash(int method, const void *key, int keylen, const unsigned char *data, int datalen, unsigned int* resultlen) 1033 { 1034 const EVP_MD* typ; 1035 char *hmac = safe_alloc(EVP_MAX_MD_SIZE); 1036 switch (method) 1037 { 1038 default: 1039 case EXTJWT_METHOD_HS256: 1040 typ = EVP_sha256(); 1041 break; 1042 case EXTJWT_METHOD_HS384: 1043 typ = EVP_sha384(); 1044 break; 1045 case EXTJWT_METHOD_HS512: 1046 typ = EVP_sha512(); 1047 break; 1048 } 1049 if (HMAC(typ, key, keylen, data, datalen, hmac, resultlen)) 1050 { /* openssl call */ 1051 return hmac; 1052 } else { 1053 safe_free(hmac); 1054 return NULL; 1055 } 1056 } 1057 1058 char *extjwt_gen_header(int method) 1059 { /* returns header json */ 1060 json_t *header = NULL; 1061 json_t *alg; 1062 char *result; 1063 1064 header = json_object(); 1065 json_object_set_new(header, "typ", json_string("JWT")); 1066 1067 switch (method) 1068 { 1069 default: 1070 case EXTJWT_METHOD_HS256: 1071 alg = json_string("HS256"); 1072 break; 1073 case EXTJWT_METHOD_HS384: 1074 alg = json_string("HS384"); 1075 break; 1076 case EXTJWT_METHOD_HS512: 1077 alg = json_string("HS512"); 1078 break; 1079 case EXTJWT_METHOD_RS256: 1080 alg = json_string("RS256"); 1081 break; 1082 case EXTJWT_METHOD_RS384: 1083 alg = json_string("RS384"); 1084 break; 1085 case EXTJWT_METHOD_RS512: 1086 alg = json_string("RS512"); 1087 break; 1088 case EXTJWT_METHOD_ES256: 1089 alg = json_string("ES256"); 1090 break; 1091 case EXTJWT_METHOD_ES384: 1092 alg = json_string("ES384"); 1093 break; 1094 case EXTJWT_METHOD_ES512: 1095 alg = json_string("ES512"); 1096 break; 1097 case EXTJWT_METHOD_NONE: 1098 alg = json_string("none"); 1099 break; 1100 } 1101 json_object_set_new(header, "alg", alg); 1102 result = json_dumps(header, JSON_COMPACT); 1103 json_decref(header); 1104 return result; 1105 } 1106 1107 char *extjwt_generate_token(const char *payload, struct extjwt_config *config) 1108 { 1109 char *header = extjwt_gen_header(config->method); 1110 size_t b64header_size = strlen(header)*4/3 + 8; // base64 has 4/3 overhead 1111 size_t b64payload_size = strlen(payload)*4/3 + 8; 1112 size_t b64sig_size = 4096*4/3 + 8; 1113 size_t b64data_size = b64header_size + b64payload_size + b64sig_size + 4; 1114 char *b64header = safe_alloc(b64header_size); 1115 char *b64payload = safe_alloc(b64payload_size); 1116 char *b64sig = safe_alloc(b64sig_size); 1117 char *b64data = safe_alloc(b64data_size); 1118 unsigned int extjwt_hashsize; 1119 char *extjwt_hash_val = NULL; 1120 char *retval = NULL; 1121 b64_encode(header, strlen(header), b64header, b64header_size); 1122 b64_encode(payload, strlen(payload), b64payload, b64payload_size); 1123 b64url(b64header); 1124 b64url(b64payload); 1125 snprintf(b64data, b64data_size, "%s.%s", b64header, b64payload); // generate first part of the token 1126 if (config->method != EXTJWT_METHOD_NONE) 1127 { 1128 extjwt_hash_val = extjwt_hash(config->method, config->secret, strlen(config->secret), b64data, strlen(b64data), &extjwt_hashsize); // calculate the signature extjwt_hash 1129 if (extjwt_hash_val) 1130 { 1131 b64_encode(extjwt_hash_val, extjwt_hashsize, b64sig, b64sig_size); 1132 b64url(b64sig); 1133 strlcat(b64data, ".", b64data_size); // append signature extjwt_hash to token 1134 strlcat(b64data, b64sig, b64data_size); 1135 retval = b64data; 1136 } 1137 } else 1138 { 1139 retval = b64data; 1140 } 1141 safe_free(header); 1142 safe_free(b64header); 1143 safe_free(b64payload); 1144 safe_free(b64sig); 1145 safe_free(extjwt_hash_val); 1146 1147 if (retval != b64data) 1148 safe_free(b64data); 1149 1150 return retval; 1151 }