/* * libwebsockets - small server side websockets and web server implementation * * Copyright (C) 2010-2017 Andy Green * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation: * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA */ #include "core/private.h" /* * fakes POLLIN on all tls guys with buffered rx * * returns nonzero if any tls guys had POLLIN faked */ int lws_tls_fake_POLLIN_for_buffered(struct lws_context_per_thread *pt) { struct lws *wsi, *wsi_next; int ret = 0; wsi = pt->tls.pending_read_list; while (wsi && wsi->position_in_fds_table != LWS_NO_FDS_POS) { wsi_next = wsi->tls.pending_read_list_next; pt->fds[wsi->position_in_fds_table].revents |= pt->fds[wsi->position_in_fds_table].events & LWS_POLLIN; ret |= pt->fds[wsi->position_in_fds_table].revents & LWS_POLLIN; wsi = wsi_next; } return !!ret; } void __lws_ssl_remove_wsi_from_buffered_list(struct lws *wsi) { struct lws_context *context = wsi->context; struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; if (!wsi->tls.pending_read_list_prev && !wsi->tls.pending_read_list_next && pt->tls.pending_read_list != wsi) /* we are not on the list */ return; /* point previous guy's next to our next */ if (!wsi->tls.pending_read_list_prev) pt->tls.pending_read_list = wsi->tls.pending_read_list_next; else wsi->tls.pending_read_list_prev->tls.pending_read_list_next = wsi->tls.pending_read_list_next; /* point next guy's previous to our previous */ if (wsi->tls.pending_read_list_next) wsi->tls.pending_read_list_next->tls.pending_read_list_prev = wsi->tls.pending_read_list_prev; wsi->tls.pending_read_list_prev = NULL; wsi->tls.pending_read_list_next = NULL; } void lws_ssl_remove_wsi_from_buffered_list(struct lws *wsi) { struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; lws_pt_lock(pt, __func__); __lws_ssl_remove_wsi_from_buffered_list(wsi); lws_pt_unlock(pt); } #if defined(LWS_WITH_ESP32) int alloc_file(struct lws_context *context, const char *filename, uint8_t **buf, lws_filepos_t *amount) { nvs_handle nvh; size_t s; int n = 0; ESP_ERROR_CHECK(nvs_open("lws-station", NVS_READWRITE, &nvh)); if (nvs_get_blob(nvh, filename, NULL, &s) != ESP_OK) { n = 1; goto bail; } *buf = lws_malloc(s + 1, "alloc_file"); if (!*buf) { n = 2; goto bail; } if (nvs_get_blob(nvh, filename, (char *)*buf, &s) != ESP_OK) { lws_free(*buf); n = 1; goto bail; } *amount = s; (*buf)[s] = '\0'; lwsl_notice("%s: nvs: read %s, %d bytes\n", __func__, filename, (int)s); bail: nvs_close(nvh); return n; } #else int alloc_file(struct lws_context *context, const char *filename, uint8_t **buf, lws_filepos_t *amount) { FILE *f; size_t s; int n = 0; f = fopen(filename, "rb"); if (f == NULL) { n = 1; goto bail; } if (fseek(f, 0, SEEK_END) != 0) { n = 1; goto bail; } s = ftell(f); if (s == (size_t)-1) { n = 1; goto bail; } if (fseek(f, 0, SEEK_SET) != 0) { n = 1; goto bail; } *buf = lws_malloc(s, "alloc_file"); if (!*buf) { n = 2; goto bail; } if (fread(*buf, s, 1, f) != 1) { lws_free(*buf); n = 1; goto bail; } *amount = s; bail: if (f) fclose(f); return n; } #endif int lws_tls_alloc_pem_to_der_file(struct lws_context *context, const char *filename, const char *inbuf, lws_filepos_t inlen, uint8_t **buf, lws_filepos_t *amount) { const uint8_t *pem, *p, *end; uint8_t *q; lws_filepos_t len; int n; if (filename) { n = alloc_file(context, filename, (uint8_t **)&pem, &len); if (n) return n; } else { pem = (const uint8_t *)inbuf; len = inlen; } /* trim the first line */ p = pem; end = p + len; if (strncmp((char *)p, "-----", 5)) goto bail; p += 5; while (p < end && *p != '\n' && *p != '-') p++; if (*p != '-') goto bail; while (p < end && *p != '\n') p++; if (p >= end) goto bail; p++; /* trim the last line */ q = (uint8_t *)end - 2; while (q > pem && *q != '\n') q--; if (*q != '\n') goto bail; *q = '\0'; *amount = lws_b64_decode_string((char *)p, (char *)pem, (int)(long long)len); *buf = (uint8_t *)pem; return 0; bail: lws_free((uint8_t *)pem); return 4; } int lws_tls_check_cert_lifetime(struct lws_vhost *v) { union lws_tls_cert_info_results ir; time_t now = (time_t)lws_now_secs(), life = 0; struct lws_acme_cert_aging_args caa; int n; if (v->tls.ssl_ctx && !v->tls.skipped_certs) { if (now < 1464083026) /* May 2016 */ /* our clock is wrong and we can't judge the certs */ return -1; n = lws_tls_vhost_cert_info(v, LWS_TLS_CERT_INFO_VALIDITY_TO, &ir, 0); if (n) return 1; life = (ir.time - now) / (24 * 3600); lwsl_notice(" vhost %s: cert expiry: %dd\n", v->name, (int)life); } else lwsl_notice(" vhost %s: no cert\n", v->name); memset(&caa, 0, sizeof(caa)); caa.vh = v; lws_broadcast(v->context, LWS_CALLBACK_VHOST_CERT_AGING, (void *)&caa, (size_t)(ssize_t)life); return 0; } int lws_tls_check_all_cert_lifetimes(struct lws_context *context) { struct lws_vhost *v = context->vhost_list; while (v) { if (lws_tls_check_cert_lifetime(v) < 0) return -1; v = v->vhost_next; } return 0; } #if !defined(LWS_WITH_ESP32) && !defined(LWS_PLAT_OPTEE) static int lws_tls_extant(const char *name) { /* it exists if we can open it... */ int fd = open(name, O_RDONLY), n; char buf[1]; if (fd < 0) return 1; /* and we can read at least one byte out of it */ n = read(fd, buf, 1); close(fd); return n != 1; } #endif /* * Returns 0 if the filepath "name" exists and can be read from. * * In addition, if "name".upd exists, backup "name" to "name.old.1" * and rename "name".upd to "name" before reporting its existence. * * There are four situations and three results possible: * * 1) LWS_TLS_EXTANT_NO: There are no certs at all (we are waiting for them to * be provisioned). We also feel like this if we need privs we don't have * any more to look in the directory. * * 2) There are provisioned certs written (xxx.upd) and we still have root * privs... in this case we rename any existing cert to have a backup name * and move the upd cert into place with the correct name. This then becomes * situation 4 for the caller. * * 3) LWS_TLS_EXTANT_ALTERNATIVE: There are provisioned certs written (xxx.upd) * but we no longer have the privs needed to read or rename them. In this * case, indicate that the caller should use temp copies if any we do have * rights to access. This is normal after we have updated the cert. * * But if we dropped privs, we can't detect the provisioned xxx.upd cert + * key, because we can't see in the dir. So we have to upgrade NO to * ALTERNATIVE when we actually have the in-memory alternative. * * 4) LWS_TLS_EXTANT_YES: The certs are present with the correct name and we * have the rights to read them. */ enum lws_tls_extant lws_tls_use_any_upgrade_check_extant(const char *name) { #if !defined(LWS_PLAT_OPTEE) int n; #if !defined(LWS_WITH_ESP32) char buf[256]; lws_snprintf(buf, sizeof(buf) - 1, "%s.upd", name); if (!lws_tls_extant(buf)) { /* ah there is an updated file... how about the desired file? */ if (!lws_tls_extant(name)) { /* rename the desired file */ for (n = 0; n < 50; n++) { lws_snprintf(buf, sizeof(buf) - 1, "%s.old.%d", name, n); if (!rename(name, buf)) break; } if (n == 50) { lwsl_notice("unable to rename %s\n", name); return LWS_TLS_EXTANT_ALTERNATIVE; } lws_snprintf(buf, sizeof(buf) - 1, "%s.upd", name); } /* desired file is out of the way, rename the updated file */ if (rename(buf, name)) { lwsl_notice("unable to rename %s to %s\n", buf, name); return LWS_TLS_EXTANT_ALTERNATIVE; } } if (lws_tls_extant(name)) return LWS_TLS_EXTANT_NO; #else nvs_handle nvh; size_t s = 8192; if (nvs_open("lws-station", NVS_READWRITE, &nvh)) { lwsl_notice("%s: can't open nvs\n", __func__); return LWS_TLS_EXTANT_NO; } n = nvs_get_blob(nvh, name, NULL, &s); nvs_close(nvh); if (n) return LWS_TLS_EXTANT_NO; #endif #endif return LWS_TLS_EXTANT_YES; } /* * LWS_TLS_EXTANT_NO : skip adding the cert * LWS_TLS_EXTANT_YES : use the cert and private key paths normally * LWS_TLS_EXTANT_ALTERNATIVE: normal paths not usable, try alternate if poss */ enum lws_tls_extant lws_tls_generic_cert_checks(struct lws_vhost *vhost, const char *cert, const char *private_key) { int n, m; /* * The user code can choose to either pass the cert and * key filepaths using the info members like this, or it can * leave them NULL; force the vhost SSL_CTX init using the info * options flag LWS_SERVER_OPTION_CREATE_VHOST_SSL_CTX; and * set up the cert himself using the user callback * LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS, which * happened just above and has the vhost SSL_CTX * in the user * parameter. */ if (!cert || !private_key) return LWS_TLS_EXTANT_NO; n = lws_tls_use_any_upgrade_check_extant(cert); if (n == LWS_TLS_EXTANT_ALTERNATIVE) return LWS_TLS_EXTANT_ALTERNATIVE; m = lws_tls_use_any_upgrade_check_extant(private_key); if (m == LWS_TLS_EXTANT_ALTERNATIVE) return LWS_TLS_EXTANT_ALTERNATIVE; if ((n == LWS_TLS_EXTANT_NO || m == LWS_TLS_EXTANT_NO) && (vhost->options & LWS_SERVER_OPTION_IGNORE_MISSING_CERT)) { lwsl_notice("Ignoring missing %s or %s\n", cert, private_key); vhost->tls.skipped_certs = 1; return LWS_TLS_EXTANT_NO; } /* * the cert + key exist */ return LWS_TLS_EXTANT_YES; } #if !defined(LWS_NO_SERVER) /* * update the cert for every vhost using the given path */ LWS_VISIBLE int lws_tls_cert_updated(struct lws_context *context, const char *certpath, const char *keypath, const char *mem_cert, size_t len_mem_cert, const char *mem_privkey, size_t len_mem_privkey) { struct lws wsi; wsi.context = context; lws_start_foreach_ll(struct lws_vhost *, v, context->vhost_list) { wsi.vhost = v; if (v->tls.alloc_cert_path && v->tls.key_path && !strcmp(v->tls.alloc_cert_path, certpath) && !strcmp(v->tls.key_path, keypath)) { lws_tls_server_certs_load(v, &wsi, certpath, keypath, mem_cert, len_mem_cert, mem_privkey, len_mem_privkey); if (v->tls.skipped_certs) lwsl_notice("%s: vhost %s: cert unset\n", __func__, v->name); } } lws_end_foreach_ll(v, vhost_next); return 0; } #endif int lws_gate_accepts(struct lws_context *context, int on) { struct lws_vhost *v = context->vhost_list; lwsl_notice("%s: on = %d\n", __func__, on); #if defined(LWS_WITH_STATS) context->updated = 1; #endif while (v) { if (v->tls.use_ssl && v->lserv_wsi && lws_change_pollfd(v->lserv_wsi, (LWS_POLLIN) * !on, (LWS_POLLIN) * on)) lwsl_notice("Unable to set accept POLLIN %d\n", on); v = v->vhost_next; } return 0; } /* comma-separated alpn list, like "h2,http/1.1" to openssl alpn format */ int lws_alpn_comma_to_openssl(const char *comma, uint8_t *os, int len) { uint8_t *oos = os, *plen = NULL; while (*comma && len > 1) { if (!plen && *comma == ' ') { comma++; continue; } if (!plen) { plen = os++; len--; } if (*comma == ',') { *plen = lws_ptr_diff(os, plen + 1); plen = NULL; comma++; } else { *os++ = *comma++; len--; } } if (plen) *plen = lws_ptr_diff(os, plen + 1); return lws_ptr_diff(os, oos); }