diff --git a/plugins/acme-client/protocol_lws_acme_client.c b/plugins/acme-client/protocol_lws_acme_client.c index e21628efe..5db2b48eb 100644 --- a/plugins/acme-client/protocol_lws_acme_client.c +++ b/plugins/acme-client/protocol_lws_acme_client.c @@ -43,12 +43,15 @@ #include typedef enum { - ACME_STATE_DIRECTORY, /* get the directory JSON using GET + parse */ - ACME_STATE_NEW_REG, /* register a new RSA key + email combo */ - ACME_STATE_NEW_AUTH, /* start the process to request a cert */ - ACME_STATE_ACCEPT_CHALL, /* notify server ready for one challenge */ - ACME_STATE_POLLING, /* he should be trying our challenge */ - ACME_STATE_POLLING_CSR, /* sent CSR, checking result */ + ACME_STATE_DIRECTORY, /* get the directory JSON using GET + parse */ + ACME_STATE_NEW_NONCE, /* get the replay nonce */ + ACME_STATE_NEW_ACCOUNT, /* register a new RSA key + email combo */ + ACME_STATE_NEW_ORDER, /* start the process to request a cert */ + ACME_STATE_AUTHZ, /* */ + ACME_STATE_START_CHALL, /* notify server ready for one challenge */ + ACME_STATE_POLLING, /* he should be trying our challenge */ + ACME_STATE_POLLING_CSR, /* sent CSR, checking result */ + ACME_STATE_DOWNLOAD_CERT, ACME_STATE_FINISHED } lws_acme_state; @@ -60,9 +63,17 @@ struct acme_connection { char challenge_uri[256]; char detail[64]; char status[16]; - char san_a[100]; - char san_b[100]; + char key_auth[256]; + char http01_mountpoint[256]; + struct lws_http_mount mount; char urls[6][100]; /* directory contents */ + char active_url[100]; + char authz_url[100]; + char order_url[100]; + char finalize_url[100]; + char cert_url[100]; + char acct_id[100]; + char *kid; lws_acme_state state; struct lws_client_connect_info i; struct lejp_ctx jctx; @@ -119,52 +130,230 @@ struct per_vhost_data__lws_acme_client { }; static int -callback_acme_client(struct lws *wsi, enum lws_callback_reasons reason, - void *user, void *in, size_t len); +callback_chall_http01(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + struct lws_vhost *vhost = lws_get_vhost(wsi); + struct acme_connection *ac = lws_vhost_user(vhost); + uint8_t buf[LWS_PRE + 2048], *start = &buf[LWS_PRE], *p = start, + *end = &buf[sizeof(buf) - LWS_PRE - 1]; + int n; -#define LWS_PLUGIN_PROTOCOL_LWS_ACME_CLIENT \ - { \ - "lws-acme-client", \ - callback_acme_client, \ - 0, \ - 512, \ - 0, NULL, 0 \ + switch (reason) { + case LWS_CALLBACK_HTTP: + lwsl_notice("%s: ca connection received, key_auth %s\n", + __func__, ac->key_auth); + + if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end)) { + lwsl_notice("%s: add status failed\n", __func__); + return -1; + } + + if (lws_add_http_header_by_token(wsi, + WSI_TOKEN_HTTP_CONTENT_TYPE, + (unsigned char *)"text/plain", 10, + &p, end)) { + lwsl_notice("%s: add content_type failed\n", __func__); + return -1; + } + + n = strlen(ac->key_auth); + if (lws_add_http_header_content_length(wsi, n, &p, end)) { + lwsl_notice("%s: add content_length failed\n", + __func__); + return -1; + } + + if (lws_add_http_header_by_token(wsi, + WSI_TOKEN_HTTP_CONTENT_DISPOSITION, + (unsigned char *)"attachment", 10, + &p, end)) { + lwsl_notice("%s: add content_dispo failed\n", __func__); + return -1; + } + + if (lws_finalize_write_http_header(wsi, start, &p, end)) { + lwsl_notice("%s: finalize http header failed\n", + __func__); + return -1; + } + + lws_callback_on_writable(wsi); + return 0; + + case LWS_CALLBACK_HTTP_WRITEABLE: + p += lws_snprintf((char *)p, end - p, "%s", ac->key_auth); + lwsl_notice("%s: len %d\n", __func__, lws_ptr_diff(p, start)); + if (lws_write(wsi, (uint8_t *)start, lws_ptr_diff(p, start), + LWS_WRITE_HTTP_FINAL) != lws_ptr_diff(p, start)) { + lwsl_err("_write content failed\n"); + return 1; + } + + if (lws_http_transaction_completed(wsi)) + return -1; + + return 0; + + default: + break; } -static const struct lws_protocols acme_protocols[] = { - LWS_PLUGIN_PROTOCOL_LWS_ACME_CLIENT, + return lws_callback_http_dummy(wsi, reason, user, in, len); +} + +static const struct lws_protocols chall_http01_protocols[] = { + { "http", callback_chall_http01, 0, 0, 0, NULL, 0 }, { NULL, NULL, 0, 0, 0, NULL, 0 } }; +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) +{ + char *buf, *start, *p, *end, *p1, *end1; + struct lws_jws jws; + int n, m; + + lws_jws_init(&jws, &jwe->jwk, context); + + /* + * This buffer is local to the function, the actual output is prepared + * into out. Only the plaintext protected header + * (which contains the public key, 512 bytes for 4096b) goes in + * here temporarily. + */ + n = LWS_PRE + 2048; + buf = malloc(n); + if (!buf) { + lwsl_notice("%s: malloc %d failed\n", __func__, n); + return -1; + } + + p = start = buf + LWS_PRE; + end = buf + n - LWS_PRE - 1; + + /* + * temporary JWS protected header plaintext + */ + if (!jwe->jose.alg || !jwe->jose.alg->alg) + goto bail; + + p += lws_snprintf(p, end - p, "{\"alg\":\"RS256\""); + if (kid) + p += lws_snprintf(p, end - p, ",\"kid\":\"%s\"", kid); + else { + p += lws_snprintf(p, end - p, ",\"jwk\":"); + m = end - p; + n = lws_jwk_export(&jwe->jwk, 0, p, &m); + if (n < 0) { + lwsl_notice("failed to export jwk\n"); + goto bail; + } + p += n; + } + p += lws_snprintf(p, end - p, ",\"url\":\"%s\"", url); + p += lws_snprintf(p, end - p, ",\"nonce\":\"%s\"}", nonce); + + /* + * prepare the signed outer JSON with all the parts in + */ + p1 = out; + end1 = out + out_len - 1; + + p1 += lws_snprintf(p1, end1 - p1, "{\"protected\":\""); + jws.map_b64.buf[LJWS_JOSE] = p1; + n = lws_jws_base64_enc(start, p - start, p1, end1 - p1); + if (n < 0) { + lwsl_notice("%s: failed to encode protected\n", __func__); + goto bail; + } + jws.map_b64.len[LJWS_JOSE] = n; + p1 += n; + + p1 += lws_snprintf(p1, end1 - p1, "\",\"payload\":\""); + jws.map_b64.buf[LJWS_PYLD] = p1; + n = lws_jws_base64_enc(payload, len, p1, end1 - p1); + if (n < 0) { + lwsl_notice("%s: failed to encode payload\n", __func__); + goto bail; + } + jws.map_b64.len[LJWS_PYLD] = n; + p1 += n; + + p1 += lws_snprintf(p1, end1 - p1, "\",\"signature\":\""); + + /* + * taking the b64 protected header and the b64 payload, sign them + * and place the signature into the packet + */ + n = lws_jws_sign_from_b64(&jwe->jose, &jws, p1, end1 - p1); + if (n < 0) { + lwsl_notice("sig gen failed\n"); + + goto bail; + } + jws.map_b64.buf[LJWS_SIG] = p1; + jws.map_b64.len[LJWS_SIG] = n; + + p1 += n; + p1 += lws_snprintf(p1, end1 - p1, "\"}"); + + free(buf); + + return p1 - out; + +bail: + lws_jws_destroy(&jws); + free(buf); + + return -1; +} + +static int +callback_acme_client(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len); + +#define LWS_PLUGIN_PROTOCOL_LWS_ACME_CLIENT \ +{ \ + "lws-acme-client", \ + callback_acme_client, \ + 0, \ + 512, \ + 0, NULL, 0 \ +} + /* directory JSON parsing */ static const char * const jdir_tok[] = { - "key-change", - "meta.terms-of-service", - "new-authz", - "new-cert", - "new-reg", - "revoke-cert", + "keyChange", + "meta.termsOfService", + "newAccount", + "newNonce", + "newOrder", + "revokeCert", }; -enum enum_jhdr_tok { + +enum enum_jdir_tok { JAD_KEY_CHANGE_URL, JAD_TOS_URL, - JAD_NEW_AUTHZ_URL, - JAD_NEW_CERT_URL, - JAD_NEW_REG_URL, + JAD_NEW_ACCOUNT_URL, + JAD_NEW_NONCE_URL, + JAD_NEW_ORDER_URL, JAD_REVOKE_CERT_URL, }; + static signed char cb_dir(struct lejp_ctx *ctx, char reason) { struct per_vhost_data__lws_acme_client *s = - (struct per_vhost_data__lws_acme_client *)ctx->user; + (struct per_vhost_data__lws_acme_client *)ctx->user; if (reason == LEJPCB_VAL_STR_START && ctx->path_match) { s->pos = 0; s->len = sizeof(s->ac->urls[0]) - 1; s->dest = s->ac->urls[ctx->path_match - 1]; - return 0; } @@ -173,7 +362,6 @@ cb_dir(struct lejp_ctx *ctx, char reason) if (s->pos + ctx->npos > s->len) { lwsl_notice("url too long\n"); - return -1; } @@ -184,6 +372,66 @@ cb_dir(struct lejp_ctx *ctx, char reason) return 0; } + +/* order JSON parsing */ + +static const char * const jorder_tok[] = { + "status", + "expires", + "identifiers[].type", + "identifiers[].value", + "authorizations", + "finalize", + "certificate" +}; + +enum enum_jorder_tok { + JAO_STATUS, + JAO_EXPIRES, + JAO_IDENTIFIERS_TYPE, + JAO_IDENTIFIERS_VALUE, + JAO_AUTHORIZATIONS, + JAO_FINALIZE, + JAO_CERT +}; + +static signed char +cb_order(struct lejp_ctx *ctx, char reason) +{ + struct acme_connection *s = (struct acme_connection *)ctx->user; + + if (reason == LEJPCB_CONSTRUCTED) + s->authz_url[0] = '\0'; + + if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match) + return 0; + + switch (ctx->path_match - 1) { + case JAO_STATUS: + lws_strncpy(s->status, ctx->buf, sizeof(s->status)); + break; + case JAO_EXPIRES: + break; + case JAO_IDENTIFIERS_TYPE: + break; + case JAO_IDENTIFIERS_VALUE: + break; + case JAO_AUTHORIZATIONS: + lws_snprintf(s->authz_url, sizeof(s->authz_url), "%s", + ctx->buf); + break; + case JAO_FINALIZE: + lws_snprintf(s->finalize_url, sizeof(s->finalize_url), "%s", + ctx->buf); + break; + case JAO_CERT: + lws_snprintf(s->cert_url, sizeof(s->cert_url), "%s", ctx->buf); + break; + } + + return 0; +} + /* authz JSON parsing */ static const char * const jauthz_tok[] = { @@ -193,10 +441,11 @@ static const char * const jauthz_tok[] = { "expires", "challenges[].type", "challenges[].status", - "challenges[].uri", + "challenges[].url", "challenges[].token", "detail" }; + enum enum_jauthz_tok { JAAZ_ID_TYPE, JAAZ_ID_VALUE, @@ -204,10 +453,11 @@ enum enum_jauthz_tok { JAAZ_EXPIRES, JAAZ_CHALLENGES_TYPE, JAAZ_CHALLENGES_STATUS, - JAAZ_CHALLENGES_URI, + JAAZ_CHALLENGES_URL, JAAZ_CHALLENGES_TOKEN, JAAZ_DETAIL, }; + static signed char cb_authz(struct lejp_ctx *ctx, char reason) { @@ -217,7 +467,6 @@ cb_authz(struct lejp_ctx *ctx, char reason) s->yes = 0; s->use = 0; s->chall_token[0] = '\0'; - s->is_sni_02 = 0; } if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match) @@ -236,19 +485,17 @@ cb_authz(struct lejp_ctx *ctx, char reason) lws_snprintf(s->detail, sizeof(s->detail), "%s", ctx->buf); break; case JAAZ_CHALLENGES_TYPE: - if (s->is_sni_02) - break; - s->use = !strcmp(ctx->buf, "tls-sni-01") || - !strcmp(ctx->buf, "tls-sni-02"); - s->is_sni_02 = !strcmp(ctx->buf, "tls-sni-02"); + lwsl_notice("JAAZ_CHALLENGES_TYPE: %s\n", ctx->buf); + s->use = !strcmp(ctx->buf, "http-01"); break; case JAAZ_CHALLENGES_STATUS: lws_strncpy(s->status, ctx->buf, sizeof(s->status)); break; - case JAAZ_CHALLENGES_URI: + case JAAZ_CHALLENGES_URL: + lwsl_notice("JAAZ_CHALLENGES_URL: %s %d\n", ctx->buf, s->use); if (s->use) { lws_strncpy(s->challenge_uri, ctx->buf, - sizeof(s->challenge_uri)); + sizeof(s->challenge_uri)); s->yes |= 2; } break; @@ -256,7 +503,7 @@ cb_authz(struct lejp_ctx *ctx, char reason) lwsl_notice("JAAZ_CHALLENGES_TOKEN: %s %d\n", ctx->buf, s->use); if (s->use) { lws_strncpy(s->chall_token, ctx->buf, - sizeof(s->chall_token)); + sizeof(s->chall_token)); s->yes |= 1; } break; @@ -274,6 +521,7 @@ static const char * const jchac_tok[] = { "token", "error.detail" }; + enum enum_jchac_tok { JCAC_TYPE, JCAC_STATUS, @@ -281,6 +529,7 @@ enum enum_jchac_tok { JCAC_TOKEN, JCAC_DETAIL, }; + static signed char cb_chac(struct lejp_ctx *ctx, char reason) { @@ -296,8 +545,7 @@ cb_chac(struct lejp_ctx *ctx, char reason) switch (ctx->path_match - 1) { case JCAC_TYPE: - if (strcmp(ctx->buf, "tls-sni-01") && - strcmp(ctx->buf, "tls-sni-02")) + if (strcmp(ctx->buf, "http-01")) return 1; break; case JCAC_STATUS: @@ -307,8 +555,7 @@ cb_chac(struct lejp_ctx *ctx, char reason) s->yes |= 2; break; case JCAC_TOKEN: - lws_strncpy(s->chall_token, ctx->buf, - sizeof(s->chall_token)); + lws_strncpy(s->chall_token, ctx->buf, sizeof(s->chall_token)); s->yes |= 1; break; case JCAC_DETAIL: @@ -319,29 +566,6 @@ cb_chac(struct lejp_ctx *ctx, char reason) return 0; } -/* https://github.com/letsencrypt/boulder/blob/release/docs/acme-divergences.md - * - * 7.1: - * - * Boulder does not implement the new-order resource. - * Instead of new-order Boulder implements the new-cert resource that is - * defined in draft-ietf-acme-02 Section 6.5. - * - * Boulder also doesn't implement the new-nonce endpoint. - * - * Boulder implements the new-account resource only under the new-reg key. - * - * Boulder implements Link: rel="next" headers from new-reg to new-authz, and - * new-authz to new-cert, as specified in draft-02, but these links are not - * provided in the latest draft, and clients should use URLs from the directory - * instead. - * - * Boulder does not provide the "index" link relation pointing at the - * directory URL. - * - * (ie, just use new-cert instead of new-order, use the directory for links) - */ - static int lws_acme_report_status(struct lws_vhost *v, int state, const char *json) { @@ -356,8 +580,8 @@ lws_acme_report_status(struct lws_vhost *v, int state, const char *json) */ 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) + struct lws **pwsi, struct lws_client_connect_info *i, + char *url, const char *method) { const char *prot, *p; char path[200], _url[256]; @@ -378,7 +602,7 @@ lws_acme_client_connect(struct lws_context *context, struct lws_vhost *vh, i->path = path; i->context = context; i->vhost = vh; - i->ssl_connection = 1; + i->ssl_connection = LCCSCF_USE_SSL; i->host = i->address; i->origin = i->address; i->method = method; @@ -399,7 +623,7 @@ lws_acme_client_connect(struct lws_context *context, struct lws_vhost *vh, static void lws_acme_finished(struct per_vhost_data__lws_acme_client *vhd) { - lwsl_debug("%s\n", __func__); + lwsl_notice("%s\n", __func__); if (vhd->ac) { if (vhd->ac->vhost) @@ -433,32 +657,30 @@ static const char * const pvo_names[] = { static int lws_acme_load_create_auth_keys(struct per_vhost_data__lws_acme_client *vhd, - int bits) + int bits) { int n; if (!lws_jwk_load(&vhd->jwk, vhd->pvop[LWS_TLS_SET_AUTH_PATH], - NULL, NULL)) + NULL, NULL)) return 0; vhd->jwk.kty = LWS_GENCRYPTO_KTY_RSA; + lwsl_notice("Generating ACME %d-bit keypair... " - "will take a little while\n", bits); + "will take a little while\n", bits); n = lws_genrsa_new_keypair(vhd->context, &vhd->rsactx, LGRSAM_PKCS1_1_5, - vhd->jwk.e, bits); + vhd->jwk.e, bits); if (n) { lwsl_notice("failed to create keypair\n"); - return 1; } lwsl_notice("...keypair generated\n"); - if (lws_jwk_save(&vhd->jwk, - vhd->pvop[LWS_TLS_SET_AUTH_PATH])) { + if (lws_jwk_save(&vhd->jwk, vhd->pvop[LWS_TLS_SET_AUTH_PATH])) { lwsl_notice("unable to save %s\n", - vhd->pvop[LWS_TLS_SET_AUTH_PATH]); - + vhd->pvop[LWS_TLS_SET_AUTH_PATH]); return 1; } @@ -467,7 +689,7 @@ lws_acme_load_create_auth_keys(struct per_vhost_data__lws_acme_client *vhd, static int lws_acme_start_acquisition(struct per_vhost_data__lws_acme_client *vhd, - struct lws_vhost *v) + struct lws_vhost *v) { char buf[128]; @@ -480,7 +702,7 @@ lws_acme_start_acquisition(struct per_vhost_data__lws_acme_client *vhd, * ...well... we should try to do something about it then... */ lwsl_notice("%s: ACME cert needs creating / updating: " - "vhost %s\n", __func__, lws_get_vhost_name(vhd->vhost)); + "vhost %s\n", __func__, lws_get_vhost_name(vhd->vhost)); vhd->ac = malloc(sizeof(*vhd->ac)); memset(vhd->ac, 0, sizeof(*vhd->ac)); @@ -504,11 +726,11 @@ lws_acme_start_acquisition(struct per_vhost_data__lws_acme_client *vhd, if (!vhd->ac->urls[0][0]) { vhd->ac->state = ACME_STATE_DIRECTORY; lws_snprintf(buf, sizeof(buf) - 1, "%s", - vhd->pvop_active[LWS_TLS_SET_DIR_URL]); + vhd->pvop_active[LWS_TLS_SET_DIR_URL]); } else { - vhd->ac->state = ACME_STATE_NEW_REG; + vhd->ac->state = ACME_STATE_NEW_ACCOUNT; lws_snprintf(buf, sizeof(buf) - 1, "%s", - vhd->ac->urls[JAD_NEW_REG_URL]); + vhd->ac->urls[JAD_NEW_ACCOUNT_URL]); } vhd->ac->real_vh_port = lws_get_vhost_port(vhd->vhost); @@ -519,15 +741,15 @@ lws_acme_start_acquisition(struct per_vhost_data__lws_acme_client *vhd, #if defined(LWS_WITH_ESP32) lws_acme_report_status(vhd->vhost, LWS_CUS_CREATE_KEYS, - "Generating keys, please wait"); + "Generating keys, please wait"); if (lws_acme_load_create_auth_keys(vhd, 2048)) goto bail; lws_acme_report_status(vhd->vhost, LWS_CUS_CREATE_KEYS, - "Auth keys created"); + "Auth keys created"); #endif if (lws_acme_client_connect(vhd->context, vhd->vhost, - &vhd->ac->cwsi, &vhd->ac->i, buf, "GET")) + &vhd->ac->cwsi, &vhd->ac->i, buf, "GET")) return 0; #if defined(LWS_WITH_ESP32) @@ -541,18 +763,17 @@ bail: static int callback_acme_client(struct lws *wsi, enum lws_callback_reasons reason, - void *user, void *in, size_t len) + void *user, void *in, size_t len) { struct per_vhost_data__lws_acme_client *vhd = - (struct per_vhost_data__lws_acme_client *) - lws_protocol_vh_priv_get(lws_get_vhost(wsi), - lws_get_protocol(wsi)); + (struct per_vhost_data__lws_acme_client *) + lws_protocol_vh_priv_get(lws_get_vhost(wsi), + lws_get_protocol(wsi)); char buf[LWS_PRE + 2536], *start = buf + LWS_PRE, *p = start, - *end = buf + sizeof(buf) - 1, digest[32], *failreason = NULL; + *end = buf + sizeof(buf) - 1, digest[32], *failreason = NULL; const struct lws_protocol_vhost_options *pvo; struct lws_acme_cert_aging_args *caa; struct acme_connection *ac = NULL; - struct lws_genhash_ctx hctx; unsigned char **pp, *pend; const char *content_type; struct lws_jwe jwe; @@ -599,15 +820,18 @@ callback_acme_client(struct lws *wsi, enum lws_callback_reasons reason, } n = 0; - for (m = 0; m < (int)LWS_ARRAY_SIZE(pvo_names); m++) - if (!vhd->pvop[m] && m >= LWS_TLS_REQ_ELEMENT_COMMON_NAME) { + for (m = 0; m < (int)LWS_ARRAY_SIZE(pvo_names); m++) { + if (!vhd->pvop[m] && + m >= LWS_TLS_REQ_ELEMENT_COMMON_NAME) { lwsl_notice("%s: require pvo '%s'\n", __func__, - pvo_names[m]); + pvo_names[m]); n |= 1; - } else + } else { if (vhd->pvop[m]) lwsl_info(" %s: %s\n", pvo_names[m], - vhd->pvop[m]); + vhd->pvop[m]); + } + } if (n) { free(vhd->pvo_data); vhd->pvo_data = NULL; @@ -628,17 +852,18 @@ callback_acme_client(struct lws *wsi, enum lws_callback_reasons reason, * still have root */ lws_snprintf(buf, sizeof(buf) - 1, "%s.upd", - vhd->pvop[LWS_TLS_SET_CERT_PATH]); - vhd->fd_updated_cert = lws_open(buf, LWS_O_WRONLY | LWS_O_CREAT | - LWS_O_TRUNC, 0600); + vhd->pvop[LWS_TLS_SET_CERT_PATH]); + vhd->fd_updated_cert = lws_open(buf, + LWS_O_WRONLY | LWS_O_CREAT | + LWS_O_TRUNC, 0600); if (vhd->fd_updated_cert < 0) { lwsl_err("unable to create update cert file %s\n", buf); return -1; } lws_snprintf(buf, sizeof(buf) - 1, "%s.upd", - vhd->pvop[LWS_TLS_SET_KEY_PATH]); + vhd->pvop[LWS_TLS_SET_KEY_PATH]); vhd->fd_updated_key = lws_open(buf, LWS_O_WRONLY | LWS_O_CREAT | - LWS_O_TRUNC, 0600); + LWS_O_TRUNC, 0600); if (vhd->fd_updated_key < 0) { lwsl_err("unable to create update key file %s\n", buf); return -1; @@ -682,7 +907,8 @@ callback_acme_client(struct lws *wsi, enum lws_callback_reasons reason, vhd->pvop_active[n] = vhd->pvop[n]; lwsl_notice("starting acme acquisition on %s: %s\n", - lws_get_vhost_name(caa->vh), vhd->pvop_active[LWS_TLS_SET_DIR_URL]); + lws_get_vhost_name(caa->vh), + vhd->pvop_active[LWS_TLS_SET_DIR_URL]); lws_acme_start_acquisition(vhd, caa->vh); break; @@ -708,16 +934,17 @@ callback_acme_client(struct lws *wsi, enum lws_callback_reasons reason, break; case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: - lwsl_notice("lws_http_client_http_response %d\n", - lws_http_client_http_response(wsi)); + lwsl_notice("%s: ESTABLISHED_CLIENT_HTTP:" + "%p, state:%d, status:%d\n", __func__, wsi, + ac->state, lws_http_client_http_response(wsi)); if (!ac) break; ac->resp = lws_http_client_http_response(wsi); /* we get a new nonce each time */ if (lws_hdr_total_length(wsi, WSI_TOKEN_REPLAY_NONCE) && - lws_hdr_copy(wsi, ac->replay_nonce, - sizeof(ac->replay_nonce), - WSI_TOKEN_REPLAY_NONCE) < 0) { + lws_hdr_copy(wsi, ac->replay_nonce, + sizeof(ac->replay_nonce), + WSI_TOKEN_REPLAY_NONCE) < 0) { lwsl_notice("%s: nonce too large\n", __func__); goto failed; @@ -726,41 +953,80 @@ callback_acme_client(struct lws *wsi, enum lws_callback_reasons reason, switch (ac->state) { case ACME_STATE_DIRECTORY: lejp_construct(&ac->jctx, cb_dir, vhd, jdir_tok, - LWS_ARRAY_SIZE(jdir_tok)); + LWS_ARRAY_SIZE(jdir_tok)); break; - case ACME_STATE_NEW_REG: - break; - case ACME_STATE_NEW_AUTH: - lejp_construct(&ac->jctx, cb_authz, ac, jauthz_tok, - LWS_ARRAY_SIZE(jauthz_tok)); - break; - - case ACME_STATE_POLLING: - case ACME_STATE_ACCEPT_CHALL: - lejp_construct(&ac->jctx, cb_chac, ac, jchac_tok, - LWS_ARRAY_SIZE(jchac_tok)); - break; - - case ACME_STATE_POLLING_CSR: - ac->cpos = 0; - if (ac->resp != 201) - break; + case ACME_STATE_NEW_NONCE: /* - * He acknowledges he will create the cert... - * get the URL to GET it from in the Location - * header. + * we try to * register our keys next. + * It's OK if it ends up * they're already registered, + * this eliminates any * gaps where we stored the key + * but registration did not complete for some reason... */ - if (lws_hdr_copy(wsi, ac->challenge_uri, - sizeof(ac->challenge_uri), + ac->state = ACME_STATE_NEW_ACCOUNT; + lws_acme_report_status(vhd->vhost, LWS_CUS_REG, NULL); + + strcpy(buf, ac->urls[JAD_NEW_ACCOUNT_URL]); + cwsi = lws_acme_client_connect(vhd->context, vhd->vhost, + &ac->cwsi, &ac->i, buf, "POST"); + if (!cwsi) { + lwsl_notice("%s: failed to connect to acme\n", + __func__); + goto failed; + } + + return -1; + + case ACME_STATE_NEW_ACCOUNT: + if (!lws_hdr_total_length(wsi, + WSI_TOKEN_HTTP_LOCATION)) { + lwsl_notice("%s: no Location\n", __func__); + goto failed; + } + + if (lws_hdr_copy(wsi, ac->acct_id, sizeof(ac->acct_id), + WSI_TOKEN_HTTP_LOCATION) < 0) { + lwsl_notice("%s: Location too large\n", + __func__); + goto failed; + } + + ac->kid = ac->acct_id; + + lwsl_notice("Location: %s\n", ac->acct_id); + break; + + case ACME_STATE_NEW_ORDER: + if (lws_hdr_copy(wsi, ac->order_url, + sizeof(ac->order_url), WSI_TOKEN_HTTP_LOCATION) < 0) { lwsl_notice("%s: missing cert location:\n", - __func__); + __func__); goto failed; } - lwsl_notice("told to fetch cert from %s\n", - ac->challenge_uri); + lejp_construct(&ac->jctx, cb_order, ac, jorder_tok, + LWS_ARRAY_SIZE(jorder_tok)); + break; + + case ACME_STATE_AUTHZ: + lejp_construct(&ac->jctx, cb_authz, ac, jauthz_tok, + LWS_ARRAY_SIZE(jauthz_tok)); + break; + + case ACME_STATE_START_CHALL: + lejp_construct(&ac->jctx, cb_chac, ac, jchac_tok, + LWS_ARRAY_SIZE(jchac_tok)); + break; + + case ACME_STATE_POLLING: + case ACME_STATE_POLLING_CSR: + lejp_construct(&ac->jctx, cb_order, ac, jorder_tok, + LWS_ARRAY_SIZE(jorder_tok)); + break; + + case ACME_STATE_DOWNLOAD_CERT: + ac->cpos = 0; break; default: @@ -771,38 +1037,40 @@ callback_acme_client(struct lws *wsi, enum lws_callback_reasons reason, case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER: if (!ac) break; - switch (ac->state) { + switch (ac->state) { case ACME_STATE_DIRECTORY: + case ACME_STATE_NEW_NONCE: break; - case ACME_STATE_NEW_REG: + + case ACME_STATE_NEW_ACCOUNT: p += lws_snprintf(p, end - p, "{" - "\"resource\":\"new-reg\"," - "\"contact\":[" - "\"mailto:%s\"" - "],\"agreement\":\"%s\"" - "}", - vhd->pvop_active[LWS_TLS_REQ_ELEMENT_EMAIL], - ac->urls[JAD_TOS_URL]); + "\"termsOfServiceAgreed\":true" + ",\"contact\": [\"mailto:%s\"]}", + vhd->pvop_active[LWS_TLS_REQ_ELEMENT_EMAIL]); puts(start); + strcpy(ac->active_url, ac->urls[JAD_NEW_ACCOUNT_URL]); pkt_add_hdrs: - if (lws_gencrypto_jwe_alg_to_definition("RSA1_5", &jwe.jose.alg)) { + if (lws_gencrypto_jwe_alg_to_definition("RSA1_5", + &jwe.jose.alg)) { ac->len = 0; lwsl_notice("%s: no RSA1_5\n", __func__); goto failed; } - jwe.jws.jwk = &vhd->jwk; - ac->len = lws_jwe_create_packet(&jwe, - start, p - start, - ac->replay_nonce, - &ac->buf[LWS_PRE], - sizeof(ac->buf) - - LWS_PRE, - lws_get_context(wsi)); + jwe.jwk = vhd->jwk; + + ac->len = jws_create_packet(&jwe, + start, p - start, + ac->replay_nonce, + ac->active_url, + ac->kid, + &ac->buf[LWS_PRE], + sizeof(ac->buf) - LWS_PRE, + lws_get_context(wsi)); if (ac->len < 0) { ac->len = 0; - lwsl_notice("lws_jwe_create_packet failed\n"); + lwsl_notice("jws_create_packet failed\n"); goto failed; } @@ -810,156 +1078,70 @@ pkt_add_hdrs: pend = (*pp) + len; ac->pos = 0; - content_type = "application/jose+json"; - if (ac->state == ACME_STATE_POLLING_CSR) - content_type = "application/pkix-cert"; + content_type = "application/jose+json"; if (lws_add_http_header_by_token(wsi, - WSI_TOKEN_HTTP_CONTENT_TYPE, - (uint8_t *)content_type, 21, pp, pend)) { + WSI_TOKEN_HTTP_CONTENT_TYPE, + (uint8_t *)content_type, 21, pp, + pend)) { lwsl_notice("could not add content type\n"); goto failed; } n = sprintf(buf, "%d", ac->len); if (lws_add_http_header_by_token(wsi, - WSI_TOKEN_HTTP_CONTENT_LENGTH, - (uint8_t *)buf, n, pp, pend)) { + WSI_TOKEN_HTTP_CONTENT_LENGTH, + (uint8_t *)buf, n, pp, pend)) { lwsl_notice("could not add content length\n"); goto failed; } lws_client_http_body_pending(wsi, 1); lws_callback_on_writable(wsi); - lwsl_notice("prepare to send ACME_STATE_NEW_REG\n"); break; - case ACME_STATE_NEW_AUTH: + + case ACME_STATE_NEW_ORDER: p += lws_snprintf(p, end - p, "{" - "\"resource\":\"new-authz\"," - "\"identifier\":{" - "\"type\":\"http-01\"," - "\"value\":\"%s\"" - "}" - "}", vhd->pvop_active[LWS_TLS_REQ_ELEMENT_COMMON_NAME]); + "\"identifiers\":[{" + "\"type\":\"dns\"," + "\"value\":\"%s\"" + "}]" + "}", + vhd->pvop_active[LWS_TLS_REQ_ELEMENT_COMMON_NAME]); + + puts(start); + strcpy(ac->active_url, ac->urls[JAD_NEW_ORDER_URL]); goto pkt_add_hdrs; - case ACME_STATE_ACCEPT_CHALL: - /* - * Several of the challenges in this document makes use - * of a key authorization string. A key authorization - * expresses a domain holder's authorization for a - * specified key to satisfy a specified challenge, by - * concatenating the token for the challenge with a key - * fingerprint, separated by a "." character: - * - * key-authz = token || '.' || - * base64(JWK_Thumbprint(accountKey)) - * - * The "JWK_Thumbprint" step indicates the computation - * specified in [RFC7638], using the SHA-256 digest. As - * specified in the individual challenges below, the - * token for a challenge is a JSON string comprised - * entirely of characters in the base64 alphabet. - * The "||" operator indicates concatenation of strings. - * - * keyAuthorization (required, string): The key - * authorization for this challenge. This value MUST - * match the token from the challenge and the client's - * account key. - * - * draft acme-01 tls-sni-01: - * - * { - * "keyAuthorization": "evaGxfADs...62jcerQ", - * } (Signed as JWS) - * - * draft acme-07 tls-sni-02: - * - * POST /acme/authz/1234/1 - * Host: example.com - * Content-Type: application/jose+json - * - * { - * "protected": base64url({ - * "alg": "ES256", - * "kid": "https://example.com/acme/acct/1", - * "nonce": "JHb54aT_KTXBWQOzGYkt9A", - * "url": "https://example.com/acme/authz/1234/1" - * }), - * "payload": base64url({ - * "keyAuthorization": "evaGxfADs...62jcerQ" - * }), - * "signature": "Q1bURgJoEslbD1c5...3pYdSMLio57mQNN4" - * } - * - * On receiving a response, the server MUST verify that - * the key authorization in the response matches the - * "token" value in the challenge and the client's - * account key. If they do not match, then the server - * MUST return an HTTP error in response to the POST - * request in which the client sent the challenge. - */ + case ACME_STATE_AUTHZ: + puts(start); + strcpy(ac->active_url, ac->authz_url); + goto pkt_add_hdrs; - lws_jwk_rfc7638_fingerprint(&vhd->jwk, digest); + case ACME_STATE_START_CHALL: p = start; end = &buf[sizeof(buf) - 1]; - p += lws_snprintf(p, end - p, - "{\"resource\":\"challenge\"," - "\"type\":\"tls-sni-0%d\"," - "\"keyAuthorization\":\"%s.", - 1 + ac->is_sni_02, - ac->chall_token); - n = lws_jws_base64_enc(digest, 32, p, end - p); - if (n < 0) - goto failed; - p += n; - p += lws_snprintf(p, end - p, "\"}"); + p += lws_snprintf(p, end - p, "{}"); puts(start); + strcpy(ac->active_url, ac->challenge_uri); goto pkt_add_hdrs; case ACME_STATE_POLLING: - break; + strcpy(ac->active_url, ac->order_url); + goto pkt_add_hdrs; case ACME_STATE_POLLING_CSR: - /* - * "To obtain a certificate for the domain, the agent - * constructs a PKCS#10 Certificate Signing Request that - * asks the Let’s Encrypt CA to issue a certificate for - * example.com with a specified public key. As usual, - * the CSR includes a signature by the private key - * corresponding to the public key in the CSR. The agent - * also signs the whole CSR with the authorized - * key for example.com so that the Let’s Encrypt CA - * knows it’s authorized." - * - * IOW we must create a new RSA keypair which will be - * the cert public + private key, and put the public - * key in the CSR. The CSR, just for transport, is also - * signed with our JWK, showing that as the owner of the - * authorized JWK, the request should be allowed. - * - * The cert comes back with our public key in it showing - * that the owner of the matching private key (we - * created that keypair) is the owner of the cert. - * - * We feed the CSR the elements we want in the cert, - * like the CN etc, and it gives us the b64URL-encoded - * CSR and the PEM-encoded (public +)private key in - * memory buffers. - */ if (ac->goes_around) break; - p += lws_snprintf(p, end - p, - "{\"resource\":\"new-cert\"," - "\"csr\":\""); + p += lws_snprintf(p, end - p, "{\"csr\":\""); n = lws_tls_acme_sni_csr_create(vhd->context, - &vhd->pvop_active[0], - (uint8_t *)p, end - p, - &ac->alloc_privkey_pem, - &ac->len_privkey_pem); + &vhd->pvop_active[0], + (uint8_t *)p, end - p, + &ac->alloc_privkey_pem, + &ac->len_privkey_pem); if (n < 0) { lwsl_notice("CSR generation failed\n"); goto failed; @@ -967,8 +1149,14 @@ pkt_add_hdrs: p += n; p += lws_snprintf(p, end - p, "\"}"); puts(start); + strcpy(ac->active_url, ac->finalize_url); goto pkt_add_hdrs; + case ACME_STATE_DOWNLOAD_CERT: + strcpy(ac->active_url, ac->cert_url); + goto pkt_add_hdrs; + break; + default: break; } @@ -976,14 +1164,16 @@ pkt_add_hdrs: case LWS_CALLBACK_CLIENT_HTTP_WRITEABLE: lwsl_notice("LWS_CALLBACK_CLIENT_HTTP_WRITEABLE\n"); + if (!ac) break; + if (ac->pos == ac->len) break; ac->buf[LWS_PRE + ac->len] = '\0'; if (lws_write(wsi, (uint8_t *)ac->buf + LWS_PRE, - ac->len, LWS_WRITE_HTTP_FINAL) < 0) + ac->len, LWS_WRITE_HTTP_FINAL) < 0) return -1; lwsl_notice("wrote %d\n", ac->len); ac->pos = ac->len; @@ -994,25 +1184,30 @@ pkt_add_hdrs: case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: if (!ac) return -1; + switch (ac->state) { + case ACME_STATE_POLLING_CSR: case ACME_STATE_POLLING: - case ACME_STATE_ACCEPT_CHALL: - case ACME_STATE_NEW_AUTH: + case ACME_STATE_START_CHALL: + case ACME_STATE_AUTHZ: + case ACME_STATE_NEW_ORDER: case ACME_STATE_DIRECTORY: ((char *)in)[len] = '\0'; puts(in); m = (int)(signed char)lejp_parse(&ac->jctx, - (uint8_t *)in, len); + (uint8_t *)in, len); if (m < 0 && m != LEJP_CONTINUE) { lwsl_notice("lejp parse failed %d\n", m); goto failed; } break; - case ACME_STATE_NEW_REG: + case ACME_STATE_NEW_ACCOUNT: ((char *)in)[len] = '\0'; puts(in); break; - case ACME_STATE_POLLING_CSR: + case ACME_STATE_DOWNLOAD_CERT: + ((char *)in)[len] = '\0'; + puts(in); /* it should be the DER cert! */ if (ac->cpos + len > sizeof(ac->buf)) { lwsl_notice("Incoming cert is too large!\n"); @@ -1029,13 +1224,19 @@ pkt_add_hdrs: /* unchunked content */ case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: lwsl_notice("%s: LWS_CALLBACK_RECEIVE_CLIENT_HTTP\n", __func__); - { - char buffer[2048 + LWS_PRE]; - char *px = buffer + LWS_PRE; - int lenx = sizeof(buffer) - LWS_PRE; + if (!ac) + return -1; + switch (ac->state) { + default: + { + char buffer[2048 + LWS_PRE]; + char *px = buffer + LWS_PRE; + int lenx = sizeof(buffer) - LWS_PRE; - if (lws_http_client_read(wsi, &px, &lenx) < 0) - return -1; + if (lws_http_client_read(wsi, &px, &lenx) < 0) + return -1; + } + break; } break; @@ -1044,6 +1245,7 @@ pkt_add_hdrs: if (!ac) return -1; + switch (ac->state) { case ACME_STATE_DIRECTORY: lejp_destruct(&ac->jctx); @@ -1053,212 +1255,125 @@ pkt_add_hdrs: for (n = 0; n < 6; n++) lwsl_notice(" %d: %s\n", n, ac->urls[n]); - /* - * So... having the directory now... we try to - * register our keys next. It's OK if it ends up - * they're already registered... this eliminates any - * gaps where we stored the key but registration did - * not complete for some reason... - */ - ac->state = ACME_STATE_NEW_REG; - lws_acme_report_status(vhd->vhost, LWS_CUS_REG, NULL); + ac->state = ACME_STATE_NEW_NONCE; - strcpy(buf, ac->urls[JAD_NEW_REG_URL]); + strcpy(buf, ac->urls[JAD_NEW_NONCE_URL]); cwsi = lws_acme_client_connect(vhd->context, vhd->vhost, - &ac->cwsi, &ac->i, buf, - "POST"); + &ac->cwsi, &ac->i, buf, + "GET"); if (!cwsi) { lwsl_notice("%s: failed to connect to acme\n", - __func__); + __func__); goto failed; } return -1; /* close the completed client connection */ - case ACME_STATE_NEW_REG: + case ACME_STATE_NEW_ACCOUNT: if ((ac->resp >= 200 && ac->resp < 299) || - ac->resp == 409) { + ac->resp == 409) { /* * Our account already existed, or exists now. * - * Move on to requesting a cert auth. */ - ac->state = ACME_STATE_NEW_AUTH; - lws_acme_report_status(vhd->vhost, LWS_CUS_AUTH, - NULL); + ac->state = ACME_STATE_NEW_ORDER; - strcpy(buf, ac->urls[JAD_NEW_AUTHZ_URL]); + strcpy(buf, ac->urls[JAD_NEW_ORDER_URL]); cwsi = lws_acme_client_connect(vhd->context, - vhd->vhost, &ac->cwsi, - &ac->i, buf, "POST"); + vhd->vhost, &ac->cwsi, + &ac->i, buf, "POST"); if (!cwsi) lwsl_notice("%s: failed to connect\n", - __func__); - return -1; /* close the completed client connection */ + __func__); + + /* close the completed client connection */ + return -1; } else { - lwsl_notice("new-reg replied %d\n", ac->resp); + lwsl_notice("newAccount replied %d\n", + ac->resp); goto failed; } return -1; /* close the completed client connection */ - case ACME_STATE_NEW_AUTH: + case ACME_STATE_NEW_ORDER: + lejp_destruct(&ac->jctx); + if (!ac->authz_url[0]) { + lwsl_notice("no authz\n"); + goto failed; + } + + /* + * Move on to requesting a cert auth. + */ + ac->state = ACME_STATE_AUTHZ; + lws_acme_report_status(vhd->vhost, LWS_CUS_AUTH, + NULL); + + strcpy(buf, ac->authz_url); + cwsi = lws_acme_client_connect(vhd->context, + vhd->vhost, &ac->cwsi, + &ac->i, buf, "POST"); + if (!cwsi) + lwsl_notice("%s: failed to connect\n", + __func__); + + return -1; /* close the completed client connection */ + + case ACME_STATE_AUTHZ: lejp_destruct(&ac->jctx); if (ac->resp / 100 == 4) { lws_snprintf(buf, sizeof(buf), - "Auth failed: %s", ac->detail); + "Auth failed: %s", ac->detail); failreason = buf; lwsl_notice("auth failed\n"); goto failed; } - lwsl_notice("chall: %s (%d)\n", ac->chall_token, ac->resp); + lwsl_notice("chall: %s (%d)\n", ac->chall_token, + ac->resp); if (!ac->chall_token[0]) { lwsl_notice("no challenge\n"); goto failed; } - - ac->state = ACME_STATE_ACCEPT_CHALL; + ac->state = ACME_STATE_START_CHALL; lws_acme_report_status(vhd->vhost, LWS_CUS_CHALLENGE, - NULL); - - /* tls-sni-01 ... what a mess. - * The stuff in - * https://tools.ietf.org/html/ - * draft-ietf-acme-acme-01#section-7.3 - * "requires" n but it's missing from let's encrypt - * tls-sni-01 challenge. The go docs say that they just - * implement one hashing round regardless - * https://godoc.org/golang.org/x/crypto/acme - * - * The go way is what is actually implemented today by - * letsencrypt - * - * "A client responds to this challenge by constructing - * a key authorization from the "token" value provided - * in the challenge and the client's account key. The - * client first computes the SHA-256 digest Z0 of the - * UTF8-encoded key authorization, and encodes Z0 in - * UTF-8 lower-case hexadecimal form." - */ - - /* tls-sni-02 - * - * SAN A MUST be constructed as follows: compute the - * SHA-256 digest of the UTF-8-encoded challenge token - * and encode it in lowercase hexadecimal form. The - * dNSName is "x.y.token.acme.invalid", where x - * is the first half of the hexadecimal representation - * and y is the second half. - */ + NULL); memset(&ac->ci, 0, sizeof(ac->ci)); - /* first compute the key authorization */ + /* compute the key authorization */ - lws_jwk_rfc7638_fingerprint(&vhd->jwk, digest); - p = start; - end = &buf[sizeof(buf) - 1]; + p = ac->key_auth; + end = p + sizeof(ac->key_auth) - 1; p += lws_snprintf(p, end - p, "%s.", ac->chall_token); + lws_jwk_rfc7638_fingerprint(&vhd->jwk, digest); n = lws_jws_base64_enc(digest, 32, p, end - p); if (n < 0) goto failed; - p += n; - if (lws_genhash_init(&hctx, LWS_GENHASH_TYPE_SHA256)) - return -1; + lwsl_notice("key_auth: '%s'\n", ac->key_auth); - if (lws_genhash_update(&hctx, (uint8_t *)start, - lws_ptr_diff(p, start))) { - lws_genhash_destroy(&hctx, NULL); + lws_snprintf(ac->http01_mountpoint, + sizeof(ac->http01_mountpoint), + "/.well-known/acme-challenge/%s", + ac->chall_token); - return -1; - } - if (lws_genhash_destroy(&hctx, digest)) - return -1; + memset(&ac->mount, 0, sizeof (struct lws_http_mount)); + ac->mount.protocol = "http"; + ac->mount.mountpoint = ac->http01_mountpoint; + ac->mount.mountpoint_len = + strlen(ac->http01_mountpoint); + ac->mount.origin_protocol = LWSMPRO_CALLBACK; - p = buf; - for (n = 0; n < 32; n++) { - p += lws_snprintf(p, end - p, "%02x", - digest[n] & 0xff); - if (n == (32 / 2) - 1) - p = buf + 64; - } - - p = ac->san_a; - if (ac->is_sni_02) { - lws_snprintf(p, sizeof(ac->san_a), - "%s.%s.token.acme.invalid", - buf, buf + 64); - - /* - * SAN B MUST be constructed as follows: compute - * the SHA-256 digest of the UTF-8 encoded key - * authorization and encode it in lowercase - * hexadecimal form. The dNSName is - * "x.y.ka.acme.invalid" where x is the first - * half of the hexadecimal representation and y - * is the second half. - */ - lws_jwk_rfc7638_fingerprint(&vhd->jwk, - (char *)digest); - - p = buf; - for (n = 0; n < 32; n++) { - p += lws_snprintf(p, end - p, "%02x", - digest[n] & 0xff); - if (n == (32 / 2) - 1) - p = buf + 64; - } - - p = ac->san_b; - lws_snprintf(p, sizeof(ac->san_b), - "%s.%s.ka.acme.invalid", - buf, buf + 64); - } else { - lws_snprintf(p, sizeof(ac->san_a), - "%s.%s.acme.invalid", buf, buf + 64); - ac->san_b[0] = '\0'; - } - - lwsl_notice("san_a: '%s'\n", ac->san_a); - lwsl_notice("san_b: '%s'\n", ac->san_b); - - /* - * tls-sni-01: - * - * The client then configures the TLS server at the - * domain such that when a handshake is initiated with - * the Server Name Indication extension set to - * "..acme.invalid", the - * corresponding generated certificate is presented. - * - * tls-sni-02: - * - * The client MUST ensure that the certificate is - * served to TLS connections specifying a Server Name - * Indication (SNI) value of SAN A. - */ - ac->ci.vhost_name = ac->san_a; - - /* - * we bind to exact iface of real vhost, so we can - * share the listen socket by SNI - */ - ac->ci.iface = ac->real_vh_iface; + ac->ci.mounts = &ac->mount; /* listen on the same port as the vhost that triggered * us */ - ac->ci.port = ac->real_vh_port; - /* Skip filling in any x509 info into the ssl_ctx. - * It will be done at the callback - * LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS - * in this callback handler (below) - */ - ac->ci.options = LWS_SERVER_OPTION_CREATE_VHOST_SSL_CTX | - LWS_SERVER_OPTION_SKIP_PROTOCOL_INIT | - LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + ac->ci.port = 80; + /* make ourselves protocols[0] for the new vhost */ - ac->ci.protocols = acme_protocols; + ac->ci.protocols = chall_http01_protocols; + /* * vhost .user points to the ac associated with the * temporary vhost @@ -1266,75 +1381,72 @@ pkt_add_hdrs: ac->ci.user = ac; ac->vhost = lws_create_vhost(lws_get_context(wsi), - &ac->ci); + &ac->ci); if (!ac->vhost) goto failed; + lwsl_notice("challenge_uri %s\n", ac->challenge_uri); + /* * The challenge-specific vhost is up... let the ACME * server know we are ready to roll... */ - ac->goes_around = 0; cwsi = lws_acme_client_connect(vhd->context, vhd->vhost, &ac->cwsi, &ac->i, ac->challenge_uri, "POST"); if (!cwsi) { - lwsl_notice("%s: failed to connect\n", - __func__); + lwsl_notice("%s: connect failed\n", __func__); goto failed; } return -1; /* close the completed client connection */ - case ACME_STATE_ACCEPT_CHALL: - /* - * he returned something like this (which we parsed) - * - * { - * "type": "tls-sni-01", - * "status": "pending", - * "uri": "https://acme-staging.api.letsencrypt.org/ - * acme/challenge/xCt7bT3FaxoIQU3Qry87t5h - * uKDcC-L-0ERcD5DLAZts/71100507", - * "token": "j2Vs-vLI_dsza4A35SFHIU03aIe2PzFRijbqCY - * dIVeE", - * "keyAuthorization": "j2Vs-vLI_dsza4A35SFHIU03aIe2 - * PzFRijbqCYdIVeE.nmOtdFd8Jikn6K8NnYYmT5 - * vCM_PwSDT8nLdOYoFXhRU" - * } - * - */ - lwsl_notice("%s: COMPLETED accept chall: %s\n", - __func__, ac->challenge_uri); + case ACME_STATE_START_CHALL: + lwsl_notice("%s: COMPLETED start chall: %s\n", + __func__, ac->challenge_uri); poll_again: ac->state = ACME_STATE_POLLING; - lws_acme_report_status(vhd->vhost, LWS_CUS_CHALLENGE, NULL); + lws_acme_report_status(vhd->vhost, LWS_CUS_CHALLENGE, + NULL); if (ac->goes_around++ == 20) { lwsl_notice("%s: too many chall retries\n", - __func__); + __func__); goto failed; } - lws_timed_callback_vh_protocol(vhd->vhost, vhd->protocol, - LWS_CALLBACK_USER + 0xac33, ac->goes_around == 1 ? 10 : 2); + strcpy(buf, ac->order_url); + cwsi = lws_acme_client_connect(vhd->context, vhd->vhost, + &ac->cwsi, &ac->i, buf, + "POST"); + if (!cwsi) { + lwsl_notice("%s: failed to connect to acme\n", + __func__); + + goto failed; + } return -1; /* close the completed client connection */ case ACME_STATE_POLLING: - if (ac->resp == 202 && - strcmp(ac->status, "invalid") && - strcmp(ac->status, "valid")) { + if (ac->resp == 202 && strcmp(ac->status, "invalid") && + strcmp(ac->status, "valid")) { + lwsl_notice("status: %s\n", ac->status); + goto poll_again; + } + + if (!strcmp(ac->status, "pending")) { lwsl_notice("status: %s\n", ac->status); goto poll_again; } if (!strcmp(ac->status, "invalid")) { - lwsl_notice("%s: polling failed\n", __func__); + lwsl_notice("%s: Challenge failed\n", __func__); lws_snprintf(buf, sizeof(buf), - "Challenge Invalid: %s", ac->detail); + "Challenge Invalid: %s", + ac->detail); failreason = buf; goto failed; } @@ -1343,7 +1455,7 @@ poll_again: /* * The challenge was validated... so delete the - * temp SNI vhost now its job is done + * temp vhost now its job is done */ if (ac->vhost) lws_vhost_destroy(ac->vhost); @@ -1359,224 +1471,141 @@ poll_again: lws_acme_report_status(vhd->vhost, LWS_CUS_REQ, NULL); ac->goes_around = 0; - strcpy(buf, ac->urls[JAD_NEW_CERT_URL]); + strcpy(buf, ac->finalize_url); cwsi = lws_acme_client_connect(vhd->context, vhd->vhost, &ac->cwsi, &ac->i, buf, "POST"); if (!cwsi) { lwsl_notice("%s: failed to connect to acme\n", - __func__); + __func__); goto failed; } return -1; /* close the completed client connection */ case ACME_STATE_POLLING_CSR: - /* - * (after POSTing the CSR)... - * - * If the CA decides to issue a certificate, then the - * server creates a new certificate resource and - * returns a URI for it in the Location header field - * of a 201 (Created) response. - * - * HTTP/1.1 201 Created - * Location: https://example.com/acme/cert/asdf - * - * If the certificate is available at the time of the - * response, it is provided in the body of the response. - * If the CA has not yet issued the certificate, the - * body of this response will be empty. The client - * should then send a GET request to the certificate URI - * to poll for the certificate. As long as the - * certificate is unavailable, the server MUST provide a - * 202 (Accepted) response and include a Retry-After - * header to indicate when the server believes the - * certificate will be issued. - */ if (ac->resp < 200 || ac->resp > 202) { lwsl_notice("CSR poll failed on resp %d\n", + ac->resp); + goto failed; + } + + if (ac->resp != 200) { + if (ac->goes_around++ == 30) { + lwsl_notice("%s: too many retries\n", + __func__); + + goto failed; + } + strcpy(buf, ac->finalize_url); + cwsi = lws_acme_client_connect(vhd->context, + vhd->vhost, + &ac->cwsi, &ac->i, buf, + "POST"); + if (!cwsi) { + lwsl_notice("%s: " + "failed to connect to acme\n", + __func__); + + goto failed; + } + /* close the completed client connection */ + return -1; + } + + ac->state = ACME_STATE_DOWNLOAD_CERT; + + strcpy(buf, ac->cert_url); + cwsi = lws_acme_client_connect(vhd->context, vhd->vhost, + &ac->cwsi, &ac->i, buf, + "POST"); + if (!cwsi) { + lwsl_notice("%s: failed to connect to acme\n", + __func__); + + goto failed; + } + return -1; + + case ACME_STATE_DOWNLOAD_CERT: + + if (ac->resp != 200) { + lwsl_notice("download cert failed on resp %d\n", ac->resp); goto failed; } + lwsl_notice("The cert was sent..\n"); - if (ac->resp == 200) { - char *pp; - int max; + lws_acme_report_status(vhd->vhost, LWS_CUS_ISSUE, NULL); - lwsl_notice("The cert was sent..\n"); + /* + * That means we have the issued cert in + * ac->buf, length in ac->cpos; and the key in + * ac->alloc_privkey_pem, length in + * ac->len_privkey_pem. + */ + n = lws_plat_write_cert(vhd->vhost, 0, + vhd->fd_updated_cert, + ac->buf, + ac->cpos); + if (n) { + lwsl_err("unable to write ACME cert! %d\n", n); + goto failed; + } - lws_acme_report_status(vhd->vhost, - LWS_CUS_ISSUE, NULL); + /* + * don't close it... we may update the certs + * again + */ + if (lws_plat_write_cert(vhd->vhost, 1, + vhd->fd_updated_key, + ac->alloc_privkey_pem, + ac->len_privkey_pem)) { + lwsl_err("unable to write ACME key!\n"); + goto failed; + } - /* - * That means we have the issued cert DER in - * ac->buf, length in ac->cpos; and the key in - * ac->alloc_privkey_pem, length in - * ac->len_privkey_pem. - * - * We write out a PEM copy of the cert, and a - * PEM copy of the private key, using the - * write-only fds we opened while we still - * had root. - * - * Estimate the size of the PEM version of the - * cert and allocate a temp buffer for it. - * - * This is a bit complicated because first we - * drop the b64url version into the buffer at - * +384, then we add the header at 0 and move - * lines of it back + '\n' to make PEM. - * - * This avoids the need for two fullsize - * allocations. - */ + /* + * we have written the persistent copies + */ + lwsl_notice("%s: Updated certs written for %s " + "to %s.upd and %s.upd\n", __func__, + vhd->pvop_active[LWS_TLS_REQ_ELEMENT_COMMON_NAME], + vhd->pvop_active[LWS_TLS_SET_CERT_PATH], + vhd->pvop_active[LWS_TLS_SET_KEY_PATH]); - max = (ac->cpos * 4) / 3 + 16 + 384; + /* notify lws there was a cert update */ - start = p = malloc(max); - if (!p) - goto failed; - - n = lws_b64_encode_string(ac->buf, ac->cpos, - start + 384, max - 384); - if (n < 0) { - free(start); - goto failed; - } - - pp = start + 384; - p += lws_snprintf(start, 64, "%s", - "-----BEGIN CERTIFICATE-----\n"); - - while (n) { - m = 65; - if (n < m) - m = n; - memcpy(p, pp, m); - n -= m; - p += m; - pp += m; - if (n) - *p++ = '\n'; - } - p += lws_snprintf(p, - max - lws_ptr_diff(p, start), - "%s", - "\n-----END CERTIFICATE-----\n"); - - n = lws_plat_write_cert(vhd->vhost, 0, - vhd->fd_updated_cert, start, - lws_ptr_diff(p, start)); - free(start); - if (n) { - lwsl_err("unable to write ACME cert! %d\n", n); - goto failed; - } - /* - * don't close it... we may update the certs - * again - */ - - if (lws_plat_write_cert(vhd->vhost, 1, - vhd->fd_updated_key, - ac->alloc_privkey_pem, - ac->len_privkey_pem)) { - lwsl_err("unable to write ACME key!\n"); - goto failed; - } - - /* - * we have written the persistent copies - */ - - lwsl_notice("%s: Updated certs written for %s " - "to %s.upd and %s.upd\n", __func__, - vhd->pvop_active[LWS_TLS_REQ_ELEMENT_COMMON_NAME], - vhd->pvop_active[LWS_TLS_SET_CERT_PATH], - vhd->pvop_active[LWS_TLS_SET_KEY_PATH]); - - /* notify lws there was a cert update */ - - if (lws_tls_cert_updated(vhd->context, + if (lws_tls_cert_updated(vhd->context, vhd->pvop_active[LWS_TLS_SET_CERT_PATH], vhd->pvop_active[LWS_TLS_SET_KEY_PATH], - ac->buf, ac->cpos, - ac->alloc_privkey_pem, - ac->len_privkey_pem)) { - lwsl_notice("problem setting certs\n"); - } - - lws_acme_finished(vhd); - lws_acme_report_status(vhd->vhost, - LWS_CUS_SUCCESS, NULL); - - return 0; + ac->buf, ac->cpos, + ac->alloc_privkey_pem, + ac->len_privkey_pem)) { + lwsl_notice("problem setting certs\n"); } - lws_acme_report_status(vhd->vhost, LWS_CUS_CONFIRM, NULL); + lws_acme_finished(vhd); + lws_acme_report_status(vhd->vhost, + LWS_CUS_SUCCESS, NULL); - /* he is preparing the cert, go again with a GET */ - - if (ac->goes_around++ == 30) { - lwsl_notice("%s: too many retries\n", - __func__); - - goto failed; - } - - strcpy(buf, ac->challenge_uri); - cwsi = lws_acme_client_connect(vhd->context, vhd->vhost, - &ac->cwsi, &ac->i, buf, - "GET"); - if (!cwsi) { - lwsl_notice("%s: failed to connect to acme\n", - __func__); - - goto failed; - } - return -1; /* close the completed client connection */ + return -1; default: break; } break; - case LWS_CALLBACK_USER + 0xac33: - if (!vhd) - break; - cwsi = lws_acme_client_connect(vhd->context, vhd->vhost, - &ac->cwsi, &ac->i, - ac->challenge_uri, - "GET"); - if (!cwsi) { - lwsl_notice("%s: failed to connect\n", __func__); - goto failed; - } + case LWS_CALLBACK_USER + 0xac33: + if (!vhd) break; - - case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS: - /* - * This goes to vhost->protocols[0], but for our temp certs - * vhost we created, we have arranged that to be our protocol, - * so the callback will come here. - * - * When we created the temp vhost, we set its pvo to point - * to the ac associated with the temp vhost. - */ - lwsl_debug("LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS\n"); - ac = (struct acme_connection *)lws_get_vhost_user( - (struct lws_vhost *)in); - - lws_acme_report_status((struct lws_vhost *)in, - LWS_CUS_CREATE_REQ, - "creating challenge cert"); - - if (lws_tls_acme_sni_cert_create((struct lws_vhost *)in, - ac->san_a, ac->san_b)) { - lwsl_err("%s: creating the sni test cert failed\n", __func__); - - return -1; + cwsi = lws_acme_client_connect(vhd->context, vhd->vhost, + &ac->cwsi, &ac->i, + ac->challenge_uri, + "GET"); + if (!cwsi) { + lwsl_notice("%s: failed to connect\n", __func__); + goto failed; } break; @@ -1587,7 +1616,7 @@ poll_again: return 0; failed: - lwsl_err("%s: failed out\n", __func__); + lwsl_notice("%s: failed out\n", __func__); lws_acme_report_status(vhd->vhost, LWS_CUS_FAILED, failreason); lws_acme_finished(vhd); @@ -1606,7 +1635,7 @@ init_protocol_lws_acme_client(struct lws_context *context, { if (c->api_magic != LWS_PLUGIN_API_MAGIC) { lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC, - c->api_magic); + c->api_magic); return 1; }