From a798db0e2b8374d8495b4ae8d1872f2214742703 Mon Sep 17 00:00:00 2001 From: Andy Green Date: Thu, 26 Oct 2017 18:53:34 +0800 Subject: [PATCH] vhost: check cert validity dates After startup, and once per day, check the validity dates on our ssl certs, and broadcast callbacks with the information so interested plugins can know. If our clock is < May 2016, we don't try to judge the certs, because clearly we don't know what time it is. --- lib/context.c | 14 ++++++++-- lib/libwebsockets.c | 33 ++++++++++++++++++++++++ lib/libwebsockets.h | 19 ++++++++++++++ lib/private-libwebsockets.h | 10 ++++++++ lib/service.c | 32 ++++++++++------------- lib/tls/tls.c | 44 ++++++++++++++++++++++++++++++-- plugins/protocol_lws_sshd_demo.c | 3 +++ 7 files changed, 132 insertions(+), 23 deletions(-) diff --git a/lib/context.c b/lib/context.c index 23b64ab0..04c95a15 100644 --- a/lib/context.c +++ b/lib/context.c @@ -196,7 +196,7 @@ lws_protocol_init(struct lws_context *context) struct lws_vhost *vh = context->vhost_list; const struct lws_protocol_vhost_options *pvo, *pvo1; struct lws wsi; - int n; + int n, any = 0; if (context->doing_protocol_init) return 0; @@ -212,7 +212,8 @@ lws_protocol_init(struct lws_context *context) wsi.vhost = vh; /* only do the protocol init once for a given vhost */ - if (vh->created_vhost_protocols) + if (vh->created_vhost_protocols || + (vh->options & LWS_SERVER_OPTION_SKIP_PROTOCOL_INIT)) goto next; /* initialize supported protocols on this vhost */ @@ -260,6 +261,10 @@ lws_protocol_init(struct lws_context *context) pvo = pvo1->options; } +#if defined(LWS_OPENSSL_SUPPORT) + any |= !!vh->ssl_ctx; +#endif + /* * inform all the protocols that they are doing their * one-time initialization if they want to. @@ -289,6 +294,9 @@ next: context->protocol_init_done = 1; + if (any) + lws_tls_check_all_cert_lifetimes(context); + return 0; } @@ -1270,6 +1278,8 @@ lws_create_context(struct lws_context_creation_info *info) LWS_EXT_CB_CLIENT_CONTEXT_CONSTRUCT, NULL, 0) < 0) goto bail; + time(&context->last_cert_check_s); + #if defined(LWS_WITH_SELFTESTS) lws_jws_selftest(); #endif diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index 7fb742d7..1b980dc1 100644 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -1432,12 +1432,45 @@ lws_rx_flow_allow_all_protocol(const struct lws_context *context, } } +int +lws_broadcast(struct lws_context *context, int reason, void *in, size_t len) +{ + struct lws_vhost *v = context->vhost_list; + struct lws wsi; + int n, ret = 0; + + memset(&wsi, 0, sizeof(wsi)); + wsi.context = context; + + while (v) { + const struct lws_protocols *p = v->protocols; + wsi.vhost = v; + + for (n = 0; n < v->count_protocols; n++) { + wsi.protocol = p; + if (p->callback && + p->callback(&wsi, reason, NULL, in, len)) + ret |= 1; + p++; + } + v = v->vhost_next; + } + + return ret; +} + LWS_VISIBLE extern const char * lws_canonical_hostname(struct lws_context *context) { return (const char *)context->canonical_hostname; } +LWS_VISIBLE LWS_EXTERN const char * +lws_get_vhost_name(struct lws_vhost *vhost) +{ + return vhost->name; +} + int user_callback_handle_rxflow(lws_callback_function callback_function, struct lws *wsi, enum lws_callback_reasons reason, void *user, diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index 0aaa505a..f6f57cb1 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -1422,6 +1422,12 @@ enum lws_callback_reasons { * callback is serialized in the lws event loop normally, even * if the lws_cancel_service[_pt]() call was from a different * thread. */ + LWS_CALLBACK_VHOST_CERT_AGING = 72, + /**< When a vhost TLS cert has its expiry checked, this callback + * is broadcast to every protocol of every vhost in case the + * protocol wants to take some action with this information. + * \p in is the lws_vhost and \p len is the number of days left + * before it expires, as a (ssize_t) */ /****** add new things just above ---^ ******/ @@ -2504,6 +2510,11 @@ enum lws_context_options { * LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS callback, which * provides the vhost SSL_CTX * in the user parameter. */ + LWS_SERVER_OPTION_SKIP_PROTOCOL_INIT = (1 << 25), + /**< (VH) You probably don't want this. It forces this vhost to not + * call LWS_CALLBACK_PROTOCOL_INIT on its protocols. It's used in the + * special case of a temporary vhost bound to a single protocol. + */ /****** add new things just above ---^ ******/ }; @@ -3013,6 +3024,14 @@ lws_vhost_get(struct lws *wsi) LWS_WARN_DEPRECATED; LWS_VISIBLE LWS_EXTERN struct lws_vhost * lws_get_vhost(struct lws *wsi); +/** + * lws_get_vhost_name() - returns the name of a vhost + * + * \param vhost: which vhost + */ +LWS_VISIBLE LWS_EXTERN const char * +lws_get_vhost_name(struct lws_vhost *vhost); + /** * lws_json_dump_vhost() - describe vhost state and stats in JSON * diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index 960c37db..c623eee3 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -1088,6 +1088,7 @@ struct lws_peer { struct lws_context { time_t last_timeout_check_s; time_t last_ws_ping_pong_check_s; + time_t last_cert_check_s; time_t time_up; const struct lws_plat_file_ops *fops; struct lws_plat_file_ops fops_platform; @@ -2368,6 +2369,7 @@ LWS_EXTERN void lwsl_emit_stderr(int level, const char *line); #define lws_ssl_remove_wsi_from_buffered_list(_a) #define lws_context_init_ssl_library(_a) #define lws_ssl_anybody_has_buffered_read_tsi(_a, _b) (0) +#define lws_tls_check_all_cert_lifetimes(_a) #else #define LWS_SSL_ENABLED(context) (context->use_ssl) LWS_EXTERN int openssl_websocket_private_data_index; @@ -2408,6 +2410,8 @@ lws_ssl_info_callback(const lws_tls_conn *ssl, int where, int ret); LWS_EXTERN int lws_tls_openssl_cert_info(X509 *x509, enum lws_tls_cert_info type, union lws_tls_cert_info_results *buf, size_t len); +LWS_EXTERN int +lws_tls_check_all_cert_lifetimes(struct lws_context *context); #ifndef LWS_NO_SERVER LWS_EXTERN int lws_context_init_server_ssl(struct lws_context_creation_info *info, @@ -2526,6 +2530,9 @@ lws_ssl_capable_write_no_ssl(struct lws *wsi, unsigned char *buf, int len); LWS_EXTERN int LWS_WARN_UNUSED_RESULT lws_ssl_pending_no_ssl(struct lws *wsi); +int +lws_tls_check_cert_lifetime(struct lws_vhost *vhost); + int lws_jws_selftest(void); #ifdef LWS_WITH_HTTP_PROXY @@ -2704,6 +2711,9 @@ lws_same_vh_protocol_remove(struct lws *wsi); LWS_EXTERN void lws_same_vh_protocol_insert(struct lws *wsi, int n); +LWS_EXTERN int +lws_broadcast(struct lws_context *context, int reason, void *in, size_t len); + #if defined(LWS_WITH_STATS) void lws_stats_atomic_bump(struct lws_context * context, diff --git a/lib/service.c b/lib/service.c index e2efee88..62644f55 100644 --- a/lib/service.c +++ b/lib/service.c @@ -1166,6 +1166,14 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, } } + /* + * check the remaining cert lifetime daily + */ + if (context->last_cert_check_s < now - (24 * 60 * 60)) { + context->last_cert_check_s = now; + + lws_tls_check_all_cert_lifetimes(context); + } /* the socket we came to service timed out, nothing to do */ if (timed_out) @@ -1234,7 +1242,6 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, switch (wsi->mode) { case LWSCM_EVENT_PIPE: { - struct lws_vhost *v = context->vhost_list; #if !defined(WIN32) && !defined(_WIN32) char s[10]; @@ -1248,31 +1255,18 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, if (n < 0) goto close_and_handled; #endif - /* * the poll() wait, or the event loop for libuv etc is a * process-wide resource that we interrupted. So let every * protocol that may be interested in the pipe event know that * it happened. */ - while (v) { - const struct lws_protocols *p = v->protocols; - wsi->vhost = v; - - for (n = 0; n < v->count_protocols; n++) { - wsi->protocol = p; - if (p->callback && p->callback(wsi, - LWS_CALLBACK_EVENT_WAIT_CANCELLED, - NULL, NULL, 0)) { - lwsl_info("closed in event cancel\n"); - goto close_and_handled; - } - p++; - } - v = v->vhost_next; + if (lws_broadcast(context, LWS_CALLBACK_EVENT_WAIT_CANCELLED, + NULL, 0)) { + lwsl_info("closed in event cancel\n"); + goto close_and_handled; } - wsi->vhost = NULL; - wsi->protocol = NULL; + goto handled; } case LWSCM_HTTP_SERVING: diff --git a/lib/tls/tls.c b/lib/tls/tls.c index 81dc0e23..c900c44f 100644 --- a/lib/tls/tls.c +++ b/lib/tls/tls.c @@ -21,8 +21,9 @@ #include "private-libwebsockets.h" -int lws_alloc_vfs_file(struct lws_context *context, const char *filename, uint8_t **buf, - lws_filepos_t *amount) +int +lws_alloc_vfs_file(struct lws_context *context, const char *filename, + uint8_t **buf, lws_filepos_t *amount) { lws_filepos_t len; lws_fop_flags_t flags = LWS_O_RDONLY; @@ -97,6 +98,45 @@ lws_ssl_remove_wsi_from_buffered_list(struct lws *wsi) wsi->pending_read_list_next = NULL; } +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; + int n; + + if (!v->ssl_ctx) + 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; + + life = (ir.time - now) / (24 * 3600); + lwsl_notice(" vhost %s: cert expiry: %dd\n", v->name, (int)life); + + 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; +} int lws_gate_accepts(struct lws_context *context, int on) diff --git a/plugins/protocol_lws_sshd_demo.c b/plugins/protocol_lws_sshd_demo.c index ca2fe3eb..b5826fcb 100644 --- a/plugins/protocol_lws_sshd_demo.c +++ b/plugins/protocol_lws_sshd_demo.c @@ -411,6 +411,9 @@ callback_lws_sshd_demo(struct lws *wsi, enum lws_callback_reasons reason, close(vhd->privileged_fd); break; + case LWS_CALLBACK_VHOST_CERT_AGING: + break; + default: if (!vhd->ssh_base_protocol) { vhd->ssh_base_protocol = lws_vhost_name_to_protocol(