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
45 typedef 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
59 struct 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
106 struct 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
132 static int
callback_chall_http01(struct lws * wsi,enum lws_callback_reasons reason,void * user,void * in,size_t len)133 callback_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
203 static 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
208 static int
jws_create_packet(struct lws_jwe * jwe,const char * payload,size_t len,const char * nonce,const char * url,const char * kid,char * out,size_t out_len,struct lws_context * context)209 jws_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
305 bail:
306 lws_jws_destroy(&jws);
307 free(buf);
308
309 return -1;
310 }
311
312 static int
313 callback_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
327 static const char * const jdir_tok[] = {
328 "keyChange",
329 "meta.termsOfService",
330 "newAccount",
331 "newNonce",
332 "newOrder",
333 "revokeCert",
334 };
335
336 enum 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
345 static signed char
cb_dir(struct lejp_ctx * ctx,char reason)346 cb_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
376 static const char * const jorder_tok[] = {
377 "status",
378 "expires",
379 "identifiers[].type",
380 "identifiers[].value",
381 "authorizations",
382 "finalize",
383 "certificate"
384 };
385
386 enum 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
396 static signed char
cb_order(struct lejp_ctx * ctx,char reason)397 cb_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
435 static 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
447 enum 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
459 static signed char
cb_authz(struct lejp_ctx * ctx,char reason)460 cb_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
515 static const char * const jchac_tok[] = {
516 "type",
517 "status",
518 "uri",
519 "token",
520 "error.detail"
521 };
522
523 enum enum_jchac_tok {
524 JCAC_TYPE,
525 JCAC_STATUS,
526 JCAC_URI,
527 JCAC_TOKEN,
528 JCAC_DETAIL,
529 };
530
531 static signed char
cb_chac(struct lejp_ctx * ctx,char reason)532 cb_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
567 static int
lws_acme_report_status(struct lws_vhost * v,int state,const char * json)568 lws_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 */
579 static struct lws *
lws_acme_client_connect(struct lws_context * context,struct lws_vhost * vh,struct lws ** pwsi,struct lws_client_connect_info * i,char * url,const char * method)580 lws_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
621 static void
lws_acme_finished(struct per_vhost_data__lws_acme_client * vhd)622 lws_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
643 static 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
657 static int
lws_acme_load_create_auth_keys(struct per_vhost_data__lws_acme_client * vhd,int bits)658 lws_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
689 static int
lws_acme_start_acquisition(struct per_vhost_data__lws_acme_client * vhd,struct lws_vhost * v)690 lws_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)
755 bail:
756 #endif
757 free(vhd->ac);
758 vhd->ac = NULL;
759
760 return 1;
761 }
762
763 static int
callback_acme_client(struct lws * wsi,enum lws_callback_reasons reason,void * user,void * in,size_t len)764 callback_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]);
1049 pkt_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);
1394 poll_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
1607 failed:
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
1617 LWS_VISIBLE const struct lws_protocols lws_acme_client_protocols[] = {
1618 LWS_PLUGIN_PROTOCOL_LWS_ACME_CLIENT
1619 };
1620
1621 LWS_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