libwebsockets/lib/tls/tls.c

465 lines
11 KiB
C

/*
* 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 "private-libwebsockets.h"
int
lws_ssl_anybody_has_buffered_read_tsi(struct lws_context *context, int tsi)
{
struct lws_context_per_thread *pt = &context->pt[tsi];
struct lws *wsi, *wsi_next;
wsi = pt->pending_read_list;
while (wsi) {
wsi_next = wsi->pending_read_list_next;
pt->fds[wsi->position_in_fds_table].revents |=
pt->fds[wsi->position_in_fds_table].events & LWS_POLLIN;
if (pt->fds[wsi->position_in_fds_table].revents & LWS_POLLIN)
return 1;
wsi = wsi_next;
}
return 0;
}
LWS_VISIBLE 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->pending_read_list_prev &&
!wsi->pending_read_list_next &&
pt->pending_read_list != wsi)
/* we are not on the list */
return;
/* point previous guy's next to our next */
if (!wsi->pending_read_list_prev)
pt->pending_read_list = wsi->pending_read_list_next;
else
wsi->pending_read_list_prev->pending_read_list_next =
wsi->pending_read_list_next;
/* point next guy's previous to our previous */
if (wsi->pending_read_list_next)
wsi->pending_read_list_next->pending_read_list_prev =
wsi->pending_read_list_prev;
wsi->pending_read_list_prev = NULL;
wsi->pending_read_list_next = NULL;
}
#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;
int n;
if (v->ssl_ctx && !v->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);
lws_broadcast(v->context, LWS_CALLBACK_VHOST_CERT_AGING, v,
(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) {
lws_tls_check_cert_lifetime(v);
v = v->vhost_next;
}
return 0;
}
#if !defined(LWS_WITH_ESP32)
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)
{
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
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.
*/
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->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->alloc_cert_path && v->key_path &&
!strcmp(v->alloc_cert_path, certpath) &&
!strcmp(v->key_path, keypath)) {
lws_tls_server_certs_load(v, &wsi, certpath, keypath,
mem_cert, len_mem_cert,
mem_privkey, len_mem_privkey);
if (v->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_info("gating accepts %d\n", on);
context->ssl_gate_accepts = !on;
#if defined(LWS_WITH_STATS)
context->updated = 1;
#endif
while (v) {
if (v->use_ssl && v->lserv_wsi &&
lws_change_pollfd(v->lserv_wsi, (LWS_POLLIN) * !on,
(LWS_POLLIN) * on))
lwsl_info("Unable to set accept POLLIN %d\n", on);
v = v->vhost_next;
}
return 0;
}