diff options
Diffstat (limited to 'thirdparty/libwebsockets/tls/tls.c')
-rw-r--r-- | thirdparty/libwebsockets/tls/tls.c | 522 |
1 files changed, 522 insertions, 0 deletions
diff --git a/thirdparty/libwebsockets/tls/tls.c b/thirdparty/libwebsockets/tls/tls.c new file mode 100644 index 0000000000..92b7c5593c --- /dev/null +++ b/thirdparty/libwebsockets/tls/tls.c @@ -0,0 +1,522 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> + * + * 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); +} + |