diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index 112a390c..a8efcaf2 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -2515,6 +2515,12 @@ enum lws_context_options { * call LWS_CALLBACK_PROTOCOL_INIT on its protocols. It's used in the * special case of a temporary vhost bound to a single protocol. */ + LWS_SERVER_OPTION_IGNORE_MISSING_CERT = (1 << 26), + /**< (VH) Don't fail if the vhost TLS cert or key are missing, just + * continue. The vhost won't be able to serve anything, but if for + * example the ACME plugin was configured to fetch a cert, this lets + * you bootstrap your vhost from having no cert to start with. + */ /****** add new things just above ---^ ******/ }; diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index 93a3ced3..f3fb29b6 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -1042,6 +1042,7 @@ struct lws_vhost { unsigned int created_vhost_protocols:1; unsigned int being_destroyed:1; + unsigned int skipped_certs:1; unsigned char default_protocol_index; unsigned char raw_protocol_index; @@ -2377,6 +2378,14 @@ LWS_EXTERN void lwsl_emit_stderr(int level, const char *line); #define lws_tls_acme_sni_cert_destroy(_a) #else #define LWS_SSL_ENABLED(context) (context->use_ssl) + +enum lws_tls_extant { + LWS_TLS_EXTANT_NO, + LWS_TLS_EXTANT_YES, + LWS_TLS_EXTANT_ALTERNATIVE +}; +LWS_EXTERN enum lws_tls_extant +lws_tls_use_any_upgrade_check_extant(const char *name); LWS_EXTERN int openssl_websocket_private_data_index; LWS_EXTERN int LWS_WARN_UNUSED_RESULT lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, int len); diff --git a/lib/server/lejp-conf.c b/lib/server/lejp-conf.c index e4191876..2a641784 100644 --- a/lib/server/lejp-conf.c +++ b/lib/server/lejp-conf.c @@ -100,6 +100,7 @@ static const char * const paths_vhosts[] = { "vhosts[].client-ssl-ciphers", "vhosts[].onlyraw", "vhosts[].client-cert-required", + "vhosts[].ignore-missing-cert", }; enum lejp_vhost_paths { @@ -148,6 +149,7 @@ enum lejp_vhost_paths { LEJPVP_CLIENT_CIPHERS, LEJPVP_FLAG_ONLYRAW, LEJPVP_FLAG_CLIENT_CERT_REQUIRED, + LEJPVP_IGNORE_MISSING_CERT, }; static const char * const parser_errs[] = { @@ -690,6 +692,14 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT; return 0; + case LEJPVP_IGNORE_MISSING_CERT: + if (arg_to_bool(ctx->buf)) + a->info->options |= LWS_SERVER_OPTION_IGNORE_MISSING_CERT; + else + a->info->options &= ~(LWS_SERVER_OPTION_IGNORE_MISSING_CERT); + + return 0; + case LEJPVP_SSL_OPTION_SET: a->info->ssl_options_set |= atol(ctx->buf); return 0; diff --git a/lib/tls/mbedtls/server.c b/lib/tls/mbedtls/server.c index 69c08a59..cb526e17 100644 --- a/lib/tls/mbedtls/server.c +++ b/lib/tls/mbedtls/server.c @@ -255,12 +255,13 @@ lws_tls_server_vhost_backend_init(struct lws_context_creation_info *info, * parameter. */ n = lws_tls_use_any_upgrade_check_extant(info->ssl_cert_filepath); - if (n < 0) + if (n == LWS_TLS_EXTANT_ALTERNATIVE) return 1; m = lws_tls_use_any_upgrade_check_extant(info->ssl_private_key_filepath); - if (m < 0) + if (m == LWS_TLS_EXTANT_ALTERNATIVE) return 1; - if ((n || m) && (info->options & LWS_SERVER_OPTION_IGNORE_MISSING_CERT)) { + if ((n == LWS_TLS_EXTANT_NO || m == LWS_TLS_EXTANT_NO) && + (info->options & LWS_SERVER_OPTION_IGNORE_MISSING_CERT)) { lwsl_notice("Ignoring missing %s or %s\n", info->ssl_cert_filepath, info->ssl_private_key_filepath); diff --git a/lib/tls/openssl/client.c b/lib/tls/openssl/client.c index 50a280d2..d51c4d40 100644 --- a/lib/tls/openssl/client.c +++ b/lib/tls/openssl/client.c @@ -347,7 +347,11 @@ lws_tls_client_create_vhost_context(struct lws_vhost *vh, /* support for client-side certificate authentication */ if (cert_filepath) { - lwsl_notice("%s: doing cert filepath\n", __func__); + if (lws_tls_use_any_upgrade_check_extant(cert_filepath) != LWS_TLS_EXTANT_YES && + (info->options & LWS_SERVER_OPTION_IGNORE_MISSING_CERT)) + return 0; + + lwsl_notice("%s: doing cert filepath %s\n", __func__, cert_filepath); n = SSL_CTX_use_certificate_chain_file(vh->ssl_client_ctx, cert_filepath); if (n < 1) { diff --git a/lib/tls/openssl/server.c b/lib/tls/openssl/server.c index 9fb4ee8b..964bbae5 100644 --- a/lib/tls/openssl/server.c +++ b/lib/tls/openssl/server.c @@ -224,12 +224,13 @@ lws_tls_server_vhost_backend_init(struct lws_context_creation_info *info, */ n = lws_tls_use_any_upgrade_check_extant(info->ssl_cert_filepath); - if (n < 0) + if (n == LWS_TLS_EXTANT_ALTERNATIVE) return 1; m = lws_tls_use_any_upgrade_check_extant(info->ssl_private_key_filepath); - if (m < 0) + if (m == LWS_TLS_EXTANT_ALTERNATIVE) return 1; - if ((n || m) && (info->options & LWS_SERVER_OPTION_IGNORE_MISSING_CERT)) { + if ((n == LWS_TLS_EXTANT_NO || m == LWS_TLS_EXTANT_NO) && + (info->options & LWS_SERVER_OPTION_IGNORE_MISSING_CERT)) { lwsl_notice("Ignoring missing %s or %s\n", info->ssl_cert_filepath, info->ssl_private_key_filepath); diff --git a/lib/tls/tls.c b/lib/tls/tls.c index c900c44f..e6e24b88 100644 --- a/lib/tls/tls.c +++ b/lib/tls/tls.c @@ -102,22 +102,23 @@ 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; + time_t now = (time_t)lws_now_secs(), life = 0; int n; - if (!v->ssl_ctx) - return -1; + 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; + 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; + 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); + 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); @@ -138,6 +139,86 @@ lws_tls_check_all_cert_lifetimes(struct lws_context *context) return 0; } +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; +} + +/* + * 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) + * + * 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. + * + * 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) +{ + char buf[256]; + int n; + + 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; + + return LWS_TLS_EXTANT_YES; +} + int lws_gate_accepts(struct lws_context *context, int on) {