1/* 2 * libwebsockets ACME client protocol plugin 3 * 4 * Copyright (C) 2010 - 2022 Andy Green <andy@warmcat.com> 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a copy 7 * of this software and associated documentation files (the "Software"), to 8 * deal in the Software without restriction, including without limitation the 9 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 * sell copies of the Software, and to permit persons to whom the Software is 11 * furnished to do so, subject to the following conditions: 12 * 13 * The above copyright notice and this permission notice shall be included in 14 * all copies or substantial portions of the Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 * IN THE SOFTWARE. 23 * 24 * This implementation follows draft 7 of the IETF standard, and falls back 25 * to whatever differences exist for Boulder's tls-sni-01 challenge. 26 * tls-sni-02 is also supported. 27 */ 28 29#if !defined (LWS_PLUGIN_STATIC) 30#if !defined(LWS_DLL) 31#define LWS_DLL 32#endif 33#if !defined(LWS_INTERNAL) 34#define LWS_INTERNAL 35#endif 36#include <libwebsockets.h> 37#endif 38 39#include <string.h> 40#include <stdlib.h> 41 42#include <sys/stat.h> 43#include <fcntl.h> 44 45typedef enum { 46 ACME_STATE_DIRECTORY, /* get the directory JSON using GET + parse */ 47 ACME_STATE_NEW_NONCE, /* get the replay nonce */ 48 ACME_STATE_NEW_ACCOUNT, /* register a new RSA key + email combo */ 49 ACME_STATE_NEW_ORDER, /* start the process to request a cert */ 50 ACME_STATE_AUTHZ, /* */ 51 ACME_STATE_START_CHALL, /* notify server ready for one challenge */ 52 ACME_STATE_POLLING, /* he should be trying our challenge */ 53 ACME_STATE_POLLING_CSR, /* sent CSR, checking result */ 54 ACME_STATE_DOWNLOAD_CERT, 55 56 ACME_STATE_FINISHED 57} lws_acme_state; 58 59struct acme_connection { 60 char buf[4096]; 61 char replay_nonce[64]; 62 char chall_token[64]; 63 char challenge_uri[256]; 64 char detail[64]; 65 char status[16]; 66 char key_auth[256]; 67 char http01_mountpoint[256]; 68 struct lws_http_mount mount; 69 char urls[6][100]; /* directory contents */ 70 char active_url[100]; 71 char authz_url[100]; 72 char order_url[100]; 73 char finalize_url[100]; 74 char cert_url[100]; 75 char acct_id[100]; 76 char *kid; 77 lws_acme_state state; 78 struct lws_client_connect_info i; 79 struct lejp_ctx jctx; 80 struct lws_context_creation_info ci; 81 struct lws_vhost *vhost; 82 83 struct lws *cwsi; 84 85 const char *real_vh_name; 86 const char *real_vh_iface; 87 88 char *alloc_privkey_pem; 89 90 char *dest; 91 int pos; 92 int len; 93 int resp; 94 int cpos; 95 96 int real_vh_port; 97 int goes_around; 98 99 size_t len_privkey_pem; 100 101 unsigned int yes; 102 unsigned int use:1; 103 unsigned int is_sni_02:1; 104}; 105 106struct per_vhost_data__lws_acme_client { 107 struct lws_context *context; 108 struct lws_vhost *vhost; 109 const struct lws_protocols *protocol; 110 111 /* 112 * the vhd is allocated for every vhost using the plugin. 113 * But ac is only allocated when we are doing the server auth. 114 */ 115 struct acme_connection *ac; 116 117 struct lws_jwk jwk; 118 struct lws_genrsa_ctx rsactx; 119 120 char *pvo_data; 121 char *pvop[LWS_TLS_TOTAL_COUNT]; 122 const char *pvop_active[LWS_TLS_TOTAL_COUNT]; 123 int count_live_pss; 124 char *dest; 125 int pos; 126 int len; 127 128 int fd_updated_cert; /* these are opened while we have root... */ 129 int fd_updated_key; /* ...if nonempty next startup will replace old */ 130}; 131 132static int 133callback_chall_http01(struct lws *wsi, enum lws_callback_reasons reason, 134 void *user, void *in, size_t len) 135{ 136 struct lws_vhost *vhost = lws_get_vhost(wsi); 137 struct acme_connection *ac = lws_vhost_user(vhost); 138 uint8_t buf[LWS_PRE + 2048], *start = &buf[LWS_PRE], *p = start, 139 *end = &buf[sizeof(buf) - 1]; 140 int n; 141 142 switch (reason) { 143 case LWS_CALLBACK_HTTP: 144 lwsl_wsi_notice(wsi, "CA connection received, key_auth %s", 145 ac->key_auth); 146 147 if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end)) { 148 lwsl_wsi_warn(wsi, "add status failed"); 149 return -1; 150 } 151 152 if (lws_add_http_header_by_token(wsi, 153 WSI_TOKEN_HTTP_CONTENT_TYPE, 154 (unsigned char *)"text/plain", 10, 155 &p, end)) { 156 lwsl_wsi_warn(wsi, "add content_type failed"); 157 return -1; 158 } 159 160 n = (int)strlen(ac->key_auth); 161 if (lws_add_http_header_content_length(wsi, (lws_filepos_t)n, &p, end)) { 162 lwsl_wsi_warn(wsi, "add content_length failed"); 163 return -1; 164 } 165 166 if (lws_add_http_header_by_token(wsi, 167 WSI_TOKEN_HTTP_CONTENT_DISPOSITION, 168 (unsigned char *)"attachment", 10, 169 &p, end)) { 170 lwsl_wsi_warn(wsi, "add content_dispo failed"); 171 return -1; 172 } 173 174 if (lws_finalize_write_http_header(wsi, start, &p, end)) { 175 lwsl_wsi_warn(wsi, "finalize http header failed"); 176 return -1; 177 } 178 179 lws_callback_on_writable(wsi); 180 return 0; 181 182 case LWS_CALLBACK_HTTP_WRITEABLE: 183 p += lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p), "%s", ac->key_auth); 184 // lwsl_notice("%s: len %d\n", __func__, lws_ptr_diff(p, start)); 185 if (lws_write(wsi, (uint8_t *)start, lws_ptr_diff_size_t(p, start), 186 LWS_WRITE_HTTP_FINAL) != lws_ptr_diff(p, start)) { 187 lwsl_wsi_err(wsi, "_write content failed"); 188 return 1; 189 } 190 191 if (lws_http_transaction_completed(wsi)) 192 return -1; 193 194 return 0; 195 196 default: 197 break; 198 } 199 200 return lws_callback_http_dummy(wsi, reason, user, in, len); 201} 202 203static const struct lws_protocols chall_http01_protocols[] = { 204 { "http", callback_chall_http01, 0, 0, 0, NULL, 0 }, 205 { NULL, NULL, 0, 0, 0, NULL, 0 } 206}; 207 208static int 209jws_create_packet(struct lws_jwe *jwe, const char *payload, size_t len, 210 const char *nonce, const char *url, const char *kid, 211 char *out, size_t out_len, struct lws_context *context) 212{ 213 char *buf, *start, *p, *end, *p1, *end1; 214 struct lws_jws jws; 215 int n, m; 216 217 lws_jws_init(&jws, &jwe->jwk, context); 218 219 /* 220 * This buffer is local to the function, the actual output is prepared 221 * into out. Only the plaintext protected header 222 * (which contains the public key, 512 bytes for 4096b) goes in 223 * here temporarily. 224 */ 225 n = LWS_PRE + 2048; 226 buf = malloc((unsigned int)n); 227 if (!buf) { 228 lwsl_warn("%s: malloc %d failed\n", __func__, n); 229 return -1; 230 } 231 232 p = start = buf + LWS_PRE; 233 end = buf + n - LWS_PRE - 1; 234 235 /* 236 * temporary JWS protected header plaintext 237 */ 238 if (!jwe->jose.alg || !jwe->jose.alg->alg) 239 goto bail; 240 241 p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), "{\"alg\":\"RS256\""); 242 if (kid) 243 p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), ",\"kid\":\"%s\"", kid); 244 else { 245 p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), ",\"jwk\":"); 246 m = lws_ptr_diff(end, p); 247 n = lws_jwk_export(&jwe->jwk, 0, p, &m); 248 if (n < 0) { 249 lwsl_notice("failed to export jwk\n"); 250 goto bail; 251 } 252 p += n; 253 } 254 p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), ",\"url\":\"%s\"", url); 255 p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), ",\"nonce\":\"%s\"}", nonce); 256 257 /* 258 * prepare the signed outer JSON with all the parts in 259 */ 260 p1 = out; 261 end1 = out + out_len - 1; 262 263 p1 += lws_snprintf(p1, lws_ptr_diff_size_t(end1, p1), "{\"protected\":\""); 264 jws.map_b64.buf[LJWS_JOSE] = p1; 265 n = lws_jws_base64_enc(start, lws_ptr_diff_size_t(p, start), p1, lws_ptr_diff_size_t(end1, p1)); 266 if (n < 0) { 267 lwsl_notice("%s: failed to encode protected\n", __func__); 268 goto bail; 269 } 270 jws.map_b64.len[LJWS_JOSE] = (uint32_t)n; 271 p1 += n; 272 273 p1 += lws_snprintf(p1, lws_ptr_diff_size_t(end1, p1), "\",\"payload\":\""); 274 jws.map_b64.buf[LJWS_PYLD] = p1; 275 n = lws_jws_base64_enc(payload, len, p1, lws_ptr_diff_size_t(end1, p1)); 276 if (n < 0) { 277 lwsl_notice("%s: failed to encode payload\n", __func__); 278 goto bail; 279 } 280 jws.map_b64.len[LJWS_PYLD] = (uint32_t)n; 281 p1 += n; 282 283 p1 += lws_snprintf(p1, lws_ptr_diff_size_t(end1, p1), "\",\"signature\":\""); 284 285 /* 286 * taking the b64 protected header and the b64 payload, sign them 287 * and place the signature into the packet 288 */ 289 n = lws_jws_sign_from_b64(&jwe->jose, &jws, p1, lws_ptr_diff_size_t(end1, p1)); 290 if (n < 0) { 291 lwsl_notice("sig gen failed\n"); 292 293 goto bail; 294 } 295 jws.map_b64.buf[LJWS_SIG] = p1; 296 jws.map_b64.len[LJWS_SIG] = (uint32_t)n; 297 298 p1 += n; 299 p1 += lws_snprintf(p1, lws_ptr_diff_size_t(end1, p1), "\"}"); 300 301 free(buf); 302 303 return lws_ptr_diff(p1, out); 304 305bail: 306 lws_jws_destroy(&jws); 307 free(buf); 308 309 return -1; 310} 311 312static int 313callback_acme_client(struct lws *wsi, enum lws_callback_reasons reason, 314 void *user, void *in, size_t len); 315 316#define LWS_PLUGIN_PROTOCOL_LWS_ACME_CLIENT \ 317{ \ 318 "lws-acme-client", \ 319 callback_acme_client, \ 320 0, \ 321 512, \ 322 0, NULL, 0 \ 323} 324 325/* directory JSON parsing */ 326 327static const char * const jdir_tok[] = { 328 "keyChange", 329 "meta.termsOfService", 330 "newAccount", 331 "newNonce", 332 "newOrder", 333 "revokeCert", 334}; 335 336enum enum_jdir_tok { 337 JAD_KEY_CHANGE_URL, 338 JAD_TOS_URL, 339 JAD_NEW_ACCOUNT_URL, 340 JAD_NEW_NONCE_URL, 341 JAD_NEW_ORDER_URL, 342 JAD_REVOKE_CERT_URL, 343}; 344 345static signed char 346cb_dir(struct lejp_ctx *ctx, char reason) 347{ 348 struct per_vhost_data__lws_acme_client *s = 349 (struct per_vhost_data__lws_acme_client *)ctx->user; 350 351 if (reason == LEJPCB_VAL_STR_START && ctx->path_match) { 352 s->pos = 0; 353 s->len = sizeof(s->ac->urls[0]) - 1; 354 s->dest = s->ac->urls[ctx->path_match - 1]; 355 return 0; 356 } 357 358 if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match) 359 return 0; 360 361 if (s->pos + ctx->npos > s->len) { 362 lwsl_notice("url too long\n"); 363 return -1; 364 } 365 366 memcpy(s->dest + s->pos, ctx->buf, ctx->npos); 367 s->pos += ctx->npos; 368 s->dest[s->pos] = '\0'; 369 370 return 0; 371} 372 373 374/* order JSON parsing */ 375 376static const char * const jorder_tok[] = { 377 "status", 378 "expires", 379 "identifiers[].type", 380 "identifiers[].value", 381 "authorizations", 382 "finalize", 383 "certificate" 384}; 385 386enum enum_jorder_tok { 387 JAO_STATUS, 388 JAO_EXPIRES, 389 JAO_IDENTIFIERS_TYPE, 390 JAO_IDENTIFIERS_VALUE, 391 JAO_AUTHORIZATIONS, 392 JAO_FINALIZE, 393 JAO_CERT 394}; 395 396static signed char 397cb_order(struct lejp_ctx *ctx, char reason) 398{ 399 struct acme_connection *s = (struct acme_connection *)ctx->user; 400 401 if (reason == LEJPCB_CONSTRUCTED) 402 s->authz_url[0] = '\0'; 403 404 if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match) 405 return 0; 406 407 switch (ctx->path_match - 1) { 408 case JAO_STATUS: 409 lws_strncpy(s->status, ctx->buf, sizeof(s->status)); 410 break; 411 case JAO_EXPIRES: 412 break; 413 case JAO_IDENTIFIERS_TYPE: 414 break; 415 case JAO_IDENTIFIERS_VALUE: 416 break; 417 case JAO_AUTHORIZATIONS: 418 lws_snprintf(s->authz_url, sizeof(s->authz_url), "%s", 419 ctx->buf); 420 break; 421 case JAO_FINALIZE: 422 lws_snprintf(s->finalize_url, sizeof(s->finalize_url), "%s", 423 ctx->buf); 424 break; 425 case JAO_CERT: 426 lws_snprintf(s->cert_url, sizeof(s->cert_url), "%s", ctx->buf); 427 break; 428 } 429 430 return 0; 431} 432 433/* authz JSON parsing */ 434 435static const char * const jauthz_tok[] = { 436 "identifier.type", 437 "identifier.value", 438 "status", 439 "expires", 440 "challenges[].type", 441 "challenges[].status", 442 "challenges[].url", 443 "challenges[].token", 444 "detail" 445}; 446 447enum enum_jauthz_tok { 448 JAAZ_ID_TYPE, 449 JAAZ_ID_VALUE, 450 JAAZ_STATUS, 451 JAAZ_EXPIRES, 452 JAAZ_CHALLENGES_TYPE, 453 JAAZ_CHALLENGES_STATUS, 454 JAAZ_CHALLENGES_URL, 455 JAAZ_CHALLENGES_TOKEN, 456 JAAZ_DETAIL, 457}; 458 459static signed char 460cb_authz(struct lejp_ctx *ctx, char reason) 461{ 462 struct acme_connection *s = (struct acme_connection *)ctx->user; 463 464 if (reason == LEJPCB_CONSTRUCTED) { 465 s->yes = 0; 466 s->use = 0; 467 s->chall_token[0] = '\0'; 468 } 469 470 if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match) 471 return 0; 472 473 switch (ctx->path_match - 1) { 474 case JAAZ_ID_TYPE: 475 break; 476 case JAAZ_ID_VALUE: 477 break; 478 case JAAZ_STATUS: 479 break; 480 case JAAZ_EXPIRES: 481 break; 482 case JAAZ_DETAIL: 483 lws_snprintf(s->detail, sizeof(s->detail), "%s", ctx->buf); 484 break; 485 case JAAZ_CHALLENGES_TYPE: 486 lwsl_notice("JAAZ_CHALLENGES_TYPE: %s\n", ctx->buf); 487 s->use = !strcmp(ctx->buf, "http-01"); 488 break; 489 case JAAZ_CHALLENGES_STATUS: 490 lws_strncpy(s->status, ctx->buf, sizeof(s->status)); 491 break; 492 case JAAZ_CHALLENGES_URL: 493 lwsl_notice("JAAZ_CHALLENGES_URL: %s %d\n", ctx->buf, s->use); 494 if (s->use) { 495 lws_strncpy(s->challenge_uri, ctx->buf, 496 sizeof(s->challenge_uri)); 497 s->yes = s->yes | 2; 498 } 499 break; 500 case JAAZ_CHALLENGES_TOKEN: 501 lwsl_notice("JAAZ_CHALLENGES_TOKEN: %s %d\n", ctx->buf, s->use); 502 if (s->use) { 503 lws_strncpy(s->chall_token, ctx->buf, 504 sizeof(s->chall_token)); 505 s->yes = s->yes | 1; 506 } 507 break; 508 } 509 510 return 0; 511} 512 513/* challenge accepted JSON parsing */ 514 515static const char * const jchac_tok[] = { 516 "type", 517 "status", 518 "uri", 519 "token", 520 "error.detail" 521}; 522 523enum enum_jchac_tok { 524 JCAC_TYPE, 525 JCAC_STATUS, 526 JCAC_URI, 527 JCAC_TOKEN, 528 JCAC_DETAIL, 529}; 530 531static signed char 532cb_chac(struct lejp_ctx *ctx, char reason) 533{ 534 struct acme_connection *s = (struct acme_connection *)ctx->user; 535 536 if (reason == LEJPCB_CONSTRUCTED) { 537 s->yes = 0; 538 s->use = 0; 539 } 540 541 if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match) 542 return 0; 543 544 switch (ctx->path_match - 1) { 545 case JCAC_TYPE: 546 if (strcmp(ctx->buf, "http-01")) 547 return 1; 548 break; 549 case JCAC_STATUS: 550 lws_strncpy(s->status, ctx->buf, sizeof(s->status)); 551 break; 552 case JCAC_URI: 553 s->yes = s->yes | 2; 554 break; 555 case JCAC_TOKEN: 556 lws_strncpy(s->chall_token, ctx->buf, sizeof(s->chall_token)); 557 s->yes = s->yes | 1; 558 break; 559 case JCAC_DETAIL: 560 lws_snprintf(s->detail, sizeof(s->detail), "%s", ctx->buf); 561 break; 562 } 563 564 return 0; 565} 566 567static int 568lws_acme_report_status(struct lws_vhost *v, int state, const char *json) 569{ 570 lws_callback_vhost_protocols_vhost(v, LWS_CALLBACK_VHOST_CERT_UPDATE, 571 (void *)json, (unsigned int)state); 572 573 return 0; 574} 575 576/* 577 * Notice: trashes i and url 578 */ 579static struct lws * 580lws_acme_client_connect(struct lws_context *context, struct lws_vhost *vh, 581 struct lws **pwsi, struct lws_client_connect_info *i, 582 char *url, const char *method) 583{ 584 const char *prot, *p; 585 char path[200], _url[256]; 586 struct lws *wsi; 587 588 memset(i, 0, sizeof(*i)); 589 i->port = 443; 590 lws_strncpy(_url, url, sizeof(_url)); 591 if (lws_parse_uri(_url, &prot, &i->address, &i->port, &p)) { 592 lwsl_err("unable to parse uri %s\n", url); 593 594 return NULL; 595 } 596 597 /* add back the leading / on path */ 598 path[0] = '/'; 599 lws_strncpy(path + 1, p, sizeof(path) - 1); 600 i->path = path; 601 i->context = context; 602 i->vhost = vh; 603 i->ssl_connection = LCCSCF_USE_SSL; 604 i->host = i->address; 605 i->origin = i->address; 606 i->method = method; 607 i->pwsi = pwsi; 608 i->protocol = "lws-acme-client"; 609 610 wsi = lws_client_connect_via_info(i); 611 if (!wsi) { 612 lws_snprintf(path, sizeof(path) - 1, 613 "Unable to connect to %s", url); 614 lwsl_notice("%s: %s\n", __func__, path); 615 lws_acme_report_status(vh, LWS_CUS_FAILED, path); 616 } 617 618 return wsi; 619} 620 621static void 622lws_acme_finished(struct per_vhost_data__lws_acme_client *vhd) 623{ 624 lwsl_notice("%s\n", __func__); 625 626 if (vhd->ac) { 627 if (vhd->ac->vhost) 628 lws_vhost_destroy(vhd->ac->vhost); 629 if (vhd->ac->alloc_privkey_pem) 630 free(vhd->ac->alloc_privkey_pem); 631 free(vhd->ac); 632 } 633 634 lws_genrsa_destroy(&vhd->rsactx); 635 lws_jwk_destroy(&vhd->jwk); 636 637 vhd->ac = NULL; 638#if defined(LWS_WITH_ESP32) 639 lws_esp32.acme = 0; /* enable scanning */ 640#endif 641} 642 643static const char * const pvo_names[] = { 644 "country", 645 "state", 646 "locality", 647 "organization", 648 "common-name", 649 "subject-alt-name", 650 "email", 651 "directory-url", 652 "auth-path", 653 "cert-path", 654 "key-path", 655}; 656 657static int 658lws_acme_load_create_auth_keys(struct per_vhost_data__lws_acme_client *vhd, 659 int bits) 660{ 661 int n; 662 663 if (!lws_jwk_load(&vhd->jwk, vhd->pvop[LWS_TLS_SET_AUTH_PATH], 664 NULL, NULL)) 665 return 0; 666 667 vhd->jwk.kty = LWS_GENCRYPTO_KTY_RSA; 668 669 lwsl_notice("Generating ACME %d-bit keypair... " 670 "will take a little while\n", bits); 671 n = lws_genrsa_new_keypair(vhd->context, &vhd->rsactx, LGRSAM_PKCS1_1_5, 672 vhd->jwk.e, bits); 673 if (n) { 674 lwsl_vhost_warn(vhd->vhost, "failed to create keypair"); 675 return 1; 676 } 677 678 lwsl_notice("...keypair generated\n"); 679 680 if (lws_jwk_save(&vhd->jwk, vhd->pvop[LWS_TLS_SET_AUTH_PATH])) { 681 lwsl_vhost_warn(vhd->vhost, "unable to save %s", 682 vhd->pvop[LWS_TLS_SET_AUTH_PATH]); 683 return 1; 684 } 685 686 return 0; 687} 688 689static int 690lws_acme_start_acquisition(struct per_vhost_data__lws_acme_client *vhd, 691 struct lws_vhost *v) 692{ 693 char buf[128]; 694 695 /* ...and we were given enough info to do the update? */ 696 697 if (!vhd->pvop[LWS_TLS_REQ_ELEMENT_COMMON_NAME]) 698 return -1; 699 700 /* 701 * ...well... we should try to do something about it then... 702 */ 703 lwsl_vhost_notice(vhd->vhost, "ACME cert needs creating / updating: " 704 "vhost %s", lws_get_vhost_name(vhd->vhost)); 705 706 vhd->ac = malloc(sizeof(*vhd->ac)); 707 memset(vhd->ac, 0, sizeof(*vhd->ac)); 708 709 /* 710 * So if we don't have it, the first job is get the directory. 711 * 712 * If we already have the directory, jump straight into trying 713 * to register our key. 714 * 715 * We always try to register the keys... if it's not the first 716 * time, we will get a JSON body in the (legal, nonfatal) 717 * response like this 718 * 719 * { 720 * "type": "urn:acme:error:malformed", 721 * "detail": "Registration key is already in use", 722 * "status": 409 723 * } 724 */ 725 if (!vhd->ac->urls[0][0]) { 726 vhd->ac->state = ACME_STATE_DIRECTORY; 727 lws_snprintf(buf, sizeof(buf) - 1, "%s", 728 vhd->pvop_active[LWS_TLS_SET_DIR_URL]); 729 } else { 730 vhd->ac->state = ACME_STATE_NEW_ACCOUNT; 731 lws_snprintf(buf, sizeof(buf) - 1, "%s", 732 vhd->ac->urls[JAD_NEW_ACCOUNT_URL]); 733 } 734 735 vhd->ac->real_vh_port = lws_get_vhost_port(vhd->vhost); 736 vhd->ac->real_vh_name = lws_get_vhost_name(vhd->vhost); 737 vhd->ac->real_vh_iface = lws_get_vhost_iface(vhd->vhost); 738 739 lws_acme_report_status(vhd->vhost, LWS_CUS_STARTING, NULL); 740 741#if defined(LWS_WITH_ESP32) 742 lws_acme_report_status(vhd->vhost, LWS_CUS_CREATE_KEYS, 743 "Generating keys, please wait"); 744 if (lws_acme_load_create_auth_keys(vhd, 2048)) 745 goto bail; 746 lws_acme_report_status(vhd->vhost, LWS_CUS_CREATE_KEYS, 747 "Auth keys created"); 748#endif 749 750 if (lws_acme_client_connect(vhd->context, vhd->vhost, 751 &vhd->ac->cwsi, &vhd->ac->i, buf, "GET")) 752 return 0; 753 754#if defined(LWS_WITH_ESP32) 755bail: 756#endif 757 free(vhd->ac); 758 vhd->ac = NULL; 759 760 return 1; 761} 762 763static int 764callback_acme_client(struct lws *wsi, enum lws_callback_reasons reason, 765 void *user, void *in, size_t len) 766{ 767 struct per_vhost_data__lws_acme_client *vhd = 768 (struct per_vhost_data__lws_acme_client *) 769 lws_protocol_vh_priv_get(lws_get_vhost(wsi), 770 lws_get_protocol(wsi)); 771 char buf[LWS_PRE + 2536], *start = buf + LWS_PRE, *p = start, 772 *end = buf + sizeof(buf) - 1, digest[32], *failreason = NULL; 773 const struct lws_protocol_vhost_options *pvo; 774 struct lws_acme_cert_aging_args *caa; 775 struct acme_connection *ac = NULL; 776 unsigned char **pp, *pend; 777 const char *content_type; 778 struct lws_jwe jwe; 779 struct lws *cwsi; 780 int n, m; 781 782 if (vhd) 783 ac = vhd->ac; 784 785 lws_jwe_init(&jwe, lws_get_context(wsi)); 786 787 switch ((int)reason) { 788 case LWS_CALLBACK_PROTOCOL_INIT: 789 if (vhd) 790 return 0; 791 vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi), 792 lws_get_protocol(wsi), 793 sizeof(struct per_vhost_data__lws_acme_client)); 794 if (!vhd) 795 return -1; 796 797 vhd->context = lws_get_context(wsi); 798 vhd->protocol = lws_get_protocol(wsi); 799 vhd->vhost = lws_get_vhost(wsi); 800 801 /* compute how much we need to hold all the pvo payloads */ 802 m = 0; 803 pvo = (const struct lws_protocol_vhost_options *)in; 804 while (pvo) { 805 m += (int)strlen(pvo->value) + 1; 806 pvo = pvo->next; 807 } 808 p = vhd->pvo_data = malloc((unsigned int)m); 809 if (!p) 810 return -1; 811 812 pvo = (const struct lws_protocol_vhost_options *)in; 813 while (pvo) { 814 start = p; 815 n = (int)strlen(pvo->value) + 1; 816 memcpy(start, pvo->value, (unsigned int)n); 817 p += n; 818 819 for (m = 0; m < (int)LWS_ARRAY_SIZE(pvo_names); m++) 820 if (!strcmp(pvo->name, pvo_names[m])) 821 vhd->pvop[m] = start; 822 823 pvo = pvo->next; 824 } 825 826 n = 0; 827 for (m = 0; m < (int)LWS_ARRAY_SIZE(pvo_names); m++) { 828 if (!vhd->pvop[m] && 829 m >= LWS_TLS_REQ_ELEMENT_COMMON_NAME && 830 m != LWS_TLS_REQ_ELEMENT_SUBJECT_ALT_NAME) { 831 lwsl_notice("%s: require pvo '%s'\n", __func__, 832 pvo_names[m]); 833 n |= 1; 834 } else { 835 if (vhd->pvop[m]) 836 lwsl_info(" %s: %s\n", pvo_names[m], 837 vhd->pvop[m]); 838 } 839 } 840 if (n) { 841 free(vhd->pvo_data); 842 vhd->pvo_data = NULL; 843 844 return -1; 845 } 846 847#if !defined(LWS_WITH_ESP32) 848 /* 849 * load (or create) the registration keypair while we 850 * still have root 851 */ 852 if (lws_acme_load_create_auth_keys(vhd, 4096)) 853 return 1; 854 855 /* 856 * in case we do an update, open the update files while we 857 * still have root 858 */ 859 lws_snprintf(buf, sizeof(buf) - 1, "%s.upd", 860 vhd->pvop[LWS_TLS_SET_CERT_PATH]); 861 vhd->fd_updated_cert = lws_open(buf, 862 LWS_O_WRONLY | LWS_O_CREAT | 863 LWS_O_TRUNC 864 /*do not replace \n to \r\n on Windows */ 865 #ifdef WIN32 866 | O_BINARY 867 #endif 868 , 0600); 869 if (vhd->fd_updated_cert < 0) { 870 lwsl_err("unable to create update cert file %s\n", buf); 871 return -1; 872 } 873 lws_snprintf(buf, sizeof(buf) - 1, "%s.upd", 874 vhd->pvop[LWS_TLS_SET_KEY_PATH]); 875 vhd->fd_updated_key = lws_open(buf, LWS_O_WRONLY | LWS_O_CREAT | 876 /*do not replace \n to \r\n on Windows */ 877 #ifdef WIN32 878 O_BINARY | 879 #endif 880 LWS_O_TRUNC, 0600); 881 if (vhd->fd_updated_key < 0) { 882 lwsl_vhost_err(vhd->vhost, "unable to create update key file %s", buf); 883 884 return -1; 885 } 886#endif 887 break; 888 889 case LWS_CALLBACK_PROTOCOL_DESTROY: 890 if (vhd && vhd->pvo_data) { 891 free(vhd->pvo_data); 892 vhd->pvo_data = NULL; 893 } 894 if (vhd) 895 lws_acme_finished(vhd); 896 break; 897 898 case LWS_CALLBACK_VHOST_CERT_AGING: 899 if (!vhd) 900 break; 901 902 caa = (struct lws_acme_cert_aging_args *)in; 903 /* 904 * Somebody is telling us about a cert some vhost is using. 905 * 906 * First see if the cert is getting close enough to expiry that 907 * we *want* to do something about it. 908 */ 909 if ((int)(ssize_t)len > 14) 910 break; 911 912 /* 913 * ...is this a vhost we were configured on? 914 */ 915 if (vhd->vhost != caa->vh) 916 return 1; 917 918 for (n = 0; n < (int)LWS_ARRAY_SIZE(vhd->pvop);n++) 919 if (caa->element_overrides[n]) 920 vhd->pvop_active[n] = caa->element_overrides[n]; 921 else 922 vhd->pvop_active[n] = vhd->pvop[n]; 923 924 lwsl_notice("starting acme acquisition on %s: %s\n", 925 lws_get_vhost_name(caa->vh), 926 vhd->pvop_active[LWS_TLS_SET_DIR_URL]); 927 928 lws_acme_start_acquisition(vhd, caa->vh); 929 break; 930 931 /* 932 * Client 933 */ 934 935 case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: 936 if (!ac) 937 break; 938 939 ac->resp = (int)lws_http_client_http_response(wsi); 940 941 /* we get a new nonce each time */ 942 if (lws_hdr_total_length(wsi, WSI_TOKEN_REPLAY_NONCE) && 943 lws_hdr_copy(wsi, ac->replay_nonce, 944 sizeof(ac->replay_nonce), 945 WSI_TOKEN_REPLAY_NONCE) < 0) { 946 lwsl_vhost_warn(vhd->vhost, "nonce too large"); 947 948 goto failed; 949 } 950 951 switch (ac->state) { 952 case ACME_STATE_DIRECTORY: 953 lejp_construct(&ac->jctx, cb_dir, vhd, jdir_tok, 954 LWS_ARRAY_SIZE(jdir_tok)); 955 break; 956 957 case ACME_STATE_NEW_NONCE: 958 /* 959 * we try to register our keys next. 960 * It's OK if it ends up they're already registered, 961 * this eliminates any gaps where we stored the key 962 * but registration did not complete for some reason... 963 */ 964 ac->state = ACME_STATE_NEW_ACCOUNT; 965 lws_acme_report_status(vhd->vhost, LWS_CUS_REG, NULL); 966 967 strcpy(buf, ac->urls[JAD_NEW_ACCOUNT_URL]); 968 cwsi = lws_acme_client_connect(vhd->context, vhd->vhost, 969 &ac->cwsi, &ac->i, buf, "POST"); 970 if (!cwsi) { 971 lwsl_vhost_warn(vhd->vhost, "failed to connect to acme"); 972 goto failed; 973 } 974 975 return -1; 976 977 case ACME_STATE_NEW_ACCOUNT: 978 if (!lws_hdr_total_length(wsi, 979 WSI_TOKEN_HTTP_LOCATION)) { 980 lwsl_vhost_warn(vhd->vhost, "no Location"); 981 goto failed; 982 } 983 984 if (lws_hdr_copy(wsi, ac->acct_id, sizeof(ac->acct_id), 985 WSI_TOKEN_HTTP_LOCATION) < 0) { 986 lwsl_vhost_warn(vhd->vhost, "Location too large"); 987 goto failed; 988 } 989 990 ac->kid = ac->acct_id; 991 992 lwsl_vhost_notice(vhd->vhost, "Location: %s", ac->acct_id); 993 break; 994 995 case ACME_STATE_NEW_ORDER: 996 if (lws_hdr_copy(wsi, ac->order_url, 997 sizeof(ac->order_url), 998 WSI_TOKEN_HTTP_LOCATION) < 0) { 999 lwsl_vhost_warn(vhd->vhost, "missing cert location"); 1000 1001 goto failed; 1002 } 1003 1004 lejp_construct(&ac->jctx, cb_order, ac, jorder_tok, 1005 LWS_ARRAY_SIZE(jorder_tok)); 1006 break; 1007 1008 case ACME_STATE_AUTHZ: 1009 lejp_construct(&ac->jctx, cb_authz, ac, jauthz_tok, 1010 LWS_ARRAY_SIZE(jauthz_tok)); 1011 break; 1012 1013 case ACME_STATE_START_CHALL: 1014 lejp_construct(&ac->jctx, cb_chac, ac, jchac_tok, 1015 LWS_ARRAY_SIZE(jchac_tok)); 1016 break; 1017 1018 case ACME_STATE_POLLING: 1019 case ACME_STATE_POLLING_CSR: 1020 lejp_construct(&ac->jctx, cb_order, ac, jorder_tok, 1021 LWS_ARRAY_SIZE(jorder_tok)); 1022 break; 1023 1024 case ACME_STATE_DOWNLOAD_CERT: 1025 ac->cpos = 0; 1026 break; 1027 1028 default: 1029 break; 1030 } 1031 break; 1032 1033 case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER: 1034 if (!ac) 1035 break; 1036 1037 switch (ac->state) { 1038 case ACME_STATE_DIRECTORY: 1039 case ACME_STATE_NEW_NONCE: 1040 break; 1041 1042 case ACME_STATE_NEW_ACCOUNT: 1043 p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), "{" 1044 "\"termsOfServiceAgreed\":true" 1045 ",\"contact\": [\"mailto:%s\"]}", 1046 vhd->pvop_active[LWS_TLS_REQ_ELEMENT_EMAIL]); 1047 1048 strcpy(ac->active_url, ac->urls[JAD_NEW_ACCOUNT_URL]); 1049pkt_add_hdrs: 1050 if (lws_gencrypto_jwe_alg_to_definition("RSA1_5", 1051 &jwe.jose.alg)) { 1052 ac->len = 0; 1053 lwsl_notice("%s: no RSA1_5\n", __func__); 1054 goto failed; 1055 } 1056 jwe.jwk = vhd->jwk; 1057 1058 ac->len = jws_create_packet(&jwe, 1059 start, lws_ptr_diff_size_t(p, start), 1060 ac->replay_nonce, 1061 ac->active_url, 1062 ac->kid, 1063 &ac->buf[LWS_PRE], 1064 sizeof(ac->buf) - LWS_PRE, 1065 lws_get_context(wsi)); 1066 if (ac->len < 0) { 1067 ac->len = 0; 1068 lwsl_notice("jws_create_packet failed\n"); 1069 goto failed; 1070 } 1071 1072 pp = (unsigned char **)in; 1073 pend = (*pp) + len; 1074 1075 ac->pos = 0; 1076 content_type = "application/jose+json"; 1077 1078 if (lws_add_http_header_by_token(wsi, 1079 WSI_TOKEN_HTTP_CONTENT_TYPE, 1080 (uint8_t *)content_type, 21, pp, 1081 pend)) { 1082 lwsl_vhost_warn(vhd->vhost, "could not add content type"); 1083 goto failed; 1084 } 1085 1086 n = sprintf(buf, "%d", ac->len); 1087 if (lws_add_http_header_by_token(wsi, 1088 WSI_TOKEN_HTTP_CONTENT_LENGTH, 1089 (uint8_t *)buf, n, pp, pend)) { 1090 lwsl_vhost_warn(vhd->vhost, "could not add content length"); 1091 goto failed; 1092 } 1093 1094 lws_client_http_body_pending(wsi, 1); 1095 lws_callback_on_writable(wsi); 1096 break; 1097 1098 case ACME_STATE_NEW_ORDER: 1099 p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), 1100 "{" 1101 "\"identifiers\":[{" 1102 "\"type\":\"dns\"," 1103 "\"value\":\"%s\"" 1104 "}]" 1105 "}", 1106 vhd->pvop_active[LWS_TLS_REQ_ELEMENT_COMMON_NAME]); 1107 1108 strcpy(ac->active_url, ac->urls[JAD_NEW_ORDER_URL]); 1109 goto pkt_add_hdrs; 1110 1111 case ACME_STATE_AUTHZ: 1112 strcpy(ac->active_url, ac->authz_url); 1113 goto pkt_add_hdrs; 1114 1115 case ACME_STATE_START_CHALL: 1116 p = start; 1117 end = &buf[sizeof(buf) - 1]; 1118 1119 p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), "{}"); 1120 strcpy(ac->active_url, ac->challenge_uri); 1121 goto pkt_add_hdrs; 1122 1123 case ACME_STATE_POLLING: 1124 strcpy(ac->active_url, ac->order_url); 1125 goto pkt_add_hdrs; 1126 1127 case ACME_STATE_POLLING_CSR: 1128 if (ac->goes_around) 1129 break; 1130 lwsl_vhost_notice(vhd->vhost, "Generating ACME CSR... may take a little while"); 1131 p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), "{\"csr\":\""); 1132 n = lws_tls_acme_sni_csr_create(vhd->context, 1133 &vhd->pvop_active[0], 1134 (uint8_t *)p, lws_ptr_diff_size_t(end, p), 1135 &ac->alloc_privkey_pem, 1136 &ac->len_privkey_pem); 1137 if (n < 0) { 1138 lwsl_vhost_warn(vhd->vhost, "CSR generation failed"); 1139 goto failed; 1140 } 1141 p += n; 1142 p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), "\"}"); 1143 strcpy(ac->active_url, ac->finalize_url); 1144 goto pkt_add_hdrs; 1145 1146 case ACME_STATE_DOWNLOAD_CERT: 1147 strcpy(ac->active_url, ac->cert_url); 1148 goto pkt_add_hdrs; 1149 break; 1150 1151 default: 1152 break; 1153 } 1154 break; 1155 1156 case LWS_CALLBACK_CLIENT_HTTP_WRITEABLE: 1157 1158 if (!ac) 1159 break; 1160 1161 if (ac->pos == ac->len) 1162 break; 1163 1164 ac->buf[LWS_PRE + ac->len] = '\0'; 1165 if (lws_write(wsi, (uint8_t *)ac->buf + LWS_PRE, 1166 (size_t)ac->len, LWS_WRITE_HTTP_FINAL) < 0) 1167 return -1; 1168 1169 ac->pos = ac->len; 1170 lws_client_http_body_pending(wsi, 0); 1171 break; 1172 1173 /* chunked content */ 1174 case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: 1175 if (!ac) 1176 return -1; 1177 1178 switch (ac->state) { 1179 case ACME_STATE_POLLING_CSR: 1180 case ACME_STATE_POLLING: 1181 case ACME_STATE_START_CHALL: 1182 case ACME_STATE_AUTHZ: 1183 case ACME_STATE_NEW_ORDER: 1184 case ACME_STATE_DIRECTORY: 1185 1186 m = lejp_parse(&ac->jctx, (uint8_t *)in, (int)len); 1187 if (m < 0 && m != LEJP_CONTINUE) { 1188 lwsl_notice("lejp parse failed %d\n", m); 1189 goto failed; 1190 } 1191 break; 1192 1193 case ACME_STATE_NEW_ACCOUNT: 1194 break; 1195 1196 case ACME_STATE_DOWNLOAD_CERT: 1197 /* 1198 * It should be the DER cert... 1199 * ACME 2.0 can send certs chain with 3 certs, store only first bytes 1200 */ 1201 if ((unsigned int)ac->cpos + len > sizeof(ac->buf)) 1202 len = sizeof(ac->buf) - (unsigned int)ac->cpos; 1203 1204 if (len) { 1205 memcpy(&ac->buf[ac->cpos], in, len); 1206 ac->cpos += (int)len; 1207 } 1208 break; 1209 default: 1210 break; 1211 } 1212 break; 1213 1214 /* unchunked content */ 1215 case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: 1216 if (!ac) 1217 return -1; 1218 1219 switch (ac->state) { 1220 default: 1221 { 1222 char buffer[2048 + LWS_PRE]; 1223 char *px = buffer + LWS_PRE; 1224 int lenx = sizeof(buffer) - LWS_PRE; 1225 1226 if (lws_http_client_read(wsi, &px, &lenx) < 0) 1227 return -1; 1228 } 1229 break; 1230 } 1231 break; 1232 1233 case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: 1234 1235 if (!ac) 1236 return -1; 1237 1238 switch (ac->state) { 1239 case ACME_STATE_DIRECTORY: 1240 lejp_destruct(&ac->jctx); 1241 1242 /* check dir validity */ 1243 1244 for (n = 0; n < 6; n++) 1245 lwsl_notice(" %d: %s\n", n, ac->urls[n]); 1246 1247 ac->state = ACME_STATE_NEW_NONCE; 1248 1249 strcpy(buf, ac->urls[JAD_NEW_NONCE_URL]); 1250 cwsi = lws_acme_client_connect(vhd->context, vhd->vhost, 1251 &ac->cwsi, &ac->i, buf, 1252 "GET"); 1253 if (!cwsi) { 1254 lwsl_notice("%s: failed to connect to acme\n", 1255 __func__); 1256 goto failed; 1257 } 1258 return -1; /* close the completed client connection */ 1259 1260 case ACME_STATE_NEW_ACCOUNT: 1261 if ((ac->resp >= 200 && ac->resp < 299) || 1262 ac->resp == 409) { 1263 /* 1264 * Our account already existed, or exists now. 1265 * 1266 */ 1267 ac->state = ACME_STATE_NEW_ORDER; 1268 1269 strcpy(buf, ac->urls[JAD_NEW_ORDER_URL]); 1270 cwsi = lws_acme_client_connect(vhd->context, 1271 vhd->vhost, &ac->cwsi, 1272 &ac->i, buf, "POST"); 1273 if (!cwsi) 1274 lwsl_notice("%s: failed to connect\n", 1275 __func__); 1276 1277 /* close the completed client connection */ 1278 return -1; 1279 } else { 1280 lwsl_notice("newAccount replied %d\n", 1281 ac->resp); 1282 goto failed; 1283 } 1284 return -1; /* close the completed client connection */ 1285 1286 case ACME_STATE_NEW_ORDER: 1287 lejp_destruct(&ac->jctx); 1288 if (!ac->authz_url[0]) { 1289 lwsl_notice("no authz\n"); 1290 goto failed; 1291 } 1292 1293 /* 1294 * Move on to requesting a cert auth. 1295 */ 1296 ac->state = ACME_STATE_AUTHZ; 1297 lws_acme_report_status(vhd->vhost, LWS_CUS_AUTH, 1298 NULL); 1299 1300 strcpy(buf, ac->authz_url); 1301 cwsi = lws_acme_client_connect(vhd->context, 1302 vhd->vhost, &ac->cwsi, 1303 &ac->i, buf, "POST"); 1304 if (!cwsi) 1305 lwsl_notice("%s: failed to connect\n", __func__); 1306 1307 return -1; /* close the completed client connection */ 1308 1309 case ACME_STATE_AUTHZ: 1310 lejp_destruct(&ac->jctx); 1311 if (ac->resp / 100 == 4) { 1312 lws_snprintf(buf, sizeof(buf), 1313 "Auth failed: %s", ac->detail); 1314 failreason = buf; 1315 lwsl_vhost_warn(vhd->vhost, "auth failed"); 1316 goto failed; 1317 } 1318 lwsl_vhost_info(vhd->vhost, "chall: %s (%d)\n", ac->chall_token, ac->resp); 1319 if (!ac->chall_token[0]) { 1320 lwsl_vhost_warn(vhd->vhost, "no challenge"); 1321 goto failed; 1322 } 1323 1324 ac->state = ACME_STATE_START_CHALL; 1325 lws_acme_report_status(vhd->vhost, LWS_CUS_CHALLENGE, 1326 NULL); 1327 1328 memset(&ac->ci, 0, sizeof(ac->ci)); 1329 1330 /* compute the key authorization */ 1331 1332 p = ac->key_auth; 1333 end = p + sizeof(ac->key_auth) - 1; 1334 1335 p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), "%s.", ac->chall_token); 1336 lws_jwk_rfc7638_fingerprint(&vhd->jwk, digest); 1337 n = lws_jws_base64_enc(digest, 32, p, lws_ptr_diff_size_t(end, p)); 1338 if (n < 0) 1339 goto failed; 1340 1341 lwsl_vhost_notice(vhd->vhost, "key_auth: '%s'", ac->key_auth); 1342 1343 lws_snprintf(ac->http01_mountpoint, 1344 sizeof(ac->http01_mountpoint), 1345 "/.well-known/acme-challenge/%s", 1346 ac->chall_token); 1347 1348 memset(&ac->mount, 0, sizeof (struct lws_http_mount)); 1349 ac->mount.protocol = "http"; 1350 ac->mount.mountpoint = ac->http01_mountpoint; 1351 ac->mount.mountpoint_len = (unsigned char) 1352 strlen(ac->http01_mountpoint); 1353 ac->mount.origin_protocol = LWSMPRO_CALLBACK; 1354 1355 ac->ci.mounts = &ac->mount; 1356 1357 /* listen on the same port as the vhost that triggered us */ 1358 ac->ci.port = 80; 1359 1360 /* make ourselves protocols[0] for the new vhost */ 1361 ac->ci.protocols = chall_http01_protocols; 1362 1363 /* 1364 * vhost .user points to the ac associated with the 1365 * temporary vhost 1366 */ 1367 ac->ci.user = ac; 1368 1369 ac->vhost = lws_create_vhost(lws_get_context(wsi), 1370 &ac->ci); 1371 if (!ac->vhost) 1372 goto failed; 1373 1374 lwsl_vhost_notice(vhd->vhost, "challenge_uri %s", ac->challenge_uri); 1375 1376 /* 1377 * The challenge-specific vhost is up... let the ACME 1378 * server know we are ready to roll... 1379 */ 1380 ac->goes_around = 0; 1381 cwsi = lws_acme_client_connect(vhd->context, vhd->vhost, 1382 &ac->cwsi, &ac->i, 1383 ac->challenge_uri, 1384 "POST"); 1385 if (!cwsi) { 1386 lwsl_vhost_warn(vhd->vhost, "Connect failed"); 1387 goto failed; 1388 } 1389 return -1; /* close the completed client connection */ 1390 1391 case ACME_STATE_START_CHALL: 1392 lwsl_vhost_notice(vhd->vhost, "COMPLETED start chall: %s", 1393 ac->challenge_uri); 1394poll_again: 1395 ac->state = ACME_STATE_POLLING; 1396 lws_acme_report_status(vhd->vhost, LWS_CUS_CHALLENGE, 1397 NULL); 1398 1399 if (ac->goes_around++ == 20) { 1400 lwsl_notice("%s: too many chall retries\n", 1401 __func__); 1402 1403 goto failed; 1404 } 1405 1406 strcpy(buf, ac->order_url); 1407 cwsi = lws_acme_client_connect(vhd->context, vhd->vhost, 1408 &ac->cwsi, &ac->i, buf, 1409 "POST"); 1410 if (!cwsi) { 1411 lwsl_vhost_warn(vhd->vhost, "failed to connect to acme"); 1412 1413 goto failed; 1414 } 1415 return -1; /* close the completed client connection */ 1416 1417 case ACME_STATE_POLLING: 1418 1419 if (ac->resp == 202 && strcmp(ac->status, "invalid") && 1420 strcmp(ac->status, "valid")) 1421 goto poll_again; 1422 1423 if (!strcmp(ac->status, "pending")) 1424 goto poll_again; 1425 1426 if (!strcmp(ac->status, "invalid")) { 1427 lwsl_vhost_warn(vhd->vhost, "Challenge failed"); 1428 lws_snprintf(buf, sizeof(buf), 1429 "Challenge Invalid: %s", 1430 ac->detail); 1431 failreason = buf; 1432 goto failed; 1433 } 1434 1435 lwsl_vhost_notice(vhd->vhost, "ACME challenge passed"); 1436 1437 /* 1438 * The challenge was validated... so delete the 1439 * temp vhost now its job is done 1440 */ 1441 if (ac->vhost) 1442 lws_vhost_destroy(ac->vhost); 1443 ac->vhost = NULL; 1444 1445 /* 1446 * now our JWK is accepted as authorized to make 1447 * requests for the domain, next move is create the 1448 * CSR signed with the JWK, and send it to the ACME 1449 * server to request the actual certs. 1450 */ 1451 ac->state = ACME_STATE_POLLING_CSR; 1452 lws_acme_report_status(vhd->vhost, LWS_CUS_REQ, NULL); 1453 ac->goes_around = 0; 1454 1455 strcpy(buf, ac->finalize_url); 1456 cwsi = lws_acme_client_connect(vhd->context, vhd->vhost, 1457 &ac->cwsi, &ac->i, buf, 1458 "POST"); 1459 if (!cwsi) { 1460 lwsl_vhost_warn(vhd->vhost, "Failed to connect to acme"); 1461 1462 goto failed; 1463 } 1464 return -1; /* close the completed client connection */ 1465 1466 case ACME_STATE_POLLING_CSR: 1467 if (ac->resp < 200 || ac->resp > 202) { 1468 lwsl_notice("CSR poll failed on resp %d\n", 1469 ac->resp); 1470 goto failed; 1471 } 1472 1473 if (ac->resp != 200) { 1474 if (ac->goes_around++ == 30) { 1475 lwsl_vhost_warn(vhd->vhost, "Too many retries"); 1476 1477 goto failed; 1478 } 1479 strcpy(buf, ac->finalize_url); 1480 cwsi = lws_acme_client_connect(vhd->context, 1481 vhd->vhost, 1482 &ac->cwsi, &ac->i, buf, 1483 "POST"); 1484 if (!cwsi) { 1485 lwsl_vhost_warn(vhd->vhost, 1486 "Failed to connect to acme"); 1487 1488 goto failed; 1489 } 1490 /* close the completed client connection */ 1491 return -1; 1492 } 1493 1494 ac->state = ACME_STATE_DOWNLOAD_CERT; 1495 1496 strcpy(buf, ac->cert_url); 1497 cwsi = lws_acme_client_connect(vhd->context, vhd->vhost, 1498 &ac->cwsi, &ac->i, buf, 1499 "POST"); 1500 if (!cwsi) { 1501 lwsl_vhost_warn(vhd->vhost, "Failed to connect to acme"); 1502 1503 goto failed; 1504 } 1505 return -1; 1506 1507 case ACME_STATE_DOWNLOAD_CERT: 1508 1509 if (ac->resp != 200) { 1510 lwsl_vhost_warn(vhd->vhost, "Download cert failed on resp %d", 1511 ac->resp); 1512 goto failed; 1513 } 1514 lwsl_vhost_notice(vhd->vhost, "The cert was sent.."); 1515 1516 lws_acme_report_status(vhd->vhost, LWS_CUS_ISSUE, NULL); 1517 1518 /* 1519 * That means we have the issued cert in 1520 * ac->buf, length in ac->cpos; and the key in 1521 * ac->alloc_privkey_pem, length in 1522 * ac->len_privkey_pem. 1523 * ACME 2.0 can send certs chain with 3 certs, we need save only first 1524 */ 1525 { 1526 char *end_cert = strstr(ac->buf, "END CERTIFICATE-----"); 1527 1528 if (end_cert) { 1529 ac->cpos = (int)(lws_ptr_diff_size_t(end_cert, ac->buf) + sizeof("END CERTIFICATE-----") - 1); 1530 } else { 1531 ac->cpos = 0; 1532 lwsl_vhost_err(vhd->vhost, "Unable to find ACME cert!"); 1533 goto failed; 1534 } 1535 } 1536 n = lws_plat_write_cert(vhd->vhost, 0, 1537 vhd->fd_updated_cert, 1538 ac->buf, 1539 (size_t)ac->cpos); 1540 if (n) { 1541 lwsl_vhost_err(vhd->vhost, "unable to write ACME cert! %d", n); 1542 goto failed; 1543 } 1544 1545 /* 1546 * don't close it... we may update the certs 1547 * again 1548 */ 1549 if (lws_plat_write_cert(vhd->vhost, 1, 1550 vhd->fd_updated_key, 1551 ac->alloc_privkey_pem, 1552 ac->len_privkey_pem)) { 1553 lwsl_vhost_err(vhd->vhost, "unable to write ACME key!"); 1554 goto failed; 1555 } 1556 1557 /* 1558 * we have written the persistent copies 1559 */ 1560 lwsl_vhost_notice(vhd->vhost, "Updated certs written for %s " 1561 "to %s.upd and %s.upd", 1562 vhd->pvop_active[LWS_TLS_REQ_ELEMENT_COMMON_NAME], 1563 vhd->pvop_active[LWS_TLS_SET_CERT_PATH], 1564 vhd->pvop_active[LWS_TLS_SET_KEY_PATH]); 1565 1566 /* notify lws there was a cert update */ 1567 1568 if (lws_tls_cert_updated(vhd->context, 1569 vhd->pvop_active[LWS_TLS_SET_CERT_PATH], 1570 vhd->pvop_active[LWS_TLS_SET_KEY_PATH], 1571 ac->buf, (size_t)ac->cpos, 1572 ac->alloc_privkey_pem, 1573 ac->len_privkey_pem)) { 1574 lwsl_vhost_warn(vhd->vhost, "problem setting certs"); 1575 } 1576 1577 lws_acme_finished(vhd); 1578 lws_acme_report_status(vhd->vhost, 1579 LWS_CUS_SUCCESS, NULL); 1580 1581 return -1; 1582 1583 default: 1584 break; 1585 } 1586 break; 1587 1588 case LWS_CALLBACK_USER + 0xac33: 1589 if (!vhd) 1590 break; 1591 cwsi = lws_acme_client_connect(vhd->context, vhd->vhost, 1592 &ac->cwsi, &ac->i, 1593 ac->challenge_uri, 1594 "GET"); 1595 if (!cwsi) { 1596 lwsl_vhost_warn(vhd->vhost, "Failed to connect"); 1597 goto failed; 1598 } 1599 break; 1600 1601 default: 1602 break; 1603 } 1604 1605 return 0; 1606 1607failed: 1608 lwsl_vhost_warn(vhd->vhost, "Failed out"); 1609 lws_acme_report_status(vhd->vhost, LWS_CUS_FAILED, failreason); 1610 lws_acme_finished(vhd); 1611 1612 return -1; 1613} 1614 1615#if !defined (LWS_PLUGIN_STATIC) 1616 1617LWS_VISIBLE const struct lws_protocols lws_acme_client_protocols[] = { 1618 LWS_PLUGIN_PROTOCOL_LWS_ACME_CLIENT 1619}; 1620 1621LWS_VISIBLE const lws_plugin_protocol_t protocol_lws_acme_client = { 1622 .hdr = { 1623 "acme client", 1624 "lws_protocol_plugin", 1625 LWS_BUILD_HASH, 1626 LWS_PLUGIN_API_MAGIC 1627 }, 1628 1629 .protocols = lws_acme_client_protocols, 1630 .count_protocols = LWS_ARRAY_SIZE(lws_acme_client_protocols), 1631 .extensions = NULL, 1632 .count_extensions = 0, 1633}; 1634 1635#endif 1636