• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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