diff --git a/include/libwebsockets/lws-secure-streams-policy.h b/include/libwebsockets/lws-secure-streams-policy.h index f64fd80d2..02122fff6 100644 --- a/include/libwebsockets/lws-secure-streams-policy.h +++ b/include/libwebsockets/lws-secure-streams-policy.h @@ -247,3 +247,15 @@ typedef struct lws_ss_policy { uint8_t client_cert; /**< which client cert to apply 0 = none, 1+ = cc 0+ */ } lws_ss_policy_t; + +LWS_VISIBLE LWS_EXTERN int +lws_ss_policy_parse_begin(struct lws_context *context, int overlay); + +LWS_VISIBLE LWS_EXTERN int +lws_ss_policy_parse_abandon(struct lws_context *context); + +LWS_VISIBLE LWS_EXTERN int +lws_ss_policy_parse(struct lws_context *context, const uint8_t *buf, size_t len); + +LWS_VISIBLE LWS_EXTERN int +lws_ss_policy_overlay(struct lws_context *context, const char *overlay); diff --git a/lib/core/context.c b/lib/core/context.c index ced00eb3a..be5e70d87 100644 --- a/lib/core/context.c +++ b/lib/core/context.c @@ -828,7 +828,7 @@ lws_create_context(const struct lws_context_creation_info *info) assert(lws_check_opt(info->options, LWS_SERVER_OPTION_EXPLICIT_VHOSTS)); - if (lws_ss_policy_parse_begin(context)) + if (lws_ss_policy_parse_begin(context, 0)) goto bail; n = lws_ss_policy_parse(context, diff --git a/lib/secure-streams/README.md b/lib/secure-streams/README.md index 59ccf319c..cde5aa67b 100644 --- a/lib/secure-streams/README.md +++ b/lib/secure-streams/README.md @@ -369,6 +369,45 @@ The secure-streams-proxy minimal example shows how this is done and fetches its real policy from warmcat.com at startup using the built-in one. +## Applying streamtype policy overlays + +This is intended for modifying policies at runtime for testing, eg, to +force error paths to be taken. After the main policy is processed, you +may parse additional, usually smaller policy fragments on top of it. + +Where streamtype names in the new fragment already exist in the current +parsed policy, the settings in the fragment are applied over the parsed +policy, overriding settings. There's a simple api to enable this by +giving it the override JSON in one string + +``` +int +lws_ss_policy_overlay(struct lws_context *context, const char *overlay); +``` + +but there are also other apis available that can statefully process +larger overlay fragments if needed. + +An example overlay fragment looks like this + +``` + { "s": [{ "captive_portal_detect": { + "endpoint": "google.com", + "http_url": "/", + "port": 80 + }}]} +``` + +ie the overlay fragment completely follows the structure of the main policy, +just misses out anything it doesn't override. + +Currently ONLY streamtypes may be overridden. + +You can see an example of this in use in `minimal-secure-streams` example +where `--force-portal` and `--force-no-internet` options cause the captive +portal detect streamtype to be overridden to force the requested kind of +outcome. + ## Captive Portal Detection If the policy contains a streamtype `captive_portal_detect` then the diff --git a/lib/secure-streams/policy.c b/lib/secure-streams/policy.c index d35c003cf..d12f6f801 100644 --- a/lib/secure-streams/policy.c +++ b/lib/secure-streams/policy.c @@ -277,6 +277,7 @@ lws_ss_policy_parser_cb(struct lejp_ctx *ctx, char reason) char **pp, dotstar[32], *q; lws_ss_trust_store_t *ts; lws_ss_metadata_t *pmd; + lws_ss_policy_t *p2; lws_retry_bo_t *b; size_t inl, outl; lws_ss_x509_t *x; @@ -336,11 +337,39 @@ lws_ss_policy_parser_cb(struct lejp_ctx *ctx, char reason) } if (reason == LEJPCB_PAIR_NAME && n != -1 && n != LTY_TRUSTSTORE) { + + p2 = NULL; + if (n == LTY_POLICY) { + /* + * We want to allow for the possibility of overlays... + * eg, we come later with a JSON snippet that overrides + * select streamtype members of a streamtype that was + * already defined + */ + p2 = (lws_ss_policy_t *)a->context->pss_policies; + + while (p2) { + if (!strncmp(p2->streamtype, + ctx->path + ctx->st[ctx->sp].p, + ctx->path_match_len - + ctx->st[ctx->sp].p)) { + lwsl_info("%s: overriding s[] %s\n", + __func__, p2->streamtype); + break; + } + + p2 = p2->next; + } + } + /* - * We do the pointers always as .b, all of the participating - * structs begin with .next and .name + * We do the pointers always as .b union member, all of the + * participating structs begin with .next and .name the same */ - a->curr[n].b = lwsac_use_zero(&a->ac, sizes[n], POL_AC_GRAIN); + if (p2) /* we may be overriding existing streamtype... */ + a->curr[n].b = (backoff_t *)p2; + else + a->curr[n].b = lwsac_use_zero(&a->ac, sizes[n], POL_AC_GRAIN); if (!a->curr[n].b) goto oom; @@ -352,11 +381,15 @@ lws_ss_policy_parser_cb(struct lejp_ctx *ctx, char reason) } a->count = 0; - a->curr[n].b->next = a->heads[n].b; - a->heads[n].b = a->curr[n].b; - pp = (char **)&a->curr[n].b->name; + if (!p2) { + a->curr[n].b->next = a->heads[n].b; + a->heads[n].b = a->curr[n].b; + pp = (char **)&a->curr[n].b->name; - goto string1; + goto string1; + } + + return 0; /* overriding */ } if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match) @@ -762,7 +795,7 @@ oom: } int -lws_ss_policy_parse_begin(struct lws_context *context) +lws_ss_policy_parse_begin(struct lws_context *context, int overlay) { struct policy_cb_args *args; char *p; @@ -773,6 +806,12 @@ lws_ss_policy_parse_begin(struct lws_context *context) return 1; } + if (overlay) + /* continue to use the existing lwsac */ + args->ac = context->ac_policy; + else + /* we don't want to see any old policy */ + context->pss_policies = NULL; context->pol_args = args; args->context = context; @@ -818,6 +857,14 @@ lws_ss_policy_parse(struct lws_context *context, const uint8_t *buf, size_t len) return m; } +int +lws_ss_policy_overlay(struct lws_context *context, const char *overlay) +{ + lws_ss_policy_parse_begin(context, 1); + return lws_ss_policy_parse(context, (const uint8_t *)overlay, + strlen(overlay)); +} + int lws_ss_policy_set(struct lws_context *context, const char *name) { diff --git a/lib/secure-streams/private-lib-secure-streams.h b/lib/secure-streams/private-lib-secure-streams.h index fc8b88e84..90dd44587 100644 --- a/lib/secure-streams/private-lib-secure-streams.h +++ b/lib/secure-streams/private-lib-secure-streams.h @@ -272,19 +272,9 @@ lws_ss_destroy_dll(struct lws_dll2 *d, void *user); int lws_sspc_destroy_dll(struct lws_dll2 *d, void *user); - -int -lws_ss_policy_parse_begin(struct lws_context *context); - -int -lws_ss_policy_parse(struct lws_context *context, const uint8_t *buf, size_t len); - int lws_ss_policy_set(struct lws_context *context, const char *name); -int -lws_ss_policy_parse_abandon(struct lws_context *context); - int lws_ss_sys_fetch_policy(struct lws_context *context); diff --git a/lib/secure-streams/system/fetch-policy/fetch-policy.c b/lib/secure-streams/system/fetch-policy/fetch-policy.c index bd0a3e446..ace853b37 100644 --- a/lib/secure-streams/system/fetch-policy/fetch-policy.c +++ b/lib/secure-streams/system/fetch-policy/fetch-policy.c @@ -45,7 +45,7 @@ ss_fetch_policy_rx(void *userobj, const uint8_t *buf, size_t len, int flags) struct lws_context *context = (struct lws_context *)m->opaque_data; if (flags & LWSSS_FLAG_SOM) { - if (lws_ss_policy_parse_begin(context)) + if (lws_ss_policy_parse_begin(context, 0)) return 1; m->partway = 1; } diff --git a/minimal-examples/secure-streams/minimal-secure-streams/README.md b/minimal-examples/secure-streams/minimal-secure-streams/README.md index 80a1c6928..cb9b3b360 100644 --- a/minimal-examples/secure-streams/minimal-secure-streams/README.md +++ b/minimal-examples/secure-streams/minimal-secure-streams/README.md @@ -23,6 +23,8 @@ Commandline option|Meaning -d |Debug verbosity in decimal, eg, -d15 -f| Force connecting to the wrong endpoint to check backoff retry flow -p| Run as proxy server for clients to connect to over unix domain socket +--force-portal|Force the SS Captive Portal Detection to feel it's behind a portal +--force-no-internet|Force the SS Captive Portal Detection to feel it can't reach the internet ``` [2019/08/12 07:16:11:0045] USR: LWS minimal secure streams [-d] [-f] diff --git a/minimal-examples/secure-streams/minimal-secure-streams/minimal-secure-streams.c b/minimal-examples/secure-streams/minimal-secure-streams/minimal-secure-streams.c index 2c5a27869..bdd429741 100644 --- a/minimal-examples/secure-streams/minimal-secure-streams/minimal-secure-streams.c +++ b/minimal-examples/secure-streams/minimal-secure-streams/minimal-secure-streams.c @@ -37,7 +37,8 @@ */ // #define VIA_LOCALHOST_SOCKS -static int interrupted, bad = 1; +static int interrupted, bad = 1, force_cpd_fail_portal, + force_cpd_fail_no_internet; static lws_state_notify_link_t nl; /* @@ -184,24 +185,9 @@ static const char * const default_ss_policy = * every DHCP acquisition */ "\"captive_portal_detect\": {" -#if 1 - /* this does the actual test */ "\"endpoint\": \"connectivitycheck.android.com\"," "\"http_url\": \"generate_204\"," "\"port\": 80," -#endif -#if 0 - /* this looks like a captive portal due to redirect */ - "\"endpoint\": \"google.com\"," - "\"http_url\": \"/\"," - "\"port\": 80," -#endif -#if 0 - /* this looks like no internet */ - "\"endpoint\": \"warmcat.com\"," - "\"http_url\": \"/\"," - "\"port\": 999," -#endif "\"protocol\": \"h1\"," "\"http_method\": \"GET\"," "\"opportunistic\": true," @@ -309,6 +295,38 @@ app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link, * state wait while we trigger the dependent action. */ switch (target) { + + case LWS_SYSTATE_INITIALIZED: /* overlay on the hardcoded policy */ + case LWS_SYSTATE_POLICY_VALID: /* overlay on the loaded policy */ + + if (target != current) + break; + + if (force_cpd_fail_portal) + + /* this makes it look like we're behind a captive portal + * because the overriden address does a redirect */ + + lws_ss_policy_overlay(context, + "{\"s\": [{\"captive_portal_detect\": {" + "\"endpoint\": \"google.com\"," + "\"http_url\": \"/\"," + "\"port\": 80" + "}}]}"); + + if (force_cpd_fail_no_internet) + + /* this looks like no internet, because the overridden + * port doesn't have anything that will connect to us */ + + lws_ss_policy_overlay(context, + "{\"s\": [{\"captive_portal_detect\": {" + "\"endpoint\": \"warmcat.com\"," + "\"http_url\": \"/\"," + "\"port\": 999" + "}}]}"); + break; + case LWS_SYSTATE_REGISTERED: size = lws_system_blob_get_size(ab); if (size) @@ -372,6 +390,14 @@ int main(int argc, const char **argv) lwsl_user("LWS secure streams test client [-d]\n"); + /* these options are mutually exclusive if given */ + + if (lws_cmdline_option(argc, argv, "--force-portal")) + force_cpd_fail_portal = 1; + + if (lws_cmdline_option(argc, argv, "--force-no-internet")) + force_cpd_fail_no_internet = 1; + info.fd_limit_per_thread = 1 + 6 + 1; info.port = CONTEXT_PORT_NO_LISTEN; #if defined(LWS_SS_USE_SSPC)