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