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