diff --git a/.travis.yml b/.travis.yml index 22419b29b..b0b9f59b0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,8 @@ env: - LWS_METHOD=lwsws2 CMAKE_ARGS="-DLWS_WITH_LWSWS=ON -DLWS_WITHOUT_EXTENSIONS=0 -DLWS_WITH_HTTP2=1 -DLWS_WITH_ACME=1 -DLWS_WITH_MINIMAL_EXAMPLES=1 -DCMAKE_BUILD_TYPE=DEBUG -DLWS_WITH_LWS_DSH=1" - LWS_METHOD=default CMAKE_ARGS="-DLWS_WITH_MINIMAL_EXAMPLES=1" - LWS_METHOD=mbedtls CMAKE_ARGS="-DLWS_WITH_MBEDTLS=1 -DLWS_WITH_HTTP2=1 -DLWS_WITH_LWSWS=1 -DLWS_WITH_MINIMAL_EXAMPLES=1 -DLWS_WITH_JOSE=1 -DCMAKE_BUILD_TYPE=DEBUG" + - LWS_METHOD=ss CMAKE_ARGS="-DLWS_WITH_SECURE_STREAMS=1 -DLWS_WITH_MINIMAL_EXAMPLES=1" + - LWS_METHOD=ss+mbedtls CMAKE_ARGS="-DLWS_WITH_MBEDTLS=1 -DLWS_WITH_SECURE_STREAMS=1 -DLWS_WITH_MINIMAL_EXAMPLES=1" - LWS_METHOD=noserver CMAKE_ARGS="-DLWS_WITHOUT_SERVER=ON -DLWS_WITH_MINIMAL_EXAMPLES=1" - LWS_METHOD=noclient CMAKE_ARGS="-DLWS_WITHOUT_CLIENT=ON -DLWS_WITH_MINIMAL_EXAMPLES=1" - LWS_METHOD=noext CMAKE_ARGS="-DLWS_WITHOUT_EXTENSIONS=ON -DLWS_WITH_MINIMAL_EXAMPLES=1" diff --git a/CMakeLists.txt b/CMakeLists.txt index 16003412d..e4bf58f67 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,6 +60,13 @@ option(LWS_WITH_SYS_DHCP_CLIENT "Build in tiny DHCP client" OFF) option(LWS_WITH_HTTP_BASIC_AUTH "Support Basic Auth" ON) option(LWS_WITH_HTTP_UNCOMMON_HEADERS "Include less common http header support" ON) +# +# Secure Streams +# +option(LWS_WITH_SECURE_STREAMS "Secure Streams protocol-agnostic API" OFF) +option(LWS_WITH_SECURE_STREAMS_PROXY_API "Secure Streams support to work across processes" OFF) +option(LWS_WITH_SECURE_STREAMS_SYS_AUTH_API_AMAZON_COM "Auth support for api.amazon.com" OFF) + # # TLS library options... all except mbedTLS are basically OpenSSL variants. # @@ -245,6 +252,11 @@ if(LWS_WITH_DISTRO_RECOMMENDED) set(LWS_ROLE_MQTT 1) endif() +if (LWS_WITH_SECURE_STREAMS_PROXY_API) + set(LWS_WITH_LWS_DSH 1) + set(LWS_WITH_UNIX_SOCK 1) +endif() + if (NOT LWS_WITH_NETWORK) set(LWS_ROLE_MQTT 0) set(LWS_ROLE_H1 0) @@ -394,6 +406,10 @@ endif() include_directories(include plugins lib/core lib/core-net lib/event-libs include/abstract lib/tls lib/roles lib/event-libs/libuv lib/event-libs/poll lib/event-libs/libevent lib/event-libs/glib lib/event-libs/libev lib/jose/jwe lib/jose/jws lib/jose lib/misc lib/roles/http lib/roles/http/compression lib/roles/h1 lib/roles/h2 lib/roles/ws lib/roles/cgi lib/roles/dbus lib/roles/raw-proxy lib/abstract lib/system/async-dns lib/roles/mqtt) +if (LWS_WITH_SECURE_STREAMS) + set(LWS_WITH_SECURE_STREAMS_SYS_AUTH_API_AMAZON_COM 1) +endif() + if (LWS_PLAT_FREERTOS) include_directories(lib/plat/freertos lib/plat/freertos/esp32) else() @@ -752,14 +768,6 @@ if (LWS_WITHOUT_DAEMONIZE OR WIN32) set(LWS_NO_DAEMONIZE 1) endif() -if (LWS_WITHOUT_SERVER) - set(LWS_WITHOUT_SERVER 1) -endif() - -if (LWS_WITHOUT_CLIENT) - set(LWS_WITHOUT_CLIENT 1) -endif() - if (LWS_WITH_LIBEV) set(LWS_WITH_LIBEV 1) endif() @@ -790,11 +798,11 @@ endif() set(LWS_WITH_CLIENT 1) if (LWS_WITHOUT_CLIENT) - set(LWS_WITH_CLIENT 0) + set(LWS_WITH_CLIENT) endif() set(LWS_WITH_SERVER 1) if (LWS_WITHOUT_SERVER) - set(LWS_WITH_SERVER 0) + set(LWS_WITH_SERVER) endif() # using any abstract protocol enables LWS_WITH_ABSTRACT @@ -1165,6 +1173,53 @@ if (LWS_WITH_NETWORK) endif() endif() + if (LWS_WITH_SECURE_STREAMS) + list(APPEND SOURCES + lib/secure-streams/secure-streams.c + lib/secure-streams/policy.c + lib/secure-streams/system/fetch-policy/fetch-policy.c + ) + if (LWS_ROLE_H1) + list(APPEND SOURCES + lib/secure-streams/protocols/ss-h1.c + ) + endif() + if (LWS_ROLE_H2) + list(APPEND SOURCES + lib/secure-streams/protocols/ss-h2.c + ) + endif() + if (LWS_ROLE_WS) + list(APPEND SOURCES + lib/secure-streams/protocols/ss-ws.c + ) + endif() + if (LWS_ROLE_MQTT) + list(APPEND SOURCES + lib/secure-streams/protocols/ss-mqtt.c + ) + endif() + + if (LWS_WITH_SECURE_STREAMS_PROXY_API) + list(APPEND SOURCES + lib/secure-streams/secure-streams-serialize.c + lib/secure-streams/secure-streams-client.c + ) + endif() + + if (LWS_WITH_SECURE_STREAMS_PROXY_API) + list(APPEND SOURCES + lib/secure-streams/secure-streams-process.c + ) + endif() + + if (LWS_WITH_SECURE_STREAMS_SYS_AUTH_API_AMAZON_COM) + list(APPEND SOURCES + lib/secure-streams/system/auth-api.amazon.com/auth.c + ) + endif() + endif() + if (LWS_WITH_STATS) list(APPEND SOURCES lib/core-net/stats.c @@ -1300,7 +1355,11 @@ endif() if (NOT LWS_WITHOUT_SERVER) list(APPEND SOURCES - lib/core-net/server.c + lib/core-net/server.c) +endif() + +if (NOT LWS_WITHOUT_SERVER OR LWS_WITH_SECURE_STREAMS_PROCESS_API) + list(APPEND SOURCES lib/roles/listen/ops-listen.c) endif() @@ -1684,7 +1743,7 @@ if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX OR COMPILER_IS_CLANG) endif() if (UNIX AND NOT LWS_PLAT_FREERTOS) - set(CMAKE_C_FLAGS "-Wall -Wsign-compare -Wuninitialized -Werror ${VISIBILITY_FLAG} -Wundef ${GCOV_FLAGS} ${CMAKE_C_FLAGS} ${ASAN_FLAGS}" ) + set(CMAKE_C_FLAGS "-Wall -Wsign-compare -Wstrict-aliasing -Wuninitialized -Werror ${VISIBILITY_FLAG} -Wundef ${GCOV_FLAGS} ${CMAKE_C_FLAGS} ${ASAN_FLAGS}" ) else() set(CMAKE_C_FLAGS "-Wall -Wsign-compare -Wuninitialized -Werror ${VISIBILITY_FLAG} ${GCOV_FLAGS} ${CMAKE_C_FLAGS}" ) endif() @@ -2666,6 +2725,59 @@ if (LWS_WITH_LWSWS) ) endif (LWS_WITH_LWSWS) +# secure streams plugins + +if (LWS_WITH_SECURE_STREAMS) + # + # Helper function for adding a secure stream plugin + # + macro(create_ss_plugin NAME S2 S3 S4 S5 S6) + + set(SSP_SRCS) + set(SSP_PUBLIC_HDR) + set(SSP_HDR) + + if ("${S2}" STREQUAL "") + else() + list(APPEND SSP_SRCS + lib/secure-streams/plugins/${NAME}/${S2}) + endif() + if ("${S3}" STREQUAL "") + else() + list(APPEND SSP_SRCS + lib/secure-streams/plugins/${NAME}/${S3}) + endif() + if ("${S4}" STREQUAL "") + else() + list(APPEND SSP_SRCS + lib/secure-streams/plugins/${NAME}/${S4}) + endif() + if ("${S5}" STREQUAL "") + else() + list(APPEND SSP_SRCS + lib/secure-streams/plugins/${NAME}/${S5}) + endif() + if ("${S6}" STREQUAL "") + else() + list(APPEND SSP_SRCS + lib/secure-streams/plugins/${NAME}/${S6}) + endif() + + source_group("Headers Private" FILES ${SSP_HDR}) + source_group("Sources" FILES ${SSP_SRCS}) + + add_library( ${NAME} STATIC + ${SSP_HDR} ${SSP_PUBLIC_HDR} ${SSP_SRCS} ) + + add_dependencies(${NAME} websockets_shared) + list(APPEND SS_PLUGINS_LIST ${NAME}) + endmacro() + + include_directories(lib/secure-streams) + + create_ss_plugin(ssp-h1url "h1url.c" "" "" "" "") +endif() + if (UNIX) # Generate and install pkgconfig. diff --git a/README.md b/README.md index 76d8bde50..528d5cc7e 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,26 @@ various scenarios, CC0-licensed (public domain) for cut-and-paste, allow you to News ---- +## Introducing Secure Streams client support + +Secure Streams is an optional layer above lws (`-DLWS_WITH_SECURE_STREAMS=1`) that +separates connectivity policy into a JSON document, which can be part of the +firmware or fetched at boot time. + +Code no longer deals with details like endpoint specification or tls cert stack used +to validate the remote server, it's all specified in JSON, eg, see +[this example](https://warmcat.com/policy/minimal-proxy.json). Even the protocol to use to talk to the +server, between h1, h2, ws or MQTT, is specified in the policy JSON and the code +itself just deals with payloads and optionally metadata, making it possible to +switch endpoints, update certs and even switch communication protocols by just +editing the JSON policy and leaving the code alone. + +Logical Secure Stream connections outlive any underlying lws connection, and support +"nailed-up" connection reacquisition and exponential backoff management. + +See [./lib/secure-streams/README.md](https://libwebsockets.org/git/libwebsockets/tree/lib/secure-streams/README.md) and the related minimal examples +for more details. + ## mqtt client support If you enable `-DLWS_ROLE_MQTT=1`, lws can now support QoS0 and QoS1 MQTT client diff --git a/cmake/lws_config.h.in b/cmake/lws_config.h.in index ba397cb2f..56691a30e 100644 --- a/cmake/lws_config.h.in +++ b/cmake/lws_config.h.in @@ -147,6 +147,9 @@ #cmakedefine LWS_WITH_POLARSSL #cmakedefine LWS_WITH_POLL #cmakedefine LWS_WITH_RANGES +#cmakedefine LWS_WITH_SECURE_STREAMS +#cmakedefine LWS_WITH_SECURE_STREAMS_SYS_AUTH_API_AMAZON_COM +#cmakedefine LWS_WITH_SECURE_STREAMS_PROXY_API #cmakedefine LWS_WITH_SELFTESTS #cmakedefine LWS_WITH_SEQUENCER #cmakedefine LWS_WITH_SERVER_STATUS diff --git a/doc-assets/ss-explain.png b/doc-assets/ss-explain.png new file mode 100644 index 000000000..14bd7e793 Binary files /dev/null and b/doc-assets/ss-explain.png differ diff --git a/include/libwebsockets.h b/include/libwebsockets.h index 6e7fb77f8..8b26586ee 100644 --- a/include/libwebsockets.h +++ b/include/libwebsockets.h @@ -576,6 +576,9 @@ struct lws; #include #include #include +#include +#include +#include #if !defined(LWS_PLAT_FREERTOS) #include diff --git a/include/libwebsockets/lws-context-vhost.h b/include/libwebsockets/lws-context-vhost.h index c379b0fc1..33143b923 100644 --- a/include/libwebsockets/lws-context-vhost.h +++ b/include/libwebsockets/lws-context-vhost.h @@ -227,6 +227,8 @@ #define lws_check_opt(c, f) ((((uint64_t)c) & ((uint64_t)f)) == ((uint64_t)f)) struct lws_plat_file_ops; +struct lws_ss_policy; +struct lws_ss_plugin; typedef int (*lws_context_ready_cb_t)(struct lws_context *context); @@ -699,7 +701,7 @@ struct lws_context_creation_info { /**< VHOST: optional retry and idle policy to apply to this vhost. * Currently only the idle parts are applied to the connections. */ - lws_state_notify_link_t **register_notifier_list; + lws_state_notify_link_t * const *register_notifier_list; /**< CONTEXT: NULL, or pointer to an array of notifiers that should * be registered during context creation, so they can see state change * events from very early on. The array should end with a NULL. */ @@ -709,6 +711,26 @@ struct lws_context_creation_info { uint8_t udp_loss_sim_rx_pc; /**< CONTEXT: percentage of udp reads we actually received * to make disappear, in order to simulate and test udp retry flow */ +#if defined(LWS_WITH_SECURE_STREAMS) + const char *pss_policies_json; /**< CONTEXT: point to a string + * containing a JSON description of the secure streams policies. Set + * to NULL if not using Secure Streams. */ + const struct lws_ss_plugin **pss_plugins; /**< CONTEXT: point to an array + * of pointers to plugin structs here, terminated with a NULL ptr. + * Set to NULL if not using Secure Streams. */ + const char *ss_proxy_bind; /**< CONTEXT: NULL, or: ss_proxy_port == 0: + * point to a string giving the Unix Domain Socket address to use (start + * with @ for abstract namespace), ss_proxy_port nonzero: set the + * network interface address (not name, it's ambiguous for ipv4/6) to + * bind the tcp connection to the proxy to */ + const char *ss_proxy_address; /**< CONTEXT: NULL, or if ss_proxy_port + * nonzero: the tcp address of the ss proxy to connect to */ + uint16_t ss_proxy_port; /* 0 = if connecting to ss proxy, do it via a + * Unix Domain Socket, "+@proxy.ss.lws" if ss_proxy_bind is NULL else + * the socket path given in ss_proxy_bind (start it with a + or +@); + * nonzero means connect via a tcp socket to the tcp address in + * ss_proxy_bind and the given port */ +#endif /* Add new things just above here ---^ * This is part of the ABI, don't needlessly break compatibility @@ -718,7 +740,7 @@ struct lws_context_creation_info { * was not built against the newer headers. */ - void *_unused[4]; /**< dummy */ + void *_unused[2]; /**< dummy */ }; /** diff --git a/include/libwebsockets/lws-secure-streams-client.h b/include/libwebsockets/lws-secure-streams-client.h new file mode 100644 index 000000000..b7244158a --- /dev/null +++ b/include/libwebsockets/lws-secure-streams-client.h @@ -0,0 +1,172 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2019 - 2020 Andy Green + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * This is the headers for secure stream api variants that deal with clients in + * different threads or even different processes. + * + * lws_ss_ when client is directly using the event loop + * lws_sstc_ when client is in a different thread to the event loop + * lws_sspc_ when client is in a different process to the event loop + * + * The client api is almost the same except the slightly diffent names. + */ + +/* + * lws_sspc_ apis... different process + */ + +/* + * Helper translation so user code written to lws_ss_ can be built for + * lws_sspc_ in one step by #define LWS_SS_USE_SSPC before including + */ + +#if defined(LWS_SS_USE_SSPC) +#define lws_ss_handle lws_sspc_handle +#define lws_ss_create lws_sspc_create +#define lws_ss_destroy lws_sspc_destroy +#define lws_ss_request_tx lws_sspc_request_tx +#define lws_ss_client_connect lws_sspc_client_connect +#define lws_ss_get_sequencer lws_sspc_get_sequencer +#define lws_ss_proxy_create lws_sspc_proxy_create +#define lws_ss_get_context lws_sspc_get_context +#define lws_ss_rideshare lws_sspc_rideshare +#define lws_ss_set_metadata lws_sspc_set_metadata +#define lws_ss_add_peer_tx_credit lws_sspc_add_peer_tx_credit +#define lws_ss_get_est_peer_tx_credit lws_sspc_get_est_peer_tx_credit +#endif + + +struct lws_sspc_handle; + +LWS_VISIBLE LWS_EXTERN int +lws_sspc_create(struct lws_context *context, int tsi, const lws_ss_info_t *ssi, + void *opaque_user_data, struct lws_sspc_handle **ppss, + struct lws_sequencer *seq_owner, const char **ppayload_fmt); + +/** + * lws_sspc_destroy() - Destroy secure stream + * + * \param ppss: pointer to lws_ss_t pointer to be destroyed + * + * Destroys the lws_ss_t pointed to by *ppss, and sets *ppss to NULL. + */ +LWS_VISIBLE LWS_EXTERN void +lws_sspc_destroy(struct lws_sspc_handle **ppss); + +/** + * lws_sspc_request_tx() - Schedule stream for tx + * + * \param pss: pointer to lws_ss_t representing stream that wants to transmit + * + * Schedules a write on the stream represented by \p pss. When it's possible to + * write on this stream, the *tx callback will occur with an empty buffer for + * the stream owner to fill in. + */ +LWS_VISIBLE LWS_EXTERN void +lws_sspc_request_tx(struct lws_sspc_handle *pss); + +/** + * lws_sspc_client_connect() - Attempt the client connect + * + * \param h: secure streams handle + * + * Starts the connection process for the secure stream. Returns 0 if OK or + * nonzero if we have already failed. + */ +LWS_VISIBLE LWS_EXTERN int +lws_sspc_client_connect(struct lws_sspc_handle *h); + +/** + * lws_sspc_get_sequencer() - Return parent sequencer pointer if any + * + * \param h: secure streams handle + * + * Returns NULL if the secure stream is not associated with a sequencer. + * Otherwise returns a pointer to the owning sequencer. You can use this to + * identify which sequencer to direct messages to, from the secure stream + * callback. + */ +LWS_VISIBLE LWS_EXTERN struct lws_sequencer * +lws_sspc_get_sequencer(struct lws_sspc_handle *h); + +/** + * lws_sspc_proxy_create() - Start a unix domain socket proxy for Secure Streams + * + * \param context: lws_context + * + * Creates a vhost that listens on an abstract namespace unix domain socket at + * address "proxy.ss.lws". Client connections to this proxy to Secure Streams + */ +LWS_VISIBLE LWS_EXTERN int +lws_sspc_proxy_create(struct lws_context *context); + +/** + * lws_ss_get_context() - convenience helper to recover the lws context + * + * \h: secure streams handle + * + * Returns the lws context. Dispenses with the need to pass a copy of it into + * your secure streams handler. + */ + +LWS_VISIBLE LWS_EXTERN struct lws_context * +lws_sspc_get_context(struct lws_sspc_handle *h); + +LWS_VISIBLE LWS_EXTERN const struct lws_protocols lws_sspc_protocols[]; + +LWS_VISIBLE LWS_EXTERN const char * +lws_sspc_rideshare(struct lws_sspc_handle *h); + + +/** + * lws_sspc_set_metadata() - allow user to bind external data to defined ss metadata + * + * \h: secure streams handle + * \name: metadata name from the policy + * \value: pointer to user-managed data to bind to name + * \len: length of the user-managed data in value + * + * Binds user-managed data to the named metadata item from the ss policy. + * If present, the metadata item is handled in a protocol-specific way using + * the associated policy information. For example, in the policy + * + * "\"metadata\":" "[" + * "{\"uptag\":" "\"X-Upload-Tag:\"}," + * "{\"ctype\":" "\"Content-Type:\"}," + * "{\"xctype\":" "\"X-Content-Type:\"}" + * "]," + * + * when the policy is using h1 is interpreted to add h1 headers of the given + * name with the value of the metadata on the left. + * + * Return 0 if OK. + */ +LWS_VISIBLE LWS_EXTERN int +lws_sspc_set_metadata(struct lws_sspc_handle *h, const char *name, + void *value, size_t len); + +LWS_VISIBLE LWS_EXTERN int +lws_sspc_add_peer_tx_credit(struct lws_sspc_handle *h, int32_t add); + +LWS_VISIBLE LWS_EXTERN int +lws_sspc_get_est_peer_tx_credit(struct lws_sspc_handle *h); diff --git a/include/libwebsockets/lws-secure-streams-policy.h b/include/libwebsockets/lws-secure-streams-policy.h new file mode 100644 index 000000000..967ef8dd2 --- /dev/null +++ b/include/libwebsockets/lws-secure-streams-policy.h @@ -0,0 +1,237 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2019 - 2020 Andy Green + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * included from libwebsockets.h + */ + +typedef int (*plugin_auth_status_cb)(struct lws_ss_handle *ss, int status); + +/** + * lws_ss_plugin_auth_t - api for an auth plugin + * + * Auth plugins create and sequence authenticated connections that can carry one + * or more streams to an endpoint. That may involve other connections to other + * places to eg, gather authenticated tokens and then make the real connection + * using the tokens. + * + * The secure stream object contains members to record which auth plugin the + * stream is bound to and an over-allocation of the secure stream object to + * contain the plugin auth private data. + * + * The auth plugin controls the state of the stream connection via the status + * callback, and handles retries. + * + * Network connections may require one kind of auth sequencing, and streams + * inside those connections another kind of auth sequencing depending on their + * role. So the secure stream object allows defining plugins for both kinds. + * + * Streams may disappear at any time and require reauth to bring a new one up. + * The auth plugin sequencer will connect / reconnect either on demand, or from + * the start and after any connectivity loss if any stream using the connection + * has the LWSSSPOLF_NAILED_UP flag. + */ + +typedef struct lws_ss_plugin { + struct lws_ss_plugin *next; + const char *name; /**< auth plugin name */ + size_t alloc; /**< size of private allocation */ + + int (*create)(struct lws_ss_handle *ss, void *info, + plugin_auth_status_cb status); + /**< called when the auth plugin is instantiated + and bound to the secure stream. status is + called back with advisory information about + the authenticated stream state as it + proceeds */ + int (*destroy)(struct lws_ss_handle *ss); + /**< called when the related secure stream is + being destroyed, and anything the auth + plugin is doing should also be destroyed */ + int (*munge)(struct lws_ss_handle *ss, char *path, + size_t path_len); + /**< if the plugin needs to munge transactions + that have metadata outside the payload (eg, + add http headers) this callback will give + it the opportunity to do so */ +} lws_ss_plugin_t; + + +typedef struct lws_ss_x509 { + struct lws_ss_x509 *next; + const char *vhost_name; /**< vhost name using cert ctx */ + const uint8_t *ca_der; /**< DER x.509 cert */ + size_t ca_der_len; /**< length of DER cert */ +} lws_ss_x509_t; + +enum { + LWSSSPOLF_OPPORTUNISTIC = (1 << 0), + /**< the connection doesn't exist unless client asks to write */ + LWSSSPOLF_NAILED_UP = (1 << 1), + /**< the connection tries to be connected the whole life of the ss */ + LWSSSPOLF_URGENT_TX = (1 << 2), + /**< this connection carries critical tx data */ + LWSSSPOLF_URGENT_RX = (1 << 3), + /**< this connection carries critical rx data */ + LWSSSPOLF_TLS = (1 << 4), + /**< stream must be connected via a tls tunnel */ + LWSSSPOLF_LONG_POLL = (1 << 5), + /**< stream used to receive async rx at arbitrary intervals */ + LWSSSPOLF_AUTH_BEARER = (1 << 6), + /**< for http, use lws_system auth token 0 in authentication: bearer */ + LWSSSPOLF_HTTP_NO_CONTENT_LENGTH = (1 << 7), + /**< don't add any content length even if we have it */ + LWSSSPOLF_QUIRK_NGHTTP2_END_STREAM = (1 << 8), + /**< set the client flag LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM */ + LWSSSPOLF_H2_QUIRK_OVERFLOWS_TXCR = (1 << 9), + /**< set the client flag LCCSCF_H2_QUIRK_OVERFLOWS_TXCR */ + LWSSSPOLF_H2_QUIRK_UNCLEAN_HPACK_STATE = (1 << 10), + /**< HPACK decoder state does not end cleanly */ + LWSSSPOLF_HTTP_MULTIPART = (1 << 11), + /**< indicates stream goes out as specifically a multipart mime POST + * section... if the tx has LWSSS_FLAG_COALESCE_CONTINUES flag then more + * multipart sections are expected. Without it, the multipart wrapper + * is closed and the http transaction issue completed when this message + * finishes. */ + LWSSSPOLF_HTTP_X_WWW_FORM_URLENCODED = (1 << 12), + /**< set up lws_system client cert */ + LWSSSPOLF_LOCAL_SINK = (1 << 13), + /**< expected to bind to a local sink only */ +}; + +typedef struct lws_ss_trust_store { + struct lws_ss_trust_store *next; + const char *name; + + lws_ss_x509_t *ssx509[8]; + int count; +} lws_ss_trust_store_t; + +enum { + LWSSSP_H1, + LWSSSP_H2, + LWSSSP_WS, + + + LWSSS_HBI_AUTH = 0, + LWSSS_HBI_DSN, + LWSSS_HBI_FWV, + LWSSS_HBI_TYPE, + + _LWSSS_HBI_COUNT /* always last */ +}; + +typedef struct lws_ss_metadata { + struct lws_ss_metadata *next; + const char *name; + void *value; + size_t length; + + uint8_t value_on_lws_heap; /* proxy does this */ +} lws_ss_metadata_t; + + +/** + * lws_ss_policy_t: policy database entry for a stream type + * + * Decides the system policy for how to implement connections of name + * .streamtype. + * + * Streams may need one kind of auth sequencing for the network connection and + * another kind of auth sequencing for the streams that are carried inside it, + * this is the purpose of .nauth and .sauth. Both are optional and may be NULL. + * + * An array of these is set at context creation time, ending with one with a + * NULL streamtype. + */ +typedef struct lws_ss_policy { + struct lws_ss_policy *next; + const char *streamtype; /**< stream type lhs to match on */ + + const char *endpoint; /**< DNS address to connect to */ + const char *rideshare_streamtype; /**< optional transport + * on another, preexisting stream of this + * streamtype name */ + const char *payload_fmt; + const char *socks5_proxy; + lws_ss_metadata_t *metadata; /* linked-list of metadata */ + + /* protocol-specific connection policy details */ + + union { + + /* details for http-related protocols... */ + + struct { + + /* common to all http-related protocols */ + + const char *method; + const char *url; + + const char *multipart_name; + const char *multipart_filename; + const char *multipart_content_type; + + const char *blob_header[_LWSSS_HBI_COUNT]; + const char *auth_preamble; + + union { +// struct { /* LWSSSP_H1 */ +// } h1; +// struct { /* LWSSSP_H2 */ +// } h2; + struct { /* LWSSSP_WS */ + const char *subprotocol; + uint8_t binary; + /* false = TEXT, true = BINARY */ + } ws; + } u; + } http; + + struct { + const char *topic; /* stream sends on this topic */ + const char *subscribe; /* stream subscribes to this topic */ + uint8_t qos; + } mqtt; + + /* details for non-http related protocols... */ + } u; + + const + struct lws_ss_plugin *plugins[2]; /**< NULL or auth plugin */ + const void *plugins_info[2]; /**< plugin-specific data */ + + const lws_ss_trust_store_t *trust_store; /**< CA certs needed for conn + validation, only set between policy parsing and vhost creation */ + + const lws_retry_bo_t *retry_bo; /**< retry policy to use */ + + uint32_t flags; /**< stream attribute flags */ + + uint16_t port; /**< endpoint port */ + + uint8_t metadata_count; /**< metadata count */ + uint8_t protocol; /**< protocol index */ + uint8_t client_cert; /**< which client cert to apply + 0 = none, 1+ = cc 0+ */ +} lws_ss_policy_t; diff --git a/include/libwebsockets/lws-secure-streams.h b/include/libwebsockets/lws-secure-streams.h new file mode 100644 index 000000000..afd8bee19 --- /dev/null +++ b/include/libwebsockets/lws-secure-streams.h @@ -0,0 +1,492 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2019 - 2020 Andy Green + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * included from libwebsockets.h + * + * + * Secure Streams is a *payload-only* client communication channel where all the + * details about the connection are held in a systemwide policy database and + * are keyed by the streamtype field... the user of the communication channel + * does not know or manage the choice of endpoint, tls CA, or even wire + * protocol. The advantage is he then does not have any dependency on any of + * those and they can be changed just by changing the policy database without + * touching the code using the stream. + * + * There are two ways secure streams interfaces to user code: + * + * 1) [Linux / RTOS] the natural, smallest interface is to call back to user + * code that only operates directly from the lws event loop thread context + * (direct callbacks from lws_ss_t) + * + * lws_thread( [user code] ---- lws ) + * + * 2) [Linux] where the user code is in a different process and communicates + * asynchronously via a proxy socket + * + * user_process{ [user code] | shim | socket-}------ lws_process{ lws } + * + * In the second, IPC, case, all packets are prepended by one or more bytes + * indicating the packet type and serializing any associated data, known as + * Serialized Secure Streams or SSS. + * + * Serialized Secure Streams + * ------------------------- + * + * On the transport, adjacent packets may be coalesced, that is, the original + * packet sizes are lost and two or more packets are combined. For that reason + * the serialization format always contains a 1-byte type and then a 2-byte + * frame length. + * + * Client to proxy + * + * - Proxied connection setup + * + * - 0: LWSSS_SER_TXPRE_STREAMTYPE + * - 1: 2-byte MSB-first rest-of-frame length + * - 3: 4 byte MSB-first initial tx credit + * - 7: the streamtype name with no NUL + * + * - Proxied tx + * + * - 0: LWSSS_SER_TXPRE_TX_PAYLOAD + * - 1: 2 byte MSB-first rest-of-frame length + * - 3: 4-byte MSB-first flags + * - 7: 4-byte MSB-first us between client requested write and wrote to proxy + * - 11: 8-byte MSB-first us resolution unix time client wrote to proxy + * - 17: payload + * + * - Proxied secure stream destroy + * + * - 0: LWSSS_SER_TXPRE_DESTROYING + * - 1: 00, 00 + * + * - Proxied metadata - sent when one metadata item set clientside + * + * - 0: LWSSS_SER_TXPRE_METADATA + * - 1: 2-byte MSB-first rest-of-frame length + * - 2: 1-byte metadata name length + * - 3: metadata name + * - ...: metadata value (for rest of packet) + * + * Proxy to client + * + * - Proxied connection setup result + * + * - 0: LWSSS_SER_RXPRE_CREATE_RESULT + * - 1: 2 byte MSB-first rest-of-frame length (usually 00, 03) + * - 3: 1 byte result, 0 = success. On failure, proxy will close connection. + * - 4: 2 byte MSB-first initial tx credit + * - 6: if present, comma-sep list of rideshare types from policy + * + * - Proxied rx + * + * - 0: LWSSS_SER_RXPRE_RX_PAYLOAD + * - 1: 2 byte MSB-first rest-of-frame length + * - 3: 4-byte MSB-first flags + * - 7: 4-byte MSB-first us between inbound read and wrote to client + * - 11: 8-byte MSB-first us resolution unix time proxy wrote to client + * - 17: (rideshare name len + rideshare name if flags & LWSSS_FLAG_RIDESHARE) + * payload + * + * - Proxied tx credit + * + * - 0: LWSSS_SER_RXPRE_TXCR_UPDATE + * - 1: 00, 04 + * - 3: 4-byte MSB-first addition tx credit bytes + * + * - Proxied state + * + * - 0: LWSSS_SER_RXPRE_CONNSTATE + * - 1: 00, 05 + * - 3: 1 byte state index + * - 7: 4-byte MSB-first ordinal + * + * + * Proxied tx may be read by the proxy but rejected due to lack of buffer space + * at the proxy. For that reason, tx must be held at the sender until it has + * been acknowledged or denied. + * + * Sinks + * ----- + * + * Sinks are logical "servers", you can register as a sink for a particular + * streamtype by using the lws_ss_create() api with ssi->register_sink set to 1. + * + * For directly fulfilled Secure Streams, new streams of that streamtype bind + * to the rx, tx and state handlers given when it was registered. + * + * - When new streams are created the registered sink handler for (*state) is + * called with event LWSSSCS_SINK_JOIN and the new client stream handle in + * the h_src parameter. + * + * - When the client stream sends something to the sink, it calls the sink's + * (*rx) with the client stream's + */ + +#define LWS_SS_MTU 1540 + +struct lws_ss_handle; +typedef uint32_t lws_ss_tx_ordinal_t; + +/* + * connection state events + */ +typedef enum { + LWSSSCS_CREATING, + LWSSSCS_DISCONNECTED, + LWSSSCS_UNREACHABLE, + LWSSSCS_AUTH_FAILED, + LWSSSCS_CONNECTED, + LWSSSCS_CONNECTING, + LWSSSCS_DESTROYING, + LWSSSCS_POLL, + LWSSSCS_ALL_RETRIES_FAILED, /* all retries in bo policy failed */ + LWSSSCS_QOS_ACK_REMOTE, /* remote peer received and acked tx */ + LWSSSCS_QOS_NACK_REMOTE, + LWSSSCS_QOS_ACK_LOCAL, /* local proxy accepted our tx */ + LWSSSCS_QOS_NACK_LOCAL, /* local proxy refused our tx */ + + LWSSSCS_SINK_JOIN, /* sinks get this when a new source + * stream joins the sink */ + LWSSSCS_SINK_PART, /* sinks get this when a new source + * stream leaves the sink */ +} lws_ss_constate_t; + +enum { + LWSSS_FLAG_SOM = (1 << 0), + /* payload contains the start of new message */ + LWSSS_FLAG_EOM = (1 << 1), + /* payload contains the end of message */ + LWSSS_FLAG_POLL = (1 << 2), + /* Not a real transmit... poll for rx if protocol needs it */ + LWSSS_FLAG_RELATED_START = (1 << 3), + /* Appears in a zero-length message indicating a message group of zero + * or more messages is now starting. */ + LWSSS_FLAG_RELATED_END = (1 << 4), + /* Appears in a zero-length message indicating a message group of zero + * or more messages has now finished. */ + LWSSS_FLAG_RIDESHARE = (1 << 5), + /* Serialized payload starts with non-default rideshare name length and + * name string without NUL, then payload */ + + /* + * In the case the secure stream is proxied across a process or thread + * boundary, eg by proxying through a socket for IPC, metadata must be + * carried in-band. A byte is prepended to each rx payload to + * differentiate what it is. + * + * Secure streams where the user is called back directly does not need + * any of this and only pure payloads are passed. + * + * rx (received by client) prepends for proxied connections + */ + + LWSSS_SER_RXPRE_RX_PAYLOAD = 0x55, + LWSSS_SER_RXPRE_CREATE_RESULT, + LWSSS_SER_RXPRE_CONNSTATE, + LWSSS_SER_RXPRE_TXCR_UPDATE, + LWSSS_SER_RXPRE_TLSNEG_ENCLAVE_SIGN, + + /* tx (send by client) prepends for proxied connections */ + + LWSSS_SER_TXPRE_STREAMTYPE = 0xaa, + LWSSS_SER_TXPRE_ONWARD_CONNECT, + LWSSS_SER_TXPRE_DESTROYING, + LWSSS_SER_TXPRE_TX_PAYLOAD, + LWSSS_SER_TXPRE_METADATA, + LWSSS_SER_TXPRE_TXCR_UPDATE, + LWSSS_SER_TXPRE_TLSNEG_ENCLAVE_SIGNED, +}; + +typedef enum { + LPCS_WAIT_INITIAL_TX = 1, /* after connect, must send streamtype */ + LPCS_REPORTING_FAIL, /* stream creation failed, wait to to tell */ + LPCS_REPORTING_OK, /* stream creation succeeded, wait to to tell */ + LPCS_OPERATIONAL, /* ready for payloads */ + LPCS_DESTROYED, + + LPCS_SENDING_INITIAL_TX = 1, /* after connect, must send streamtype */ + LPCS_WAITING_CREATE_RESULT, /* wait to hear if proxy ss create OK */ + LPCS_LOCAL_CONNECTED, /* we are in touch with the proxy */ + LPCS_ONWARD_CONNECT, /* request onward ss connection */ + +} lws_ss_conn_states_t; + +/** + * lws_ss_info_t: information about stream to be created + * + * Prepare this struct with information about what the stream type is and how + * the stream should interface with your code, and pass it to lws_ss_create() + * to create the requested stream. + */ + +typedef struct lws_ss_info { + const char *streamtype; /**< type of stream we want to create */ + size_t user_alloc; /**< size of user allocation */ + size_t handle_offset; /**< offset of handle stg in user_alloc type, + set to offsetof(mytype, my_handle_member) */ + size_t opaque_user_data_offset; + /**< offset of opaque user data ptr in user_alloc type, set to + offsetof(mytype, opaque_ud_member) */ + + int (*rx)(void *userobj, const uint8_t *buf, size_t len, + int flags); + /**< callback with rx payload for this stream */ + int (*tx)(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, + size_t *len, int *flags); + /**< callback to send payload on this stream... 0 = send as set in + * len and flags, 1 = do not send anything (ie, not even 0 len frame) */ + int (*state)(void *userobj, void *h_src /* ss handle type */, + lws_ss_constate_t state, lws_ss_tx_ordinal_t ack); + /**< advisory cb about state of stream and QoS status if applicable... + * h_src is only used with sinks and LWSSSCS_SINK_JOIN/_PART events. + * Return nonzero to indicate you want to destroy the stream. */ + int manual_initial_tx_credit; + /**< 0 = manage any tx credit automatically, nonzero explicitly sets the + * peer stream to have the given amount of tx credit, if the protocol + * can support it. */ + char register_sink; + /**< If set, we're not creating a specific stream, but registering + * ourselves as the "sink" for .streamtype. It's analogous to saying + * we want to be the many-to-one "server" for .streamtype; when other + * streams are created with that streamtype, they should be forwarded + * to this stream owner, where they join and part from the sink via + * (*state) LWSSSCS_SINK_JOIN / _PART events, the new client handle + * being provided in the h_src parameter. + */ +} lws_ss_info_t; + +/** + * lws_ss_create() - Create secure stream + * + * \param context: the lws context to create this inside + * \param tsi: service thread index to create on (normally 0) + * \param ssi: pointer to lws_ss_info_t filled in with info about desired stream + * \param opaque_user_data: opaque data to set in the stream's user object + * \param ppss: pointer to secure stream handle pointer set on exit + * \param ppayload_fmt: NULL or pointer to a string ptr to take payload format + * name from the policy + * + * Requests a new secure stream described by \p ssi be created. If successful, + * the stream is created, its state callback called with LWSSSCS_CREATING, *ppss + * is set to point to the handle, and it returns 0. If it failed, it returns + * nonzero. + * + * Along with the opaque stream object, streams overallocate + * + * 1) a user data struct whose size is set in ssi + * 2) nauth plugin instantiation data (size set in the plugin struct) + * 3) sauth plugin instantiation data (size set in the plugin struct) + * 4) space for a copy of the stream type name + * + * The user data struct is initialized to all zeros, then the .handle_offset and + * .opaque_user_data_offset fields of the ssi are used to prepare the user data + * struct with the ss handle that was created, and a copy of the + * opaque_user_data pointer given as an argument. + * + * If you want to set up the stream with specific information, point to it in + * opaque_user_data and use the copy of that pointer in your user data member + * for it starting from the LWSSSCS_CREATING state call. + * + * Since different endpoints chosen by the policy may require different payload + * formats, \p ppayload_fmt is set to point to the name of the needed payload + * format from the policy database if non-NULL. + */ +LWS_VISIBLE LWS_EXTERN int +lws_ss_create(struct lws_context *context, int tsi, const lws_ss_info_t *ssi, + void *opaque_user_data, struct lws_ss_handle **ppss, + struct lws_sequencer *seq_owner, const char **ppayload_fmt); + +/** + * lws_ss_destroy() - Destroy secure stream + * + * \param ppss: pointer to lws_ss_t pointer to be destroyed + * + * Destroys the lws_ss_t pointed to by *ppss, and sets *ppss to NULL. + */ +LWS_VISIBLE LWS_EXTERN void +lws_ss_destroy(struct lws_ss_handle **ppss); + +/** + * lws_ss_request_tx() - Schedule stream for tx + * + * \param pss: pointer to lws_ss_t representing stream that wants to transmit + * + * Schedules a write on the stream represented by \p pss. When it's possible to + * write on this stream, the *tx callback will occur with an empty buffer for + * the stream owner to fill in. + */ +LWS_VISIBLE LWS_EXTERN void +lws_ss_request_tx(struct lws_ss_handle *pss); + +/** + * lws_ss_request_tx() - Schedule stream for tx + * + * \param pss: pointer to lws_ss_t representing stream that wants to transmit + * \param len: the length of the write in bytes + * + * Schedules a write on the stream represented by \p pss. When it's possible to + * write on this stream, the *tx callback will occur with an empty buffer for + * the stream owner to fill in. + * + * This api variant should be used when it's possible the payload will go out + * over h1 with x-web-form-urlencoded or similar Content-Type. + */ +LWS_VISIBLE LWS_EXTERN void +lws_ss_request_tx_len(struct lws_ss_handle *pss, unsigned long len); + + +/** + * lws_ss_client_connect() - Attempt the client connect + * + * \param h: secure streams handle + * + * Starts the connection process for the secure stream. Returns 0 if OK or + * nonzero if we have already failed. + */ +LWS_VISIBLE LWS_EXTERN int +lws_ss_client_connect(struct lws_ss_handle *h); + +/** + * lws_ss_get_sequencer() - Return parent sequencer pointer if any + * + * \param h: secure streams handle + * + * Returns NULL if the secure stream is not associated with a sequencer. + * Otherwise returns a pointer to the owning sequencer. You can use this to + * identify which sequencer to direct messages to, from the secure stream + * callback. + */ +LWS_VISIBLE LWS_EXTERN struct lws_sequencer * +lws_ss_get_sequencer(struct lws_ss_handle *h); + +/** + * lws_ss_proxy_create() - Start a unix domain socket proxy for Secure Streams + * + * \param context: lws_context + * \param bind: if port is 0, unix domain path with leading @ for abstract. + * if port nonzero, NULL, or network interface to bind listen to + * \param port: tcp port to listen on + * + * Creates a vhost that listens either on an abstract namespace unix domain + * socket (port = 0) or a tcp listen socket (port nonzero). If bind is NULL + * and port is 0, the abstract unix domain socket defaults to "proxy.ss.lws". + * + * Client connections to this proxy to Secure Streams are fulfilled using the + * policy local to the proxy and the data passed between the client and the + * proxy using serialized Secure Streams protocol. + */ +LWS_VISIBLE LWS_EXTERN int +lws_ss_proxy_create(struct lws_context *context, const char *bind, int port); + +/** + * lws_ss_state_name() - convenience helper to get a printable conn state name + * + * \param state: the connection state index + * + * Returns a printable name for the connection state index passed in. + */ +LWS_VISIBLE LWS_EXTERN const char * +lws_ss_state_name(int state); + +/** + * lws_ss_get_context() - convenience helper to recover the lws context + * + * \param h: secure streams handle + * + * Returns the lws context. Dispenses with the need to pass a copy of it into + * your secure streams handler. + */ +LWS_VISIBLE LWS_EXTERN struct lws_context * +lws_ss_get_context(struct lws_ss_handle *h); + +/** + * lws_ss_rideshare() - find the current streamtype when types rideshare + * + * \param h: the stream handle + * + * Under some conditions, the payloads may be structured using protocol- + * specific formatting, eg, http multipart mime. It's possible to map the + * logical partitions in the payload to different stream types using + * the policy "rideshare" feature. + * + * This api lets the callback code find out which rideshare stream type the + * current payload chunk belongs to. + */ +LWS_VISIBLE LWS_EXTERN const char * +lws_ss_rideshare(struct lws_ss_handle *h); + + +/** + * lws_ss_set_metadata() - allow user to bind external data to defined ss metadata + * + * \param h: secure streams handle + * \param name: metadata name from the policy + * \param value: pointer to user-managed data to bind to name + * \param len: length of the user-managed data in value + * + * Binds user-managed data to the named metadata item from the ss policy. + * If present, the metadata item is handled in a protocol-specific way using + * the associated policy information. For example, in the policy + * + * "\"metadata\":" "[" + * "{\"uptag\":" "\"X-Upload-Tag:\"}," + * "{\"ctype\":" "\"Content-Type:\"}," + * "{\"xctype\":" "\"\"}" + * "]," + * + * when the policy is using h1 is interpreted to add h1 headers of the given + * name with the value of the metadata on the left. + * + * Return 0 if OK or nonzero if, eg, metadata name does not exist on the + * streamtype. + */ +LWS_VISIBLE LWS_EXTERN int +lws_ss_set_metadata(struct lws_ss_handle *h, const char *name, + void *value, size_t len); + + +/** + * lws_ss_add_peer_tx_credit() - allow peer to transmit more to us + * + * \param h: secure streams handle + * \param add: additional tx credit (signed) + * + * Indicate to remote peer that we can accept \p add bytes more payload being + * sent to us. + */ +LWS_VISIBLE LWS_EXTERN int +lws_ss_add_peer_tx_credit(struct lws_ss_handle *h, int32_t add); + +/** + * lws_ss_get_est_peer_tx_credit() - get our current estimate of peer's tx credit + * + * \param h: secure streams handle + * + * Based on what credit we gave it, and what we have received, report our + * estimate of peer's tx credit usable to transmit to us. This may be outdated + * in that some or all of its credit may already have been expended by sending + * stuff to us that is in flight already. + */ +LWS_VISIBLE LWS_EXTERN int +lws_ss_get_est_peer_tx_credit(struct lws_ss_handle *h); diff --git a/include/libwebsockets/lws-sequencer.h b/include/libwebsockets/lws-sequencer.h index 4b50b824f..a08f61328 100644 --- a/include/libwebsockets/lws-sequencer.h +++ b/include/libwebsockets/lws-sequencer.h @@ -49,6 +49,14 @@ typedef enum { LWSSEQ_WSI_CONN_FAIL, /* wsi we bound to us has failed to connect */ LWSSEQ_WSI_CONN_CLOSE, /* wsi we bound to us has closed */ + + LWSSEQ_SS_STATE_BASE, /* secure streams owned by a sequencer provide + * automatic messages about state changes on + * the sequencer, passing the oridinal in the + * event argument field. The message index is + * LWSSEQ_SS_STATE_BASE + the enum from + * lws_ss_constate_t */ + LWSSEQ_USER_BASE = 100 /* define your events from here */ } lws_seq_events_t; diff --git a/lib/core-net/close.c b/lib/core-net/close.c index 631225491..062f410ca 100644 --- a/lib/core-net/close.c +++ b/lib/core-net/close.c @@ -234,8 +234,9 @@ __lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason, const char *caller) { struct lws_context_per_thread *pt; - struct lws *wsi1, *wsi2; + const struct lws_protocols *pro; struct lws_context *context; + struct lws *wsi1, *wsi2; int n, ccb; lwsl_info("%s: %p: caller: %s\n", __func__, wsi, caller); @@ -585,8 +586,15 @@ just_kill_connection: */ ccb = 1; + pro = wsi->protocol; + +#if defined(LWS_WITH_CLIENT) + if (!ccb && (lwsi_state_PRE_CLOSE(wsi) & LWSIFS_NOT_EST) && + lwsi_role_client(wsi)) { + lws_inform_client_conn_fail(wsi, "Closed before conn", 18); + } +#endif if (ccb) { - const struct lws_protocols *pro = wsi->protocol; if (!wsi->protocol && wsi->vhost && wsi->vhost->protocols) pro = &wsi->vhost->protocols[0]; diff --git a/lib/core-net/connect.c b/lib/core-net/connect.c index 450c85314..1b6d82f75 100644 --- a/lib/core-net/connect.c +++ b/lib/core-net/connect.c @@ -313,8 +313,14 @@ lws_client_connect_via_info(const struct lws_client_connect_info *i) i->uri_replace_to); #endif - if (i->method && (!strcmp(i->method, "RAW") || - !strcmp(i->method, "MQTT"))) { + if (i->method && (!strcmp(i->method, "RAW") // || +// !strcmp(i->method, "MQTT") + )) { + + /* + * Not for MQTT here, since we don't know if we will + * pipeline it or not... + */ #if defined(LWS_WITH_TLS) diff --git a/lib/core-net/network.c b/lib/core-net/network.c index 8c8954420..99e561995 100644 --- a/lib/core-net/network.c +++ b/lib/core-net/network.c @@ -213,7 +213,7 @@ lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port, memset(&sin, 0, sizeof(sin)); #if defined(LWS_WITH_UNIX_SOCK) - if (LWS_UNIX_SOCK_ENABLED(vhost)) { + if (!port && LWS_UNIX_SOCK_ENABLED(vhost)) { v = (struct sockaddr *)&serv_unix; n = sizeof(struct sockaddr_un); memset(&serv_unix, 0, sizeof(serv_unix)); @@ -313,7 +313,7 @@ lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port, } #if defined(LWS_WITH_UNIX_SOCK) - if (LWS_UNIX_SOCK_ENABLED(vhost)) { + if (!port && LWS_UNIX_SOCK_ENABLED(vhost)) { uid_t uid = vhost->context->uid; gid_t gid = vhost->context->gid; diff --git a/lib/core-net/output.c b/lib/core-net/output.c index fcb6a5a0e..83b8f9a4e 100644 --- a/lib/core-net/output.c +++ b/lib/core-net/output.c @@ -264,10 +264,10 @@ lws_write(struct lws *wsi, unsigned char *buf, size_t len, wsi->detlat.acc_size = m; wsi->detlat.type = LDLT_WRITE; if (wsi->detlat.earliest_write_req_pre_write) - wsi->detlat.latencies[LAT_DUR_PROXY_RX_TO_ONWARD_TX] = + wsi->detlat.latencies[LAT_DUR_PROXY_PROXY_REQ_TO_WRITE] = us - wsi->detlat.earliest_write_req_pre_write; else - wsi->detlat.latencies[LAT_DUR_PROXY_RX_TO_ONWARD_TX] = 0; + wsi->detlat.latencies[LAT_DUR_PROXY_PROXY_REQ_TO_WRITE] = 0; wsi->detlat.latencies[LAT_DUR_USERCB] = lws_now_usecs() - us; lws_det_lat_cb(wsi->context, &wsi->detlat); diff --git a/lib/core-net/private-lib-core-net.h b/lib/core-net/private-lib-core-net.h index 1507a2713..f6f0d979a 100644 --- a/lib/core-net/private-lib-core-net.h +++ b/lib/core-net/private-lib-core-net.h @@ -359,6 +359,15 @@ struct lws_context_per_thread { struct lws_dll2_owner seq_owner; /* list of lws_sequencer-s */ lws_dll2_owner_t attach_owner; /* pending lws_attach */ +#if defined(LWS_WITH_SECURE_STREAMS) + lws_dll2_owner_t ss_owner; +#endif +#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API) || \ + defined(LWS_WITH_SECURE_STREAMS_THREAD_API) + lws_dll2_owner_t ss_dsh_owner; + lws_dll2_owner_t ss_client_owner; +#endif + struct lws_dll2_owner pt_sul_owner; #if defined (LWS_WITH_SEQUENCER) @@ -588,9 +597,10 @@ struct lws_vhost { int log_fd; #endif - unsigned int allocated_vhost_protocols:1; - unsigned int created_vhost_protocols:1; - unsigned int being_destroyed:1; + uint8_t allocated_vhost_protocols:1; + uint8_t created_vhost_protocols:1; + uint8_t being_destroyed:1; + uint8_t from_ss_policy:1; unsigned char default_protocol_index; unsigned char raw_protocol_index; @@ -1150,6 +1160,9 @@ lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len); lws_usec_t __lws_seq_timeout_check(struct lws_context_per_thread *pt, lws_usec_t usnow); +lws_usec_t +__lws_ss_timeout_check(struct lws_context_per_thread *pt, lws_usec_t usnow); + struct lws * LWS_WARN_UNUSED_RESULT lws_client_connect_2_dnsreq(struct lws *wsi); diff --git a/lib/core-net/socks5-client.c b/lib/core-net/socks5-client.c index 7d04d4dbc..0e2c51485 100644 --- a/lib/core-net/socks5-client.c +++ b/lib/core-net/socks5-client.c @@ -86,7 +86,7 @@ lws_set_socks(struct lws_vhost *vhost, const char *socks) } } - lwsl_notice("%s: Connections via Socks5 %s:%u\n", __func__, + lwsl_debug("%s: Connections via Socks5 %s:%u\n", __func__, vhost->socks_proxy_address, vhost->socks_proxy_port); return 0; diff --git a/lib/core-net/vhost.c b/lib/core-net/vhost.c index eab28d093..dd9e52601 100644 --- a/lib/core-net/vhost.c +++ b/lib/core-net/vhost.c @@ -71,6 +71,24 @@ const struct lws_protocols *available_abstract_protocols[] = { }; #endif +#if defined(LWS_WITH_SECURE_STREAMS) +const struct lws_protocols *available_secstream_protocols[] = { +#if defined(LWS_ROLE_H1) + &protocol_secstream_h1, +#endif +#if defined(LWS_ROLE_H2) + &protocol_secstream_h2, +#endif +#if defined(LWS_ROLE_WS) + &protocol_secstream_ws, +#endif +#if defined(LWS_ROLE_MQTT) + &protocol_secstream_mqtt, +#endif + NULL +}; +#endif + static const char * const mount_protocols[] = { "http://", "https://", @@ -456,7 +474,7 @@ lws_create_vhost(struct lws_context *context, struct lws_plugin *plugin = context->plugin_list; #endif struct lws_protocols *lwsp; - int m, f = !info->pvo, fx = 0, abs_pcol_count = 0; + int m, f = !info->pvo, fx = 0, abs_pcol_count = 0, sec_pcol_count = 0; char buf[96]; #if ((defined(LWS_CLIENT_HTTP_PROXYING) && defined(LWS_WITH_CLIENT)) \ || defined(LWS_WITH_SOCKS5)) && defined(LWS_HAVE_GETENV) @@ -581,6 +599,9 @@ lws_create_vhost(struct lws_context *context, #if defined(LWS_WITH_ABSTRACT) abs_pcol_count = (int)LWS_ARRAY_SIZE(available_abstract_protocols) - 1; #endif +#if defined(LWS_WITH_SECURE_STREAMS) + sec_pcol_count = (int)LWS_ARRAY_SIZE(available_secstream_protocols) - 1; +#endif /* * give the vhost a unified list of protocols including: @@ -592,7 +613,7 @@ lws_create_vhost(struct lws_context *context, */ lwsp = lws_zalloc(sizeof(struct lws_protocols) * (vh->count_protocols + - abs_pcol_count + + abs_pcol_count + sec_pcol_count + context->plugin_protocol_count + fx + 1), "vhost-specific plugin table"); @@ -633,6 +654,14 @@ lws_create_vhost(struct lws_context *context, } #endif +#if defined(LWS_WITH_SECURE_STREAMS) + for (n = 0; n < sec_pcol_count; n++) { + memcpy(&lwsp[m++], available_secstream_protocols[n], + sizeof(*lwsp)); + vh->count_protocols++; + } +#endif + /* * 3: For compatibility, all protocols enabled on vhost if only * the default vhost exists. Otherwise only vhosts who ask @@ -1287,6 +1316,8 @@ lws_vhost_destroy(struct lws_vhost *vh) lws_vhost_destroy1(vh); + lwsl_debug("%s: count_bound_wsi %d\n", __func__, vh->count_bound_wsi); + if (!vh->count_bound_wsi) { /* * After listen handoff, there are already no wsi bound to this @@ -1406,6 +1437,7 @@ lws_vhost_active_conns(struct lws *wsi, struct lws **nwsi, const char *adsin) return ACTIVE_CONNS_QUEUED; } +#if defined(LWS_ROLE_H2) || defined(LWS_ROLE_MQTT) if (wsi->mux.parent_wsi) { /* * We already decided... @@ -1415,6 +1447,7 @@ lws_vhost_active_conns(struct lws *wsi, struct lws **nwsi, const char *adsin) return ACTIVE_CONNS_MUXED; } +#endif lws_vhost_lock(wsi->vhost); /* ----------------------------------- { */ @@ -1448,7 +1481,7 @@ lws_vhost_active_conns(struct lws *wsi, struct lws **nwsi, const char *adsin) * connection that it doesn't support pipelining... */ if (w->keepalive_rejected) { - lwsl_info("defeating pipelining due to no " + lwsl_notice("defeating pipelining due to no " "keepalive on server\n"); goto solo; } diff --git a/lib/core-net/wsi-timeout.c b/lib/core-net/wsi-timeout.c index cfbb7d2b1..843869f96 100644 --- a/lib/core-net/wsi-timeout.c +++ b/lib/core-net/wsi-timeout.c @@ -294,7 +294,7 @@ lws_validity_cb(lws_sorted_usec_list_t *sul) /* schedule a protocol-dependent ping */ - lwsl_info("%s: wsi %p: scheduling validity check\n", __func__, wsi); + lwsl_notice("%s: wsi %p: scheduling validity check\n", __func__, wsi); if (wsi->role_ops && wsi->role_ops->issue_keepalive) wsi->role_ops->issue_keepalive(wsi, 0); diff --git a/lib/core/context.c b/lib/core/context.c index 9ad693937..8b0759455 100644 --- a/lib/core/context.c +++ b/lib/core/context.c @@ -114,7 +114,7 @@ lws_state_notify_protocol_init(struct lws_state_manager *mgr, lws_system_do_attach(&context->pt[n]); #if defined(LWS_WITH_SYS_DHCP_CLIENT) - if (current == LWS_SYSTATE_DHCP) { + if (target == LWS_SYSTATE_DHCP) { /* * Don't let it past here until at least one iface has been * configured for operation with DHCP @@ -125,6 +125,40 @@ lws_state_notify_protocol_init(struct lws_state_manager *mgr, } #endif +#if defined(LWS_WITH_SECURE_STREAMS_SYS_AUTH_API_AMAZON_COM) + /* + * Skip this if we are running something without the policy for it + */ + if (target == LWS_SYSTATE_AUTH1 && + context->pss_policies && + !lws_system_blob_get_size(lws_system_get_blob(context, + LWS_SYSBLOB_TYPE_AUTH, + 0))) { + lwsl_info("%s: AUTH1 state triggering api.amazon.com auth\n", __func__); + /* + * Start trying to acquire it if it's not already in progress + * returns nonzero if we determine it's not needed + */ + if (!lws_ss_sys_auth_api_amazon_com(context)) + return 1; + } +#endif + +#if defined(LWS_WITH_SECURE_STREAMS) + /* + * Skip this if we are running something without the policy for it + */ + if (target == LWS_SYSTATE_POLICY_VALID && + context->pss_policies && !context->policy_updated) { + /* + * Start trying to acquire it if it's not already in progress + * returns nonzero if we determine it's not needed + */ + if (!lws_ss_sys_fetch_policy(context)) + return 1; + } +#endif + /* protocol part */ if (context->protocol_init_done) @@ -245,6 +279,15 @@ lws_create_context(const struct lws_context_creation_info *info) __func__, context->udp_loss_sim_tx_pc, context->udp_loss_sim_rx_pc); +#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API) + context->ss_proxy_bind = info->ss_proxy_bind; + context->ss_proxy_port = info->ss_proxy_port; + context->ss_proxy_address = info->ss_proxy_address; + lwsl_notice("%s: using ss proxy bind '%s', port %d, ads '%s'\n", + __func__, context->ss_proxy_bind, context->ss_proxy_port, + context->ss_proxy_address); +#endif + #if defined(LWS_WITH_NETWORK) context->count_threads = count_threads; #if defined(LWS_WITH_DETAILED_LATENCY) @@ -258,6 +301,11 @@ lws_create_context(const struct lws_context_creation_info *info) #endif #endif +#if defined(LWS_WITH_SECURE_STREAMS) + context->pss_policies_json = info->pss_policies_json; + context->pss_plugins = info->pss_plugins; +#endif + /* if he gave us names, set the uid / gid */ if (lws_plat_drop_app_privileges(context, 0)) goto bail; @@ -634,7 +682,6 @@ lws_create_context(const struct lws_context_creation_info *info) context->user_space = info->user; - #if defined(LWS_WITH_SERVER) strcpy(context->canonical_hostname, "unknown"); #if defined(LWS_WITH_NETWORK) @@ -755,6 +802,33 @@ lws_create_context(const struct lws_context_creation_info *info) goto fail_clean_pipes; } +#if defined(LWS_WITH_SECURE_STREAMS) + + if (context->pss_policies_json) { + /* + * You must create your context with the explicit vhosts flag + * in order to use secure streams + */ + assert(lws_check_opt(info->options, + LWS_SERVER_OPTION_EXPLICIT_VHOSTS)); + + if (lws_ss_policy_parse_begin(context)) + goto bail; + + n = lws_ss_policy_parse(context, + (uint8_t *)context->pss_policies_json, + strlen(context->pss_policies_json)); + if (n != LEJP_CONTINUE && n < 0) + goto bail; + + if (lws_ss_policy_set(context, "hardcoded")) { + lwsl_err("%s: policy set failed\n", __func__); + goto bail; + } + } else + lws_create_vhost(context, info); +#endif + lws_context_init_extensions(info, context); lwsl_info(" mem: per-conn: %5lu bytes + protocol rx buf\n", @@ -859,9 +933,10 @@ static void lws_context_destroy3(struct lws_context *context) { struct lws_context **pcontext_finalize = context->pcontext_finalize; -#if defined(LWS_WITH_NETWORK) int n; +#if defined(LWS_WITH_NETWORK) + lwsl_debug("%s\n", __func__); for (n = 0; n < context->count_threads; n++) { @@ -905,6 +980,10 @@ lws_context_destroy3(struct lws_context *context) compatible_close(context->latencies_fd); #endif + for (n = 0; n < LWS_SYSBLOB_TYPE_COUNT; n++) + lws_system_blob_destroy( + lws_system_get_blob(context, n, 0)); + lws_free(context); lwsl_info("%s: ctx %p freed\n", __func__, context); @@ -921,6 +1000,7 @@ lws_context_destroy2(struct lws_context *context) { #if defined(LWS_WITH_NETWORK) struct lws_vhost *vh = NULL, *vh1; + int n; #endif #if defined(LWS_WITH_PEER_LIMITS) uint32_t nu; @@ -932,6 +1012,48 @@ lws_context_destroy2(struct lws_context *context) context->being_destroyed2 = 1; #if defined(LWS_WITH_NETWORK) + + /* + * We're going to trash things like vhost-protocols + * So we need to finish dealing with wsi close that + * might make callbacks first + */ + for (n = 0; n < context->count_threads; n++) { + struct lws_context_per_thread *pt = &context->pt[n]; + + (void)pt; + +#if defined(LWS_WITH_SECURE_STREAMS) + lws_dll2_foreach_safe(&pt->ss_owner, NULL, lws_ss_destroy_dll); + if (context->ac_policy) + lwsac_free(&context->ac_policy); +#endif + +#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API) + lws_dll2_foreach_safe(&pt->ss_client_owner, NULL, lws_sspc_destroy_dll); +#endif + +#if defined(LWS_WITH_SEQUENCER) + lws_seq_destroy_all_on_pt(pt); +#endif + LWS_FOR_EVERY_AVAILABLE_ROLE_START(ar) { + if (ar->pt_init_destroy) + ar->pt_init_destroy(context, NULL, pt, 1); + } LWS_FOR_EVERY_AVAILABLE_ROLE_END; + +#if defined(LWS_WITH_CGI) + role_ops_cgi.pt_init_destroy(context, NULL, pt, 1); +#endif + + if (context->event_loop_ops->destroy_pt) + context->event_loop_ops->destroy_pt(context, n); + +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + while (pt->http.ah_list) + _lws_destroy_ah(pt, pt->http.ah_list); +#endif + } + /* * free all the per-vhost allocations */ @@ -982,6 +1104,7 @@ lws_context_destroy2(struct lws_context *context) lws_check_deferred_free(context, 0, 1); #endif + #if LWS_MAX_SMP > 1 lws_mutex_refcount_destroy(&context->mr); #endif @@ -1185,3 +1308,13 @@ out: context->inside_context_destroy = 0; #endif } + +struct lws_context * +lws_system_context_from_system_mgr(lws_state_manager_t *mgr) +{ +#if defined(LWS_WITH_NETWORK) + return lws_container_of(mgr, struct lws_context, mgr_system); +#else + return NULL; +#endif +} diff --git a/lib/core/private-lib-core.h b/lib/core/private-lib-core.h index 9a639b0ce..f1343836d 100644 --- a/lib/core/private-lib-core.h +++ b/lib/core/private-lib-core.h @@ -197,6 +197,9 @@ struct lws; #if defined(LWS_WITH_NETWORK) #include "private-lib-event-libs.h" +#if defined(LWS_WITH_SECURE_STREAMS) +#include "private-lib-secure-streams.h" +#endif struct lws_io_watcher { #ifdef LWS_WITH_LIBEV @@ -357,6 +360,14 @@ struct lws_context { lws_async_dns_t async_dns; #endif +#if defined(LWS_WITH_SECURE_STREAMS_SYS_AUTH_API_AMAZON_COM) + void *pol_args; + struct lws_ss_handle *hss_auth; + struct lws_ss_handle *hss_fetch_policy; + lws_sorted_usec_list_t sul_api_amazon_com; + lws_sorted_usec_list_t sul_api_amazon_com_kick; +#endif + lws_state_manager_t mgr_system; lws_state_notify_link_t protocols_notify; #if defined (LWS_WITH_SYS_DHCP_CLIENT) @@ -397,6 +408,11 @@ struct lws_context { #endif #endif /* NETWORK */ +#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API) + const char *ss_proxy_bind; + const char *ss_proxy_address; +#endif + #if defined(LWS_WITH_FILE_OPS) const struct lws_plat_file_ops *fops; #endif @@ -425,6 +441,14 @@ struct lws_context { #endif const lws_system_ops_t *system_ops; + +#if defined(LWS_WITH_SECURE_STREAMS) + const char *pss_policies_json; + const lws_ss_policy_t *pss_policies; + const lws_ss_plugin_t **pss_plugins; + struct lwsac *ac_policy; +#endif + void *external_baggage_free_on_destroy; const struct lws_token_limits *token_limits; void *user_space; @@ -487,6 +511,7 @@ struct lws_context { unsigned int done_protocol_destroy_cb:1; unsigned int finalize_destroy_after_internal_loops_stopped:1; unsigned int max_fds_unrelated_to_ulimit:1; + unsigned int policy_updated:1; short count_threads; short plugin_protocol_count; @@ -494,6 +519,9 @@ struct lws_context { short server_string_len; unsigned short ws_ping_pong_interval; unsigned short deprecation_pending_listen_close_count; +#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API) + uint16_t ss_proxy_port; +#endif uint8_t max_fi; uint8_t udp_loss_sim_tx_pc; diff --git a/lib/misc/lwsac/lwsac.c b/lib/misc/lwsac/lwsac.c index 3ca48a707..596b08215 100644 --- a/lib/misc/lwsac/lwsac.c +++ b/lib/misc/lwsac/lwsac.c @@ -146,6 +146,7 @@ _lwsac_use(struct lwsac **head, size_t ensure, size_t chunk_size, char backfill) if (al >= alloc - hp) alloc = al + hp; + lwsl_debug("%s: alloc %d for %d\n", __func__, (int)alloc, (int)ensure); bf = malloc(alloc); if (!bf) { lwsl_err("%s: OOM trying to alloc %llud\n", __func__, diff --git a/lib/roles/h2/http2.c b/lib/roles/h2/http2.c index 1c333852a..d08084ae6 100644 --- a/lib/roles/h2/http2.c +++ b/lib/roles/h2/http2.c @@ -2611,7 +2611,7 @@ lws_h2_client_stream_long_poll_rxonly(struct lws *wsi) wsi->h2.long_poll = 1; wsi->h2.send_END_STREAM = 1; - lws_header_table_detach(wsi, 0); + // lws_header_table_detach(wsi, 0); lws_callback_on_writable(wsi); diff --git a/lib/roles/http/client/client-handshake.c b/lib/roles/http/client/client-handshake.c index 6d7090239..e81a4fc8b 100644 --- a/lib/roles/http/client/client-handshake.c +++ b/lib/roles/http/client/client-handshake.c @@ -200,6 +200,31 @@ send_hs: else { /* for a method = "RAW" connection, this makes us * established */ + +#if defined(LWS_WITH_TLS) + if (wsi->tls.use_ssl & LCCSCF_USE_SSL) { + + /* we can retry this... just cook the SSL BIO the first time */ + + if (lws_ssl_client_bio_create(wsi) < 0) { + lwsl_err("%s: bio_create failed\n", __func__); + goto failed; + } + + //#if !defined(LWS_WITH_SYS_ASYNC_DNS) + if (wsi->tls.use_ssl & LCCSCF_USE_SSL) { + n = lws_ssl_client_connect1(wsi); + if (!n) + return wsi; + if (n < 0) { + lwsl_err("%s: lws_ssl_client_connect1 failed\n", __func__); + goto failed; + } + } + //#endif + } +#endif + #if 0 #if defined(LWS_WITH_SYS_ASYNC_DNS) if (wsi->tls.use_ssl & LCCSCF_USE_SSL) { diff --git a/lib/roles/http/parsers.c b/lib/roles/http/parsers.c index 5afe0acfc..7abedb6a9 100644 --- a/lib/roles/http/parsers.c +++ b/lib/roles/http/parsers.c @@ -286,10 +286,10 @@ reset: lws_pt_unlock(pt); +#if defined(LWS_WITH_CLIENT) #if defined(LWS_ROLE_MQTT) connect_via_info2: #endif -#if defined(LWS_WITH_CLIENT) if (lwsi_role_client(wsi) && lwsi_state(wsi) == LRS_UNCONNECTED) if (!lws_http_client_connect_via_info2(wsi)) /* our client connect has failed, the wsi @@ -349,6 +349,7 @@ int __lws_header_table_detach(struct lws *wsi, int autoservice) lws_peer_track_ah_detach(context, wsi->peer); #endif ah->wsi = NULL; /* no owner */ + wsi->http.ah = NULL; pwsi = &pt->http.ah_wait_list; diff --git a/lib/roles/http/server/server.c b/lib/roles/http/server/server.c index f9cae3263..eb45bb43a 100644 --- a/lib/roles/http/server/server.c +++ b/lib/roles/http/server/server.c @@ -1900,8 +1900,8 @@ lws_http_to_fallback(struct lws *wsi, unsigned char *obuf, size_t olen) lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); n = LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED; - if (wsi->role_ops->adoption_cb[lwsi_role_server(wsi)]) - n = wsi->role_ops->adoption_cb[lwsi_role_server(wsi)]; + if (wsi->role_ops->adoption_cb[0]) + n = wsi->role_ops->adoption_cb[0]; ipbuf[0] = '\0'; #if !defined(LWS_PLAT_OPTEE) diff --git a/lib/roles/mqtt/mqtt.c b/lib/roles/mqtt/mqtt.c index 808ca37c1..bb07feb53 100644 --- a/lib/roles/mqtt/mqtt.c +++ b/lib/roles/mqtt/mqtt.c @@ -1705,6 +1705,8 @@ lws_mqtt_client_send_publish(struct lws *wsi, lws_mqtt_publish_param_t *pub, do_write: + // lwsl_hexdump_err(start, lws_ptr_diff(p, start)); + if (lws_write(nwsi, start, lws_ptr_diff(p, start), LWS_WRITE_BINARY) != lws_ptr_diff(p, start)) { lwsl_err("%s: write failed\n", __func__); diff --git a/lib/secure-streams/README.md b/lib/secure-streams/README.md new file mode 100644 index 000000000..baf7c370c --- /dev/null +++ b/lib/secure-streams/README.md @@ -0,0 +1,398 @@ +# Secure Streams + +Secure Streams is a client api that strictly separates payload from any metadata. +That includes the endpoint address for the connection, the tls CA and even the +protocol used to connect to the endpoint. + +The user api just receives and transmits payload, and receives advisory connection +state information. + +The details about how the connections for different types of secure stream should +be made are held in JSON "policy database" initially passed in to the context +creation, but able to be updated from a remote copy. + +![overview](../doc-assets/ss-explain.png) + +# JSON Policy Database + +Example JSON policy... formatting is shown for clarity but whitespace can be +omitted in the actual policy. + +Ordering is not critical in itself, but forward references are not allowed, +things must be defined before they are allowed to be referenced later in the +JSON. + + +``` +{ + "release": "01234567", + "product": "myproduct", + "schema-version": 1, + "retry": [{ + "default": { + "backoff": [1000, 2000, 3000, 5000, 10000], + "conceal": 5, + "jitterpc": 20 + } + }], + "certs": [{ + "isrg_root_x1": "MIIFazCCA1OgAw...AnX5iItreGCc=" + }, { + "LEX3_isrg_root_x1": "MIIFjTCCA3WgAwIB...WEsikxqEt" + }], + "trust_stores": [{ + "le_via_isrg": ["isrg_root_x1", "LEX3_isrg_root_x1"] + }], + "s": [{ + "mintest": { + "endpoint": "warmcat.com", + "port": 4443, + "protocol": "h1get", + "aux": "index.html", + "plugins": [], + "tls": true, + "opportunistic": true, + "retry": "default", + "tls_trust_store": "le_via_isrg" + } + }] +} +``` + +### `Release` + +Identifies the policy version + +### `Product` + +Identifies the product the policy should apply to + +### `Schema-version` + +The minimum version of the policy parser required to parse this policy + +### `via-socks5` + +Optional redirect for Secure Streams client traffic through a socks5 +proxy given in the format `address:port`, eg, `127.0.0.1:12345`. + +### `retry` + +A list of backoff schemes referred to in the policy + +### `backoff` + +An array of ms delays for each retry in turn + +### `conceal` + +The number of retries to conceal from higher layers before giving errors. If +this is larger than the number of times in the backoff array, then the last time +is used for the extra delays + +### `jitterpc` + +Percentage of the delay times mentioned in the backoff array that may be +randomly added to the figure from the array. For example with an array entry of +1000ms, and jitterpc of 20%, actual delays will be chosen randomly from 1000ms +through 1200ms. This is to stop retry storms triggered by a single event like +an outage becoming synchronized into a DoS. + +### `certs` + +Certificates needed for validation should be listed here each with a name. The +format is base64 DER, which is the same as the part of PEM that is inside the +start and end lines. + +### `trust_stores` + +Chains of certificates given in the `certs` section may be named and described +inside the `trust_stores` section. Each entry in `trust_stores` is created as +a vhost + tls context with the given name. Stream types can later be associated +with one of these to enforce validity checking of the remote server. + +Entries should be named using "name" and the stack array defined using "stack" + +### `s` + +These are an array of policies for the supported stream type names. + +### `endpoint` + +The DNS address the secure stream should connect to + +### `port` + +The port number as an integer on the endpoint to connect to + +### `protocol` + +The wire protocol to connect to the endpoint with. Currently supported +streamtypes are + +|Wire protocol|Description| +|---|---| +|h1|http/1| +|h2|http/2| +|ws|http/1 Websockets| +|mqtt|mqtt 3.1.1| + +### `plugins` + +Array of plugin names to apply to the stream, if any + +### `tls` + +Set to `true` to enforce the stream travelling in a tls tunnel + +### `client cert` + +Set if the stream needs to authenticate itself using a tls client certificate. +Set to the certificate index counting from 0+. The certificates are managed +using lws_sytstem blobs. + +### `opportunistic` + +Set to `true` if the connection may be left dropped except when in use + +### `nailed_up` + +Set to `true` to have lws retry if the connection carrying this stream should +ever drop. + +### `retry` + +The name of the policy described in the `retry` section to apply to this +connection for retry + backoff + +### `tls_trust_store` + +The name of the trust store described in the `trust_stores` section to apply +to validate the remote server cert. + +## http transport + +### `http_method` + +HTTP method to use with http-related protocols, like GET or POST. +Not required for ws. + +### `http_url` + +Url path to use with http-related protocols + +The URL path can include metatadata like this + +"/mypath?whatever=${metadataname}" + +${metadataname} will be replaced by the current value of the +same metadata name. The metadata names must be listed in the +"metadata": [ ] section. + +### `http_auth_header` + +The name of the header that takes the auth token, with a trailing ':', eg + +``` + "http_auth_header": "authorization:" +``` + +### `http_dsn_header` + +The name of the header that takes the dsn token, with a trailing ':', eg + +``` + "http_dsn_header": "x-dsn:" +``` + +### `http_fwv_header` + +The name of the header that takes the firmware version token, with a trailing ':', eg + +``` + "http_fwv_header": "x-fw-version:" +``` + +### `http_devtype_header` + +The name of the header that takes the device type token, with a trailing ':', eg + +``` + "http_devtype_header": "x-device-type:" +``` + +### `http_auth_preamble` + +An optional string that precedes the auth token, eg + +``` + "http_auth_preamble": "bearer " +``` + +### `auth_hexify` + +Convert the auth token to hex ('A' -> "41") before transporting. Not necessary if the +auth token is already in printable string format suitable for transport. Needed if the +auth token is a chunk of 8-bit binary. + +### `nghttp2_quirk_end_stream` + +Set this to `true` if the peer server has the quirk it won't send a response until we have +sent an `END_STREAM`, even though we have sent headers with `END_HEADERS`. + +### `h2q_oflow_txcr` + +Set this to `true` if the peer server has the quirk it sends an maximum initial tx credit +of 0x7fffffff and then later increments it illegally. + +### `http_multipart_name` + +Indicates this stream goes out using multipart mime, and provides the name part of the +multipart header + +### `http_multipart_filename` + +Indicates this stream goes out using multipart mime, and provides the filename part of the +multipart header + +### `http_multipart_content_type` + +The `content-type` to mark up the multipart mime section with if present + +### `http_www_form_urlencoded` + +Indicate the data is sent in `x-www-form-urlencoded` form + +### `rideshare` + +For special cases where one logically separate stream travels with another when using this +protocol. Eg, a single multipart mime transaction carries content from two or more streams. + +## ws transport + +### `ws_subprotocol` + +Name of the ws subprotocol to use. + +### `ws_binary` + +Use if the ws messages are binary + +## MQTT transport + +### `mqtt_topic` + +Set the topic this streamtype uses for writes + +### `mqtt_subscribe` + +Set the topic this streamtype subscribes to + +### `mqtt qos` + +Set the QOS level for this streamtype + +## Loading and using updated remote policy + +If the default, hardcoded policy includes a streamtype `fetch_policy`, +during startup when lws_system reaches the POLICY state, lws will use +a Secure Stream of type `fetch_policy` to download, parse and update +the policy to use it. + +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. + +## Stream serialization and proxying + +By default Secure Streams expects to make the outgoing connection described in +the policy in the same process / thread, this suits the case where all the +participating clients are in the same statically-linked image. + +In this case the `lws_ss_` apis are fulfilled locally by secure-streams.c and +policy.c for policy lookups. + +However it also supports serialization, where the SS api can be streamed over +another transport such as a Unix Domain Socket connection. This suits the case +where the clients are actually in different processes in, eg, Linux or Android. + +In those cases, you run a proxy process (minimal-secure-streams-proxy) that +listens on a Unix Domain Socket and is connected to by one or more other +processes that pass their SS API activity to the proxy for fulfilment (or +onward proxying). + +In this case the proxy uses secure-streams.c and policy.c as before to fulfil +the inbound proxy streams, but uses secure-streams-serialize.c to serialize and +deserialize the proxied SS API activity. The proxy clients define +LWS_SS_USE_SSPC either very early in their sources before the includes, or on +the compiler commandline... this causes the lws_ss_ apis to be replaced at +preprocessor time with lws_sspc_ equivalents. These serialize the api action +and pass it to the proxy over a Unix Domain Socket for fulfilment, the results +and state changes etc are streamed over the Unix Domain Socket and presented to +the application exactly the same as if it was being fulfilled locally. + +To demonstrate this, some minimal examples, eg, minimal-secure-streams and +mimimal-secure-streams-avs build themselves both ways, once with direct SS API +fulfilment and once with Unix Domain Socket proxying and -client appended on the +executable name. To test the -client variants, run minimal-secure-streams-proxy +on the same machine. + +## Complicated scenarios with secure streams proxy + +As mentioned above, Secure Streams has two modes, by default the application +directly parses the policy and makes the outgoing connections itself. +However when configured at cmake with + +``` +-DLWS_WITH_SOCKS=1 -DLWS_WITH_SECURE_STREAMS=1 -DLWS_WITH_SECURE_STREAMS_PROXY_API=1 -DLWS_WITH_MINIMAL_EXAMPLES=1 +``` + +and define `LWS_SS_USE_SSPC` when building the application, applications forward +their network requests to a local or remote SS proxy for fulfilment... and only +the SS proxy has the system policy. By default, the SS proxy is on the local +machine and is connected to via a Unix Domain Socket, but tcp links are also +possible. (Note the proxied traffic is not encrypyed by default.) + +Using the configuration above, the example SS applications are built two ways, +once for direct connection fulfilment (eg, `./bin/lws-minimal-secure-streams`), +and once with `LWS_SS_USE_SSPC` also defined so it connects via an SS proxy, +(eg, `./bin/lws-minimal-secure-streams-client`). + +## Testing an example scenario with SS Proxy and socks5 proxy + +``` + [ SS application ] --- tcp --- [ socks 5 proxy ] --- tcp --- [ SS proxy ] --- internet +``` + +In this scenario, everything is on localhost, the socks5 proxy listens on :1337 and +the SS proxy listens on :1234. The SS application connects to the socks5 +proxy to get to the SS proxy, which then goes out to the internet + +### 1 Start the SS proxy + +Tell it to listen on lo interface on port 1234 + +``` +$ ./bin/lws-minimal-secure-streams-proxy -p 1234 -i lo +``` + +### 2 Start the SOCKS5 proxy + +``` +$ ssh -D 1337 -N -v localhost +``` + +The -v makes connections to the proxy visible in the terminal for testing + +### 3 Run the SS application + +The application is told to make all connections via the socks5 proxy at +127.0.0.1:1337, and to fulfil its SS connections via an SS proxy, binding +connections to 127.0.0.1 (ipv4 lo interface, -1), to 127.0.0.1:1234 (-a/-p). + +``` +socks_proxy=127.0.0.1:1337 ./bin/lws-minimal-secure-streams-client -p 1234 -i 127.0.0.1 -a 127.0.0.1 +``` + +You can confirm this goes through the ssh socks5 proxy to get to the SS proxy +and fulfil the connection. diff --git a/lib/secure-streams/plugins/ssp-h1url/h1url.c b/lib/secure-streams/plugins/ssp-h1url/h1url.c new file mode 100644 index 000000000..2e47c714c --- /dev/null +++ b/lib/secure-streams/plugins/ssp-h1url/h1url.c @@ -0,0 +1,40 @@ +/* + * ssp-h1url plugin + * + * Written in 2010-2020 by Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * CC0 so it can be used as a template for your own secure streams plugins + * licensed how you like. + */ + +#include + +static int +ssp_h1url_create(struct lws_ss_handle *ss, void *info, plugin_auth_status_cb status) +{ + return 0; +} + +static int +ssp_h1url_destroy(struct lws_ss_handle *ss) +{ + return 0; +} + +static int +ssp_h1url_munge(struct lws_ss_handle *ss, char *path, size_t path_len) +{ + return 0; +} + +/* this is the only exported symbol */ +const lws_ss_plugin_t ssp_h1url = { + .name = "h1url", + .alloc = 0, + .create = ssp_h1url_create, + .destroy = ssp_h1url_destroy, + .munge = ssp_h1url_munge +}; diff --git a/lib/secure-streams/policy.c b/lib/secure-streams/policy.c new file mode 100644 index 000000000..0ef8f1d54 --- /dev/null +++ b/lib/secure-streams/policy.c @@ -0,0 +1,942 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2019 - 2020 Andy Green + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include + +typedef struct backoffs { + struct backoffs *next; + const char *name; + lws_retry_bo_t r; +} backoff_t; + +static const char * const lejp_tokens_policy[] = { + "release", + "product", + "schema-version", + "via-socks5", + "retry[].*.backoff", + "retry[].*.conceal", + "retry[].*.jitterpc", + "retry[].*.svalidping", + "retry[].*.svalidhup", + "retry[].*", + "certs[].*", + "trust_stores[].name", + "trust_stores[].stack", + "s[].*.endpoint", + "s[].*.via-socks5", + "s[].*.protocol", + "s[].*.port", + "s[].*.plugins", + "s[].*.tls", + "s[].*.client_cert", + "s[].*.opportunistic", + "s[].*.nailed_up", + "s[].*.urgent_tx", + "s[].*.urgent_rx", + "s[].*.long_poll", + "s[].*.retry", + "s[].*.tls_trust_store", + "s[].*.metadata", + "s[].*.metadata[].*", + + "s[].*.http_auth_header", + "s[].*.http_dsn_header", + "s[].*.http_fwv_header", + "s[].*.http_devtype_header", + + "s[].*.http_auth_preamble", + + "s[].*.http_no_content_length", + "s[].*.rideshare", /* streamtype name this rides shotgun with */ + "s[].*.payload_fmt", + "s[].*.http_method", + "s[].*.http_url", + "s[].*.nghttp2_quirk_end_stream", + "s[].*.h2q_oflow_txcr", + "s[].*.http_multipart_name", + "s[].*.http_multipart_filename", + "s[].*.http_mime_content_type", + "s[].*.http_www_form_urlencoded", + "s[].*.ws_subprotocol", + "s[].*.ws_binary", + "s[].*.local_sink", + "s[].*.mqtt_topic", + "s[].*.mqtt_subscribe", + "s[].*.mqtt_qos", + "s[].*", +}; + +typedef enum { + LSSPPT_RELEASE, + LSSPPT_PRODUCT, + LSSPPT_SCHEMA_VERSION, + LSSPPT_VIA_SOCKS5, + LSSPPT_BACKOFF, + LSSPPT_CONCEAL, + LSSPPT_JITTERPC, + LSSPPT_VALIDPING_S, + LSSPPT_VALIDHUP_S, + LSSPPT_RETRY, + LSSPPT_CERTS, + LSSPPT_TRUST_STORES_NAME, + LSSPPT_TRUST_STORES_STACK, + LSSPPT_ENDPOINT, + LSSPPT_VH_VIA_SOCKS5, + LSSPPT_PROTOCOL, + LSSPPT_PORT, + LSSPPT_PLUGINS, + LSSPPT_TLS, + LSSPPT_TLS_CLIENT_CERT, + LSSPPT_OPPORTUNISTIC, + LSSPPT_NAILED_UP, + LSSPPT_URGENT_TX, + LSSPPT_URGENT_RX, + LSSPPT_LONG_POLL, + LSSPPT_RETRYPTR, + LSSPPT_TRUST, + LSSPPT_METADATA, + LSSPPT_METADATA_ITEM, + + LSSPPT_HTTP_AUTH_HEADER, + LSSPPT_HTTP_DSN_HEADER, + LSSPPT_HTTP_FWV_HEADER, + LSSPPT_HTTP_TYPE_HEADER, + + LSSPPT_HTTP_AUTH_PREAMBLE, + LSSPPT_HTTP_NO_CONTENT_LENGTH, + LSSPPT_RIDESHARE, + LSSPPT_PAYLOAD_FORMAT, + LSSPPT_HTTP_METHOD, + LSSPPT_HTTP_URL, + LSSPPT_NGHTTP2_QUIRK_END_STREAM, + LSSPPT_H2_QUIRK_OVERFLOWS_TXCR, + LSSPPT_HTTP_MULTIPART_NAME, + LSSPPT_HTTP_MULTIPART_FILENAME, + LSSPPT_HTTP_MULTIPART_CONTENT_TYPE, + LSSPPT_HTTP_WWW_FORM_URLENCODED, + LSSPPT_WS_SUBPROTOCOL, + LSSPPT_WS_BINARY, + LSSPPT_LOCAL_SINK, + LSSPPT_MQTT_TOPIC, + LSSPPT_MQTT_SUBSCRIBE, + LSSPPT_MQTT_QOS, + LSSPPT_STREAMTYPES +} policy_token_t; + +union u { + backoff_t *b; + lws_ss_x509_t *x; + lws_ss_trust_store_t *t; + lws_ss_policy_t *p; +}; + +enum { + LTY_BACKOFF, + LTY_X509, + LTY_TRUSTSTORE, + LTY_POLICY, + + _LTY_COUNT /* always last */ +}; + +struct policy_cb_args { + struct lejp_ctx jctx; + struct lws_context *context; + struct lwsac *ac; + + const char *socks5_proxy; + + struct lws_b64state b64; + + union u heads[_LTY_COUNT]; + union u curr[_LTY_COUNT]; + + uint8_t *p; + + int count; +}; + +#define POL_AC_INITIAL 2048 +#define POL_AC_GRAIN 800 +#define MAX_CERT_TEMP 2048 /* used to discover actual cert size for realloc */ + +static uint8_t sizes[] = { + sizeof(backoff_t), + sizeof(lws_ss_x509_t), + sizeof(lws_ss_trust_store_t), + sizeof(lws_ss_policy_t), +}; + +static const char *protonames[] = { + "h1", /* LWSSSP_H1 */ + "h2", /* LWSSSP_H2 */ + "ws", /* LWSSSP_WS */ + "mqtt", /* LWSSSP_MQTT */ +}; + +lws_ss_metadata_t * +lws_ss_policy_metadata(const lws_ss_policy_t *p, const char *name) +{ + lws_ss_metadata_t *pmd = p->metadata; + + while (pmd) { + if (pmd->name && !strcmp(name, pmd->name)) + return pmd; + pmd = pmd->next; + } + + return NULL; +} + +lws_ss_metadata_t * +lws_ss_policy_metadata_index(const lws_ss_policy_t *p, size_t index) +{ + lws_ss_metadata_t *pmd = p->metadata; + + while (pmd) { + if (pmd->length == index) + return pmd; + pmd = pmd->next; + } + + return NULL; +} + +int +lws_ss_set_metadata(struct lws_ss_handle *h, const char *name, + void *value, size_t len) +{ + lws_ss_metadata_t *omd = lws_ss_policy_metadata(h->policy, name); + + if (!omd) { + lwsl_err("%s: unknown metadata %s\n", __func__, name); + return 1; + } + + h->metadata[omd->length].name = name; + h->metadata[omd->length].value = value; + h->metadata[omd->length].length = len; + + return 0; +} + +lws_ss_metadata_t * +lws_ss_get_handle_metadata(struct lws_ss_handle *h, const char *name) +{ + lws_ss_metadata_t *omd = lws_ss_policy_metadata(h->policy, name); + + if (!omd) + return NULL; + + return &h->metadata[omd->length]; +} + +static signed char +lws_ss_policy_parser_cb(struct lejp_ctx *ctx, char reason) +{ + struct policy_cb_args *a = (struct policy_cb_args *)ctx->user; + const lws_ss_plugin_t **pin; + char **pp, dotstar[32], *q; + lws_ss_trust_store_t *ts; + lws_ss_metadata_t *pmd; + lws_retry_bo_t *b; + size_t inl, outl; + lws_ss_x509_t *x; + uint8_t *extant; + backoff_t *bot; + int n = -1; + + lwsl_debug("%s: %d %d %s\n", __func__, reason, ctx->path_match - 1, + ctx->path); + + switch (ctx->path_match - 1) { + case LSSPPT_RETRY: + n = LTY_BACKOFF; + break; + case LSSPPT_CERTS: + n = LTY_X509; + break; + case LSSPPT_TRUST_STORES_NAME: + case LSSPPT_TRUST_STORES_STACK: + n = LTY_TRUSTSTORE; + break; + case LSSPPT_STREAMTYPES: + n = LTY_POLICY; + break; + } + + if (reason == LEJPCB_ARRAY_START && + (ctx->path_match - 1 == LSSPPT_PLUGINS || + ctx->path_match - 1 == LSSPPT_METADATA)) + a->count = 0; + + if (reason == LEJPCB_ARRAY_END && + ctx->path_match - 1 == LSSPPT_TRUST_STORES_STACK && !a->count) { + lwsl_err("%s: at least one cert required in trust store\n", + __func__); + goto oom; + } + + if (reason == LEJPCB_OBJECT_END && a->p) { + /* + * Allocate a just-the-right-size buf for the cert DER now + * we decoded it into the a->p temp buffer and know the exact + * size + */ + a->curr[LTY_X509].x->ca_der = lws_malloc(a->count, "ssx509"); + if (!a->curr[LTY_X509].x->ca_der) + goto oom; + memcpy((uint8_t *)a->curr[LTY_X509].x->ca_der, a->p, a->count); + a->curr[LTY_X509].x->ca_der_len = a->count; + + /* + * ... and then we can free the temp buffer + */ + lws_free_set_NULL(a->p); + + return 0; + } + + if (reason == LEJPCB_PAIR_NAME && n != -1 && n != LTY_TRUSTSTORE) { + /* + * We do the pointers always as .b, all of the participating + * structs begin with .next and .name + */ + a->curr[n].b = lwsac_use_zero(&a->ac, sizes[n], POL_AC_GRAIN); + if (!a->curr[n].b) + goto oom; + + if (n == LTY_X509) { + a->p = lws_malloc(MAX_CERT_TEMP, "cert temp"); + if (!a->p) + goto oom; + memset(&a->b64, 0, sizeof(a->b64)); + } + + 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; + + goto string1; + } + + if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match) + return 0; + + switch (ctx->path_match - 1) { + + /* strings */ + + case LSSPPT_RELEASE: + break; + + case LSSPPT_PRODUCT: + break; + + case LSSPPT_SCHEMA_VERSION: + break; + + case LSSPPT_VIA_SOCKS5: + /* the global / default proxy */ + pp = (char **)&a->socks5_proxy; + goto string2; + + case LSSPPT_BACKOFF: + b = &a->curr[LTY_BACKOFF].b->r; + if (b->retry_ms_table_count == 8) { + lwsl_err("%s: > 8 backoff levels\n", __func__); + return 1; + } + if (!b->retry_ms_table_count) { + b->retry_ms_table = (uint32_t *)lwsac_use_zero(&a->ac, + sizeof(uint32_t) * 8, POL_AC_GRAIN); + if (!b->retry_ms_table) + goto oom; + } + + ((uint32_t *)b->retry_ms_table) + [b->retry_ms_table_count++] = atoi(ctx->buf); + break; + + case LSSPPT_CONCEAL: + a->curr[LTY_BACKOFF].b->r.conceal_count = atoi(ctx->buf); + break; + + case LSSPPT_JITTERPC: + a->curr[LTY_BACKOFF].b->r.jitter_percent = atoi(ctx->buf); + break; + + case LSSPPT_VALIDPING_S: + a->curr[LTY_BACKOFF].b->r.secs_since_valid_ping = atoi(ctx->buf); + break; + + case LSSPPT_VALIDHUP_S: + a->curr[LTY_BACKOFF].b->r.secs_since_valid_hangup = atoi(ctx->buf); + break; + + case LSSPPT_CERTS: + if (a->count + ctx->npos >= MAX_CERT_TEMP) { + lwsl_err("%s: cert too big\n", __func__); + goto oom; + } + inl = ctx->npos; + outl = MAX_CERT_TEMP - a->count; + + lws_b64_decode_stateful(&a->b64, ctx->buf, &inl, + a->p + a->count, &outl, + reason == LEJPCB_VAL_STR_END); + a->count += outl; + if (inl != ctx->npos) { + lwsl_err("%s: b64 decode fail\n", __func__); + goto oom; + } + break; + + case LSSPPT_TRUST_STORES_NAME: + /* + * We do the pointers always as .b, all of the participating + * structs begin with .next and .name + */ + a->curr[LTY_TRUSTSTORE].b = lwsac_use_zero(&a->ac, + sizes[LTY_TRUSTSTORE], POL_AC_GRAIN); + if (!a->curr[LTY_TRUSTSTORE].b) + goto oom; + + a->count = 0; + a->curr[LTY_TRUSTSTORE].b->next = a->heads[LTY_TRUSTSTORE].b; + a->heads[LTY_TRUSTSTORE].b = a->curr[LTY_TRUSTSTORE].b; + pp = (char **)&a->curr[LTY_TRUSTSTORE].b->name; + + goto string2; + + case LSSPPT_TRUST_STORES_STACK: + if (a->count >= (int)LWS_ARRAY_SIZE( + a->curr[LTY_TRUSTSTORE].t->ssx509)) { + lwsl_err("%s: trust store too big\n", __func__); + goto oom; + } + lwsl_debug("%s: trust stores stack %.*s\n", __func__, + ctx->npos, ctx->buf); + x = a->heads[LTY_X509].x; + while (x) { + if (!strncmp(x->vhost_name, ctx->buf, ctx->npos)) { + a->curr[LTY_TRUSTSTORE].t->ssx509[a->count++] = x; + a->curr[LTY_TRUSTSTORE].t->count++; + + return 0; + } + x = x->next; + } + lws_strnncpy(dotstar, ctx->buf, ctx->npos, sizeof(dotstar)); + lwsl_err("%s: unknown trust store entry %s\n", __func__, + dotstar); + goto oom; + + case LSSPPT_ENDPOINT: + pp = (char **)&a->curr[LTY_POLICY].p->endpoint; + goto string2; + + case LSSPPT_VH_VIA_SOCKS5: + pp = (char **)&a->curr[LTY_POLICY].p->socks5_proxy; + goto string2; + + case LSSPPT_PORT: + a->curr[LTY_POLICY].p->port = atoi(ctx->buf); + break; + + case LSSPPT_HTTP_METHOD: + pp = (char **)&a->curr[LTY_POLICY].p->u.http.method; + goto string2; + + case LSSPPT_HTTP_URL: + pp = (char **)&a->curr[LTY_POLICY].p->u.http.url; + goto string2; + + case LSSPPT_RIDESHARE: + pp = (char **)&a->curr[LTY_POLICY].p->rideshare_streamtype; + goto string2; + + case LSSPPT_PAYLOAD_FORMAT: + pp = (char **)&a->curr[LTY_POLICY].p->payload_fmt; + goto string2; + + case LSSPPT_PLUGINS: + pin = a->context->pss_plugins; + if (a->count == + (int)LWS_ARRAY_SIZE(a->curr[LTY_POLICY].p->plugins)) { + lwsl_err("%s: too many plugins\n", __func__); + + goto oom; + } + if (!pin) + break; + while (*pin) { + if (!strncmp((*pin)->name, ctx->buf, ctx->npos)) { + a->curr[LTY_POLICY].p->plugins[a->count++] = *pin; + return 0; + } + pin++; + } + lwsl_err("%s: unknown plugin\n", __func__); + goto oom; + + case LSSPPT_TLS: + if (reason == LEJPCB_VAL_TRUE) + a->curr[LTY_POLICY].p->flags |= LWSSSPOLF_TLS; + break; + + case LSSPPT_TLS_CLIENT_CERT: + a->curr[LTY_POLICY].p->client_cert = atoi(ctx->buf) + 1; + break; + + case LSSPPT_OPPORTUNISTIC: + if (reason == LEJPCB_VAL_TRUE) + a->curr[LTY_POLICY].p->flags |= LWSSSPOLF_OPPORTUNISTIC; + break; + case LSSPPT_NAILED_UP: + if (reason == LEJPCB_VAL_TRUE) + a->curr[LTY_POLICY].p->flags |= LWSSSPOLF_NAILED_UP; + break; + case LSSPPT_URGENT_TX: + if (reason == LEJPCB_VAL_TRUE) + a->curr[LTY_POLICY].p->flags |= LWSSSPOLF_URGENT_TX; + break; + case LSSPPT_URGENT_RX: + if (reason == LEJPCB_VAL_TRUE) + a->curr[LTY_POLICY].p->flags |= LWSSSPOLF_URGENT_RX; + break; + case LSSPPT_LONG_POLL: + if (reason == LEJPCB_VAL_TRUE) + a->curr[LTY_POLICY].p->flags |= LWSSSPOLF_LONG_POLL; + break; + case LSSPPT_HTTP_WWW_FORM_URLENCODED: + if (reason == LEJPCB_VAL_TRUE) + a->curr[LTY_POLICY].p->flags |= + LWSSSPOLF_HTTP_X_WWW_FORM_URLENCODED; + break; + + case LSSPPT_RETRYPTR: + bot = a->heads[LTY_BACKOFF].b; + while (bot) { + if (!strncmp(ctx->buf, bot->name, ctx->npos)) { + a->curr[LTY_POLICY].p->retry_bo = &bot->r; + + return 0; + } + bot = bot->next; + } + lwsl_err("%s: unknown backoff scheme\n", __func__); + + return -1; + + case LSSPPT_TRUST: + ts = a->heads[LTY_TRUSTSTORE].t; + while (ts) { + if (!strncmp(ctx->buf, ts->name, ctx->npos)) { + a->curr[LTY_POLICY].p->trust_store = ts; + return 0; + } + ts = ts->next; + } + lws_strnncpy(dotstar, ctx->buf, ctx->npos, sizeof(dotstar)); + lwsl_err("%s: unknown trust store name %s\n", __func__, + dotstar); + + return -1; + + case LSSPPT_METADATA: + break; + + case LSSPPT_METADATA_ITEM: + pmd = a->curr[LTY_POLICY].p->metadata; + a->curr[LTY_POLICY].p->metadata = lwsac_use_zero(&a->ac, + sizeof(lws_ss_metadata_t) + ctx->npos + + (ctx->path_match_len - ctx->st[ctx->sp - 2].p + 1) + 2, + POL_AC_GRAIN); + a->curr[LTY_POLICY].p->metadata->next = pmd; + + q = (char *)a->curr[LTY_POLICY].p->metadata + + sizeof(lws_ss_metadata_t); + a->curr[LTY_POLICY].p->metadata->name = q; + memcpy(q, ctx->path + ctx->st[ctx->sp - 2].p + 1, + ctx->path_match_len - ctx->st[ctx->sp - 2].p); + + q += ctx->path_match_len - ctx->st[ctx->sp - 2].p; + a->curr[LTY_POLICY].p->metadata->value = q; + memcpy(q, ctx->buf, ctx->npos); + + a->curr[LTY_POLICY].p->metadata->length = /* the index in handle->metadata */ + a->curr[LTY_POLICY].p->metadata_count++; + break; + + case LSSPPT_HTTP_AUTH_HEADER: + case LSSPPT_HTTP_DSN_HEADER: + case LSSPPT_HTTP_FWV_HEADER: + case LSSPPT_HTTP_TYPE_HEADER: + pp = (char **)&a->curr[LTY_POLICY].p->u.http.blob_header[ + (ctx->path_match - 1) - LSSPPT_HTTP_AUTH_HEADER]; + goto string2; + + case LSSPPT_HTTP_AUTH_PREAMBLE: + pp = (char **)&a->curr[LTY_POLICY].p->u.http.auth_preamble; + goto string2; + + case LSSPPT_HTTP_NO_CONTENT_LENGTH: + if (reason == LEJPCB_VAL_TRUE) + a->curr[LTY_POLICY].p->flags |= + LWSSSPOLF_HTTP_NO_CONTENT_LENGTH; + break; + + case LSSPPT_NGHTTP2_QUIRK_END_STREAM: + if (reason == LEJPCB_VAL_TRUE) + a->curr[LTY_POLICY].p->flags |= + LWSSSPOLF_QUIRK_NGHTTP2_END_STREAM; + break; + case LSSPPT_H2_QUIRK_OVERFLOWS_TXCR: + if (reason == LEJPCB_VAL_TRUE) + a->curr[LTY_POLICY].p->flags |= + LWSSSPOLF_H2_QUIRK_OVERFLOWS_TXCR; + break; + case LSSPPT_HTTP_MULTIPART_NAME: + a->curr[LTY_POLICY].p->flags |= LWSSSPOLF_HTTP_MULTIPART; + pp = (char **)&a->curr[LTY_POLICY].p->u.http.multipart_name; + goto string2; + case LSSPPT_HTTP_MULTIPART_FILENAME: + a->curr[LTY_POLICY].p->flags |= LWSSSPOLF_HTTP_MULTIPART; + pp = (char **)&a->curr[LTY_POLICY].p->u.http.multipart_filename; + goto string2; + case LSSPPT_HTTP_MULTIPART_CONTENT_TYPE: + a->curr[LTY_POLICY].p->flags |= LWSSSPOLF_HTTP_MULTIPART; + pp = (char **)&a->curr[LTY_POLICY].p->u.http.multipart_content_type; + goto string2; + case LSSPPT_WS_SUBPROTOCOL: + pp = (char **)&a->curr[LTY_POLICY].p->u.http.u.ws.subprotocol; + goto string2; + + case LSSPPT_WS_BINARY: + a->curr[LTY_POLICY].p->u.http.u.ws.binary = + reason == LEJPCB_VAL_TRUE; + break; + case LSSPPT_LOCAL_SINK: + if (reason == LEJPCB_VAL_TRUE) + a->curr[LTY_POLICY].p->flags |= LWSSSPOLF_LOCAL_SINK; + break; + + case LSSPPT_MQTT_TOPIC: + pp = (char **)&a->curr[LTY_POLICY].p->u.mqtt.topic; + goto string2; + + case LSSPPT_MQTT_SUBSCRIBE: + pp = (char **)&a->curr[LTY_POLICY].p->u.mqtt.subscribe; + goto string2; + + case LSSPPT_MQTT_QOS: + a->curr[LTY_POLICY].p->u.mqtt.qos = atoi(ctx->buf); + break; + + case LSSPPT_PROTOCOL: + a->curr[LTY_POLICY].p->protocol = 0xff; + for (n = 0; n < (int)LWS_ARRAY_SIZE(protonames); n++) + if (strlen(protonames[n]) == ctx->npos && + !strncmp(ctx->buf, protonames[n], ctx->npos)) + a->curr[LTY_POLICY].p->protocol = (uint8_t)n; + + if (a->curr[LTY_POLICY].p->protocol != 0xff) + break; + lws_strnncpy(dotstar, ctx->buf, ctx->npos, sizeof(dotstar)); + lwsl_err("%s: unknown protocol name %s\n", __func__, dotstar); + return -1; + } + + return 0; + +string2: + /* + * If we can do const string folding, reuse the existing string rather + * than make a new entry + */ + extant = lwsac_scan_extant(a->ac, (uint8_t *)ctx->buf, ctx->npos, 1); + if (extant) { + *pp = (char *)extant; + + return 0; + } + *pp = lwsac_use_backfill(&a->ac, ctx->npos + 1, POL_AC_GRAIN); + if (!*pp) + goto oom; + memcpy(*pp, ctx->buf, ctx->npos); + (*pp)[ctx->npos] = '\0'; + + return 0; + +string1: + n = ctx->st[ctx->sp].p; + *pp = lwsac_use_backfill(&a->ac, ctx->path_match_len + 1 - n, + POL_AC_GRAIN); + if (!*pp) + goto oom; + memcpy(*pp, ctx->path + n, ctx->path_match_len - n); + (*pp)[ctx->path_match_len - n] = '\0'; + + return 0; + +oom: + lwsl_err("%s: OOM\n", __func__); + lws_free_set_NULL(a->p); + lwsac_free(&a->ac); + + return -1; +} + +int +lws_ss_policy_parse_begin(struct lws_context *context) +{ + struct policy_cb_args *args; + char *p; + + args = lws_zalloc(sizeof(struct policy_cb_args), __func__); + if (!args) { + lwsl_err("%s: OOM\n", __func__); + + return 1; + } + + context->pol_args = args; + args->context = context; + p = lwsac_use(&args->ac, 1, POL_AC_INITIAL); + if (!p) { + lwsl_err("%s: OOM\n", __func__); + lws_free_set_NULL(context->pol_args); + + return -1; + } + *p = 0; + lejp_construct(&args->jctx, lws_ss_policy_parser_cb, args, + lejp_tokens_policy, LWS_ARRAY_SIZE(lejp_tokens_policy)); + + return 0; +} + +int +lws_ss_policy_parse_abandon(struct lws_context *context) +{ + struct policy_cb_args *args = (struct policy_cb_args *)context->pol_args; + + lejp_destruct(&args->jctx); + lws_free_set_NULL(context->pol_args); + + return 0; +} + +int +lws_ss_policy_parse(struct lws_context *context, const uint8_t *buf, size_t len) +{ + struct policy_cb_args *args = (struct policy_cb_args *)context->pol_args; + int m; + + m = (int)(signed char)lejp_parse(&args->jctx, buf, len); + if (m == LEJP_CONTINUE || m >= 0) + return m; + + lwsl_err("%s: parse failed: %d: %s\n", __func__, m, + lejp_error_to_string(m)); + lws_ss_policy_parse_abandon(context); + + return m; +} + +int +lws_ss_policy_set(struct lws_context *context, const char *name) +{ + struct policy_cb_args *args = (struct policy_cb_args *)context->pol_args; + lws_ss_trust_store_t *ts; + struct lws_vhost *v; + lws_ss_x509_t *x; + char buf[16]; + int m, ret = 0; + + /* + * Parsing seems to have succeeded, and we're going to use the new + * policy that's laid out in args->ac + */ + + lejp_destruct(&args->jctx); + + if (context->ac_policy) { + + /* + * So this is a bit fun-filled, we already had a policy in + * force, perhaps it was the default policy that's just good for + * fetching the real policy, and we're doing that now. + * + * We can destroy all the policy-related direct allocations + * easily because they're cleanly in a single lwsac... + */ + lwsac_free(&context->ac_policy); + + /* + * ...but when we did the trust stores, we created vhosts for + * each. We need to destroy those now too, and recreate new + * ones from the new policy, perhaps with different X.509s. + */ + + v = context->vhost_list; + while (v) { + if (v->from_ss_policy) { + struct lws_vhost *vh = v->vhost_next; + lwsl_debug("%s: destroying vh %p\n", __func__, v); + lws_vhost_destroy(v); + v = vh; + continue; + } + v = v->vhost_next; + } + + lws_check_deferred_free(context, 0, 1); + } + + context->pss_policies = args->heads[LTY_POLICY].p; + context->ac_policy = args->ac; + + lws_humanize(buf, sizeof(buf), lwsac_total_alloc(args->ac), + humanize_schema_si_bytes); + if (lwsac_total_alloc(args->ac)) + m = (int)((lwsac_total_overhead(args->ac) * 100) / + lwsac_total_alloc(args->ac)); + else + m = 0; + + lwsl_notice("%s: %s, pad %d%c: %s\n", __func__, buf, m, '%', name); + + /* Create vhosts for each type of trust store */ + + ts = args->heads[LTY_TRUSTSTORE].t; + while (ts) { + struct lws_context_creation_info i; + + memset(&i, 0, sizeof(i)); + + /* + * We get called from context creation... instantiates + * vhosts with client tls contexts set up for each unique CA. + * + * Create the vhost with the first (mandatory) entry in the + * trust store... + */ + + v = lws_get_vhost_by_name(context, ts->name); + if (!v) { + int n; + + i.options = context->options; + i.vhost_name = ts->name; + lwsl_debug("%s: %s\n", __func__, i.vhost_name); + i.client_ssl_ca_mem = ts->ssx509[0]->ca_der; + i.client_ssl_ca_mem_len = ts->ssx509[0]->ca_der_len; + i.port = CONTEXT_PORT_NO_LISTEN; + lwsl_info("%s: %s trust store initial '%s'\n", __func__, + ts->name, ts->ssx509[0]->vhost_name); + + v = lws_create_vhost(context, &i); + if (!v) { + lwsl_err("%s: failed to create vhost %s\n", + __func__, ts->name); + ret = 1; + } else + v->from_ss_policy = 1; + + for (n = 1; v && n < ts->count; n++) { + lwsl_info("%s: add '%s' to trust store\n", + __func__, ts->ssx509[n]->vhost_name); + if (lws_tls_client_vhost_extra_cert_mem(v, + ts->ssx509[n]->ca_der, + ts->ssx509[n]->ca_der_len)) { + lwsl_err("%s: add extra cert failed\n", + __func__); + ret = 1; + } + } + } + + ts = ts->next; + } + +#if defined(LWS_WITH_SOCKS5) + + /* + * ... we need to go through every vhost updating its understanding of + * which socks5 proxy to use... + */ + + v = context->vhost_list; + while (v) { + lws_set_socks(v, args->socks5_proxy); + v = v->vhost_next; + } + if (context->vhost_system) + lws_set_socks(context->vhost_system, args->socks5_proxy); + + if (args->socks5_proxy) + lwsl_notice("%s: global socks5 proxy: %s\n", __func__, + args->socks5_proxy); +#endif + + /* now we processed the x.509 CAs, we can free all of our originals */ + + x = args->heads[LTY_X509].x; + while (x) { + /* + * Free all the DER buffers now they have been parsed into + * tls library X.509 objects + */ + lws_free((void *)x->ca_der); + x->ca_der = NULL; + x = x->next; + } + + /* and we can discard the parsing args object now, invalidating args */ + + lws_free_set_NULL(context->pol_args); + + return ret; +} + +const lws_ss_policy_t * +lws_ss_policy_lookup(const struct lws_context *context, const char *streamtype) +{ + const lws_ss_policy_t *p = context->pss_policies; + + if (!streamtype) + return NULL; + + while (p) { + if (!strcmp(p->streamtype, streamtype)) + return p; + p = p->next; + } + + return NULL; +} diff --git a/lib/secure-streams/private-lib-secure-streams.h b/lib/secure-streams/private-lib-secure-streams.h new file mode 100644 index 000000000..b22211a31 --- /dev/null +++ b/lib/secure-streams/private-lib-secure-streams.h @@ -0,0 +1,347 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2019 - 2020 Andy Green + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +/* + * Secure Stream state + */ + +typedef enum { + SSSEQ_IDLE, + SSSEQ_TRY_CONNECT, + SSSEQ_TRY_CONNECT_NAUTH, + SSSEQ_TRY_CONNECT_SAUTH, + SSSEQ_RECONNECT_WAIT, + SSSEQ_DO_RETRY, + SSSEQ_CONNECTED, +} lws_ss_seq_state_t; + + +/** + * lws_ss_handle_t: publicly-opaque secure stream object implementation + */ + +typedef struct lws_ss_handle { + lws_ss_info_t info; /**< copy of stream creation info */ + struct lws_dll2 list; /**< pt lists active ss */ + struct lws_dll2 to_list; /**< pt lists ss with pending to-s */ + + struct lws_dll2_owner src_list; /**< sink's list of bound sources */ + + struct lws_context *context; /**< lws context we are created on */ + const lws_ss_policy_t *policy; /**< system policy for stream */ + + struct lws_sequencer *seq; /**< owning sequencer if any */ + struct lws *wsi; /**< the stream wsi if any */ + + void *nauthi; /**< the nauth plugin instance data */ + void *sauthi; /**< the sauth plugin instance data */ + + lws_ss_metadata_t *metadata; + const lws_ss_policy_t *rideshare; + + struct lws_ss_handle *h_sink; /**< sink we are bound to, or NULL */ + void *sink_obj;/**< sink's private object representing us */ + + lws_sorted_usec_list_t sul; + lws_ss_tx_ordinal_t txord; + + /* protocol-specific connection helpers */ + + union { + + /* ...for http-related protocols... */ + + struct { + + /* common to all http-related protocols */ + + /* incoming multipart parsing */ + + char boundary[24]; /* --boundary from headers */ + uint8_t boundary_len; /* length of --boundary */ + uint8_t boundary_seq; /* current match amount */ + uint8_t boundary_dashes; /* check for -- after */ + uint8_t boundary_post; /* swallow post CRLF */ + + uint8_t som:1; /* SOM has been sent */ + uint8_t any:1; /* any content has been sent */ + + + uint8_t good_respcode:1; /* 200 type response code */ + + union { + struct { /* LWSSSP_H1 */ + } h1; + struct { /* LWSSSP_H2 */ + } h2; + struct { /* LWSSSP_WS */ + } ws; + } u; + } http; + + /* details for non-http related protocols... */ +#if defined(LWS_ROLE_MQTT) + struct { + lws_mqtt_topic_elem_t topic_qos; + lws_mqtt_topic_elem_t sub_top; + lws_mqtt_subscribe_param_t sub_info; + } mqtt; +#endif + } u; + + unsigned long writeable_len; + + lws_ss_constate_t connstate;/**< public connection state */ + lws_ss_seq_state_t seqstate; /**< private connection state */ + + uint16_t retry; /**< retry / backoff tracking */ + int16_t temp16; + + uint8_t tsi; /**< service thread idx, usually 0 */ + uint8_t subseq; /**< emulate SOM tracking */ + uint8_t txn_ok; /**< 1 = transaction was OK */ + + uint8_t hanging_som:1; + uint8_t inside_msg:1; + uint8_t being_serialized:1; /* we are not the consumer */ +} lws_ss_handle_t; + +/* connection helper that doesn't need to hang around after connection starts */ + +union lws_ss_contemp { +#if defined(LWS_ROLE_MQTT) + lws_mqtt_client_connect_param_t ccp; +#endif +}; + +/* + * When allocating the opaque handle, we overallocate for: + * + * 1) policy->nauth_plugin->alloc (.nauthi) if any + * 2) policy->sauth_plugin->alloc (.sauthi) if any + * 3) copy of creation info stream type pointed to by info.streamtype... this + * may be arbitrarily long and since it may be coming from socket ipc and be + * temporary at creation time, we need a place for the copy to stay in scope + * 4) copy of info->streamtype contents + */ + + +/* the user object allocation is immediately after the ss object allocation */ +#define ss_to_userobj(ss) ((void *)&(ss)[1]) + +/* + * serialization parser state + */ + +enum { + KIND_C_TO_P, + KIND_SS_TO_P, +}; + +struct lws_ss_serialization_parser { + char streamtype[32]; + char rideshare[32]; + char metadata_name[32]; + + uint64_t ust_pwait; + + lws_ss_metadata_t *ssmd; + + int ps; + int ctr; + + uint32_t usd_phandling; + uint32_t flags; + int32_t temp32; + + int32_t txcr_out; + int32_t txcr_in; + uint16_t rem; + + uint8_t type; + uint8_t frag1; + uint8_t slen; + uint8_t rsl_pos; + uint8_t rsl_idx; +}; + +/* + * Unlike locally-fulfilled SS, SSS doesn't have to hold metadata on client side + * but pass it through to the proxy. The client side doesn't know the real + * metadata names that are available in the policy (since it's hardcoded in code + * no point passing them back to the client from the policy). Because of that, + * it doesn't know how many to allocate when we create the sspc_handle either. + * + * So we use a linked-list of changed-but-not-yet-proxied metadata allocated + * on the heap and items removed as they are proxied out. Anything on the list + * is sent to the proxy before any requested tx is handled. + * + * This is also used to queue tx credit changes + */ + +typedef struct lws_sspc_metadata { + lws_dll2_t list; + char name[32]; /* empty string, then actually TCXR */ + size_t len; + int tx_cr_adjust; + + /* the value of length .len is overallocated after this */ +} lws_sspc_metadata_t; + + +typedef struct lws_sspc_handle { + char rideshare_list[128]; + lws_ss_info_t ssi; + lws_sorted_usec_list_t sul_retry; + + struct lws_ss_serialization_parser parser; + + lws_dll2_owner_t metadata_owner; + + struct lws_dll2 client_list; + struct lws_tx_credit txc; + + struct lws *cwsi; + + struct lws_dsh *dsh; + struct lws_context *context; + + lws_usec_t us_earliest_write_req; + + lws_ss_conn_states_t state; + + int16_t temp16; + + uint32_t ord; + + uint8_t rideshare_ofs[4]; + uint8_t conn_req; + uint8_t rsidx; + + uint8_t destroying:1; +} lws_sspc_handle_t; + +int +lws_ss_deserialize_parse(struct lws_ss_serialization_parser *par, + struct lws_context *context, + struct lws_dsh *dsh, const uint8_t *cp, size_t len, + lws_ss_conn_states_t *state, void *parconn, + lws_ss_handle_t **pss, lws_ss_info_t *ssi, char client); +int +lws_ss_serialize_rx_payload(struct lws_dsh *dsh, const uint8_t *buf, + size_t len, int flags, const char *rsp); +int +lws_ss_deserialize_tx_payload(struct lws_dsh *dsh, struct lws *wsi, + lws_ss_tx_ordinal_t ord, uint8_t *buf, + size_t *len, int *flags); +int +lws_ss_serialize_state(struct lws_dsh *dsh, lws_ss_constate_t state, + lws_ss_tx_ordinal_t ack); + +void +lws_ss_serialize_state_transition(lws_ss_conn_states_t *state, int new_state); + +const lws_ss_policy_t * +lws_ss_policy_lookup(const struct lws_context *context, const char *streamtype); + +/* can be used as a cb from lws_dll2_foreach_safe() to destroy ss */ +int +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); + +int +lws_ss_event_helper(lws_ss_handle_t *h, lws_ss_constate_t cs); + +int +lws_ss_backoff(lws_ss_handle_t *h); + +int +lws_ss_set_timeout_us(lws_ss_handle_t *h, lws_usec_t us); + +void +ss_proxy_onward_txcr(void *userobj, int bump); + +int +lws_ss_serialize_txcr(struct lws_dsh *dsh, int txcr); + +int +lws_ss_sys_auth_api_amazon_com(struct lws_context *context); + +lws_ss_metadata_t * +lws_ss_get_handle_metadata(struct lws_ss_handle *h, const char *name); +lws_ss_metadata_t * +lws_ss_policy_metadata_index(const lws_ss_policy_t *p, size_t index); + +lws_ss_metadata_t * +lws_ss_policy_metadata(const lws_ss_policy_t *p, const char *name); + +int +lws_ss_exp_cb_metadata(void *priv, const char *name, char *out, size_t *pos, + size_t olen, size_t *exp_ofs); + +typedef int (* const secstream_protocol_connect_munge_t)(lws_ss_handle_t *h, + char *buf, size_t len, struct lws_client_connect_info *i, + union lws_ss_contemp *ct); + +typedef int (* const secstream_protocol_add_txcr_t)(lws_ss_handle_t *h, int add); + +typedef int (* const secstream_protocol_get_txcr_t)(lws_ss_handle_t *h); + +struct ss_pcols { + const char *name; + const char *alpn; + const char *protocol_name; + const secstream_protocol_connect_munge_t munge; + const secstream_protocol_add_txcr_t tx_cr_add; + const secstream_protocol_get_txcr_t tx_cr_est; +}; + +extern const struct ss_pcols ss_pcol_h1; +extern const struct ss_pcols ss_pcol_h2; +extern const struct ss_pcols ss_pcol_ws; +extern const struct ss_pcols ss_pcol_mqtt; + +extern const struct lws_protocols protocol_secstream_h1; +extern const struct lws_protocols protocol_secstream_h2; +extern const struct lws_protocols protocol_secstream_ws; +extern const struct lws_protocols protocol_secstream_mqtt; + diff --git a/lib/secure-streams/protocols/README.md b/lib/secure-streams/protocols/README.md new file mode 100644 index 000000000..3c02e8bc7 --- /dev/null +++ b/lib/secure-streams/protocols/README.md @@ -0,0 +1,38 @@ +# Lws Protocol bindings for Secure Streams + +This directory contains the code wiring up normal lws protocols +to Secure Streams. + +## The lws_protocols callback + +This is the normal lws struct lws_protocols callback that handles events and +traffic on the lws protocol being supported. + +The various events and traffic are converted into calls using the Secure +Streams api, and Secure Streams events. + +## The connect_munge helper + +Different protocols have different semantics in the arguments to the client +connect function, this protocol-specific helper is called to munge the +connect_info struct to match the details of the protocol selected. + +The `ss->policy->aux` string is used to hold protocol-specific information +passed in the from the policy, eg, the URL path or websockets subprotocol +name. + +## The (library-private) ss_pcols export + +Each protocol binding exports two things to other parts of lws (they +are not exported to user code) + + - a struct lws_protocols, including a pointer to the callback + + - a struct ss_pcols describing how secure_streams should use, including + a pointer to the related connect_munge helper. + +In ./lib/core-net/vhost.c, enabled protocols are added to vhost protcols +lists so they may be used. And in ./lib/secure-streams/secure-streams.c, +enabled struct ss_pcols are listed and checked for matches when the user +creates a new Secure Stream. + diff --git a/lib/secure-streams/protocols/ss-h1.c b/lib/secure-streams/protocols/ss-h1.c new file mode 100644 index 000000000..c9c6185cc --- /dev/null +++ b/lib/secure-streams/protocols/ss-h1.c @@ -0,0 +1,571 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2019 - 2020 Andy Green + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * This is the glue that wires up h1 to Secure Streams. + */ + +#include + +#if !defined(LWS_PLAT_FREERTOS) || defined(LWS_ROLE_H2) +static int +ss_http_multipart_parser(lws_ss_handle_t *h, void *in, size_t len) +{ + uint8_t *q = (uint8_t *)in; + int pending_issue = 0, n = 0; + + /* let's stick it in the boundary state machine first */ + while (n < (int)len) { + if (h->u.http.boundary_seq != h->u.http.boundary_len) { + if (q[n] == h->u.http.boundary[h->u.http.boundary_seq]) + h->u.http.boundary_seq++; + else { + h->u.http.boundary_seq = 0; + h->u.http.boundary_dashes = 0; + h->u.http.boundary_post = 0; + } + goto around; + } + + /* + * We already matched the boundary string, now we're + * looking if there's a -- afterwards + */ + if (h->u.http.boundary_dashes < 2) { + if (q[n] == '-') { + h->u.http.boundary_dashes++; + goto around; + } + /* there was no final -- ... */ + } + + if (h->u.http.boundary_dashes == 2) { + /* + * It's an EOM boundary: issue pending + multipart EOP + */ + lwsl_debug("%s: seen EOP, n %d pi %d\n", + __func__, n, pending_issue); + /* + * It's possible we already started the decode before + * the end of the last packet. Then there is no + * remainder to send. + */ + if (n >= pending_issue + h->u.http.boundary_len + + (h->u.http.any ? 2 : 0) + 1) + h->info.rx(ss_to_userobj(h), + &q[pending_issue], + n - pending_issue - + h->u.http.boundary_len - 1 - + (h->u.http.any ? 2 : 0) /* crlf */, + (!h->u.http.som ? LWSSS_FLAG_SOM : 0) | + LWSSS_FLAG_EOM | LWSSS_FLAG_RELATED_END); + + /* + * Peer may not END_STREAM us + */ + return 0; + //return -1; + } + + /* how about --boundaryCRLF */ + + if (h->u.http.boundary_post < 2) { + if ((!h->u.http.boundary_post && q[n] == '\x0d') || + (h->u.http.boundary_post && q[n] == '\x0a')) { + h->u.http.boundary_post++; + goto around; + } + /* there was no final CRLF ... it's wrong */ + + return -1; + } + if (h->u.http.boundary_post != 2) + goto around; + + /* + * We have a starting "--boundaryCRLF" or intermediate + * "CRLF--boundaryCRLF" boundary + */ + lwsl_debug("%s: b_post = 2 (pi %d)\n", __func__, pending_issue); + h->u.http.boundary_seq = 0; + h->u.http.boundary_post = 0; + + if (n >= pending_issue && (h->u.http.any || !h->u.http.som)) { + /* Intermediate... do the EOM */ + lwsl_debug("%s: seen interm EOP n %d pi %d\n", __func__, + n, pending_issue); + /* + * It's possible we already started the decode before + * the end of the last packet. Then there is no + * remainder to send. + */ + if (n >= pending_issue + h->u.http.boundary_len + + (h->u.http.any ? 2 : 0)) + h->info.rx(ss_to_userobj(h), &q[pending_issue], + n - pending_issue - + h->u.http.boundary_len - + (h->u.http.any ? 2 /* crlf */ : 0), + (!h->u.http.som ? LWSSS_FLAG_SOM : 0) | + LWSSS_FLAG_EOM); + } + + /* Next message starts after this boundary */ + + pending_issue = n; + h->u.http.som = 0; + +around: + n++; + } + + if (pending_issue != n) { + h->info.rx(ss_to_userobj(h), &q[pending_issue], n - pending_issue, + (!h->u.http.som ? LWSSS_FLAG_SOM : 0)); + h->u.http.any = 1; + h->u.http.som = 1; + } + + return 0; +} +#endif + +static const uint8_t blob_idx[] = { + LWS_SYSBLOB_TYPE_AUTH, + LWS_SYSBLOB_TYPE_DEVICE_SERIAL, + LWS_SYSBLOB_TYPE_DEVICE_FW_VERSION, + LWS_SYSBLOB_TYPE_DEVICE_TYPE, +}; + +int +secstream_h1(struct lws *wsi, enum lws_callback_reasons reason, void *user, + void *in, size_t len) +{ + lws_ss_handle_t *h = (lws_ss_handle_t *)lws_get_opaque_user_data(wsi); + uint8_t buf[LWS_PRE + 1520], *p = &buf[LWS_PRE], + *end = &buf[sizeof(buf) - 1]; + int f = 0, m, status; + size_t buflen; + + switch (reason) { + + /* because we are protocols[0] ... */ + case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: + assert(h); + assert(h->policy); + lwsl_info("%s: h: %p, %s CLIENT_CONNECTION_ERROR: %s\n", __func__, + h, h->policy->streamtype, in ? (char *)in : "(null)"); + lws_ss_event_helper(h, LWSSSCS_UNREACHABLE); + h->wsi = NULL; + lws_ss_backoff(h); + break; + + case LWS_CALLBACK_CLOSED_CLIENT_HTTP: + if (!h) + break; + lwsl_info("%s: h: %p, %s LWS_CALLBACK_CLOSED_CLIENT_HTTP\n", + __func__, h, + h->policy ? h->policy->streamtype : "no policy"); + h->wsi = NULL; + //bad = status != 200; + //lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */ + if (h->policy && !(h->policy->flags & LWSSSPOLF_OPPORTUNISTIC) && + !h->txn_ok && !wsi->context->being_destroyed) + lws_ss_backoff(h); + if (lws_ss_event_helper(h, LWSSSCS_DISCONNECTED)) + lws_ss_destroy(&h); + break; + + + case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: + status = lws_http_client_http_response(wsi); + lwsl_info("%s: LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: %d\n", __func__, status); + // if (!status) + /* it's just telling use we connected / joined the nwsi */ + // break; + h->u.http.good_respcode = (status >= 200 && status < 300); + // lwsl_err("%s: good resp %d %d\n", __func__, status, h->u.http.good_respcode); + + if (h->u.http.good_respcode) + lwsl_info("%s: Connected streamtype %s, %d\n", __func__, + h->policy->streamtype, status); + else + lwsl_warn("%s: Connected streamtype %s, BAD %d\n", __func__, + h->policy->streamtype, status); + + h->hanging_som = 0; + + h->retry = 0; + h->seqstate = SSSEQ_CONNECTED; + lws_ss_set_timeout_us(h, LWS_SET_TIMER_USEC_CANCEL); + lws_ss_event_helper(h, LWSSSCS_CONNECTED); + + /* + * Since it's an http transaction we initiated... this is + * proof of connection validity + */ + lws_validity_confirmed(wsi); + +#if !defined(LWS_PLAT_FREERTOS) || defined(LWS_ROLE_H2) + + if (lws_hdr_copy(wsi, (char *)buf, sizeof(buf), + WSI_TOKEN_HTTP_CONTENT_TYPE) > 0 && + /* multipart/form-data; + * boundary=----WebKitFormBoundarycc7YgAPEIHvgE9Bf */ + + (!strncmp((char *)buf, "multipart/form-data", 19) || + !strncmp((char *)buf, "multipart/related", 17))) { + struct lws_tokenize ts; + lws_tokenize_elem e; + + // puts((const char *)buf); + + memset(&ts, 0, sizeof(ts)); + ts.start = (char *)buf; + ts.len = strlen(ts.start); + ts.flags = LWS_TOKENIZE_F_RFC7230_DELIMS | + LWS_TOKENIZE_F_SLASH_NONTERM | + LWS_TOKENIZE_F_MINUS_NONTERM; + + h->u.http.boundary[0] = '\0'; + do { + e = lws_tokenize(&ts); + if (e == LWS_TOKZE_TOKEN_NAME_EQUALS && + !strncmp(ts.token, "boundary", 8) && + ts.token_len == 8) { + e = lws_tokenize(&ts); + if (e != LWS_TOKZE_TOKEN) + goto malformed; + h->u.http.boundary[0] = '\x0d'; + h->u.http.boundary[1] = '\x0a'; + h->u.http.boundary[2] = '-'; + h->u.http.boundary[3] = '-'; + lws_strnncpy(h->u.http.boundary + 4, + ts.token, ts.token_len, + sizeof(h->u.http.boundary) - 4); + h->u.http.boundary_len = ts.token_len + 4; + h->u.http.boundary_seq = 2; + h->u.http.boundary_dashes = 0; + } + } while (e > 0); + lwsl_info("%s: multipart boundary '%s' len %d\n", __func__, + h->u.http.boundary, h->u.http.boundary_len); + + /* inform the ss that a related message group begins */ + + if (h->u.http.boundary[0]) + h->info.rx(ss_to_userobj(h), NULL, 0, + LWSSS_FLAG_RELATED_START); + + // lws_header_table_detach(wsi, 0); + } + break; +malformed: + lwsl_notice("%s: malformed multipart header\n", __func__); + return -1; +#else + break; +#endif + + case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER: + if (h->writeable_len) + wsi->http.writeable_len = h->writeable_len; + + { + uint8_t **p = (uint8_t **)in, *end = (*p) + len, + *oin = *(uint8_t **)in; + + /* + * blob-based headers + */ + + for (m = 0; m < _LWSSS_HBI_COUNT; m++) { + int o = 0, n; + + if (!h->policy->u.http.blob_header[m]) + continue; + + if (m == LWSSS_HBI_AUTH && + h->policy->u.http.auth_preamble) + o = lws_snprintf((char *)buf, sizeof(buf), "%s", + h->policy->u.http.auth_preamble); + + if (o > (int)sizeof(buf) - 2) + return -1; + + buflen = sizeof(buf) - o - 2; + n = lws_system_blob_get( + lws_system_get_blob(wsi->context, blob_idx[m], 0), + buf + o, &buflen, 0); + if (n < 0) + return -1; + + buf[o + buflen] = '\0'; + lwsl_debug("%s: adding blob %d: %s\n", __func__, m, buf); + + if (lws_add_http_header_by_name(wsi, + (uint8_t *)h->policy->u.http.blob_header[m], + buf, buflen + o, p, end)) + return -1; + } + + /* + * metadata-based headers + */ + + for (m = 0; m < h->policy->metadata_count; m++) { + lws_ss_metadata_t *polmd; + + /* has to have a header string listed */ + if (!h->metadata[m].value) + continue; + + polmd = lws_ss_policy_metadata_index(h->policy, m); + + assert(polmd); + /* has to have a value */ + if (polmd->value && ((uint8_t *)polmd->value)[0]) { + if (lws_add_http_header_by_name(wsi, + polmd->value, + h->metadata[m].value, + h->metadata[m].length, p, end)) + return -1; + } + } + + /* + * Content-length on POST if we have the length information + */ + + if (!strcmp(h->policy->u.http.method, "POST") && + wsi->http.writeable_len) { + if (!(h->policy->flags & + LWSSSPOLF_HTTP_NO_CONTENT_LENGTH)) { + int n = lws_snprintf((char *)buf, 20, "%u", + (unsigned int)wsi->http.writeable_len); + if (lws_add_http_header_by_token(wsi, + WSI_TOKEN_HTTP_CONTENT_LENGTH, + buf, n, p, end)) + return -1; + } + lws_client_http_body_pending(wsi, 1); + } + + (void)oin; + // if (*p != oin) + // lwsl_hexdump_notice(oin, lws_ptr_diff(*p, oin)); + + } + + break; + + /* chunks of chunked content, with header removed */ + case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: + lwsl_debug("%s: RECEIVE_CLIENT_HTTP_READ: read %d\n", + __func__, (int)len); + if (!h) + return 0; + +#if !defined(LWS_PLAT_FREERTOS) || defined(LWS_ROLE_H2) + if (h->u.http.boundary[0]) + return ss_http_multipart_parser(h, in, len); +#endif + + if (!h->subseq) { + f |= LWSSS_FLAG_SOM; + h->hanging_som = 1; + h->subseq = 1; + } + + // lwsl_notice("%s: HTTP_READ: client side sent len %d fl 0x%x\n", + // __func__, (int)len, (int)f); + + h->info.rx(ss_to_userobj(h), (const uint8_t *)in, len, f); + + return 0; /* don't passthru */ + + /* uninterpreted http content */ + case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: + { + char *px = (char *)buf + LWS_PRE; /* guarantees LWS_PRE */ + int lenx = sizeof(buf) - LWS_PRE; + + if (lws_http_client_read(wsi, &px, &lenx) < 0) + return -1; + } + lws_set_timeout(wsi, 99, 30); + + return 0; /* don't passthru */ + + case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: + lwsl_debug("%s: LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n", __func__); + if (h->hanging_som) + h->info.rx(ss_to_userobj(h), NULL, 0, LWSSS_FLAG_EOM); + + wsi->http.writeable_len = h->writeable_len = 0; + + if (h->u.http.good_respcode) + lws_ss_event_helper(h, LWSSSCS_QOS_ACK_REMOTE); + else + lws_ss_event_helper(h, LWSSSCS_QOS_NACK_REMOTE); + + h->wsi = NULL; + h->txn_ok = 1; + //bad = status != 200; + lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */ + break; + + case LWS_CALLBACK_CLIENT_HTTP_WRITEABLE: + lwsl_info("%s: LWS_CALLBACK_CLIENT_HTTP_WRITEABLE\n", __func__); + if (!h) + return 0; + + if (!h->rideshare) + h->rideshare = h->policy; + +#if !defined(LWS_PLAT_FREERTOS) || defined(LWS_ROLE_H2) + if (!h->inside_msg && h->rideshare->u.http.multipart_name) + lws_client_http_multipart(wsi, + h->rideshare->u.http.multipart_name, + h->rideshare->u.http.multipart_filename, + h->rideshare->u.http.multipart_content_type, + (char **)&p, (char *)end); + + buflen = lws_ptr_diff(end, p); + if (h->policy->u.http.multipart_name) + buflen -= 24; /* allow space for end of multipart */ + +#endif + + if (h->info.tx(ss_to_userobj(h), h->txord++, p, &buflen, &f)) { + /* don't want to send anything */ + lwsl_debug("%s: dont want to write\n", __func__); + return 0; + } + + lwsl_info("%s: WRITEABLE: user tx says len %d fl 0x%x\n", + __func__, (int)buflen, (int)f); + + p += buflen; + + if (f & LWSSS_FLAG_EOM) { +#if !defined(LWS_PLAT_FREERTOS) || defined(LWS_ROLE_H2) + /* end of rideshares */ + if (!h->rideshare->rideshare_streamtype) { + lws_client_http_body_pending(wsi, 0); + if (h->rideshare->u.http.multipart_name) + lws_client_http_multipart(wsi, NULL, NULL, NULL, + (char **)&p, (char *)end); + } else { +#endif + h->rideshare = lws_ss_policy_lookup(wsi->context, + h->rideshare->rideshare_streamtype); + lws_callback_on_writable(wsi); +#if !defined(LWS_PLAT_FREERTOS) || defined(LWS_ROLE_H2) + } +#endif + + h->inside_msg = 0; + } else { + /* otherwise we can spin with zero length writes */ + if (!f && !lws_ptr_diff(p, buf + LWS_PRE)) + break; + h->inside_msg = 1; + lws_callback_on_writable(wsi); + } + + lwsl_info("%s: lws_write %d %d\n", __func__, + lws_ptr_diff(p, buf + LWS_PRE), f); + + if (lws_write(wsi, buf + LWS_PRE, lws_ptr_diff(p, buf + LWS_PRE), + LWS_WRITE_HTTP) != (int)lws_ptr_diff(p, buf + LWS_PRE)) { + lwsl_err("%s: write failed\n", __func__); + return -1; + } + + lws_set_timeout(wsi, 0, 0); + break; + + default: + break; + } + + return lws_callback_http_dummy(wsi, reason, user, in, len); +} + +const struct lws_protocols protocol_secstream_h1 = { + "lws-secstream-h1", + secstream_h1, + 0, + 0, +}; + +/* + * Munge connect info according to protocol-specific considerations... this + * usually means interpreting aux in a protocol-specific way and using the + * pieces at connection setup time, eg, http url pieces. + * + * len bytes of buf can be used for things with scope until after the actual + * connect. + */ + +static int +secstream_connect_munge_h1(lws_ss_handle_t *h, char *buf, size_t len, + struct lws_client_connect_info *i, + union lws_ss_contemp *ct) +{ + size_t used_in, used_out; + lws_strexp_t exp; + + if (!h->policy->u.http.url) + return 0; + +#if !defined(LWS_PLAT_FREERTOS) || defined(LWS_ROLE_H2) + if (h->policy->flags & LWSSSPOLF_HTTP_MULTIPART) + i->ssl_connection |= LCCSCF_HTTP_MULTIPART_MIME; + + if (h->policy->flags & LWSSSPOLF_HTTP_X_WWW_FORM_URLENCODED) + i->ssl_connection |= LCCSCF_HTTP_X_WWW_FORM_URLENCODED; +#endif + + /* protocol aux is the path part */ + + i->path = buf; + buf[0] = '/'; + + lws_strexp_init(&exp, (void *)h, lws_ss_exp_cb_metadata, buf + 1, len - 1); + + if (lws_strexp_expand(&exp, h->policy->u.http.url, + strlen(h->policy->u.http.url), + &used_in, &used_out) != LSTRX_DONE) + return 1; + + return 0; +} + + +const struct ss_pcols ss_pcol_h1 = { + "h1", + "http/1.1", + "lws-secstream-h1", + secstream_connect_munge_h1, + NULL +}; diff --git a/lib/secure-streams/protocols/ss-h2.c b/lib/secure-streams/protocols/ss-h2.c new file mode 100644 index 000000000..941183c1c --- /dev/null +++ b/lib/secure-streams/protocols/ss-h2.c @@ -0,0 +1,183 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2019 - 2020 Andy Green + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include + +extern int +secstream_h1(struct lws *wsi, enum lws_callback_reasons reason, void *user, + void *in, size_t len); + +static int +secstream_h2(struct lws *wsi, enum lws_callback_reasons reason, void *user, + void *in, size_t len) +{ + lws_ss_handle_t *h = (lws_ss_handle_t *)lws_get_opaque_user_data(wsi); + int n; + + switch (reason) { + + case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: + +#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API) + if (h->being_serialized) { + /* + * We are the proxy-side SS for a remote client... we + * need to inform the client about the initial tx credit + * to write to it that the remote h2 server set up + */ + lwsl_info("%s: reporting initial tx cr from server %d\n", + __func__, wsi->txc.tx_cr); + ss_proxy_onward_txcr((void *)&h[1], wsi->txc.tx_cr); + } +#endif + + n = secstream_h1(wsi, reason, user, in, len); + + if (!n && (h->policy->flags & LWSSSPOLF_LONG_POLL)) { + lwsl_notice("%s: h2 client %p entering LONG_POLL\n", + __func__, wsi); + lws_h2_client_stream_long_poll_rxonly(wsi); + } + return n; + + case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: + // lwsl_err("%s: h2 COMPLETED_CLIENT_HTTP\n", __func__); + h->info.rx(ss_to_userobj(h), NULL, 0, LWSSS_FLAG_EOM); + h->wsi = NULL; + h->txn_ok = 1; + //bad = status != 200; + lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */ + break; + + case LWS_CALLBACK_WSI_TX_CREDIT_GET: + /* + * The peer has sent us additional tx credit... + */ + lwsl_info("%s: LWS_CALLBACK_WSI_TX_CREDIT_GET: %d\n", + __func__, (int32_t)len); + +#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API) + if (h->being_serialized) + /* we are the proxy-side SS for a remote client */ + ss_proxy_onward_txcr((void *)&h[1], (int)len); +#endif + break; + + default: + break; + } + + return secstream_h1(wsi, reason, user, in, len); +} + +const struct lws_protocols protocol_secstream_h2 = { + "lws-secstream-h2", + secstream_h2, + 0, + 0, +}; + +/* + * Munge connect info according to protocol-specific considerations... this + * usually means interpreting aux in a protocol-specific way and using the + * pieces at connection setup time, eg, http url pieces. + * + * len bytes of buf can be used for things with scope until after the actual + * connect. + */ + +int +secstream_connect_munge_h2(lws_ss_handle_t *h, char *buf, size_t len, + struct lws_client_connect_info *i, + union lws_ss_contemp *ct) +{ + if (h->policy->flags & LWSSSPOLF_QUIRK_NGHTTP2_END_STREAM) + i->ssl_connection |= LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM; + + if (h->policy->flags & LWSSSPOLF_H2_QUIRK_OVERFLOWS_TXCR) + i->ssl_connection |= LCCSCF_H2_QUIRK_OVERFLOWS_TXCR; + + if (h->policy->flags & LWSSSPOLF_HTTP_MULTIPART) + i->ssl_connection |= LCCSCF_HTTP_MULTIPART_MIME; + + if (h->policy->flags & LWSSSPOLF_HTTP_X_WWW_FORM_URLENCODED) + i->ssl_connection |= LCCSCF_HTTP_X_WWW_FORM_URLENCODED; + + i->ssl_connection |= LCCSCF_PIPELINE; + + i->alpn = "h2"; + + /* initial peer tx credit */ + + if (h->info.manual_initial_tx_credit) { + i->ssl_connection |= LCCSCF_H2_MANUAL_RXFLOW; + i->manual_initial_tx_credit = h->info.manual_initial_tx_credit; + lwsl_info("%s: initial txcr %d\n", __func__, + i->manual_initial_tx_credit); + } + + if (!h->policy->u.http.url) + return 0; + + /* protocol aux is the path part */ + + i->path = buf; + lws_snprintf(buf, len, "/%s", h->policy->u.http.url); + + return 0; +} + +static int +secstream_tx_credit_add_h2(lws_ss_handle_t *h, int add) +{ + lwsl_info("%s: h %p: add %d\n", __func__, h, add); + if (h->wsi) + return lws_h2_update_peer_txcredit(h->wsi, LWS_H2_STREAM_SID, add); + + return 0; +} + +static int +secstream_tx_credit_est_h2(lws_ss_handle_t *h) +{ + if (h->wsi) { + lwsl_info("%s: h %p: est %d\n", __func__, h, + lws_h2_get_peer_txcredit_estimate(h->wsi)); + + return lws_h2_get_peer_txcredit_estimate(h->wsi); + } + + lwsl_info("%s: h %p: Unknown (0)\n", __func__, h); + + return 0; +} + +const struct ss_pcols ss_pcol_h2 = { + "h2", + NULL, + "lws-secstream-h2", + secstream_connect_munge_h2, + secstream_tx_credit_add_h2, + secstream_tx_credit_est_h2 +}; diff --git a/lib/secure-streams/protocols/ss-mqtt.c b/lib/secure-streams/protocols/ss-mqtt.c new file mode 100644 index 000000000..2d53c6620 --- /dev/null +++ b/lib/secure-streams/protocols/ss-mqtt.c @@ -0,0 +1,248 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2019 - 2020 Andy Green + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include + +static int +secstream_mqtt(struct lws *wsi, enum lws_callback_reasons reason, void *user, + void *in, size_t len) +{ + lws_ss_handle_t *h = (lws_ss_handle_t *)lws_get_opaque_user_data(wsi); + lws_mqtt_publish_param_t mqpp, *pmqpp; + uint8_t buf[LWS_PRE + 1400]; + size_t buflen; + int f = 0; + + switch (reason) { + + /* because we are protocols[0] ... */ + case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: + lwsl_info("%s: CLIENT_CONNECTION_ERROR: %s\n", __func__, + in ? (char *)in : "(null)"); + if (!h) + break; + lws_ss_event_helper(h, LWSSSCS_UNREACHABLE); + h->wsi = NULL; + lws_ss_backoff(h); + break; + + case LWS_CALLBACK_MQTT_CLIENT_CLOSED: + if (!h) + break; + f = lws_ss_event_helper(h, LWSSSCS_DISCONNECTED); + if (h->wsi) + lws_set_opaque_user_data(h->wsi, NULL); + h->wsi = NULL; + if (f) { + lws_ss_destroy(&h); + break; + } + + if (h->policy && !(h->policy->flags & LWSSSPOLF_OPPORTUNISTIC) && + !h->txn_ok && !wsi->context->being_destroyed) + lws_ss_backoff(h); + break; + + case LWS_CALLBACK_MQTT_CLIENT_ESTABLISHED: + /* + * Make sure the handle wsi points to the stream wsi not the + * original nwsi, in the case it was migrated + */ + h->wsi = wsi; + h->retry = 0; + h->seqstate = SSSEQ_CONNECTED; + lws_ss_set_timeout_us(h, LWS_SET_TIMER_USEC_CANCEL); + lws_ss_event_helper(h, LWSSSCS_CONNECTED); + if (h->policy->u.mqtt.topic) + lws_callback_on_writable(wsi); + break; + + case LWS_CALLBACK_MQTT_CLIENT_RX: + // lwsl_user("LWS_CALLBACK_CLIENT_RECEIVE: read %d\n", (int)len); + if (!h) + return 0; + + pmqpp = (lws_mqtt_publish_param_t *)in; + + f = 0; + if (!pmqpp->payload_pos) + f |= LWSSS_FLAG_SOM; + if (pmqpp->payload_pos + len == pmqpp->payload_len) + f |= LWSSS_FLAG_EOM; + + h->subseq = 1; + + h->info.rx(ss_to_userobj(h), (const uint8_t *)pmqpp->payload, + len, f); + + return 0; /* don't passthru */ + + case LWS_CALLBACK_MQTT_SUBSCRIBED: + wsi->mqtt->done_subscribe = 1; + lws_callback_on_writable(wsi); + break; + + case LWS_CALLBACK_MQTT_ACK: + lws_ss_event_helper(h, LWSSSCS_QOS_ACK_REMOTE); + break; + + case LWS_CALLBACK_MQTT_CLIENT_WRITEABLE: + if (!h) + return 0; + lwsl_notice("%s: ss %p: WRITEABLE\n", __func__, h); + + if (h->seqstate != SSSEQ_CONNECTED) { + lwsl_warn("%s: seqstate %d\n", __func__, h->seqstate); + break; + } + + if (h->policy->u.mqtt.subscribe && !wsi->mqtt->done_subscribe) { + + /* + * The policy says to subscribe to something, and we + * haven't done it yet + */ + + lwsl_warn("%s: subscribing %s\n", __func__, h->policy->u.mqtt.subscribe); + + memset(&h->u.mqtt.sub_top, 0, sizeof(h->u.mqtt.sub_top)); + h->u.mqtt.sub_top.name = h->policy->u.mqtt.subscribe; + h->u.mqtt.sub_top.qos = h->policy->u.mqtt.qos; + memset(&h->u.mqtt.sub_info, 0, sizeof(h->u.mqtt.sub_info)); + h->u.mqtt.sub_info.num_topics = 1; + h->u.mqtt.sub_info.topic = &h->u.mqtt.sub_top; + + if (lws_mqtt_client_send_subcribe(wsi, &h->u.mqtt.sub_info)) { + lwsl_notice("%s: unable to subscribe", __func__); + return -1; + } + + return 0; + } + + + buflen = sizeof(buf) - LWS_PRE; + if (h->info.tx(ss_to_userobj(h), h->txord++, buf + LWS_PRE, + &buflen, &f)) + /* don't want to send anything */ + return 0; + + memset(&mqpp, 0, sizeof(mqpp)); + mqpp.topic = (char *)h->policy->u.mqtt.topic; + mqpp.topic_len = strlen(mqpp.topic); + mqpp.packet_id = h->txord - 1; + mqpp.payload = buf + LWS_PRE; + if (h->writeable_len) + mqpp.payload_len = h->writeable_len; + else + mqpp.payload_len = buflen; + + lwsl_notice("%s: payload len %d\n", __func__, (int)mqpp.payload_len); + + mqpp.qos = h->policy->u.mqtt.qos; + + if (lws_mqtt_client_send_publish(wsi, &mqpp, + (const char *)buf + LWS_PRE, buflen, + f & LWSSS_FLAG_EOM)) { + lwsl_notice("%s: failed to publish\n", __func__); + + return -1; + } + + return 0; + + default: + break; + } + + return lws_callback_http_dummy(wsi, reason, user, in, len); +} + +const struct lws_protocols protocol_secstream_mqtt = { + "lws-secstream-mqtt", + secstream_mqtt, + 0, + 0, +}; +/* + * Munge connect info according to protocol-specific considerations... this + * usually means interpreting aux in a protocol-specific way and using the + * pieces at connection setup time, eg, http url pieces. + * + * len bytes of buf can be used for things with scope until after the actual + * connect. + * + * For ws, protocol aux is ; + */ + +static int +secstream_connect_munge_mqtt(lws_ss_handle_t *h, char *buf, size_t len, + struct lws_client_connect_info *i, + union lws_ss_contemp *ct) +{ + memset(&ct->ccp, 0, sizeof(ct->ccp)); + + ct->ccp.client_id = "lwsMqttClient"; + ct->ccp.keep_alive = 60; + ct->ccp.clean_start = 1; + ct->ccp.will_param.topic = "good/bye"; + ct->ccp.will_param.message = "sign-off"; + ct->ccp.will_param.qos = 0; + ct->ccp.will_param.retain = 0; + + lwsl_notice("%s\n", __func__); + + h->u.mqtt.topic_qos.name = h->policy->u.mqtt.subscribe; + h->u.mqtt.topic_qos.qos = h->policy->u.mqtt.qos; + + i->method = "MQTT"; + i->mqtt_cp = &ct->ccp; + + i->alpn = "x-amzn-mqtt-ca"; + + /* share connections where possible */ + i->ssl_connection |= LCCSCF_PIPELINE; + +/* + if (!h->policy->u.http.url) + return 0; + + // protocol aux is the path part ; ws subprotocol name + + i->path = NULL; + lws_snprintf(buf, len, "/%s", h->policy->u.mqtt.topic); + +// i->protocol = h->policy->u.mqtt.u.ws.subprotocol; + + lwsl_notice("%s: url %s, ws subprotocol %s\n", __func__, buf, i->protocol); +*/ + return 0; +} + +const struct ss_pcols ss_pcol_mqtt = { + "MQTT", + "x-amzn-mqtt-ca", //"mqtt/3.1.1", + "lws-secstream-mqtt", + secstream_connect_munge_mqtt +}; diff --git a/lib/secure-streams/protocols/ss-ws.c b/lib/secure-streams/protocols/ss-ws.c new file mode 100644 index 000000000..ac1dc32ab --- /dev/null +++ b/lib/secure-streams/protocols/ss-ws.c @@ -0,0 +1,165 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2019 - 2020 Andy Green + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include + +static int +secstream_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user, + void *in, size_t len) +{ + lws_ss_handle_t *h = (lws_ss_handle_t *)lws_get_opaque_user_data(wsi); + uint8_t buf[LWS_PRE + 1400]; + size_t buflen; + int f = 0, f1; + + switch (reason) { + + /* because we are protocols[0] ... */ + case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: + lwsl_info("%s: CLIENT_CONNECTION_ERROR: %s\n", __func__, + in ? (char *)in : "(null)"); + if (!h) + break; + lws_ss_event_helper(h, LWSSSCS_UNREACHABLE); + h->wsi = NULL; + lws_ss_backoff(h); + break; + + case LWS_CALLBACK_CLIENT_CLOSED: + if (!h) + break; + f = lws_ss_event_helper(h, LWSSSCS_DISCONNECTED); + if (h->wsi) + lws_set_opaque_user_data(h->wsi, NULL); + h->wsi = NULL; + + if (f) { + lws_ss_destroy(&h); + break; + } + + if (h->policy && !(h->policy->flags & LWSSSPOLF_OPPORTUNISTIC) && + !h->txn_ok && !wsi->context->being_destroyed) + lws_ss_backoff(h); + break; + + case LWS_CALLBACK_CLIENT_ESTABLISHED: + h->retry = 0; + h->seqstate = SSSEQ_CONNECTED; + lws_ss_set_timeout_us(h, LWS_SET_TIMER_USEC_CANCEL); + lws_ss_event_helper(h, LWSSSCS_CONNECTED); + break; + + case LWS_CALLBACK_CLIENT_RECEIVE: + // lwsl_user("LWS_CALLBACK_CLIENT_RECEIVE: read %d\n", (int)len); + if (!h) + return 0; + if (lws_is_first_fragment(wsi)) + f |= LWSSS_FLAG_SOM; + if (lws_is_final_fragment(wsi)) + f |= LWSSS_FLAG_EOM; + // lws_frame_is_binary(wsi); + + h->subseq = 1; + + h->info.rx(ss_to_userobj(h), (const uint8_t *)in, len, f); + + return 0; /* don't passthru */ + + case LWS_CALLBACK_CLIENT_WRITEABLE: + if (!h) + return 0; + // lwsl_notice("%s: ss %p: WRITEABLE\n", __func__, h); + + if (h->seqstate != SSSEQ_CONNECTED) { + lwsl_warn("%s: seqstate %d\n", __func__, h->seqstate); + break; + } + + buflen = sizeof(buf) - LWS_PRE; + if (h->info.tx(ss_to_userobj(h), h->txord++, buf + LWS_PRE, + &buflen, &f)) + /* don't want to send anything */ + return 0; + + f1 = lws_write_ws_flags(LWS_WRITE_BINARY, + !!(f & LWSSS_FLAG_SOM), + !!(f & LWSSS_FLAG_EOM)); + + if (lws_write(wsi, buf + LWS_PRE, buflen, f1) != (int)buflen) { + lwsl_err("%s: write failed\n", __func__); + return -1; + } + + return 0; + + default: + break; + } + + return lws_callback_http_dummy(wsi, reason, user, in, len); +} + +const struct lws_protocols protocol_secstream_ws = { + "lws-secstream-ws", + secstream_ws, + 0, + 0, +}; +/* + * Munge connect info according to protocol-specific considerations... this + * usually means interpreting aux in a protocol-specific way and using the + * pieces at connection setup time, eg, http url pieces. + * + * len bytes of buf can be used for things with scope until after the actual + * connect. + * + * For ws, protocol aux is ; + */ + +static int +secstream_connect_munge_ws(lws_ss_handle_t *h, char *buf, size_t len, + struct lws_client_connect_info *i, + union lws_ss_contemp *ct) +{ + lwsl_notice("%s\n", __func__); + + if (!h->policy->u.http.url) + return 0; + + /* protocol aux is the path part ; ws subprotocol name */ + + i->path = h->policy->u.http.url; + lws_snprintf(buf, len, "/%s", h->policy->u.http.url); + + i->protocol = h->policy->u.http.u.ws.subprotocol; + + lwsl_notice("%s: url %s, ws subprotocol %s\n", __func__, buf, i->protocol); + + return 0; +} + +const struct ss_pcols ss_pcol_ws = { + "ws", "http/1.1", "lws-secstream-ws", secstream_connect_munge_ws +}; diff --git a/lib/secure-streams/secure-streams-client.c b/lib/secure-streams/secure-streams-client.c new file mode 100644 index 000000000..67d73be67 --- /dev/null +++ b/lib/secure-streams/secure-streams-client.c @@ -0,0 +1,574 @@ +/* + * lws-minimal-secure-streams-client + * + * Written in 2010-2020 by Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * + * This client does not perform any INET networking... instead it opens a unix + * domain socket on a proxy that is listening for it, and that creates the + * actual secure stream connection. + * + * We are able to use the usual secure streams api in the client process, with + * payloads and connection state information proxied over the unix domain + * socket and fulfilled in the proxy process. + * + * The public client helper pieces are built as part of lws + */ +#include + +static void +lws_sspc_sul_retry_cb(lws_sorted_usec_list_t *sul) +{ + lws_sspc_handle_t *h = lws_container_of(sul, lws_sspc_handle_t, sul_retry); + static struct lws_client_connect_info i; + + /* + * We may have started up before the system proxy, so be prepared with + * a sul to retry at 1Hz + */ + + memset(&i, 0, sizeof i); + i.context = h->context; + if (h->context->ss_proxy_port) { /* tcp */ + i.address = h->context->ss_proxy_address; + i.port = h->context->ss_proxy_port; + i.iface = h->context->ss_proxy_bind; + } else { + if (h->context->ss_proxy_bind) + i.address = h->context->ss_proxy_bind; + else + i.address = "+@proxy.ss.lws"; + } + i.host = i.address; + i.origin = i.address; + i.method = "RAW"; + i.protocol = lws_sspc_protocols[0].name; + i.local_protocol_name = lws_sspc_protocols[0].name; + i.path = ""; + i.pwsi = &h->cwsi; + i.opaque_user_data = (void *)h; + + if (!lws_client_connect_via_info(&i)) { + lws_sul_schedule(h->context, 0, &h->sul_retry, + lws_sspc_sul_retry_cb, LWS_US_PER_SEC); + + return; + } +} + +static int +lws_sspc_serialize_metadata(lws_sspc_metadata_t *md, uint8_t *p) +{ + int n, txc; + + if (md->name[0] == '\0') { + + lwsl_info("%s: sending tx credit update %d\n", __func__, + md->tx_cr_adjust); + + p[0] = LWSSS_SER_TXPRE_TXCR_UPDATE; + lws_ser_wu16be(&p[1], 4); + lws_ser_wu32be(&p[3], md->tx_cr_adjust); + + n = 7; + + } else { + + lwsl_info("%s: sending metadata\n", __func__); + + p[0] = LWSSS_SER_TXPRE_METADATA; + txc = strlen(md->name); + n = txc + 1 + md->len; + lws_ser_wu16be(&p[1], n); + p[3] = txc; + memcpy(&p[4], md->name, txc); + memcpy(&p[4 + txc], &md[1], md->len); + n = 4 + txc + md->len; + } + + lws_dll2_remove(&md->list); + lws_free(md); + + return n; +} + +static int +callback_sspc_client(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + lws_sspc_handle_t *h = (lws_sspc_handle_t *)lws_get_opaque_user_data(wsi); + uint8_t s[32], pkt[LWS_PRE + 1400], *p = pkt + LWS_PRE; + void *m = (void *)((uint8_t *)&h[1]); + const uint8_t *cp; + lws_usec_t us; + int flags, n; + + switch (reason) { + case LWS_CALLBACK_PROTOCOL_INIT: + break; + + case LWS_CALLBACK_PROTOCOL_DESTROY: + break; + + case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: + lwsl_warn("%s: CONNECTION_ERROR\n", __func__); + lws_set_opaque_user_data(wsi, NULL); + h->cwsi = NULL; + lws_sul_schedule(h->context, 0, &h->sul_retry, + lws_sspc_sul_retry_cb, LWS_US_PER_SEC); + break; + + case LWS_CALLBACK_RAW_CONNECTED: + if (!h) + return -1; + lwsl_info("%s: CONNECTED (%s)\n", __func__, h->ssi.streamtype); + + h->state = LPCS_SENDING_INITIAL_TX; + h->dsh = lws_dsh_create(NULL, (LWS_PRE + LWS_SS_MTU) * 160, 1); + if (!h->dsh) + return -1; + + lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CLIENT_HS_SEND, 3); + lws_callback_on_writable(wsi); + break; + + case LWS_CALLBACK_RAW_CLOSE: + /* + * our ss proxy Unix Domain socket has closed... + */ + lwsl_notice("%s: LWS_CALLBACK_RAW_CLOSE: proxy conn down\n", __func__); + h->cwsi = NULL; + //lws_sspc_destroy(&h); + break; + + case LWS_CALLBACK_RAW_RX: + lwsl_info("%s: RAW_RX: rx %d\n", __func__, (int)len); + + if (!h || !h->cwsi) { + lwsl_err("%s: rx with bad conn state\n", __func__); + + return -1; + } + + if (lws_ss_deserialize_parse(&h->parser, lws_get_context(wsi), + h->dsh, in, len, &h->state, h, + (lws_ss_handle_t **)m, &h->ssi, 1)) + return -1; + + if (wsi && h->state == LPCS_LOCAL_CONNECTED) + lws_set_timeout(wsi, 0, 0); + + break; + + case LWS_CALLBACK_RAW_WRITEABLE: + + /* + * We can transmit something to the proxy... + */ + + if (!h) + break; + + lwsl_info("%s: WRITEABLE %p: (%s) state %d\n", __func__, wsi, + h->ssi.streamtype, h->state); + + n = 0; + cp = s; + s[1] = 0; + switch (h->state) { + case LPCS_SENDING_INITIAL_TX: + n = strlen(h->ssi.streamtype) + 4; + + s[0] = LWSSS_SER_TXPRE_STREAMTYPE; + lws_ser_wu16be(&s[1], n); + lws_ser_wu32be(&s[3], h->txc.peer_tx_cr_est); + //h->txcr_out = txc; + lws_strncpy((char *)&s[7], h->ssi.streamtype, sizeof(s) - 7); + n += 3; + h->state = LPCS_WAITING_CREATE_RESULT; + break; + + case LPCS_LOCAL_CONNECTED: + if (!h->conn_req) + break; + + /* + * Do we need to prioritize sending any metadata + * changes? + */ + + if (h->metadata_owner.count) { + lws_sspc_metadata_t *md = lws_container_of( + lws_dll2_get_tail(&h->metadata_owner), + lws_sspc_metadata_t, list); + + cp = p; + n = lws_sspc_serialize_metadata(md, p); + + /* in case anything else to write */ + lws_callback_on_writable(h->cwsi); + + break; + } + + + h->conn_req = 0; + s[0] = LWSSS_SER_TXPRE_ONWARD_CONNECT; + s[1] = 0; + s[2] = 0; + n = 3; + break; + + case LPCS_OPERATIONAL: + + /* + * Do we want to adjust the peer's ability to write + * to us? + */ + + /* + * Do we need to prioritize sending any metadata + * changes? + */ + + if (h->metadata_owner.count) { + lws_sspc_metadata_t *md = lws_container_of( + lws_dll2_get_tail(&h->metadata_owner), + lws_sspc_metadata_t, list); + + cp = p; + n = lws_sspc_serialize_metadata(md, p); + + /* in case anything else to write */ + lws_callback_on_writable(h->cwsi); + + break; + } + + + /* we can't write anything if we don't have credit */ + if (h->txc.tx_cr <= 0) { + lwsl_notice("%s: WRITEABLE / OPERATIONAL:" + " lack credit (%d)\n", __func__, + h->txc.tx_cr); + break; + } + + len = sizeof(pkt) - LWS_PRE - 19; + flags = 0; + if (h->ssi.tx(m, h->ord++, pkt + LWS_PRE + 19, &len, &flags)) + break; + + h->txc.tx_cr -= len; + + cp = p; + n = len + 19; + us = lws_now_usecs(); + p[0] = LWSSS_SER_TXPRE_TX_PAYLOAD; + lws_ser_wu16be(&p[1], len + 19 - 3); + lws_ser_wu32be(&p[3], flags); + /* time spent here waiting to send this */ + lws_ser_wu32be(&p[7], us - h->us_earliest_write_req); + /* ust that the client write happened */ + lws_ser_wu64be(&p[11], us); + h->us_earliest_write_req = 0; + + if (flags & LWSSS_FLAG_EOM) + if (h->rsidx + 1 < (int)LWS_ARRAY_SIZE(h->rideshare_ofs) && + h->rideshare_ofs[h->rsidx + 1]) + h->rsidx++; + + break; + default: + break; + } + + if (!n) + break; + + // lwsl_hexdump_notice(cp, n); + + n = lws_write(wsi, (uint8_t *)cp, n, LWS_WRITE_RAW); + if (n < 0) { + lwsl_notice("%s: WRITEABLE: %d\n", __func__, n); + + goto hangup; + } + break; + + default: + break; + } + + return lws_callback_http_dummy(wsi, reason, user, in, len); + +hangup: + lwsl_warn("hangup\n"); + /* hang up on him */ + return -1; +} + +const struct lws_protocols lws_sspc_protocols[] = { + { + "ssproxy-protocol", + callback_sspc_client, + 0, + 2048, 2048, NULL, 0 + }, + { NULL, NULL, 0, 0, 0, NULL, 0 } +}; + +int +lws_sspc_create(struct lws_context *context, int tsi, const lws_ss_info_t *ssi, + void *opaque_user_data, lws_sspc_handle_t **ppss, + struct lws_sequencer *seq_owner, const char **ppayload_fmt) +{ + lws_sspc_handle_t *h; + uint8_t *ua; + char *p; + + lwsl_notice("%s: streamtype %s\n", __func__, ssi->streamtype); + + /* allocate the handle (including ssi), the user alloc, + * and the streamname */ + + h = malloc(sizeof(lws_sspc_handle_t) + ssi->user_alloc + + strlen(ssi->streamtype) + 1); + memset(h, 0, sizeof(*h)); + memcpy(&h->ssi, ssi, sizeof(*ssi)); + ua = (uint8_t *)&h[1]; + memset(ua, 0, ssi->user_alloc); + p = (char *)ua + ssi->user_alloc; + memcpy(p, ssi->streamtype, strlen(ssi->streamtype) + 1); + h->ssi.streamtype = (const char *)p; + h->context = context; + if (!ssi->manual_initial_tx_credit) + h->txc.peer_tx_cr_est = 500000000; + else + h->txc.peer_tx_cr_est = ssi->manual_initial_tx_credit; + + lws_dll2_add_head(&h->client_list, &context->pt[tsi].ss_client_owner); + + /* fill in the things the real api does for the caller */ + + *((void **)(ua + ssi->opaque_user_data_offset)) = opaque_user_data; + *((void **)(ua + ssi->handle_offset)) = h; + + if (ppss) + *ppss = h; + + /* try the actual connect */ + + lws_sspc_sul_retry_cb(&h->sul_retry); + + return 0; +} + +/* used on context destroy when iterating listed lws_ss on a pt */ + +int +lws_sspc_destroy_dll(struct lws_dll2 *d, void *user) +{ + lws_sspc_handle_t *h = lws_container_of(d, lws_sspc_handle_t, client_list); + + lws_sspc_destroy(&h); + + return 0; +} + + +void +lws_sspc_destroy(lws_sspc_handle_t **ph) +{ + lws_sspc_handle_t *h; + void *m; + + lwsl_debug("%s\n", __func__); + + if (!*ph) + return; + + h = *ph; + m = (void *)((uint8_t *)&h[1]); + + if (h->destroying) + return; + + h->destroying = 1; + + lws_sul_schedule(h->context, 0, &h->sul_retry, NULL, + LWS_SET_TIMER_USEC_CANCEL); + lws_dll2_remove(&h->client_list); + + if (h->dsh) + lws_dsh_destroy(&h->dsh); + if (h->cwsi) { + struct lws *wsi = h->cwsi; + h->cwsi = NULL; + lws_set_timeout(wsi, 1, LWS_TO_KILL_SYNC); + } + + /* clean out any pending metadata changes that didn't make it */ + + lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, + lws_dll2_get_head(&(*ph)->metadata_owner)) { + lws_sspc_metadata_t *md = + lws_container_of(d, lws_sspc_metadata_t, list); + + lws_dll2_remove(&md->list); + lws_free(md); + + } lws_end_foreach_dll_safe(d, d1); + + h->ssi.state(m, NULL, LWSSSCS_DESTROYING, 0); + *ph = NULL; + free(h); +} + +void +lws_sspc_request_tx(lws_sspc_handle_t *h) +{ + if (!h || !h->cwsi) + return; + + if (!h->us_earliest_write_req) + h->us_earliest_write_req = lws_now_usecs(); + + lws_callback_on_writable(h->cwsi); +} + +int +lws_sspc_client_connect(lws_sspc_handle_t *h) +{ + if (!h || h->state == LPCS_OPERATIONAL) + return 0; + + assert(h->state == LPCS_LOCAL_CONNECTED); + h->conn_req = 1; + if (h->cwsi) + lws_callback_on_writable(h->cwsi); + + return 0; +} + +struct lws_context * +lws_sspc_get_context(struct lws_sspc_handle *h) +{ + return h->context; +} + +const char * +lws_sspc_rideshare(struct lws_sspc_handle *h) +{ + /* + * ...the serialized RX rideshare name if any... + */ + + if (h->parser.rideshare[0]) { + lwsl_info("%s: parser %s\n", __func__, h->parser.rideshare); + return h->parser.rideshare; + } + + /* + * The tx rideshare index + */ + + if (h->rideshare_list[0]) { + lwsl_info("%s: tx list %s\n", __func__, + &h->rideshare_list[h->rideshare_ofs[h->rsidx]]); + return &h->rideshare_list[h->rideshare_ofs[h->rsidx]]; + } + + /* + * ... otherwise default to our stream type name + */ + + lwsl_info("%s: def %s\n", __func__, h->ssi.streamtype); + + return h->ssi.streamtype; +} + +static int +_lws_sspc_set_metadata(struct lws_sspc_handle *h, const char *name, + void *value, size_t len, int tx_cr_adjust) +{ + lws_sspc_metadata_t *md; + + /* + * Are we replacing a pending metadata of the same name? It's not + * efficient to do this but user code can do what it likes... let's + * optimize away the old one. + * + * Tx credit adjust always has name "" + */ + + lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, + lws_dll2_get_head(&h->metadata_owner)) { + md = lws_container_of(d, lws_sspc_metadata_t, list); + + if (!strcmp(name, md->name)) { + lws_dll2_remove(&md->list); + lws_free(md); + break; + } + + } lws_end_foreach_dll_safe(d, d1); + + /* + * We have to stash the metadata and pass it to the proxy + */ + + md = lws_malloc(sizeof(*md) + len, "set metadata"); + if (!md) { + lwsl_err("%s: OOM\n", __func__); + + return 1; + } + + memset(md, 0, sizeof(*md)); + + md->tx_cr_adjust = tx_cr_adjust; + h->txc.peer_tx_cr_est += tx_cr_adjust; + + lws_strncpy(md->name, name, sizeof(md->name)); + md->len = len; + if (len) + memcpy(&md[1], value, len); + + lws_dll2_add_tail(&md->list, &h->metadata_owner); + + if (len) { + lwsl_info("%s: set metadata %s\n", __func__, name); + lwsl_hexdump_info(value, len); + } else + lwsl_info("%s: serializing tx cr adj %d\n", __func__, + (int)tx_cr_adjust); + + if (h->cwsi) + lws_callback_on_writable(h->cwsi); + + return 0; +} + +int +lws_sspc_set_metadata(struct lws_sspc_handle *h, const char *name, + void *value, size_t len) +{ + return _lws_sspc_set_metadata(h, name, value, len, 0); +} + +int +lws_sspc_add_peer_tx_credit(struct lws_sspc_handle *h, int32_t bump) +{ + lwsl_notice("%s: %d\n", __func__, bump); + return _lws_sspc_set_metadata(h, "", NULL, 0, (int)bump); +} + +int +lws_sspc_get_est_peer_tx_credit(struct lws_sspc_handle *h) +{ + return h->txc.peer_tx_cr_est; +} diff --git a/lib/secure-streams/secure-streams-process.c b/lib/secure-streams/secure-streams-process.c new file mode 100644 index 000000000..8ee0d11b4 --- /dev/null +++ b/lib/secure-streams/secure-streams-process.c @@ -0,0 +1,533 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2019 - 2020 Andy Green + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * + * When the user code is in a different process, a non-tls unix domain socket + * proxy is used to asynchronusly transfer buffers in each direction via the + * network stack, without explicit IPC + * + * user_process{ [user code] | shim | socket-}------ lws_process{ lws } + * + * Lws exposes a listening unix domain socket in this case, the user processes + * connect to it and pass just info.streamtype in an initial tx packet. All + * packets are prepended by a 1-byte type field when used in this mode. See + * lws-secure-streams.h for documentation and definitions. + * + * Proxying in either direction can face the situation it cannot send the onward + * packet immediately and is subject to separating the write request from the + * write action. To make the best use of memory, a single preallocated buffer + * stashes pending packets in all four directions (c->p, p->c, p->ss, ss->p). + * This allows it to adapt to different traffic patterns without wasted areas + * dedicated to traffic that isn't coming in a particular application. + * + * A shim is provided to monitor the process' unix domain socket and regenerate + * the secure sockets api there with callbacks happening in the process thread + * context. + * + * This file implements the listening unix domain socket proxy... this code is + * only going to run on a Linux-class device with its implications about memory + * availability. + */ + +#include + +/* + * Because both sides of the connection share the conn, we allocate it + * during accepted adoption, and both sides point to it. + * + * The last one of the accepted side and the onward side to close frees it. + */ + +struct conn { + struct lws_ss_serialization_parser parser; + + lws_dsh_t *dsh; /* unified buffer for both sides */ + struct lws *wsi; /* the client side */ + lws_ss_handle_t *ss; /* the onward, ss side */ + + lws_ss_conn_states_t state; +}; + +struct raw_pss { + struct conn *conn; +}; + +/* + * Proxy - onward secure-stream handler + */ + +typedef struct ss_proxy_onward { + lws_ss_handle_t *ss; + struct conn *conn; +} ss_proxy_t; + + +/* secure streams payload interface */ + +static int +ss_proxy_onward_rx(void *userobj, const uint8_t *buf, size_t len, int flags) +{ + ss_proxy_t *m = (ss_proxy_t *)userobj; + const char *rsp = NULL; + int n; + + /* + * The onward secure stream connection has received something. + */ + + if (m->ss->rideshare != m->ss->policy && m->ss->rideshare) { + rsp = m->ss->rideshare->streamtype; + flags |= LWSSS_FLAG_RIDESHARE; + } + + n = lws_ss_serialize_rx_payload(m->conn->dsh, buf, len, flags, rsp); + if (n) + return n; + + if (m->conn->wsi) /* if possible, request client conn write */ + lws_callback_on_writable(m->conn->wsi); + + return 0; +} + +/* + * we are transmitting buffered payload originally from the client on to the ss + */ + +static int +ss_proxy_onward_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, + size_t *len, int *flags) +{ + ss_proxy_t *m = (ss_proxy_t *)userobj; + void *p; + size_t si; + + if (!m->conn->ss || m->conn->state != LPCS_OPERATIONAL) { + lwsl_notice("%s: ss not ready\n", __func__); + *len = 0; + + return 1; + } + + /* + * The onward secure stream says that we could send something to it + * (by putting it in buf, and setting *len and *flags) + */ + + if (lws_ss_deserialize_tx_payload(m->conn->dsh, m->ss->wsi, + ord, buf, len, flags)) + return 1; + + if (!lws_dsh_get_head(m->conn->dsh, KIND_C_TO_P, (void **)&p, &si)) + lws_ss_request_tx(m->conn->ss); + + if (!*len && !*flags) + return 1; /* we don't actually want to send anything */ + + lwsl_info("%s: onward tx %d fl 0x%x\n", __func__, (int)*len, *flags); + +#if 0 + { + int ff = open("/tmp/z", O_RDWR | O_CREAT | O_APPEND, 0666); + if (ff == -1) + lwsl_err("%s: errno %d\n", __func__, errno); + write(ff, buf, *len); + close(ff); + } +#endif + + return 0; +} + +static int +ss_proxy_onward_state(void *userobj, void *sh, + lws_ss_constate_t state, lws_ss_tx_ordinal_t ack) +{ + ss_proxy_t *m = (ss_proxy_t *)userobj; + + switch (state) { + case LWSSSCS_CREATING: + break; + + case LWSSSCS_DESTROYING: + if (!m->conn) + break; + if (!m->conn->wsi) { + /* + * Our onward secure stream is closing and our client + * connection has already gone away... destroy the conn. + */ + lwsl_info("%s: Destroying conn\n", __func__); + lws_dsh_destroy(&m->conn->dsh); + free(m->conn); + m->conn = NULL; + return 0; + } else + lwsl_info("%s: ss DESTROYING, wsi up\n", __func__); + break; + + default: + break; + } + if (!m->conn) { + lwsl_warn("%s: dropping state due to conn not up\n", __func__); + + return 0; + } + + lws_ss_serialize_state(m->conn->dsh, state, ack); + + if (m->conn->wsi) /* if possible, request client conn write */ + lws_callback_on_writable(m->conn->wsi); + + return 0; +} + +void +ss_proxy_onward_txcr(void *userobj, int bump) +{ + ss_proxy_t *m = (ss_proxy_t *)userobj; + + if (!m->conn) + return; + + lws_ss_serialize_txcr(m->conn->dsh, bump); + + if (m->conn->wsi) /* if possible, request client conn write */ + lws_callback_on_writable(m->conn->wsi); +} + +/* + * Client - Proxy connection on unix domain socket + */ + +static int +callback_ss_proxy(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + struct raw_pss *pss = (struct raw_pss *)user; + const lws_ss_policy_t *rsp; + struct conn *conn = NULL; + lws_ss_info_t ssi; + const uint8_t *cp; +#if defined(LWS_WITH_DETAILED_LATENCY) + lws_usec_t us; +#endif + char s[128]; + uint8_t *p; + size_t si; + char pay; + int n; + + if (pss) + conn = pss->conn; + + switch (reason) { + case LWS_CALLBACK_PROTOCOL_INIT: + break; + + case LWS_CALLBACK_PROTOCOL_DESTROY: + break; + + /* callbacks related to raw socket descriptor "accepted side" */ + + case LWS_CALLBACK_RAW_ADOPT: + lwsl_info("LWS_CALLBACK_RAW_ADOPT\n"); + if (!pss) + return -1; + pss->conn = malloc(sizeof(struct conn)); + if (!pss->conn) + return -1; + memset(pss->conn, 0, sizeof(*pss->conn)); + + pss->conn->dsh = lws_dsh_create(&pt->ss_dsh_owner, + LWS_SS_MTU * 160, 2); + if (!pss->conn->dsh) { + free(pss->conn); + + return -1; + } + + pss->conn->wsi = wsi; + pss->conn->state = LPCS_WAIT_INITIAL_TX; + + /* + * Client is expected to follow the unix domain socket + * acceptance up rapidly with an initial tx containing the + * streamtype name. We can't create the stream until then. + */ + lws_set_timeout(wsi, + PENDING_TIMEOUT_AWAITING_CLIENT_HS_SEND, 3); + break; + + case LWS_CALLBACK_RAW_CLOSE: + lwsl_info("LWS_CALLBACK_RAW_CLOSE:\n"); + + /* + * the client unix domain socket connection has closed... + * eg, client has exited or otherwise has definitively finished + * with the proxying and onward connection + */ + + if (!conn) + break; + + if (conn->ss) { + lwsl_info("%s: destroying ss\n", __func__); + /* sever relationship with ss about to be deleted */ + lws_set_opaque_user_data(wsi, NULL); + + conn->wsi = NULL; + + + lws_ss_destroy(&conn->ss); + /* conn may have gone */ + break; + } + + if (conn->state == LPCS_DESTROYED || !conn->ss) { + /* + * There's no onward secure stream and our client + * connection is closing. Destroy the conn. + */ + lws_dsh_destroy(&conn->dsh); + free(conn); + pss->conn = NULL; + } else + lwsl_debug("%s: CLOSE; ss=%p\n", __func__, conn->ss); + + break; + + case LWS_CALLBACK_RAW_RX: + lwsl_info("%s: RX: rx %d\n", __func__, (int)len); + + if (!conn || !conn->wsi) { + lwsl_err("%s: rx with bad conn state\n", __func__); + + return -1; + } + + // lwsl_hexdump_info(in, len); + + if (conn->state == LPCS_WAIT_INITIAL_TX) { + memset(&ssi, 0, sizeof(ssi)); + ssi.user_alloc = sizeof(ss_proxy_t); + ssi.handle_offset = offsetof(ss_proxy_t, ss); + ssi.opaque_user_data_offset = + offsetof(ss_proxy_t, conn); + ssi.rx = ss_proxy_onward_rx; + ssi.tx = ss_proxy_onward_tx; + ssi.state = ss_proxy_onward_state; + } + + if (lws_ss_deserialize_parse(&conn->parser, + lws_get_context(wsi), conn->dsh, in, len, + &conn->state, conn, &conn->ss, &ssi, 0)) { + lwsl_err("%s: RAW_RX: deserialize_parse fail\n", __func__); + return -1; + } + + if (conn->state == LPCS_REPORTING_FAIL || + conn->state == LPCS_REPORTING_OK) + lws_callback_on_writable(conn->wsi); + + break; + + case LWS_CALLBACK_RAW_WRITEABLE: + // lwsl_notice("LWS_CALLBACK_RAW_PROXY_SRV_WRITEABLE\n"); + + /* + * We can transmit something back to the client from the dsh + * of stuff we received on its behalf from the ss + */ + + if (!conn || !conn->wsi) + break; + + n = 0; + pay = 0; + s[3] = 0; + cp = (const uint8_t *)s; + switch (conn->state) { + case LPCS_REPORTING_FAIL: + s[3] = 1; + /* fallthru */ + case LPCS_REPORTING_OK: + s[0] = LWSSS_SER_RXPRE_CREATE_RESULT; + s[1] = 0; + s[2] = 1; + + n = 4; + + /* + * If there's rideshare sequencing, it's added after the + * first 4 bytes or the create result, comma-separated + */ + + rsp = conn->ss->policy; + + while (rsp) { + if (n != 4 && n < (int)sizeof(s) - 2) + s[n++] = ','; + n += lws_snprintf(&s[n], sizeof(s) - n, + "%s", rsp->streamtype); + rsp = lws_ss_policy_lookup(wsi->context, + rsp->rideshare_streamtype); + } + s[2] = n - 3; + conn->state = LPCS_OPERATIONAL; + lws_set_timeout(wsi, 0, 0); + break; + case LPCS_OPERATIONAL: + if (lws_dsh_get_head(conn->dsh, KIND_SS_TO_P, + (void **)&p, &si)) + break; + cp = p; + +#if defined(LWS_WITH_DETAILED_LATENCY) + if (cp[0] == LWSSS_SER_RXPRE_RX_PAYLOAD && + wsi->context->detailed_latency_cb) { + + /* + * we're fulfilling rx that came in on ss + * by sending it back out to the client on + * the Unix Domain Socket + * + * + 7 u32 write will compute latency here... + * + 11 u32 ust we received from ss + * + * lws_write will report it and fill in + * LAT_DUR_PROXY_CLIENT_REQ_TO_WRITE + */ + + us = lws_now_usecs(); + lws_ser_wu32be(&p[7], us - + lws_ser_ru64be(&p[11])); + lws_ser_wu64be(&p[11], us); + + wsi->detlat.acc_size = + wsi->detlat.req_size = si - 19; + /* time proxy held it */ + wsi->detlat.latencies[ + LAT_DUR_PROXY_RX_TO_ONWARD_TX] = + lws_ser_ru32be(&p[7]); + } +#endif + + pay = 1; + n = (int)si; + break; + default: + break; + } +again: + if (!n) + break; + + n = lws_write(wsi, (uint8_t *)cp, n, LWS_WRITE_RAW); + if (n < 0) { + lwsl_info("%s: WRITEABLE: %d\n", __func__, n); + + goto hangup; + } + + switch (conn->state) { + case LPCS_REPORTING_FAIL: + goto hangup; + case LPCS_OPERATIONAL: + if (pay) + lws_dsh_free((void **)&p); + if (!lws_dsh_get_head(conn->dsh, KIND_SS_TO_P, + (void **)&p, &si)) { + if (!lws_send_pipe_choked(wsi)) { + cp = p; + pay = 1; + n = (int)si; + goto again; + } + lws_callback_on_writable(wsi); + } + break; + default: + break; + } + break; + + default: + break; + } + + return lws_callback_http_dummy(wsi, reason, user, in, len); + +hangup: + //lws_ss_destroy(&conn->ss); + //conn->state = LPCS_DESTROYED; + + /* hang up on him */ + return -1; +} + +static const struct lws_protocols protocols[] = { + { + "ssproxy-protocol", + callback_ss_proxy, + sizeof(struct raw_pss), + 2048, 2048, NULL, 0 + }, + { NULL, NULL, 0, 0, 0, NULL, 0 } +}; + +/* + * called from create_context() + */ + +int +lws_ss_proxy_create(struct lws_context *context, const char *bind, int port) +{ + struct lws_context_creation_info info; + + memset(&info, 0, sizeof(info)); + + info.vhost_name = "ssproxy"; + info.options = LWS_SERVER_OPTION_ADOPT_APPLY_LISTEN_ACCEPT_CONFIG; + info.port = port; + if (!port) { + if (!bind) + bind = "@proxy.ss.lws"; + info.options |= LWS_SERVER_OPTION_UNIX_SOCK; + } + info.iface = bind; + info.unix_socket_perms = "root:root"; + info.listen_accept_role = "raw-skt"; + info.listen_accept_protocol = "ssproxy-protocol"; + info.protocols = protocols; + + if (!lws_create_vhost(context, &info)) { + lwsl_err("%s: Failed to create ss proxy vhost\n", __func__); + + return 1; + } + + return 0; +} diff --git a/lib/secure-streams/secure-streams-serialize.c b/lib/secure-streams/secure-streams-serialize.c new file mode 100644 index 000000000..cd85f0ae2 --- /dev/null +++ b/lib/secure-streams/secure-streams-serialize.c @@ -0,0 +1,926 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2019 - 2020 Andy Green + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * + * In the case Secure Streams protocol needs to pass through a buffer, + * or a streamed connection, the protocol metadata must be serialized. This + * file provides internal apis to perform the serialization and deserialization + * in and out of an lws_dsh fifo-type buffer. + */ + +#include + +typedef enum { + RPAR_TYPE, + RPAR_LEN_MSB, + RPAR_LEN_LSB, + + RPAR_FLAG_B3, + RPAR_FLAG_B2, + RPAR_FLAG_B1, + RPAR_FLAG_B0, + + RPAR_LATA3, + RPAR_LATA2, + RPAR_LATA1, + RPAR_LATA0, + + RPAR_LATB7, + RPAR_LATB6, + RPAR_LATB5, + RPAR_LATB4, + RPAR_LATB3, + RPAR_LATB2, + RPAR_LATB1, + RPAR_LATB0, + + RPAR_RIDESHARE_LEN, + RPAR_RIDESHARE, + + RPAR_RESULT_CREATION_RIDESHARE, + + RPAR_METADATA_NAMELEN, + RPAR_METADATA_NAME, + RPAR_METADATA_VALUE, + + RPAR_PAYLOAD, + + RPAR_RX_TXCR_UPDATE, + + RPAR_STREAMTYPE, + RPAR_INITTXC0, + + RPAR_TXCR0, + + RPAR_RESULT_CREATION, + + RPAR_STATEINDEX, + RPAR_ORD3, + RPAR_ORD2, + RPAR_ORD1, + RPAR_ORD0, +} rx_parser_t; + +#if defined(_DEBUG) +static const char *sn[] = { + "unset", + + "LPCS_WAIT_INITIAL_TX", + "LPCS_REPORTING_FAIL", + "LPCS_REPORTING_OK", + "LPCS_OPERATIONAL", + "LPCS_DESTROYED", + + "LPCS_SENDING_INITIAL_TX", + "LPCS_WAITING_CREATE_RESULT", + "LPCS_LOCAL_CONNECTED", + "LPCS_ONWARD_CONNECT", +}; +#endif + +void +lws_ss_serialize_state_transition(lws_ss_conn_states_t *state, int new_state) +{ +#if defined(_DEBUG) + lwsl_info("%s: %s -> %s\n", __func__, sn[*state], sn[new_state]); +#endif + *state = new_state; +} + + +/* + * event loop received something and is queueing it for the foreign side of + * the dsh to consume later as serialized rx + */ + +int +lws_ss_serialize_rx_payload(struct lws_dsh *dsh, const uint8_t *buf, + size_t len, int flags, const char *rsp) +{ + lws_usec_t us = lws_now_usecs(); + uint8_t pre[128]; + int est = 19, l = 0; + + if (flags & LWSSS_FLAG_RIDESHARE) { + /* + * We should have the rideshare name if we have been told it's + * on a non-default rideshare + */ + assert(rsp); + l = strlen(rsp); + est += 1 + l; + } else + assert(!rsp); + + // lwsl_user("%s: len %d, flags: %d\n", __func__, (int)len, flags); + // lwsl_hexdump_info(buf, len); + + pre[0] = LWSSS_SER_RXPRE_RX_PAYLOAD; + lws_ser_wu16be(&pre[1], len + est - 3); + lws_ser_wu32be(&pre[3], flags); + lws_ser_wu32be(&pre[7], 0); /* write will compute latency here... */ + lws_ser_wu64be(&pre[11], us); /* ... and set this to the write time */ + + /* + * If we are on a non-default rideshare, append the non-default name to + * the headers of the payload part, 1-byte length first + */ + + if (flags & LWSSS_FLAG_RIDESHARE) { + pre[19] = (uint8_t)l; + memcpy(&pre[20], rsp, l); + } + + if (lws_dsh_alloc_tail(dsh, KIND_SS_TO_P, pre, est, buf, len)) { + lwsl_err("%s: unable to alloc in dsh 1\n", __func__); + + return 1; + } + + return 0; +} + +/* + * event loop is consuming dsh-buffered, already-serialized tx from the + * foreign side + */ + +int +lws_ss_deserialize_tx_payload(struct lws_dsh *dsh, struct lws *wsi, + lws_ss_tx_ordinal_t ord, uint8_t *buf, + size_t *len, int *flags) +{ + uint8_t *p; + size_t si; + + if (lws_dsh_get_head(dsh, KIND_C_TO_P, (void **)&p, &si)) { + *len = 0; + return 0; + } + + /* + * The packet in the dsh has a proxying serialization header, process + * and strip it so we just forward the payload + */ + + if (*len <= si - 23 || si < 23) { + /* + * What comes out of the dsh needs to fit in the tx buffer + */ + lwsl_err("%s: *len = %d, si = %d\n", __func__, (int)*len, (int)si); + assert(0); + return 1; + } + if (p[0] != LWSSS_SER_TXPRE_TX_PAYLOAD) { + assert(0); + return 1; + } + + *len = lws_ser_ru16be(&p[1]) - (23 - 3); + assert(*len == si - 23); + + memcpy(buf, p + 23, si - 23); + + *flags = lws_ser_ru32be(&p[3]); + +#if defined(LWS_WITH_DETAILED_LATENCY) + if (wsi && wsi->context->detailed_latency_cb) { + /* + * use the proxied latency information to compute the client + * and our delays, and apply to wsi. + * + * + 7 u32 us held at client before written + * +11 u32 us taken for transit to proxy + * +15 u64 ustime when proxy got packet from client + */ + lws_usec_t us = lws_now_usecs(); + + wsi->detlat.acc_size = wsi->detlat.req_size = si - 23; + wsi->detlat.latencies[LAT_DUR_PROXY_CLIENT_REQ_TO_WRITE] = + lws_ser_ru32be(&p[7]); + wsi->detlat.latencies[LAT_DUR_PROXY_CLIENT_WRITE_TO_PROXY_RX] = + lws_ser_ru32be(&p[11]); + wsi->detlat.latencies[LAT_DUR_PROXY_RX_TO_ONWARD_TX] = + us - lws_ser_ru64be(&p[15]); + + wsi->detlat.latencies[LAT_DUR_USERCB] = 0; + } +#endif + + // lwsl_user("%s: len %d, flags: %d\n", __func__, (int)*len, *flags); + // lwsl_hexdump_info(buf, *len); + + lws_dsh_free((void **)&p); + + return 0; +} + +/* + * event loop side is issuing state, serialize and put it in the dbuf for + * the foreign side to consume later + */ + +int +lws_ss_serialize_state(struct lws_dsh *dsh, lws_ss_constate_t state, + lws_ss_tx_ordinal_t ack) +{ + uint8_t pre[8]; + + lwsl_info("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name(state), + (unsigned int)ack); + + pre[0] = LWSSS_SER_RXPRE_CONNSTATE; + pre[1] = 0; + pre[2] = 5; + pre[3] = (uint8_t)state; + lws_ser_wu32be(&pre[4], ack); + + if (lws_dsh_alloc_tail(dsh, KIND_SS_TO_P, pre, 8, NULL, 0)) { + lwsl_err("%s: unable to alloc in dsh 2\n", __func__); + + return 1; + } + + return 0; +} + +/* + * event loop side was told about remote peer tx credit window update, serialize + * and put it in the dbuf for the foreign side to consume later + */ + +int +lws_ss_serialize_txcr(struct lws_dsh *dsh, int txcr) +{ + uint8_t pre[7]; + + lwsl_info("%s: %d\n", __func__, txcr); + + pre[0] = LWSSS_SER_RXPRE_TXCR_UPDATE; + pre[1] = 0; + pre[2] = 4; + lws_ser_wu32be(&pre[3], txcr); + + if (lws_dsh_alloc_tail(dsh, KIND_SS_TO_P, pre, 7, NULL, 0)) { + lwsl_err("%s: unable to alloc in dsh 2\n", __func__); + + return 1; + } + + return 0; +} + +/* + * event loop side is consuming serialized data from the client via dsh, parse + * it using a bytewise parser for the serialization header(s)... + * it's possibly coalesced + */ + +int +lws_ss_deserialize_parse(struct lws_ss_serialization_parser *par, + struct lws_context *context, + struct lws_dsh *dsh, const uint8_t *cp, size_t len, + lws_ss_conn_states_t *state, void *parconn, + lws_ss_handle_t **pss, lws_ss_info_t *ssi, char client) +{ + lws_ss_metadata_t *pm; + lws_sspc_handle_t *h; + uint8_t pre[23]; + lws_usec_t us; + uint32_t flags; + uint8_t *p; + int n; + + while (len--) { + switch (par->ps) { + case RPAR_TYPE: + par->type = *cp++; + par->ps++; + break; + + case RPAR_LEN_MSB: /* this is remaining frame length */ + par->rem = (*cp++) << 8; + par->ps++; + break; + + case RPAR_LEN_LSB: + par->rem |= *cp++; + switch (par->type) { + + /* event loop side */ + + case LWSSS_SER_TXPRE_TX_PAYLOAD: + if (client) + goto hangup; + if (*state != LPCS_OPERATIONAL) + goto hangup; + par->ps = RPAR_FLAG_B3; + break; + + case LWSSS_SER_TXPRE_DESTROYING: + if (client) + goto hangup; + par->ps = RPAR_TYPE; + lwsl_notice("%s: DESTROYING\n", __func__); + goto hangup; + + case LWSSS_SER_TXPRE_ONWARD_CONNECT: + if (client) + goto hangup; + if (*state != LPCS_OPERATIONAL) + goto hangup; + par->ps = RPAR_TYPE; + if (*pss) + lws_ss_client_connect(*pss); + break; + + case LWSSS_SER_TXPRE_STREAMTYPE: + if (client) + goto hangup; + if (*state != LPCS_WAIT_INITIAL_TX) + goto hangup; + if (par->rem < 4) + goto hangup; + par->ctr = 0; + par->ps = RPAR_INITTXC0; + break; + + case LWSSS_SER_TXPRE_METADATA: + if (client) + goto hangup; + if (par->rem < 3) + goto hangup; + par->ctr = 0; + par->ps = RPAR_METADATA_NAMELEN; + break; + + case LWSSS_SER_TXPRE_TXCR_UPDATE: + par->ps = RPAR_TXCR0; + par->ctr = 0; + break; + + /* client side */ + + case LWSSS_SER_RXPRE_RX_PAYLOAD: + if (!client) + goto hangup; + if (*state != LPCS_OPERATIONAL && + *state != LPCS_LOCAL_CONNECTED) { + lwsl_err("rx in state %d\n", *state); + goto hangup; + } + par->rideshare[0] = '\0'; + par->ps = RPAR_FLAG_B3; + break; + + case LWSSS_SER_RXPRE_CREATE_RESULT: + if (!client) + goto hangup; + if (*state != LPCS_WAITING_CREATE_RESULT) { + lwsl_err("a2\n"); + goto hangup; + } + if (par->rem < 1) { + lwsl_err("a3\n"); + goto hangup; + } + par->ps = RPAR_RESULT_CREATION; + break; + + case LWSSS_SER_RXPRE_CONNSTATE: + if (!client) + goto hangup; + if (*state != LPCS_LOCAL_CONNECTED && + *state != LPCS_OPERATIONAL) { + lwsl_err("a4\n"); + goto hangup; + } + if (par->rem < 4) { + lwsl_err("a5\n"); + goto hangup; + } + par->ps = RPAR_STATEINDEX; + break; + + case LWSSS_SER_RXPRE_TXCR_UPDATE: + par->ctr = 0; + par->ps = RPAR_RX_TXCR_UPDATE; + break; + + default: + lwsl_notice("%s: bad type 0x%x\n", __func__, + par->type); + goto hangup; + } + break; + + case RPAR_FLAG_B3: + case RPAR_FLAG_B2: + case RPAR_FLAG_B1: + case RPAR_FLAG_B0: + par->flags <<= 8; + par->flags |= *cp++; + par->ps++; + if (!par->rem--) + goto hangup; + break; + + case RPAR_LATA3: + case RPAR_LATA2: + case RPAR_LATA1: + case RPAR_LATA0: + par->usd_phandling <<= 8; + par->usd_phandling |= *cp++; + par->ps++; + if (!par->rem--) + goto hangup; + break; + + case RPAR_LATB7: + case RPAR_LATB6: + case RPAR_LATB5: + case RPAR_LATB4: + case RPAR_LATB3: + case RPAR_LATB2: + case RPAR_LATB1: + case RPAR_LATB0: + par->ust_pwait <<= 8; + par->ust_pwait |= *cp++; + par->ps++; + par->frag1 = 1; + if (!par->rem--) + goto hangup; + + if (par->ps == RPAR_RIDESHARE_LEN && + !(par->flags & LWSSS_FLAG_RIDESHARE)) + par->ps = RPAR_PAYLOAD; + + if (par->rem) + break; + + /* fallthru - handle 0-length payload */ + + if (!(par->flags & LWSSS_FLAG_RIDESHARE)) + goto payload_ff; + goto hangup; + + /* + * Inbound rideshare info is provided on the RX packet + * itself + */ + + case RPAR_RIDESHARE_LEN: + par->slen = *cp++; + par->ctr = 0; + par->ps++; + if (par->rem-- < par->slen) + goto hangup; + break; + + case RPAR_RIDESHARE: + par->rideshare[par->ctr++] = *cp++; + if (!par->rem--) + goto hangup; + if (par->ctr != par->slen) + break; + par->ps = RPAR_PAYLOAD; + if (par->rem) + break; + + /* fallthru - handle 0-length payload */ + + case RPAR_PAYLOAD: +payload_ff: + n = (int)len + 1; + if (n > par->rem) + n = par->rem; + if (n > 1380) + n = 1380; + + /* deal with refragmented SOM / EOM flags */ + + flags = par->flags & LWSSS_FLAG_RELATED_START; + if (par->frag1) + flags |= par->flags & + (LWSSS_FLAG_SOM | LWSSS_FLAG_POLL); + + if (par->rem == n) + flags |= par->flags & (LWSSS_FLAG_EOM | + LWSSS_FLAG_RELATED_END); + + par->frag1 = 0; + us = lws_now_usecs(); + + if (!client) { + /* + * Proxy - we received some serialized tx from + * the client. + * + * The header for buffering private to the + * proxy is 23 bytes vs 19 to hold the + * current time when it was buffered + */ + + lwsl_info("%s: C2P RX: len %d\n", __func__, (int)n); + + p = pre; + pre[0] = LWSSS_SER_TXPRE_TX_PAYLOAD; + lws_ser_wu16be(&p[1], n + 23 - 3); + lws_ser_wu32be(&p[3], par->flags); + /* us held at client before written */ + lws_ser_wu32be(&p[7], par->usd_phandling); + /* us taken for transit to proxy */ + lws_ser_wu32be(&p[11], us - par->ust_pwait); + /* time used later to find proxy hold time */ + lws_ser_wu64be(&p[15], us); + + if (lws_dsh_alloc_tail(dsh, KIND_C_TO_P, pre, + 23, cp, n)) { + lwsl_err("%s: unable to alloc in dsh 3\n", + __func__); + + return 1; + } + + lws_ss_request_tx(*pss); + } else { + + /* + * Client receives some RX from proxy + * + * Pass whatever payload we have to ss user + */ + + lwsl_info("%s: P2C RX: len %d\n", __func__, (int)n); + + h = lws_container_of(par, lws_sspc_handle_t, parser); + h->txc.peer_tx_cr_est -= n; + + ssi->rx((void *)pss, (uint8_t *)cp, n, flags); + +#if defined(LWS_WITH_DETAILED_LATENCY) + if (lws_det_lat_active(context)) { + lws_detlat_t d; + + d.type = LDLT_READ; + d.acc_size = d.req_size = n; + d.latencies[LAT_DUR_USERCB] = + lws_now_usecs() - us; + d.latencies[LAT_DUR_PROXY_CLIENT_REQ_TO_WRITE] = + par->usd_phandling; + d.latencies[LAT_DUR_PROXY_CLIENT_WRITE_TO_PROXY_RX] = + us - par->ust_pwait; + + lws_det_lat_cb(context, &d); + } +#endif + } + + if (n) { + cp += n; + par->rem -= n; + len = (len + 1) - n; + } + if (!par->rem) + par->ps = RPAR_TYPE; + break; + + case RPAR_RX_TXCR_UPDATE: + if (!--par->rem && par->ctr != 3) + goto hangup; + + par->temp32 = (par->temp32 << 8) | *cp++; + if (++par->ctr < 4) + break; + + /* + * Proxy is telling us remote endpoint is allowing us + * par->temp32 more bytes tx credit to write to it + */ + + h = lws_container_of(par, lws_sspc_handle_t, parser); + h->txc.tx_cr += par->temp32; + lwsl_info("%s: RX_PEER_TXCR: %d\n", __func__, par->temp32); + lws_sspc_request_tx(h); /* in case something waiting */ + par->ctr = 0; + par->ps = RPAR_TYPE; + break; + + case RPAR_INITTXC0: + if (!--par->rem) + goto hangup; + + par->temp32 = (par->temp32 << 8) | *cp++; + if (++par->ctr < 4) + break; + + par->txcr_out = par->temp32; + par->ctr = 0; + par->ps = RPAR_STREAMTYPE; + break; + + /* + * These are the client adjusting our / the remote peer ability + * to send back to him. He's sending a signed u32 BE + */ + + case RPAR_TXCR0: + + par->temp32 = (par->temp32 << 8) | *cp++; + if (++par->ctr < 4) { + if (!--par->rem) + goto hangup; + break; + } + + if (--par->rem) + goto hangup; + + if (!client) { + /* + * We're the proxy, being told by the client + * that it wants to allow more tx from the peer + * on the onward connection towards it. + */ +#if defined(LWS_ROLE_H2) || defined(LWS_ROLE_MQTT) + if ((*pss)->wsi) { + lws_wsi_tx_credit((*pss)->wsi, + LWSTXCR_PEER_TO_US, + par->temp32); + lwsl_notice("%s: proxy RX_PEER_TXCR: +%d (est %d)\n", + __func__, par->temp32, + (*pss)->wsi->txc.peer_tx_cr_est); + lws_ss_request_tx(*pss); + } else +#endif + lwsl_info("%s: dropping TXCR\n", __func__); + } else { + /* + * We're the client, being told by the proxy + * about tx credit being given to us from the + * remote peer, allowing the client to write to + * it. + */ + h = lws_container_of(par, lws_sspc_handle_t, parser); + h->txc.tx_cr += par->temp32; + lwsl_info("%s: client RX_PEER_TXCR: %d\n", + __func__, par->temp32); + lws_sspc_request_tx(h); /* in case something waiting */ + } + par->ps = RPAR_TYPE; + break; + + case RPAR_METADATA_NAMELEN: + if (!--par->rem) + goto hangup; + par->slen = *cp++; + if (par->slen >= sizeof(par->metadata_name) - 1) + goto hangup; + par->ctr = 0; + par->ps++; + break; + + case RPAR_METADATA_NAME: + if (!--par->rem) + goto hangup; + par->metadata_name[par->ctr++] = *cp++; + if (par->ctr != par->slen) + break; + par->ps = RPAR_METADATA_VALUE; + + /* only non-client side can receive these */ + + /* + * This is the policy's metadata list for the given + * name + */ + pm = lws_ss_policy_metadata((*pss)->policy, + par->metadata_name); + if (!pm) { + lwsl_err("%s: metadata %s not in proxy policy\n", + __func__, par->metadata_name); + + goto hangup; + } + + par->ssmd = &(*pss)->metadata[pm->length]; + + if (par->ssmd->value_on_lws_heap) + lws_free_set_NULL(par->ssmd->value); + par->ssmd->value_on_lws_heap = 0; + + par->ssmd->value = lws_malloc(par->rem + 1, "metadata"); + if (!par->ssmd->value) { + lwsl_err("%s: OOM mdv\n", __func__); + goto hangup; + } + par->ssmd->length = par->rem; + /* mark it as needing cleanup */ + par->ssmd->value_on_lws_heap = 1; + par->ctr = 0; + break; + + case RPAR_METADATA_VALUE: + ((uint8_t *)(par->ssmd->value))[par->ctr++] = *cp++; + if (--par->rem) + break; + + /* we think we got all the value */ + lwsl_info("%s: RPAR_METADATA_VALUE for %s (len %d)\n", + __func__, par->ssmd->name, + (int)par->ssmd->length); + lwsl_hexdump_info(par->ssmd->value, par->ssmd->length); + par->ps = RPAR_TYPE; + break; + + case RPAR_STREAMTYPE: + if (client) + goto hangup; + if (par->ctr == sizeof(par->streamtype) - 1) + goto hangup; + + /* + * We're the proxy, creating an SS on behalf of a + * client + */ + + par->streamtype[par->ctr++] = *cp++; + if (--par->rem) + break; + + par->ps = RPAR_TYPE; + par->streamtype[par->ctr] = '\0'; + lwsl_notice("%s: creating proxied ss '%s', txcr %d\n", + __func__, par->streamtype, par->txcr_out); + + ssi->streamtype = par->streamtype; + if (par->txcr_out) + ssi->manual_initial_tx_credit = par->txcr_out; + + if (lws_ss_create(context, 0, ssi, parconn, pss, NULL, NULL)) { + /* + * We're unable to create the onward secure + * stream he asked for... schedule a chance to + * inform him + */ + lwsl_err("%s: create '%s' fail\n", + __func__, par->streamtype); + *state = LPCS_REPORTING_FAIL; + } else { + lwsl_debug("%s: create '%s' OK\n", + __func__, par->streamtype); + *state = LPCS_REPORTING_OK; + } + + if (*pss) { + (*pss)->being_serialized = 1; + lwsl_notice("%s: Created SS initial credit %d\n", + __func__, par->txcr_out); + (*pss)->info.manual_initial_tx_credit = par->txcr_out; + } + + /* parent needs to schedule write on client conn */ + break; + + /* clientside states */ + + case RPAR_RESULT_CREATION: + if (*cp++) { + lwsl_err("%s: stream creation failed\n", + __func__); + goto hangup; + } + + lws_ss_serialize_state_transition(state, + LPCS_LOCAL_CONNECTED); + h = lws_container_of(par, lws_sspc_handle_t, parser); + if (h->cwsi) + lws_callback_on_writable(h->cwsi); + + /* + * This is telling us that the streamtype could be (and + * was) created at the proxy. It's not telling us that + * the onward peer connection could be connected. + * + * We'll get a proxied state() coming later that informs + * us about the situation with that. + */ + + par->rsl_pos = 0; + par->rsl_idx = 0; + h = lws_container_of(par, lws_sspc_handle_t, parser); + memset(&h->rideshare_ofs[0], 0, sizeof(h->rideshare_ofs[0])); + h->rideshare_list[0] = '\0'; + h->rsidx = 0; + + if (!--par->rem) + par->ps = RPAR_TYPE; + else { + par->ps = RPAR_RESULT_CREATION_RIDESHARE; + if (par->rem >= sizeof(h->rideshare_list)) + goto hangup; + } + break; + + case RPAR_RESULT_CREATION_RIDESHARE: + h = lws_container_of(par, lws_sspc_handle_t, parser); + if (*cp == ',') { + cp++; + h->rideshare_list[par->rsl_pos++] = '\0'; + if (par->rsl_idx == LWS_ARRAY_SIZE(h->rideshare_ofs)) + goto hangup; + h->rideshare_ofs[++par->rsl_idx] = par->rsl_pos; + } else + h->rideshare_list[par->rsl_pos++] = *cp++; + if (!--par->rem) + par->ps = RPAR_TYPE; + break; + + case RPAR_STATEINDEX: + par->ctr = *cp++; + par->ps = RPAR_ORD3; + break; + + case RPAR_ORD3: + par->flags = (*cp++) << 24; + par->ps++; + break; + + case RPAR_ORD2: + par->flags |= (*cp++) << 16; + par->ps++; + break; + + case RPAR_ORD1: + par->flags |= (*cp++) << 8; + par->ps++; + break; + + case RPAR_ORD0: + par->flags |= *cp++; + par->ps++; + par->ps = RPAR_TYPE; + + /* + * we received a proxied state change + */ + + switch (par->ctr) { + case LWSSSCS_DISCONNECTED: + case LWSSSCS_UNREACHABLE: + case LWSSSCS_AUTH_FAILED: + lws_ss_serialize_state_transition(state, + LPCS_LOCAL_CONNECTED); + break; + case LWSSSCS_CONNECTED: + lwsl_info("%s: CONNECTED %s\n", __func__, + ssi->streamtype); + lws_ss_serialize_state_transition(state, + LPCS_OPERATIONAL); + break; + default: + break; + } + + if (par->ctr < 0 || par->ctr > 9) + goto hangup; + +#if defined(_DEBUG) + lwsl_info("%s: forwarding proxied state %s\n", + __func__, sn[par->ctr]); +#endif + if (ssi->state((void *)pss, NULL, par->ctr, par->flags)) + goto hangup; + break; + + + default: + goto hangup; + } + } + + return 0; + +hangup: + return -1; +} diff --git a/lib/secure-streams/secure-streams.c b/lib/secure-streams/secure-streams.c new file mode 100644 index 000000000..652f6c9a7 --- /dev/null +++ b/lib/secure-streams/secure-streams.c @@ -0,0 +1,569 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2019 - 2020 Andy Green + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include + +static const struct ss_pcols *ss_pcols[] = { +#if defined(LWS_ROLE_H1) + &ss_pcol_h1, /* LWSSSP_H1 */ +#else + NULL, +#endif +#if defined(LWS_ROLE_H2) + &ss_pcol_h2, /* LWSSSP_H2 */ +#else + NULL, +#endif +#if defined(LWS_ROLE_WS) + &ss_pcol_ws, /* LWSSSP_WS */ +#else + NULL, +#endif +#if defined(LWS_ROLE_MQTT) + &ss_pcol_mqtt, /* LWSSSP_MQTT */ +#else + NULL, +#endif +}; + +static const char *state_names[] = { + "LWSSSCS_CREATING", + "LWSSSCS_DISCONNECTED", + "LWSSSCS_UNREACHABLE", + "LWSSSCS_AUTH_FAILED", + "LWSSSCS_CONNECTED", + "LWSSSCS_CONNECTING", + "LWSSSCS_DESTROYING", + "LWSSSCS_POLL", + "LWSSSCS_ALL_RETRIES_FAILED", + "LWSSSCS_QOS_ACK_REMOTE", + "LWSSSCS_QOS_NACK_REMOTE", + "LWSSSCS_QOS_ACK_LOCAL", + "LWSSSCS_QOS_NACK_LOCAL", +}; + +const char * +lws_ss_state_name(int state) +{ + if (state >= (int)LWS_ARRAY_SIZE(state_names)) + return "unknown"; + + return state_names[state]; +} + +int +lws_ss_event_helper(lws_ss_handle_t *h, lws_ss_constate_t cs) +{ + if (!h) + return 0; + +#if defined(LWS_WITH_SEQUENCER) + /* + * A parent sequencer for the ss is optional, if we have one, keep it + * informed of state changes on the ss connection + */ + if (h->seq && cs != LWSSSCS_DESTROYING) + lws_seq_queue_event(h->seq, LWSSEQ_SS_STATE_BASE + cs, + (void *)h, NULL); +#endif + + if (h->h_sink &&h->h_sink->info.state(h->sink_obj, h->h_sink, cs, 0)) + return 1; + + return h->info.state(ss_to_userobj(h), NULL, cs, 0); +} + +static void +lws_ss_timeout_sul_check_cb(lws_sorted_usec_list_t *sul) +{ + lws_ss_handle_t *h = lws_container_of(sul, lws_ss_handle_t, sul); + + lwsl_err("%s: retrying ss h %p after backoff\n", __func__, h); + /* we want to retry... */ + h->seqstate = SSSEQ_DO_RETRY; + + lws_ss_request_tx(h); +} + +int +lws_ss_exp_cb_metadata(void *priv, const char *name, char *out, size_t *pos, + size_t olen, size_t *exp_ofs) +{ + lws_ss_handle_t *h = (lws_ss_handle_t *)priv; + const char *replace = NULL; + size_t total, budget; + lws_ss_metadata_t *md = lws_ss_policy_metadata(h->policy, name); + + if (!md) { + lwsl_err("%s: Unknown metadata %s\n", __func__, name); + + return LSTRX_FATAL_NAME_UNKNOWN; + } + + lwsl_info("%s %s %d\n", __func__, name, (int)md->length); + + replace = h->metadata[md->length].value; + total = h->metadata[md->length].length; + // lwsl_hexdump_err(replace, total); + + budget = olen - *pos; + total -= *exp_ofs; + if (total < budget) + budget = total; + + memcpy(out + *pos, replace + (*exp_ofs), budget); + *exp_ofs += budget; + *pos += budget; + + if (budget == total) + return LSTRX_DONE; + + return LSTRX_FILLED_OUT; +} + +int +lws_ss_set_timeout_us(lws_ss_handle_t *h, lws_usec_t us) +{ + struct lws_context_per_thread *pt = &h->context->pt[h->tsi]; + + h->sul.cb = lws_ss_timeout_sul_check_cb; + __lws_sul_insert(&pt->pt_sul_owner, &h->sul, us); + + return 0; +} + +int +lws_ss_backoff(lws_ss_handle_t *h) +{ + uint64_t ms; + char conceal; + + if (h->seqstate == SSSEQ_RECONNECT_WAIT) + return 0; + + /* figure out what we should do about another retry */ + + lwsl_info("%s: ss %p: retry backoff after failure\n", __func__, h); + ms = lws_retry_get_delay_ms(h->context, h->policy->retry_bo, + &h->retry, &conceal); + if (!conceal) { + lwsl_info("%s: ss %p: abandon conn attempt \n",__func__, h); + h->seqstate = SSSEQ_IDLE; + lws_ss_event_helper(h, LWSSSCS_ALL_RETRIES_FAILED); + return 1; + } + + h->seqstate = SSSEQ_RECONNECT_WAIT; + lws_ss_set_timeout_us(h, ms * LWS_US_PER_MS); + + lwsl_info("%s: ss %p: retry wait %"PRIu64"ms\n", __func__, h, ms); + + return 0; +} + +int +lws_ss_client_connect(lws_ss_handle_t *h) +{ + struct lws_client_connect_info i; + const struct ss_pcols *ssp; + union lws_ss_contemp ct; + char path[128]; + + if (!h->policy) { + lwsl_err("%s: ss with no policy\n", __func__); + + return -1; + } + + /* + * We are already bound to a sink? + */ + + if (h->h_sink) + return 0; + + memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */ + i.context = h->context; + + if (h->policy->flags & LWSSSPOLF_TLS) { + lwsl_info("%s: using tls\n", __func__); + i.ssl_connection = LCCSCF_USE_SSL; + + if (!h->policy->trust_store) { + lwsl_err("%s: tls required but no policy trust store\n", + __func__); + + return -1; + } + + i.vhost = lws_get_vhost_by_name(h->context, + h->policy->trust_store->name); + if (!i.vhost) { + lwsl_err("%s: missing vh for policy ca\n", __func__); + + return -1; + } + } + + i.address = h->policy->endpoint; + i.port = h->policy->port; + i.host = i.address; + i.origin = i.address; + i.opaque_user_data = h; + i.seq = h->seq; + i.retry_and_idle_policy = h->policy->retry_bo; + i.sys_tls_client_cert = h->policy->client_cert; + + i.path = ""; + + ssp = ss_pcols[(int)h->policy->protocol]; + if (!ssp) { + lwsl_err("%s: unsupported protocol\n", __func__); + + return -1; + } + i.alpn = ssp->alpn; + + /* + * For http, we can get the method from the http object, override in + * the protocol-specific munge callback below if not http + */ + i.method = h->policy->u.http.method; + i.protocol = ssp->protocol_name; /* lws protocol name */ + i.local_protocol_name = i.protocol; + + ssp->munge(h, path, sizeof(path), &i, &ct); + + i.pwsi = &h->wsi; + + if (h->policy->plugins[0] && h->policy->plugins[0]->munge) + h->policy->plugins[0]->munge(h, path, sizeof(path)); + + lwsl_info("%s: connecting %s, '%s' '%s' %s\n", __func__, i.method, + i.alpn, i.address, i.path); + + h->txn_ok = 0; + if (lws_ss_event_helper(h, LWSSSCS_CONNECTING)) + return -1; + + if (!lws_client_connect_via_info(&i)) { + lws_ss_event_helper(h, LWSSSCS_UNREACHABLE); + lws_ss_backoff(h); + + return 1; + } + + return 0; +} + + +/* + * Public API + */ + +/* + * Create either a stream or a sink + */ + +int +lws_ss_create(struct lws_context *context, int tsi, const lws_ss_info_t *ssi, + void *opaque_user_data, lws_ss_handle_t **ppss, + struct lws_sequencer *seq_owner, const char **ppayload_fmt) +{ + struct lws_context_per_thread *pt = &context->pt[tsi]; + const lws_ss_policy_t *pol; + lws_ss_metadata_t *smd; + lws_ss_handle_t *h; + size_t size; + void **v; + char *p; + int n; + + pol = lws_ss_policy_lookup(context, ssi->streamtype); + if (!pol) { + lwsl_err("%s: unknown stream type %s\n", __func__, + ssi->streamtype); + return 1; + } + + if (ssi->register_sink) { + /* + * This can register a secure streams sink as well as normal + * secure streams connections. If that's what's happening, + * confirm the policy agrees that this streamtype should be + * directed to a sink. + */ + if (!(pol->flags & LWSSSPOLF_LOCAL_SINK)) { + /* + * Caller wanted to create a sink for this streamtype, + * but the policy does not agree the streamtype should + * be routed to a local sink. + */ + lwsl_err("%s: %s policy does not allow local sink\n", + __func__, ssi->streamtype); + + return 1; + } + } else { + + if (!(pol->flags & LWSSSPOLF_LOCAL_SINK)) { + + } +// lws_dll2_foreach_safe(&pt->ss_owner, NULL, lws_ss_destroy_dll); + } + + /* + * We overallocate and point to things in the overallocation... + * + * 1) the user_alloc from the stream info + * 2) network auth plugin instantiation data + * 3) stream auth plugin instantiation data + * 4) as many metadata pointer structs as the policy tells + * 5) the streamtype name (length is not aligned) + * + * ... when we come to destroy it, just one free to do. + */ + + size = sizeof(*h) + ssi->user_alloc + strlen(ssi->streamtype) + 1; + if (pol->plugins[0]) + size += pol->plugins[0]->alloc; + if (pol->plugins[1]) + size += pol->plugins[1]->alloc; + size += pol->metadata_count * sizeof(lws_ss_metadata_t); + + h = lws_zalloc(size, __func__); + if (!h) + return 2; + + h->info = *ssi; + h->policy = pol; + h->context = context; + h->tsi = tsi; + h->seq = seq_owner; + + /* start of overallocated area */ + p = (char *)&h[1]; + + /* set the handle pointer in the user data struct */ + v = (void **)(p + ssi->handle_offset); + *v = h; + + /* set the opaque user data in the user data struct */ + v = (void **)(p + ssi->opaque_user_data_offset); + *v = opaque_user_data; + + p += ssi->user_alloc; + + if (pol->plugins[0]) { + h->nauthi = p; + p += pol->plugins[0]->alloc; + } + if (pol->plugins[1]) { + h->sauthi = p; + p += pol->plugins[1]->alloc; + } + + if (pol->metadata_count) { + h->metadata = (lws_ss_metadata_t *)p; + p += pol->metadata_count * sizeof(lws_ss_metadata_t); + + lwsl_info("%s: %s metadata count %d\n", __func__, + pol->streamtype, pol->metadata_count); + } + + smd = pol->metadata; + for (n = 0; n < pol->metadata_count; n++) { + h->metadata[n].name = smd->name; + if (n + 1 == pol->metadata_count) + h->metadata[n].next = NULL; + else + h->metadata[n].next = &h->metadata[n + 1]; + smd = smd->next; + } + + memcpy(p, ssi->streamtype, strlen(ssi->streamtype) + 1); + h->info.streamtype = p; + + lws_pt_lock(pt, __func__); + lws_dll2_add_head(&h->list, &pt->ss_owner); + lws_pt_unlock(pt); + + if (ppss) + *ppss = h; + + if (ppayload_fmt) + *ppayload_fmt = pol->payload_fmt; + + if (ssi->register_sink) { + /* + * + */ + } + + lws_ss_event_helper(h, LWSSSCS_CREATING); + + if (!ssi->register_sink && (h->policy->flags & LWSSSPOLF_NAILED_UP)) + if (lws_ss_client_connect(h)) + lws_ss_backoff(h); + + return 0; +} + +void +lws_ss_destroy(lws_ss_handle_t **ppss) +{ + struct lws_context_per_thread *pt; + lws_ss_handle_t *h = *ppss; + lws_ss_metadata_t *pmd; + + if (!h) + return; + + if (h->wsi) { + /* + * Don't let the wsi point to us any more, + * we (the ss object bound to the wsi) are going away now + */ +// lws_set_opaque_user_data(h->wsi, NULL); + lws_set_timeout(h->wsi, 1, LWS_TO_KILL_SYNC); + } + + pt = &h->context->pt[h->tsi]; + + lws_pt_lock(pt, __func__); + *ppss = NULL; + lws_dll2_remove(&h->list); + lws_dll2_remove(&h->to_list); + lws_ss_event_helper(h, LWSSSCS_DESTROYING); + lws_pt_unlock(pt); + + /* in proxy case, metadata value on heap may need cleaning up */ + + pmd = h->metadata; + while (pmd) { + lwsl_info("%s: pmd %p\n", __func__, pmd); + if (pmd->value_on_lws_heap) + lws_free_set_NULL(pmd->value); + pmd = pmd->next; + } + + lws_sul_schedule(h->context, 0, &h->sul, NULL, LWS_SET_TIMER_USEC_CANCEL); + + lws_free_set_NULL(h); +} + +void +lws_ss_request_tx(lws_ss_handle_t *h) +{ + lwsl_info("%s: wsi %p\n", __func__, h->wsi); + + if (h->wsi) { + lws_callback_on_writable(h->wsi); + + return; + } + + if (h->seqstate != SSSEQ_IDLE && + h->seqstate != SSSEQ_DO_RETRY) + return; + + h->seqstate = SSSEQ_TRY_CONNECT; + lws_ss_event_helper(h, LWSSSCS_POLL); + + if (lws_ss_client_connect(h)) + lws_ss_backoff(h); +} + +void +lws_ss_request_tx_len(lws_ss_handle_t *h, unsigned long len) +{ + if (h->wsi) + h->wsi->http.writeable_len = len; + else + h->writeable_len = len; + lws_ss_request_tx(h); +} + +/* + * private helpers + */ + +/* used on context destroy when iterating listed lws_ss on a pt */ + +int +lws_ss_destroy_dll(struct lws_dll2 *d, void *user) +{ + lws_ss_handle_t *h = lws_container_of(d, lws_ss_handle_t, list); + + lws_ss_destroy(&h); + + return 0; +} + +struct lws_sequencer * +lws_ss_get_sequencer(lws_ss_handle_t *h) +{ + return h->seq; +} + +struct lws_context * +lws_ss_get_context(struct lws_ss_handle *h) +{ + return h->context; +} + +const char * +lws_ss_rideshare(struct lws_ss_handle *h) +{ + if (!h->rideshare) + return h->policy->streamtype; + + return h->rideshare->streamtype; +} + +int +lws_ss_add_peer_tx_credit(struct lws_ss_handle *h, int32_t bump) +{ + const struct ss_pcols *ssp; + + ssp = ss_pcols[(int)h->policy->protocol]; + + if (h->wsi && ssp && ssp->tx_cr_add) + return ssp->tx_cr_add(h, bump); + + return 0; +} + +int +lws_ss_get_est_peer_tx_credit(struct lws_ss_handle *h) +{ + const struct ss_pcols *ssp; + + ssp = ss_pcols[(int)h->policy->protocol]; + + if (h->wsi && ssp && ssp->tx_cr_add) + return ssp->tx_cr_est(h); + + return 0; +} diff --git a/lib/secure-streams/system/auth-api.amazon.com/auth.c b/lib/secure-streams/system/auth-api.amazon.com/auth.c new file mode 100644 index 000000000..697ae77b4 --- /dev/null +++ b/lib/secure-streams/system/auth-api.amazon.com/auth.c @@ -0,0 +1,276 @@ +/* + * LWA auth support for Secure Streams + * + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2019 - 2020 Andy Green + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include + +typedef struct ss_api_amazon_auth { + struct lws_ss_handle *ss; + void *opaque_data; + /* ... application specific state ... */ + struct lejp_ctx jctx; + lws_sorted_usec_list_t sul; + size_t pos; + int expires_secs; +} ss_api_amazon_auth_t; + +static const char * const lejp_tokens_lwa[] = { + "access_token", + "expires_in", +}; + +typedef enum { + LSSPPT_ACCESS_TOKEN, + LSSPPT_EXPIRES_IN, +} lejp_tokens_t; + +enum { + AUTH_IDX_LWA, + AUTH_IDX_ROOT, +}; + +static void +lws_ss_sys_auth_api_amazon_com_kick(lws_sorted_usec_list_t *sul) +{ + struct lws_context *context = lws_container_of(sul, struct lws_context, + sul_api_amazon_com_kick); + + lws_state_transition_steps(&context->mgr_system, + LWS_SYSTATE_OPERATIONAL); +} + +static void +lws_ss_sys_auth_api_amazon_com_renew(lws_sorted_usec_list_t *sul) +{ + struct lws_context *context = lws_container_of(sul, struct lws_context, + sul_api_amazon_com); + + /* if nothing is there to intercept anything, go all the way */ + lws_state_transition_steps(&context->mgr_system, + LWS_SYSTATE_OPERATIONAL); +} + +static signed char +auth_api_amazon_com_parser_cb(struct lejp_ctx *ctx, char reason) +{ + ss_api_amazon_auth_t *m = (ss_api_amazon_auth_t *)ctx->user; + struct lws_context *context = (struct lws_context *)m->opaque_data; + + if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match) + return 0; + + switch (ctx->path_match - 1) { + case LSSPPT_ACCESS_TOKEN: + if (!ctx->npos) + break; + if (lws_system_blob_heap_append(lws_system_get_blob(context, + LWS_SYSBLOB_TYPE_AUTH, + AUTH_IDX_LWA), + (const uint8_t *)ctx->buf, + ctx->npos)) { + lwsl_err("%s: unable to store auth token\n", __func__); + return -1; + } + break; + case LSSPPT_EXPIRES_IN: + m->expires_secs = atoi(ctx->buf); + lws_sul_schedule(context, 0, &context->sul_api_amazon_com, + lws_ss_sys_auth_api_amazon_com_renew, + (uint64_t)m->expires_secs * LWS_US_PER_SEC); + break; + } + + return 0; +} + +/* secure streams payload interface */ + +static int +ss_api_amazon_auth_rx(void *userobj, const uint8_t *buf, size_t len, int flags) +{ + ss_api_amazon_auth_t *m = (ss_api_amazon_auth_t *)userobj; + struct lws_context *context = (struct lws_context *)m->opaque_data; + lws_system_blob_t *ab; + size_t total; + int n; + + ab = lws_system_get_blob(context, LWS_SYSBLOB_TYPE_AUTH, AUTH_IDX_LWA); + + if (buf) { + if (flags & LWSSS_FLAG_SOM) { + lejp_construct(&m->jctx, auth_api_amazon_com_parser_cb, + m, lejp_tokens_lwa, + LWS_ARRAY_SIZE(lejp_tokens_lwa)); + lws_system_blob_heap_empty(ab); + } + + n = (int)(signed char)lejp_parse(&m->jctx, buf, len); + if (n < 0) { + lejp_destruct(&m->jctx); + lws_system_blob_destroy( + lws_system_get_blob(context, + LWS_SYSBLOB_TYPE_AUTH, + AUTH_IDX_LWA)); + + return -1; + } + } + if (!(flags & LWSSS_FLAG_EOM)) + return 0; + + /* we should have the auth token now */ + + total = lws_system_blob_get_size(ab); + lwsl_notice("%s: acquired %u-byte api.amazon.com auth token, exp %ds\n", + __func__, (unsigned int)total, m->expires_secs); + + lejp_destruct(&m->jctx); + + /* we move the system state at auth connection close */ + + return 0; +} + +static int +ss_api_amazon_auth_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, + size_t *len, int *flags) +{ + ss_api_amazon_auth_t *m = (ss_api_amazon_auth_t *)userobj; + struct lws_context *context = (struct lws_context *)m->opaque_data; + lws_system_blob_t *ab; + size_t total; + int n; + + /* + * We send out auth slot AUTH_IDX_ROOT, it's the LWA user / device + * identity token + */ + + ab = lws_system_get_blob(context, LWS_SYSBLOB_TYPE_AUTH, AUTH_IDX_ROOT); + total = lws_system_blob_get_size(ab); + + n = lws_system_blob_get(ab, buf, len, m->pos); + if (n < 0) + return 1; + + if (!m->pos) + *flags |= LWSSS_FLAG_SOM; + + m->pos += *len; + + if (m->pos == total) { + *flags |= LWSSS_FLAG_EOM; + m->pos = 0; /* for next time */ + } + + return 0; +} + +static int +ss_api_amazon_auth_state(void *userobj, void *sh, lws_ss_constate_t state, + lws_ss_tx_ordinal_t ack) +{ + ss_api_amazon_auth_t *m = (ss_api_amazon_auth_t *)userobj; + struct lws_context *context = (struct lws_context *)m->opaque_data; + size_t s; + + lwsl_info("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name(state), + (unsigned int)ack); + + switch (state) { + case LWSSSCS_CREATING: + case LWSSSCS_CONNECTING: + s = lws_system_blob_get_size( + lws_system_get_blob(context, LWS_SYSBLOB_TYPE_AUTH, + AUTH_IDX_ROOT)); + lws_ss_request_tx_len(m->ss, s); + m->pos = 0; + break; + + case LWSSSCS_DISCONNECTED: + /* + * We defer moving the system state forward until we have + * closed our connection + tls for the auth action... this is + * because on small systems, we need that memory recovered + * before we can make another connection subsequently. + * + * At this point, we're ultimately being called from within + * the wsi close process, the tls tunnel is not freed yet. + * Use a sul to actually do it next time around the event loop + * when the close process for the auth wsi has completed and + * the related tls is already freed. + */ + s = lws_system_blob_get_size( + lws_system_get_blob(context, LWS_SYSBLOB_TYPE_AUTH, + AUTH_IDX_LWA)); + + if (s) + lws_sul_schedule(context, 0, + &context->sul_api_amazon_com_kick, + lws_ss_sys_auth_api_amazon_com_kick, 1); + break; + + case LWSSSCS_DESTROYING: + lws_sul_schedule(context, 0, &context->sul_api_amazon_com, + NULL, LWS_SET_TIMER_USEC_CANCEL); + lws_system_blob_destroy( + lws_system_get_blob(context, LWS_SYSBLOB_TYPE_AUTH, + AUTH_IDX_LWA)); + break; + + default: + break; + } + + return 0; +} + +int +lws_ss_sys_auth_api_amazon_com(struct lws_context *context) +{ + lws_ss_info_t ssi; + + if (context->hss_auth) /* already exists */ + return 0; + + /* We're making an outgoing secure stream ourselves */ + + memset(&ssi, 0, sizeof(ssi)); + ssi.handle_offset = offsetof(ss_api_amazon_auth_t, ss); + ssi.opaque_user_data_offset = offsetof(ss_api_amazon_auth_t, opaque_data); + ssi.rx = ss_api_amazon_auth_rx; + ssi.tx = ss_api_amazon_auth_tx; + ssi.state = ss_api_amazon_auth_state; + ssi.user_alloc = sizeof(ss_api_amazon_auth_t); + ssi.streamtype = "api_amazon_com_auth"; + + if (lws_ss_create(context, 0, &ssi, context, &context->hss_auth, + NULL, NULL)) { + lwsl_info("%s: Create LWA auth ss failed (policy?)\n", __func__); + return 1; + } + + return 0; +} diff --git a/lib/secure-streams/system/fetch-policy/fetch-policy.c b/lib/secure-streams/system/fetch-policy/fetch-policy.c new file mode 100644 index 000000000..bd0a3e446 --- /dev/null +++ b/lib/secure-streams/system/fetch-policy/fetch-policy.c @@ -0,0 +1,159 @@ +/* + * Policy fetching for Secure Streams + * + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2019 - 2020 Andy Green + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include + +typedef struct ss_fetch_policy { + struct lws_ss_handle *ss; + void *opaque_data; + /* ... application specific state ... */ + + lws_sorted_usec_list_t sul; + + uint8_t partway; +} ss_fetch_policy_t; + +/* secure streams payload interface */ + +static int +ss_fetch_policy_rx(void *userobj, const uint8_t *buf, size_t len, int flags) +{ + ss_fetch_policy_t *m = (ss_fetch_policy_t *)userobj; + struct lws_context *context = (struct lws_context *)m->opaque_data; + + if (flags & LWSSS_FLAG_SOM) { + if (lws_ss_policy_parse_begin(context)) + return 1; + m->partway = 1; + } + + if (len && lws_ss_policy_parse(context, buf, len) < 0) + return 1; + + if (flags & LWSSS_FLAG_EOM) + m->partway = 2; + + return 0; +} + +static int +ss_fetch_policy_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, + size_t *len, int *flags) +{ + return 1; +} + +static void +policy_set(lws_sorted_usec_list_t *sul) +{ + ss_fetch_policy_t *m = lws_container_of(sul, ss_fetch_policy_t, sul); + struct lws_context *context = (struct lws_context *)m->opaque_data; + + /* + * We get called if the policy parse was successful, just after the + * ss connection close that was using the vhost from the old policy + */ + + if (lws_ss_policy_set(context, "updated")) + lwsl_err("%s: policy set failed\n", __func__); + else { + context->policy_updated = 1; + lws_state_transition_steps(&context->mgr_system, + LWS_SYSTATE_OPERATIONAL); + } +} + +static int +ss_fetch_policy_state(void *userobj, void *sh, lws_ss_constate_t state, + lws_ss_tx_ordinal_t ack) +{ + ss_fetch_policy_t *m = (ss_fetch_policy_t *)userobj; + struct lws_context *context = (struct lws_context *)m->opaque_data; + + lwsl_info("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name(state), + (unsigned int)ack); + + switch (state) { + case LWSSSCS_CREATING: + lws_ss_request_tx(m->ss); + break; + case LWSSSCS_CONNECTING: + break; + + case LWSSSCS_DISCONNECTED: + lwsl_info("%s: DISCONNECTED\n", __func__); + switch (m->partway) { + case 1: + lws_ss_policy_parse_abandon(context); + break; + + case 2: + lws_sul_schedule(context, 0, &m->sul, policy_set, 1); + break; + } + m->partway = 0; + break; + + default: + break; + } + + return 0; +} + +int +lws_ss_sys_fetch_policy(struct lws_context *context) +{ + lws_ss_info_t ssi; + + if (context->hss_fetch_policy) /* already exists */ + return 0; + + /* We're making an outgoing secure stream ourselves */ + + memset(&ssi, 0, sizeof(ssi)); + ssi.handle_offset = offsetof(ss_fetch_policy_t, ss); + ssi.opaque_user_data_offset = offsetof(ss_fetch_policy_t, opaque_data); + ssi.rx = ss_fetch_policy_rx; + ssi.tx = ss_fetch_policy_tx; + ssi.state = ss_fetch_policy_state; + ssi.user_alloc = sizeof(ss_fetch_policy_t); + ssi.streamtype = "fetch_policy"; + + if (lws_ss_create(context, 0, &ssi, context, &context->hss_fetch_policy, + NULL, NULL)) { + /* + * If there's no fetch_policy streamtype, it can just be we're + * running on a proxied client with no policy of its own, + * it's OK. + */ + lwsl_info("%s: Create LWA auth ss failed (policy?)\n", __func__); + + return 1; + } + + return 0; +} diff --git a/lib/system/async-dns/async-dns-parse.c b/lib/system/async-dns/async-dns-parse.c index 00f53f2d1..48bc56a79 100644 --- a/lib/system/async-dns/async-dns-parse.c +++ b/lib/system/async-dns/async-dns-parse.c @@ -340,14 +340,17 @@ do_cb: if (p + 14 > e) return -1; - - /* it should have exactly reached rrpaylen */ +#if 0 + /* it should have exactly reached rrpaylen if only one + * CNAME, else somewhere in the middle */ if (p != pay + rrpaylen) { lwsl_err("%s: cname name bad len %d\n", __func__, rrpaylen); return -1; } +#endif + lwsl_notice("%s: recursing looking for %s\n", __func__, stack[stp].name); lwsl_info("%s: recursing looking for %s\n", __func__, stack[stp].name); diff --git a/lib/system/ntpclient/ntpclient.c b/lib/system/ntpclient/ntpclient.c index 1ab4cff42..2316fddd8 100644 --- a/lib/system/ntpclient/ntpclient.c +++ b/lib/system/ntpclient/ntpclient.c @@ -134,7 +134,7 @@ callback_ntpc(struct lws *wsi, enum lws_callback_reasons reason, void *user, if (v) break; - lwsl_debug("%s: LWS_CALLBACK_PROTOCOL_INIT\n", __func__); + lwsl_debug("%s: LWS_CALLBACK_PROTOCOL_INIT:\n", __func__); lws_protocol_vh_priv_zalloc(wsi->vhost, wsi->protocol, sizeof(*v)); v = (struct vhd_ntpc *)lws_protocol_vh_priv_get(wsi->vhost, diff --git a/lib/system/system.c b/lib/system/system.c index 7b6d70457..e5c8723d9 100644 --- a/lib/system/system.c +++ b/lib/system/system.c @@ -56,6 +56,8 @@ lws_system_blob_heap_append(lws_system_blob_t *b, const uint8_t *buf, size_t len { assert(!b->is_direct); + lwsl_debug("%s: blob %p\n", __func__, b); + if (lws_buflist_append_segment(&b->u.bl, buf, len) < 0) return -1; @@ -126,6 +128,7 @@ lws_system_blob_destroy(lws_system_blob_t *b) { if (!b) return; + lwsl_info("%s: blob %p\n", __func__, b); if (!b->is_direct) lws_buflist_destroy_all_segments(&b->u.bl); } diff --git a/lib/tls/openssl/openssl-ssl.c b/lib/tls/openssl/openssl-ssl.c index 6e59caf1e..2a6ff9527 100644 --- a/lib/tls/openssl/openssl-ssl.c +++ b/lib/tls/openssl/openssl-ssl.c @@ -60,6 +60,8 @@ int lws_ssl_get_error(struct lws *wsi, int n) lwsl_debug("%s: %p %d -> %d (errno %d)\n", __func__, wsi->tls.ssl, n, m, errno); + assert (errno != 9); + return m; } diff --git a/minimal-examples/README.md b/minimal-examples/README.md index 2f271dbc2..956256d53 100644 --- a/minimal-examples/README.md +++ b/minimal-examples/README.md @@ -6,6 +6,7 @@ dbus-server|Minimal examples showing how to integrate DBUS into lws event loop http-client|Minimal examples providing an http client http-server|Minimal examples providing an http server raw|Minimal examples related to adopting raw file or socket descriptors into the event loop +secure-streams|Minimal examples related to the Secure Streams client api ws-client|Minimal examples providing a ws client ws-server|Minimal examples providing a ws server (and an http server) diff --git a/minimal-examples/http-client/minimal-http-client-h2-rxflow/minimal-http-client.c b/minimal-examples/http-client/minimal-http-client-h2-rxflow/minimal-http-client.c index 9c9149bc8..4d0d88a63 100644 --- a/minimal-examples/http-client/minimal-http-client-h2-rxflow/minimal-http-client.c +++ b/minimal-examples/http-client/minimal-http-client-h2-rxflow/minimal-http-client.c @@ -39,7 +39,7 @@ drain_cb(lws_sorted_usec_list_t *sul) { struct pss *pss = lws_container_of(sul, struct pss, sul); - lws_h2_update_peer_txcredit(pss->wsi, LWS_H2_STREAM_SID, each); + lws_wsi_tx_credit(pss->wsi, LWSTXCR_PEER_TO_US, each); lws_sul_schedule(lws_get_context(pss->wsi), 0, &pss->sul, drain_cb, 250 * LWS_US_PER_MS); diff --git a/minimal-examples/secure-streams/README.md b/minimal-examples/secure-streams/README.md new file mode 100644 index 000000000..5815630fa --- /dev/null +++ b/minimal-examples/secure-streams/README.md @@ -0,0 +1,14 @@ +# Secure Streams + +Secure Streams is a client API that strictly decouples the policy for connections +from the payloads. The user code only deals with the stream type name and payloads, +a policy database set at `lws_context` creation time decides all policy about the +connection, including the endpoint, tls CA, and even the wire protocol. + +|name|demonstrates| +---|--- +minimal-secure-streams|Minimal secure streams client / proxy example +minimal-secure-streams-tx|Proxy used for client-tx test below +minimal-secure-streams-client-tx|Secure streams client showing tx and rx + + diff --git a/minimal-examples/secure-streams/minimal-secure-streams-alexa/CMakeLists.txt b/minimal-examples/secure-streams/minimal-secure-streams-alexa/CMakeLists.txt new file mode 100644 index 000000000..b28d84e0a --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-alexa/CMakeLists.txt @@ -0,0 +1,93 @@ +cmake_minimum_required(VERSION 2.8) +include(CheckCSourceCompiles) + +set(SAMP lws-minimal-secure-streams-alexa) +set(SRCS main.c alexa.c audio.c) + +# If we are being built as part of lws, confirm current build config supports +# reqconfig, else skip building ourselves. +# +# If we are being built externally, confirm installed lws was configured to +# support reqconfig, else error out with a helpful message about the problem. +# +MACRO(require_lws_config reqconfig _val result) + + if (DEFINED ${reqconfig}) + if (${reqconfig}) + set (rq 1) + else() + set (rq 0) + endif() + else() + set(rq 0) + endif() + + if (${_val} EQUAL ${rq}) + set(SAME 1) + else() + set(SAME 0) + endif() + + if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME}) + if (${_val}) + message("${SAMP}: skipping as lws being built without ${reqconfig}") + else() + message("${SAMP}: skipping as lws built with ${reqconfig}") + endif() + set(${result} 0) + else() + if (LWS_WITH_MINIMAL_EXAMPLES) + set(MET ${SAME}) + else() + CHECK_C_SOURCE_COMPILES("#include \nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig}) + if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig}) + set(HAS_${reqconfig} 0) + else() + set(HAS_${reqconfig} 1) + endif() + if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val})) + set(MET 1) + else() + set(MET 0) + endif() + endif() + if (NOT MET) + if (${_val}) + message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}") + else() + message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project") + endif() + endif() + endif() +ENDMACRO() + + +set(requirements 1) +require_lws_config(LWS_ROLE_H1 1 requirements) +require_lws_config(LWS_WITHOUT_CLIENT 0 requirements) +require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements) +require_lws_config(LWS_WITH_ALSA 1 requirements) + +if (requirements) + add_executable(${SAMP} ${SRCS}) + + if (websockets_shared) + target_link_libraries(${SAMP} websockets_shared asound pv_porcupine mpg123) + add_dependencies(${SAMP} websockets_shared) + else() + target_link_libraries(${SAMP} websockets asound pv_porcupine mpg123) + endif() + + if (LWS_WITH_SECURE_STREAMS_PROXY_API) + add_compile_options(-DLWS_SS_USE_SSPC) + + add_executable(${SAMP}-client ${SRCS}) + if (websockets_shared) + target_link_libraries(${SAMP}-client websockets_shared asound pv_porcupine mpg123) + add_dependencies(${SAMP}-client websockets_shared) + else() + target_link_libraries(${SAMP}-client websockets asound pv_porcupine mpg123) + endif() + endif() + +endif() diff --git a/minimal-examples/secure-streams/minimal-secure-streams-alexa/README.md b/minimal-examples/secure-streams/minimal-secure-streams-alexa/README.md new file mode 100644 index 000000000..66fed3755 --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-alexa/README.md @@ -0,0 +1,77 @@ +# lws secure streams alexa + +This demonstrates AVS Alexa usage using secure streams. It connects to AVS, +uses your linux computer's microphone to wait for the 'alexa' wakeword, sends +the utterance to AVS and plays back the result. + +## build + +There are some special build considerations: + +1) Build lws with cmake options `-DLWS_WITH_ALSA=1 -DLWS_WITH_SECURE_STREAMS=1` + +2) Install distro build dependency packages: + + |Dependency|Ubuntu package|Fedora Package| + |---|---|---| + |libasound|libasound2-dev|alsa-lib-devel| + |mpg123|libmpg123-dev|mpg123-devel| + +3) Clone Picovoice Porcupine Apache-licensed demo version from here + + https://github.com/Picovoice/porcupine + + It provides binary libs for wakeword detection on various platforms. Copy + the headers and binary lib to your build context, eg, for native x86_64 + +``` + $ sudo cp ./include/* /usr/include + $ sudo cp ./lib/linux/x86_64/libpv_porcupine.* /usr/lib + $ sudo ldconfig +``` + + Enter the minimal example dir for secure-streams-alexa and make the sample + +``` + $ cd ./minimal-examples/secure-streams/minimal-secure-streams-alexa + $ cmake . + $ make +``` + +## usage + +``` + $ ./lws-minimal-secure-streams-alexa +[2019/10/16 16:22:01:1097] U: LWS secure streams - Alex voice test [-d] +[2019/10/16 16:22:01:1115] N: lws_create_context: creating Secure Streams policy +[2019/10/16 16:22:01:1115] N: lwsac_use: alloc 1532 for 1 +[2019/10/16 16:22:01:1119] N: lwsac_use: alloc 288 for 168 +[2019/10/16 16:22:01:1119] N: lws_ss_policy_set: policy lwsac size: 1.796KiB, pad 11% +[2019/10/16 16:22:02:4114] N: lws_ss_client_connect: connecting 0 api.amazon.com /auth/o2/token +[2019/10/16 16:22:02:8686] N: auth_api_amazon_com_parser_cb: expires in 3600 +[2019/10/16 16:22:02:8686] N: ss_api_amazon_auth_rx: acquired 656-byte api.amazon.com auth token +[2019/10/16 16:22:02:8754] N: lws_ss_client_connect: connecting 1 alexa.na.gateway.devices.a2z.com /v20160207/directives +[2019/10/16 16:22:02:3182] N: secstream_h2: h2 client entering LONG_POLL +[2019/10/16 16:22:02:3183] U: Connected to Alexa... speak "Alexa, ..." +[2019/10/16 16:22:06:9380] W: ************* Wakeword +[2019/10/16 16:22:06:9380] N: avs_query_start: +[2019/10/16 16:22:06:9381] N: lws_ss_client_connect: connecting 1 alexa.na.gateway.devices.a2z.com /v20160207/events +[2019/10/16 16:22:06:9381] N: lws_vhost_active_conns: just join h2 directly +[2019/10/16 16:22:06:9384] N: metadata done +[2019/10/16 16:22:06:1524] N: est: 42 1 +[2019/10/16 16:22:06:3723] N: est: 108 1 +[2019/10/16 16:22:07:5914] N: est: 352 1 +[2019/10/16 16:22:07:8112] N: est: 4284 1 +[2019/10/16 16:22:07:0300] N: est: 3369 1 +[2019/10/16 16:22:07:2325] N: est: 577 1 +[2019/10/16 16:22:08:4519] N: est: 9 1 +[2019/10/16 16:22:08:6716] N: est: 3 1 +[2019/10/16 16:22:08:6718] N: est: 11 1 +[2019/10/16 16:22:08:8915] N: est: 10 1 +[2019/10/16 16:22:08:8915] W: callback_audio: ended capture +[2019/10/16 16:22:09:0993] N: identified reply... +^C[2019/10/16 16:22:14:3067] U: Disconnected from Alexa +[2019/10/16 16:22:14:3123] U: Completed +$ + +``` diff --git a/minimal-examples/secure-streams/minimal-secure-streams-alexa/alexa.c b/minimal-examples/secure-streams/minimal-secure-streams-alexa/alexa.c new file mode 100644 index 000000000..ba1fa7e2f --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-alexa/alexa.c @@ -0,0 +1,678 @@ +/* + * lws-minimal-secure-streams-alexa + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "private.h" + +struct lws_ss_handle *hss_avs_event, *hss_avs_sync; + +/* this is the type for the long poll event channel */ + +typedef struct ss_avs_event { + struct lws_ss_handle *ss; + void *opaque_data; + /* ... application specific state ... */ + + struct lejp_ctx jctx; +} ss_avs_event_t; + +enum { + LAMP3STATE_IDLE, + LAMP3STATE_SPOOLING, + LAMP3STATE_DRAINING, +}; + +/* this is the type for the utterance metadata (and audio rideshares) */ + +typedef struct ss_avs_metadata { + struct lws_ss_handle *ss; + void *opaque_data; + /* ... application specific state ... */ + + struct lws_buflist *dribble; /* next mp3 data while draining last */ + + struct lejp_ctx jctx; + size_t pos; + size_t mp3_in; + mpg123_handle *mh; + + lws_sorted_usec_list_t sul; + + uint8_t stash_eom[16]; + + uint8_t se_head; + uint8_t se_tail; + + char mp3_state; + char first_mp3; + uint8_t mp3_mime_match; + uint8_t seen; + uint8_t inside_mp3; + +} ss_avs_metadata_t; + +/* + * The remote server only seems to give us a budget of 10s to consume the + * results, after that it doesn't drop the stream, but doesn't send us anything + * further on it. + * + * This makes it impossible to optimize buffering for incoming mp3 since we + * have to go ahead and take it before the 10s is up. + */ + +#define MAX_MP3_IN_BUFFERING_BYTES 32768 + +/* + * Structure of JSON metadata for utterance handling + */ + +static const char *metadata = "{" + "\"event\": {" + "\"header\": {" + "\"namespace\": \"SpeechRecognizer\"," + "\"name\": \"Recognize\"," + "\"messageId\": \"message-123\"," + "\"dialogRequestId\": \"dialog-request-321\"" + "}," + "\"payload\": {" + "\"profile\":" "\"CLOSE_TALK\"," + "\"format\":" "\"AUDIO_L16_RATE_16000_CHANNELS_1\"" + "}" + "}" +"}"; + +/* + * avs metadata + */ + +static void +use_buffer_250ms(lws_sorted_usec_list_t *sul) +{ + ss_avs_metadata_t *m = lws_container_of(sul, ss_avs_metadata_t, sul); + struct lws_context *context = (struct lws_context *)m->opaque_data; + int est = lws_ss_get_est_peer_tx_credit(m->ss); + + lwsl_notice("%s: est txcr %d\n", __func__, est); + + if (est < MAX_MP3_IN_BUFFERING_BYTES - (MAX_MP3_IN_BUFFERING_BYTES / 4)) { + lwsl_notice(" adding %d\n", MAX_MP3_IN_BUFFERING_BYTES / 4); + lws_ss_add_peer_tx_credit(m->ss, MAX_MP3_IN_BUFFERING_BYTES / 4); + } + + lws_sul_schedule(context, 0, &m->sul, use_buffer_250ms, + 250 * LWS_US_PER_MS); +} + +static const char *mp3_mimetype = "application/octet-stream", + *match2 = "\x0d\x0a\x0d\x0a"; + +static int +ss_avs_mp3_open(ss_avs_metadata_t *m) +{ + int r; + + lwsl_notice("%s\n", __func__); + + m->first_mp3 = 1; + m->mh = mpg123_new(NULL, NULL); + if (!m->mh) { + lwsl_err("%s: unable to make new mp3\n", + __func__); + goto bail; + } + mpg123_format_none(m->mh); + r = mpg123_format(m->mh, 16000, MPG123_M_MONO, + MPG123_ENC_SIGNED_16); + if (r) { + lwsl_err("%s: mpg123 format failed %d\n", + __func__, r); + goto bail1; + } + r = mpg123_open_feed(m->mh); + if (r) { + lwsl_err("%s: mpg123 open feed failed %d\n", + __func__, r); + goto bail1; + } + + return 0; + +bail1: + mpg123_delete(m->mh); + m->mh = NULL; + +bail: + return 1; +} + +static int +ss_avs_metadata_rx(void *userobj, const uint8_t *buf, size_t len, int flags); + +/* + * This is called when the mp3 has drained it's input buffer and destroyed + * itself. + */ + +static int +drain_end_cb(void *v) +{ + ss_avs_metadata_t *m = (ss_avs_metadata_t *)v; + struct lws_context *context = (struct lws_context *)m->opaque_data; + int tot = 0; + + lwsl_err("%s\n", __func__); + + /* + * We have drained and destroyed the existing mp3 session. Is there + * a new one pending? + */ + + m->first_mp3 = 1; + m->mp3_state = LAMP3STATE_IDLE; + + if (lws_buflist_total_len(&m->dribble)) { + /* we started another one */ + + /* resume tx credit top up */ + lws_sul_schedule(context, 0, &m->sul, use_buffer_250ms, 1); + + if (ss_avs_mp3_open(m)) + return 1; + + m->mp3_state = LAMP3STATE_SPOOLING; + + /* + * Dump what we stashed from draining into the new mp3 + */ + + while (lws_buflist_total_len(&m->dribble)) { + size_t s; + uint8_t *u, t; + + s = lws_buflist_next_segment_len(&m->dribble, &u); + t = m->stash_eom[m->se_tail]; + lwsl_notice("%s: preload %d: %d\n", __func__, (int)s, t); + + mpg123_feed(m->mh, u, s); + lws_buflist_use_segment(&m->dribble, s); + if (m->first_mp3) { + play_mp3(m->mh, NULL, NULL); + m->first_mp3 = 0; + } + + tot += s; + + m->se_tail = (m->se_tail + 1) % sizeof(m->stash_eom); + if (t) { + lwsl_notice("%s: preloaded EOM\n", __func__); + + /* + * We stashed the whole of the message, we need + * to also do the EOM processing. We will come + * back here if there's another message in the + * stash. + */ + + m->mp3_state = LAMP3STATE_DRAINING; + if (m->mh) + play_mp3(NULL, drain_end_cb, m); + + lws_ss_add_peer_tx_credit(m->ss, tot); +#if 0 + /* + * Put a hold on bringing in any more data + */ + lws_sul_schedule(context, 0, &m->sul, NULL, + LWS_SET_TIMER_USEC_CANCEL); +#endif + /* destroy our copy of the handle */ + m->mh = NULL; + + break; + } + } + + lws_ss_add_peer_tx_credit(m->ss, tot); + } + + return 0; +} + +static int +ss_avs_metadata_rx(void *userobj, const uint8_t *buf, size_t len, int flags) +{ + ss_avs_metadata_t *m = (ss_avs_metadata_t *)userobj; + struct lws_context *context = (struct lws_context *)m->opaque_data; + int n = 0, hit = 0; + + lwsl_notice("%s: len %d, flags %d (est peer txcr %d)\n", __func__, + (int)len, flags, lws_ss_get_est_peer_tx_credit(m->ss)); + + // lwsl_hexdump_warn(buf, len); + + if ((flags & LWSSS_FLAG_SOM) && !m->mh && !m->seen) { + m->mp3_mime_match = 0; + m->seen = 0; + m->inside_mp3 = 0; + } + + if (!m->inside_mp3) { + /* + * Identify the part with the mp3 in, if any + */ + + while (n < (int)len - 24) { + if (!m->seen) { + if (buf[n] == mp3_mimetype[m->mp3_mime_match]) { + m->mp3_mime_match++; + if (m->mp3_mime_match == 24) { + m->mp3_mime_match = 0; + m->seen = 1; + n++; + continue; + } + } else + m->mp3_mime_match = 0; + } else { + if (buf[n] == match2[m->mp3_mime_match]) { + m->mp3_mime_match++; + if (m->mp3_mime_match == 4) { + m->seen = 0; + m->mp3_mime_match = 0; + hit = 1; + n++; + buf += n; + len -= n; + lwsl_notice("identified reply...\n"); + m->inside_mp3 = 1; + break; + } + } else + m->mp3_mime_match = 0; + } + + n++; + } + + if (!hit) { + lws_ss_add_peer_tx_credit(m->ss, len); + return 0; + } + } + + // lwsl_notice("%s: state %d\n", __func__, m->mp3_state); + + switch (m->mp3_state) { + case LAMP3STATE_IDLE: + + if (hit) { + + lws_ss_add_peer_tx_credit(m->ss, n); + + if (ss_avs_mp3_open(m)) + goto bail; + + lws_sul_schedule(context, 0, &m->sul, use_buffer_250ms, 1); + m->mp3_state = LAMP3STATE_SPOOLING; + break; + } + + lws_ss_add_peer_tx_credit(m->ss, len); + + if (!m->inside_mp3) + break; + + /* fallthru */ + + case LAMP3STATE_SPOOLING: + + if (m->dribble) + goto draining; + + if (len) { + /* + * We are shoving encoded mp3 into mpg123-allocated heap + * buffers... unfortunately mpg123 doesn't seem to + * expose where it is in its allocated input so we can + * track how much is stashed. Instead while in playback + * mode, we assume 64kbps mp3 encoding, ie, 8KB/s, and + * run a sul that allows an additional 2KB tx credit + * every 250ms, with 4KB initial credit. + */ + lwsl_notice("%s: SPOOL %d\n", __func__, (int)len); + mpg123_feed(m->mh, buf, len); + + if (m->first_mp3) { + lws_sul_schedule(context, 0, &m->sul, + use_buffer_250ms, 1); + // lws_ss_add_peer_tx_credit(m->ss, + // len + (MAX_MP3_IN_BUFFERING_BYTES / 2)); + play_mp3(m->mh, NULL, NULL); + } //else + // lws_ss_add_peer_tx_credit(m->ss, len); + m->first_mp3 = 0; + } + + if (flags & LWSSS_FLAG_EOM) { + /* + * This means one "message" / mime part with mp3 data + * has finished coming in. But there may be whole other + * parts with other mp3s following, with potentially + * different mp3 parameters. So we want to tell this + * one to drain and finish and destroy the current mp3 + * object before we go on. + * + * But not knowing the length of the current one, there + * will already be outstanding tx credit at the server, + * so it's going to spam us with the next part before we + * have the new mp3 sink for it. + */ + lwsl_notice("%s: EOM\n", __func__); + m->mp3_mime_match = 0; + m->seen = 0; + m->mp3_state = LAMP3STATE_DRAINING; + /* from input POV, we're no longer inside an mp3 */ + m->inside_mp3 = 0; + if (m->mh) + play_mp3(NULL, drain_end_cb, m); +#if 0 + /* + * Put a hold on bringing in any more data + */ + lws_sul_schedule(context, 0, &m->sul, NULL, + LWS_SET_TIMER_USEC_CANCEL); +#endif + /* destroy our copy of the handle */ + m->mh = NULL; + } + break; + + case LAMP3STATE_DRAINING: + +draining: + if (buf && len && m->inside_mp3) { + lwsl_notice("%s: DRAINING: stashing %d: %d %d %d\n", + __func__, (int)len, !!(flags & LWSSS_FLAG_EOM), + m->se_head, m->se_tail); + lwsl_hexdump_notice(buf, len); + if (lws_buflist_append_segment(&m->dribble, buf, len) < 0) + goto bail; + + m->stash_eom[m->se_head] = !!(flags & LWSSS_FLAG_EOM); + m->se_head = (m->se_head + 1) % sizeof(m->stash_eom); + lwsl_notice("%s: next head %d\n", __func__, m->se_head); + + lws_ss_add_peer_tx_credit(m->ss, len); + } + + if (flags & LWSSS_FLAG_EOM) { + if (!len && m->se_head != m->se_tail) { + /* 0-len EOM... retrospectively mark last stash */ + lwsl_notice("%s: retro EOM\n", __func__); + m->stash_eom[(m->se_head - 1) % sizeof(m->stash_eom)] = 1; + } + + lwsl_notice("%s: Draining EOM\n", __func__); + m->inside_mp3 = 0; + } + /* + * Don't provide any additional tx credit... we're just + * mopping up the overspill from the previous mp3 credit + */ + break; + } + + return 0; + +bail: + return -1; +} + +/* + * Because this is multipart mime in h2 currently, use a "rideshare" to handle + * first the native metadata on this secure stream, then the "rideshare" audio + * stream mentioned in the policy. + * + * Lws takes care of interleaving the multipart mime pieces since the policy + * calls for it. + */ + +static int +ss_avs_metadata_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, + size_t *len, int *flags) +{ + ss_avs_metadata_t *m = (ss_avs_metadata_t *)userobj; + size_t tot; + int n; + + // lwsl_notice("%s %d\n", __func__, (int)m->pos); + + if ((long)m->pos < 0) { + *len = 0; + lwsl_info("%s: skip\n", __func__); + return 1; + } + + if (!strcmp(lws_ss_rideshare(m->ss), "avs_audio")) { + + /* audio rideshare part */ + + if (!m->pos) + *flags |= LWSSS_FLAG_SOM; + + n = spool_capture(buf, *len); + if (n > 0) + *len = n; + else + *len = 0; + if (!n) { + lwsl_info("%s: trying to skip tx\n", __func__); + return 1; + } + + m->pos += *len; + + if (n < 0) { + *flags |= LWSSS_FLAG_EOM; + m->pos = (long)-1l; /* ban subsequent until new stream */ + } + + lwsl_notice("%s: tx audio %d\n", __func__, (int)*len); + +#if 0 + { + int ff = open("/tmp/z1", O_RDWR | O_CREAT | O_APPEND, 0666); + if (ff == -1) + lwsl_err("%s: errno %d\n", __func__, errno); + write(ff, buf, *len); + close(ff); + } +#endif + + return 0; + } + + /* metadata part */ + + tot = strlen(metadata); + + if (!m->pos) + *flags |= LWSSS_FLAG_SOM; + + if (*len > tot - m->pos) + *len = tot - m->pos; + + memcpy(buf, metadata + m->pos, *len); + + m->pos += *len; + + if (m->pos == tot) { + lwsl_notice("metadata done\n"); + *flags |= LWSSS_FLAG_EOM; + m->pos = 0; /* for next time */ + } + + return 0; +} + +static int +ss_avs_metadata_state(void *userobj, void *sh, + lws_ss_constate_t state, lws_ss_tx_ordinal_t ack) +{ + ss_avs_metadata_t *m = (ss_avs_metadata_t *)userobj; + struct lws_context *context = (struct lws_context *)m->opaque_data; + + lwsl_notice("%s: %p: %s, ord 0x%x\n", __func__, m->ss, + lws_ss_state_name(state), (unsigned int)ack); + + switch (state) { + case LWSSSCS_CREATING: + lws_ss_client_connect(m->ss); + break; + case LWSSSCS_CONNECTING: + m->pos = 0; + break; + case LWSSSCS_CONNECTED: + lwsl_info("%s: CONNECTED\n", __func__); + lws_ss_request_tx(m->ss); + break; + case LWSSSCS_DISCONNECTED: + lws_sul_schedule(context, 0, &m->sul, NULL, + LWS_SET_TIMER_USEC_CANCEL); + //if (m->mh) { + play_mp3(NULL, NULL, NULL); + m->mh = NULL; + //} + /* + * For this stream encapsulating an alexa exchange, dropping + * is the end of its life + */ + return 1; + + case LWSSSCS_DESTROYING: + lws_buflist_destroy_all_segments(&m->dribble); + break; + default: + break; + } + + return 0; +} + +/* + * avs event + */ + +static int +ss_avs_event_rx(void *userobj, const uint8_t *buf, size_t len, int flags) +{ + return 0; +} + +static int +ss_avs_event_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, + size_t *len, int *flags) +{ + return 1; /* don't transmit anything */ +} + +static int +ss_avs_event_state(void *userobj, void *sh, + lws_ss_constate_t state, lws_ss_tx_ordinal_t ack) +{ + lwsl_info("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name(state), + (unsigned int)ack); + + switch (state) { + case LWSSSCS_CREATING: + mpg123_init(); + break; + case LWSSSCS_CONNECTING: + break; + case LWSSSCS_CONNECTED: + lwsl_user("Connected to Alexa... speak \"Alexa, ...\"\n"); + break; + case LWSSSCS_DISCONNECTED: + lwsl_user("Disconnected from Alexa\n"); + break; + case LWSSSCS_DESTROYING: + mpg123_exit(); + break; + default: + break; + } + + return 0; +} + +int +avs_query_start(struct lws_context *context) +{ + lws_ss_info_t ssi; + + lwsl_notice("%s:\n", __func__); + + memset(&ssi, 0, sizeof(ssi)); + ssi.handle_offset = offsetof(ss_avs_metadata_t, ss); + ssi.opaque_user_data_offset = offsetof(ss_avs_metadata_t, opaque_data); + ssi.rx = ss_avs_metadata_rx; + ssi.tx = ss_avs_metadata_tx; + ssi.state = ss_avs_metadata_state; + ssi.user_alloc = sizeof(ss_avs_metadata_t); + ssi.streamtype = "avs_metadata"; + + ssi.manual_initial_tx_credit = 8192; + + if (lws_ss_create(context, 0, &ssi, context, &hss_avs_sync, NULL, NULL)) { + lwsl_err("%s: failed to create avs metadata secstream\n", + __func__); + + return 1; + } + + lwsl_user("%s: created query stream %p\n", __func__, hss_avs_sync); + + return 0; +} + +int +avs_example_start(struct lws_context *context) +{ + lws_ss_info_t ssi; + + if (hss_avs_event) + return 0; + + lwsl_info("%s: Starting AVS stream\n", __func__); + + /* AVS wants us to establish the long poll event stream first */ + + memset(&ssi, 0, sizeof(ssi)); + ssi.handle_offset = offsetof(ss_avs_event_t, ss); + ssi.opaque_user_data_offset = offsetof(ss_avs_event_t, opaque_data); + ssi.rx = ss_avs_event_rx; + ssi.tx = ss_avs_event_tx; + ssi.state = ss_avs_event_state; + ssi.user_alloc = sizeof(ss_avs_event_t); + ssi.streamtype = "avs_event"; + + if (lws_ss_create(context, 0, &ssi, context, &hss_avs_event, NULL, NULL)) { + lwsl_err("%s: failed to create avs event secure stream\n", + __func__); + return 1; + } + + return 0; +} diff --git a/minimal-examples/secure-streams/minimal-secure-streams-alexa/alexa_linux.ppn b/minimal-examples/secure-streams/minimal-secure-streams-alexa/alexa_linux.ppn new file mode 100644 index 000000000..839156df9 Binary files /dev/null and b/minimal-examples/secure-streams/minimal-secure-streams-alexa/alexa_linux.ppn differ diff --git a/minimal-examples/secure-streams/minimal-secure-streams-alexa/audio.c b/minimal-examples/secure-streams/minimal-secure-streams-alexa/audio.c new file mode 100644 index 000000000..6233db4a0 --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-alexa/audio.c @@ -0,0 +1,469 @@ +/* + * alsa audio handling + * + * Written in 2010-2020 by Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "private.h" + +extern struct lws_ss_handle *hss_avs_event, *hss_avs_sync; + +int +avs_query_start(struct lws_context *context); + +enum { + MODE_IDLE, + MODE_CAPTURING, + MODE_PLAYING +}; + +struct raw_vhd { + int16_t p[8 * 1024]; /* 500ms at 16kHz 16-bit PCM */ + pv_porcupine_object_t *porc; + snd_pcm_t *pcm_capture; + snd_pcm_t *pcm_playback; + snd_pcm_hw_params_t *params; + snd_pcm_uframes_t frames; + int16_t *porcbuf; + + mpg123_handle *mh; + + mp3_done_cb done_cb; + void *opaque; + + int mode; + int rate; + + int porc_spf; + int filefd; + int rpos; + int wpos; + int porcpos; + int npos; + int times; + int quietcount; + int anycount; + + int wplay; + int rplay; + + char last_wake_detect; + char destroy_mh_on_drain; +}; + +static struct raw_vhd *avhd; + +/* + * called from alexa.c to grab the next chunk of audio capture buffer + * for upload + */ + +int +spool_capture(uint8_t *buf, size_t len) +{ + int16_t *sam = (int16_t *)buf; + size_t s, os; + + if (avhd->mode != MODE_CAPTURING) + return -1; + + os = s = len / 2; + + while (s && avhd->wpos != avhd->npos) { + *sam++ = avhd->p[avhd->npos]; + avhd->npos = (avhd->npos + 1) % LWS_ARRAY_SIZE(avhd->p); + s--; + } + + lwsl_info("Copied %d samples (%d %d)\n", (int)(os - s), + avhd->wpos, avhd->npos); + + return (os - s) * 2; +} + +/* + * Called from alexa.c to control when the mp3 playback should begin and end + */ + +int +play_mp3(mpg123_handle *mh, mp3_done_cb cb, void *opaque) +{ + if (mh) { + avhd->mh = mh; + avhd->mode = MODE_PLAYING; + snd_pcm_prepare(avhd->pcm_playback); + + return 0; + } + + avhd->destroy_mh_on_drain = 1; + avhd->done_cb = cb; + avhd->opaque = opaque; + + return 0; +} + +/* + * Helper used to set alsa hwparams on both capture and playback channels + */ + +static int +set_hw_params(struct lws_vhost *vh, snd_pcm_t **pcm, int type) +{ + unsigned int rate = pv_sample_rate(); /* it's 16kHz */ + snd_pcm_hw_params_t *params; + lws_sock_file_fd_type u; + struct pollfd pfd; + struct lws *wsi1; + int n; + + n = snd_pcm_open(pcm, "default", type, SND_PCM_NONBLOCK); + if (n < 0) { + lwsl_err("%s: Can't open default for playback: %s\n", + __func__, snd_strerror(n)); + + return -1; + } + + if (snd_pcm_poll_descriptors(*pcm, &pfd, 1) != 1) { + lwsl_err("%s: failed to get playback desc\n", __func__); + return -1; + } + + u.filefd = (lws_filefd_type)(long long)pfd.fd; + wsi1 = lws_adopt_descriptor_vhost(vh, LWS_ADOPT_RAW_FILE_DESC, u, + "lws-audio-test", NULL); + if (!wsi1) { + lwsl_err("%s: Failed to adopt playback desc\n", __func__); + goto bail; + } + if (type == SND_PCM_STREAM_PLAYBACK) + lws_rx_flow_control(wsi1, 0); /* no POLLIN */ + + snd_pcm_hw_params_malloc(¶ms); + snd_pcm_hw_params_any(*pcm, params); + + n = snd_pcm_hw_params_set_access(*pcm, params, + SND_PCM_ACCESS_RW_INTERLEAVED); + if (n < 0) + goto bail1; + + n = snd_pcm_hw_params_set_format(*pcm, params, SND_PCM_FORMAT_S16_LE); + if (n < 0) + goto bail1; + + n = snd_pcm_hw_params_set_channels(*pcm, params, 1); + if (n < 0) + goto bail1; + + n = snd_pcm_hw_params_set_rate_near(*pcm, params, &rate, 0); + if (n < 0) + goto bail1; + + lwsl_notice("%s: %s rate %d\n", __func__, + type == SND_PCM_STREAM_PLAYBACK ? "Playback" : "Capture", rate); + + n = snd_pcm_hw_params(*pcm, params); + snd_pcm_hw_params_free(params); + if (n < 0) + goto bail; + + return 0; + +bail1: + snd_pcm_hw_params_free(params); +bail: + lwsl_err("%s: Set hw params failed: %s\n", __func__, snd_strerror(n)); + + return -1; +} + +/* + * The lws RAW file protocol handler that wraps ALSA. + * + * The timing is coming from ALSA capture channel... since they are both set to + * 16kHz, it's enough just to have the one. + */ + +static int +callback_audio(struct lws *wsi, enum lws_callback_reasons reason, void *user, + void *in, size_t len) +{ + struct raw_vhd *vhd = (struct raw_vhd *)lws_protocol_vh_priv_get( + lws_get_vhost(wsi), lws_get_protocol(wsi)); + uint16_t rands[50]; + int16_t temp[256]; + bool det; + long avg; + int n, s; + + switch (reason) { + case LWS_CALLBACK_PROTOCOL_INIT: + + if (avhd) /* just on one vhost */ + return 0; + + avhd = vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi), + lws_get_protocol(wsi), sizeof(struct raw_vhd)); + + /* + * Set up the wakeword library + */ + + n = pv_porcupine_init("porcupine_params.pv", "alexa_linux.ppn", + 1.0, &vhd->porc); + if (n) { + lwsl_err("%s: porcupine init fail %d\n", __func__, n); + + return -1; + } + vhd->porc_spf = pv_porcupine_frame_length(); + vhd->porcbuf = malloc(vhd->porc_spf * 2); + lwsl_info("%s: %s porc frame length is %d samples\n", __func__, + lws_get_vhost_name(lws_get_vhost(wsi)), + vhd->porc_spf); + + vhd->rate = pv_sample_rate(); /* 16kHz */ + + /* set up alsa */ + + if (set_hw_params(lws_get_vhost(wsi), &vhd->pcm_playback, + SND_PCM_STREAM_PLAYBACK)) { + lwsl_err("%s: Can't open default for playback\n", + __func__); + + return -1; + } + + if (set_hw_params(lws_get_vhost(wsi), &vhd->pcm_capture, + SND_PCM_STREAM_CAPTURE)) { + lwsl_err("%s: Can't open default for capture\n", + __func__); + + return -1; + } + + snd_config_update_free_global(); + + break; + + case LWS_CALLBACK_PROTOCOL_DESTROY: + lwsl_info("%s: LWS_CALLBACK_PROTOCOL_DESTROY\n", __func__); + if (!vhd) + break; + + if (vhd->porcbuf) { + free(vhd->porcbuf); + vhd->porcbuf = NULL; + } + if (vhd->pcm_playback) { + snd_pcm_drop(vhd->pcm_playback); + snd_pcm_close(vhd->pcm_playback); + vhd->pcm_playback = NULL; + } + if (vhd->pcm_capture) { + snd_pcm_drop(vhd->pcm_capture); + snd_pcm_close(vhd->pcm_capture); + vhd->pcm_capture = NULL; + } + if (vhd->porc) { + pv_porcupine_delete(vhd->porc); + vhd->porc = NULL; + } + + /* avoid most of the valgrind mess from alsa */ + snd_config_update_free_global(); + + break; + + case LWS_CALLBACK_RAW_CLOSE_FILE: + lwsl_info("%s: closed\n", __func__); + break; + + case LWS_CALLBACK_RAW_RX_FILE: + /* we come here about every 250ms */ + + /* + * Playing back the mp3? + */ + if (vhd->mode == MODE_PLAYING && vhd->mh) { + size_t amt, try; + + do { + try = snd_pcm_avail(vhd->pcm_playback); + if (try > LWS_ARRAY_SIZE(vhd->p)) + try = LWS_ARRAY_SIZE(vhd->p); + + n = mpg123_read(vhd->mh, (uint8_t *)vhd->p, + try * 2, &amt); + lwsl_info("%s: PLAYING: mpg123 read %d, n %d\n", + __func__, (int)amt, n); + if (n == MPG123_NEW_FORMAT) { + snd_pcm_start(vhd->pcm_playback); + memset(vhd->p, 0, try); + snd_pcm_writei(vhd->pcm_playback, + vhd->p, try / 2); + snd_pcm_prepare(vhd->pcm_playback); + } + } while (n == MPG123_NEW_FORMAT); + + if (amt) { + n = snd_pcm_writei(vhd->pcm_playback, + vhd->p, amt / 2); + if (n < 0) + lwsl_notice("%s: snd_pcm_writei: %d %s\n", + __func__, n, snd_strerror(n)); + if (n == -EPIPE) { + lwsl_err("%s: did EPIPE prep\n", __func__); + snd_pcm_prepare(vhd->pcm_playback); + } + } else + if (vhd->destroy_mh_on_drain && + n != MPG123_NEW_FORMAT) { + snd_pcm_drain(vhd->pcm_playback); + vhd->destroy_mh_on_drain = 0; + lwsl_notice("%s: mp3 destroyed\n", + __func__); + mpg123_close(vhd->mh); + mpg123_delete(vhd->mh); + vhd->mh = NULL; + vhd->mode = MODE_IDLE; + + if (vhd->done_cb) + vhd->done_cb(vhd->opaque); + } + } + + /* + * Get the capture data + */ + + n = snd_pcm_readi(vhd->pcm_capture, temp, LWS_ARRAY_SIZE(temp)); + s = 0; + while (s < n) { + vhd->p[(vhd->wpos + s) % LWS_ARRAY_SIZE(vhd->p)] = temp[s]; + s++; + } + + if (vhd->mode == MODE_CAPTURING) { + + /* + * We are recording an utterance. + * + * Estimate the sound density in the frame by picking 50 + * samples at random and averaging the sampled + * [abs()^2] / 10000 to create a Figure of Merit. + * + * Speaking on my laptop gets us 1000 - 5000, silence + * is typ under 30. The wakeword tells us there was + * speech at the start, end the capture when there's + * ~750ms (12000 samples) under 125 FOM. + */ + +#define SILENCE_THRESH 125 + + avg = 0; + lws_get_random(lws_get_context(wsi), rands, sizeof(rands)); + for (s = 0; s < (int)LWS_ARRAY_SIZE(rands); s++) { + long q; + + q = temp[rands[s] % n]; + + avg += (q * q); + } + avg = (avg / (int)LWS_ARRAY_SIZE(rands)) / 10000; + + lwsl_notice("est audio energy: %ld %d\n", avg, vhd->mode); + + /* + * Only start looking for "silence" after 1.5s, in case + * he does a long pause after the wakeword + */ + + if (vhd->anycount < (3 *vhd->rate) / 2 && + avg < SILENCE_THRESH) { + vhd->quietcount += n; + /* then 500ms of "silence" does it for us */ + if (vhd->quietcount >= ((vhd->rate * 3) / 4)) { + lwsl_warn("%s: ended capture\n", __func__); + vhd->mode = MODE_IDLE; + vhd->quietcount = 0; + } + } + + /* if we're not "silent", reset the count */ + if (avg > SILENCE_THRESH * 2) + vhd->quietcount = 0; + + /* + * Since we are in capturing mode, we have something + * new to send now. + * + * We must send an extra one at the end so we can finish + * the tx. + */ + lws_ss_request_tx(hss_avs_sync); + } + + /* + * Just waiting for a wakeword + */ + + while (vhd->mode == MODE_IDLE) { + int m = 0, ppold = vhd->porcpos; + + s = (vhd->wpos - vhd->porcpos) % LWS_ARRAY_SIZE(vhd->p); + if (s < vhd->porc_spf) + goto eol; + + while (m < vhd->porc_spf) { + vhd->porcbuf[m++] = avhd->p[vhd->porcpos]; + vhd->porcpos = (vhd->porcpos + 1) % + LWS_ARRAY_SIZE(vhd->p); + } + + if (pv_porcupine_process(vhd->porc, vhd->porcbuf, &det)) + lwsl_err("%s: porc_process failed\n", __func__); + + if (!det && vhd->last_wake_detect && + vhd->mode == MODE_IDLE) { + lwsl_warn("************* Wakeword\n"); + if (!avs_query_start(lws_get_context(wsi))) { + vhd->mode = MODE_CAPTURING; + vhd->quietcount = 0; + vhd->last_wake_detect = det; + vhd->npos = ppold; + break; + } + } + vhd->last_wake_detect = det; + } + +eol: + vhd->wpos = (vhd->wpos + n) % LWS_ARRAY_SIZE(vhd->p); + break; + + default: + break; + } + + return 0; +} + +struct lws_protocols protocol_audio_test = + { "lws-audio-test", callback_audio, 0, 0 }; diff --git a/minimal-examples/secure-streams/minimal-secure-streams-alexa/main.c b/minimal-examples/secure-streams/minimal-secure-streams-alexa/main.c new file mode 100644 index 000000000..d498b42a2 --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-alexa/main.c @@ -0,0 +1,417 @@ +/* + * lws-minimal-secure-streams-alexa + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + */ + +#include +#include +#include + +extern int +avs_example_start(struct lws_context *context); + +static int interrupted; +static lws_state_notify_link_t nl; + +#if !defined(LWS_SS_USE_SSPC) + +/* + * If not using the proxy, we need to bring our own policy + */ + +static const char * const default_ss_policy = + "{" + "\"release\":" "\"01234567\"," + "\"product\":" "\"myproduct\"," + "\"schema-version\":" "1," + "\"retry\": [" /* named backoff / retry strategies */ + "{\"default\": {" + "\"backoff\": [" "1000," + "2000," + "3000," + "5000," + "10000" + "]," + "\"conceal\":" "5," + "\"jitterpc\":" "20," + "\"svalidping\":" "60," + "\"svalidhup\":" "64" + "}}" + "]," + "\"certs\": [" /* named individual certificates in BASE64 DER */ + "{\"digicert_global_root_g2\": \"" /* api.amazon.com 2038-01 */ + "MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh" + "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3" + "d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH" + "MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT" + "MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j" + "b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG" + "9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI" + "2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx" + "1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ" + "q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz" + "tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ" + "vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP" + "BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV" + "5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY" + "1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4" + "NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG" + "Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91" + "8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe" + "pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl" + "MrY=" + "\"}," + "{\"digicert_global_ca_g2\": \"" /* api.amazon.com 2028-08 */ + "MIIEizCCA3OgAwIBAgIQDI7gyQ1qiRWIBAYe4kH5rzANBgkqhkiG9w0BAQsFADBh" + "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3" + "d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH" + "MjAeFw0xMzA4MDExMjAwMDBaFw0yODA4MDExMjAwMDBaMEQxCzAJBgNVBAYTAlVT" + "MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxHjAcBgNVBAMTFURpZ2lDZXJ0IEdsb2Jh" + "bCBDQSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANNIfL7zBYZd" + "W9UvhU5L4IatFaxhz1uvPmoKR/uadpFgC4przc/cV35gmAvkVNlW7SHMArZagV+X" + "au4CLyMnuG3UsOcGAngLH1ypmTb+u6wbBfpXzYEQQGfWMItYNdSWYb7QjHqXnxr5" + "IuYUL6nG6AEfq/gmD6yOTSwyOR2Bm40cZbIc22GoiS9g5+vCShjEbyrpEJIJ7RfR" + "ACvmfe8EiRROM6GyD5eHn7OgzS+8LOy4g2gxPR/VSpAQGQuBldYpdlH5NnbQtwl6" + "OErXb4y/E3w57bqukPyV93t4CTZedJMeJfD/1K2uaGvG/w/VNfFVbkhJ+Pi474j4" + "8V4Rd6rfArMCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0P" + "AQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYYaHR0cDovL29j" + "c3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6Ly9jcmw0LmRp" + "Z2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RHMi5jcmwwN6A1oDOGMWh0dHA6" + "Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RHMi5jcmwwPQYD" + "VR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2lj" + "ZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFCRuKy3QapJRUSVpAaqaR6aJ50AgMB8GA1Ud" + "IwQYMBaAFE4iVCAYlebjbuYP+vq5Eu0GF485MA0GCSqGSIb3DQEBCwUAA4IBAQAL" + "OYSR+ZfrqoGvhOlaOJL84mxZvzbIRacxAxHhBsCsMsdaVSnaT0AC9aHesO3ewPj2" + "dZ12uYf+QYB6z13jAMZbAuabeGLJ3LhimnftiQjXS8X9Q9ViIyfEBFltcT8jW+rZ" + "8uckJ2/0lYDblizkVIvP6hnZf1WZUXoOLRg9eFhSvGNoVwvdRLNXSmDmyHBwW4co" + "atc7TlJFGa8kBpJIERqLrqwYElesA8u49L3KJg6nwd3jM+/AVTANlVlOnAM2BvjA" + "jxSZnE0qnsHhfTuvcqdFuhOWKU4Z0BqYBvQ3lBetoxi6PrABDJXWKTUgNX31EGDk" + "92hiHuwZ4STyhxGs6QiA" + "\"}," + "{\"amazon_root_ca_1\": \"" + "MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF" + "ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6" + "b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL" + "MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv" + "b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj" + "ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM" + "9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw" + "IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6" + "VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L" + "93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm" + "jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC" + "AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA" + "A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI" + "U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs" + "N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv" + "o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU" + "5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy" + "rqXRfboQnoZsG4q5WTP468SQvvG5" + "\"}," + "{\"starfield_services_root_ca\": \"" + "MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx" + "EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT" + "HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs" + "ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5" + "MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD" + "VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy" + "ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy" + "dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI" + "hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p" + "OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2" + "8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K" + "Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe" + "hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk" + "6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw" + "DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q" + "AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI" + "bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB" + "ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z" + "qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd" + "iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn" + "0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN" + "sSi6" + "\"}," + "{\"starfield_class_2_ca\": \"" + "MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl" + "MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp" + "U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw" + "NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE" + "ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp" + "ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3" + "DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf" + "8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN" + "+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0" + "X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa" + "K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA" + "1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G" + "A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR" + "zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0" + "YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD" + "bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w" + "DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3" + "L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D" + "eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl" + "xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp" + "VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY" + "WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q=" + "\"}" + "]," + "\"trust_stores\": [" /* named cert chains */ + "{" /* chain for alexa.na.gateway.devices.a2z.com */ + "\"name\": \"avs_via_starfield\"," + "\"stack\": [" + "\"starfield_class_2_ca\"," + "\"starfield_services_root_ca\"" + "]" + "}," + "{" /* chain for api.amazon.com */ + "\"name\": \"api_amazon_com\"," + "\"stack\": [" + "\"digicert_global_ca_g2\"," + "\"digicert_global_root_g2\"" + "]" + "}" + "]," + "\"s\": [" /* the supported stream types */ + "{\"api_amazon_com_auth\": {" + "\"endpoint\":" "\"api.amazon.com\"," + "\"port\":" "443," + "\"protocol\":" "\"h1\"," + "\"http_method\":" "\"POST\"," + "\"http_url\":" "\"auth/o2/token\"," + "\"plugins\":" "[]," + "\"opportunistic\":" "true," + "\"tls\":" "true," + "\"h2q_oflow_txcr\":" "true," + "\"http_www_form_urlencoded\":" "true," + "\"http_no_content_length\":" "true," + "\"retry\":" "\"default\"," + "\"tls_trust_store\":" "\"api_amazon_com\"" + "}}," + /* + * long poll event listener + */ + "{\"avs_event\": {" + "\"endpoint\":" "\"alexa.na.gateway.devices.a2z.com\"," + "\"port\":" "443," + "\"protocol\":" "\"h2\"," + "\"http_method\":" "\"GET\"," + "\"http_url\":" "\"v20160207/directives\"," + "\"h2q_oflow_txcr\":" "true," + "\"http_auth_header\":" "\"authorization:\"," + "\"http_auth_preamble\":" "\"Bearer \"," + "\"nailed_up\":" "true," + "\"long_poll\":" "true," + "\"retry\":" "\"default\"," + "\"plugins\":" "[]," + "\"tls\":" "true," + "\"tls_trust_store\":" "\"avs_via_starfield\"" + "}}," + /* + * Utterance metadata and audio send and reply processing. + * + * "Rideshare" and http_multipart_mime means these both go out + * in one multipart http transaction. + */ + "{\"avs_metadata\": {" + "\"endpoint\":" "\"alexa.na.gateway.devices.a2z.com\"," + "\"port\":" "443," + "\"protocol\":" "\"h2\"," + "\"http_method\":" "\"POST\"," + "\"http_url\":" "\"v20160207/events\"," + "\"opportunistic\":" "true," + "\"h2q_oflow_txcr\":" "true," + "\"http_auth_header\":" "\"authorization:\"," + "\"http_auth_preamble\":" "\"Bearer \"," + "\"http_multipart_name\":" "\"metadata\"," + "\"http_mime_content_type\":" "\"application/json; charset=UTF-8\"," + "\"http_no_content_length\":" "true," + "\"rideshare\":" "\"avs_audio\"," + "\"retry\":" "\"default\"," + "\"plugins\":" "[]," + "\"tls\":" "true," + "\"tls_trust_store\":" "\"avs_via_starfield\"" + "}}," + "{\"avs_audio\": {" + "\"endpoint\":" "\"alexa.na.gateway.devices.a2z.com\"," + "\"port\":" "443," + "\"protocol\":" "\"h2\"," + "\"http_method\":" "\"POST\"," + "\"http_url\":" "\"v20160207/events\"," + "\"plugins\":" "[]," + "\"tls\":" "true," + "\"h2q_oflow_txcr\":" "true," + "\"http_auth_header\":" "\"authorization:\"," + "\"http_auth_preamble\":" "\"Bearer \"," + "\"http_multipart_name\":" "\"audio\"," + "\"http_mime_content_type\":" "\"application/octet-stream\"," + "\"http_no_content_length\":" "true," + "\"retry\":" "\"default\"," + "\"tls_trust_store\":" "\"avs_via_starfield\"" + "}}" + "]" + "}" +; + +#endif + +static const char *canned_root_token_payload = + "grant_type=refresh_token" + "&refresh_token=Atzr|IwEBIJedGXjDqsU_vMxykqOMg" + "SHfYe3CPcedueWEMWSDMaDnEmiW8RlR1Kns7Cb4B-TOSnqp7ifVsY4BMY2B8tpHfO39XP" + "zfu9HapGjTR458IyHX44FE71pWJkGZ79uVBpljP4sazJuk8XS3Oe_yLnm_DIO6fU1nU3Y" + "0flYmsOiOAQE_gRk_pdlmEtHnpMA-9rLw3mkY5L89Ty9kUygBsiFaYatouROhbsTn8-jW" + "k1zZLUDpT6ICtBXSnrCIg0pUbZevPFhTwdXd6eX-u4rq0W-XaDvPWFO7au-iPb4Zk5eZE" + "iX6sissYrtNmuEXc2uHu7MnQO1hHCaTdIO2CANVumf-PHSD8xseamyh04sLV5JgFzY45S" + "KvKMajiUZuLkMokOx86rjC2Hdkx5DO7G-dbG1ufBDG-N79pFMSs7Ck5pc283IdLoJkCQc" + "AGvTX8o8I29QqkcGou-9TKhOJmpX8As94T61ok0UqqEKPJ7RhfQHHYdCtsdwxgvfVr9qI" + "xL_hDCcTho8opCVX-6QhJHl6SQFlTw13" + "&client_id=" + "amzn1.application-oa2-client.4823334c434b4190a2b5a42c07938a2d"; + +/* + * Register the root token, and make the sticky AVS connection at the + * appropriate times during system startup + */ + +static int +app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link, + int current, int target) +{ + struct lws_context *context = lws_system_context_from_system_mgr(mgr); + lws_system_blob_t *ab = lws_system_get_blob(context, + LWS_SYSBLOB_TYPE_AUTH, 1 /* AUTH_IDX_ROOT */); + size_t size; + + /* + * For the things we care about, let's notice if we are trying to get + * past them when we haven't solved them yet, and make the system + * state wait while we trigger the dependent action. + */ + switch (target) { + case LWS_SYSTATE_REGISTERED: + size = lws_system_blob_get_size(ab); + if (size) + break; + + /* let's register our canned root token so auth can use it */ + lws_system_blob_direct_set(ab, + (const uint8_t *)canned_root_token_payload, + strlen(canned_root_token_payload)); + break; + case LWS_SYSTATE_OPERATIONAL: + if (current == target) + avs_example_start(context); + break; + case LWS_SYSTATE_POLICY_INVALID: + /* + * This is a NOP since we used direct set... but in a real + * system this could easily change to be done on the heap, then + * this would be important + */ + lws_system_blob_destroy(lws_system_get_blob(context, + LWS_SYSBLOB_TYPE_AUTH, + 1 /* AUTH_IDX_ROOT */)); + break; + } + + return 0; +} + +static void +sigint_handler(int sig) +{ + interrupted = 1; +} + +static lws_state_notify_link_t * const app_notifier_list[] = { + &nl, NULL +}; + +extern struct lws_protocols protocol_audio_test; +static const struct lws_protocols *protocols[] = { + &protocol_audio_test, +#if defined(LWS_SS_USE_SSPC) + lws_sspc_protocols, +#endif + NULL +}; + +int main(int argc, const char **argv) +{ + struct lws_context_creation_info info; + struct lws_context *context; + int n = 0; + + signal(SIGINT, sigint_handler); + memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_cmdline_option_handle_builtin(argc, argv, &info); + + lwsl_user("LWS secure streams - Alexa voice test [-d]\n"); + + info.fd_limit_per_thread = 1 + 6 + 1; +#if !defined(LWS_SS_USE_SSPC) + info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS | + LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + info.pss_policies_json = default_ss_policy; +#else + { + const char *p; + + /* connect to ssproxy via UDS by default, else via + * tcp connection to this port */ + if ((p = lws_cmdline_option(argc, argv, "-p"))) + info.ss_proxy_port = atoi(p); + + /* UDS "proxy.ss.lws" in abstract namespace, else this socket + * path; when -p given this can specify the network interface + * to bind to */ + if ((p = lws_cmdline_option(argc, argv, "-i"))) + info.ss_proxy_bind = p; + + /* if -p given, -a specifies the proxy address to connect to */ + if ((p = lws_cmdline_option(argc, argv, "-a"))) + info.ss_proxy_address = p; + } +#endif + info.port = CONTEXT_PORT_NO_LISTEN; + info.pprotocols = protocols; + +#if defined(LWS_WITH_DETAILED_LATENCY) + info.detailed_latency_cb = lws_det_lat_plot_cb; + info.detailed_latency_filepath = "/tmp/lws-latency-ssproxy"; +#endif + + /* integrate us with lws system state management when context created */ + nl.name = "app"; + nl.notify_cb = app_system_state_nf; + info.register_notifier_list = app_notifier_list; + + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + /* create an explicit vhost so the sound protocol is initialized */ + + info.vhost_name = "asound"; + if (!lws_create_vhost(context, &info)) { + lwsl_err("lws init failed\n"); + goto bail; + } + + /* the event loop */ + + while (n >= 0 && !interrupted) + n = lws_service(context, 0); + +bail: + lws_context_destroy(context); + lwsl_user("Completed\n"); + + return 0; +} diff --git a/minimal-examples/secure-streams/minimal-secure-streams-alexa/porcupine_params.pv b/minimal-examples/secure-streams/minimal-secure-streams-alexa/porcupine_params.pv new file mode 100644 index 000000000..4d88bb5a2 Binary files /dev/null and b/minimal-examples/secure-streams/minimal-secure-streams-alexa/porcupine_params.pv differ diff --git a/minimal-examples/secure-streams/minimal-secure-streams-alexa/private.h b/minimal-examples/secure-streams/minimal-secure-streams-alexa/private.h new file mode 100644 index 000000000..a4920b198 --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-alexa/private.h @@ -0,0 +1,8 @@ +typedef int (*mp3_done_cb)(void *opaque); + +int +play_mp3(mpg123_handle *mh, mp3_done_cb cb, void *opaque); + + +int +spool_capture(uint8_t *buf, size_t len); diff --git a/minimal-examples/secure-streams/minimal-secure-streams-avs/CMakeLists.txt b/minimal-examples/secure-streams/minimal-secure-streams-avs/CMakeLists.txt new file mode 100644 index 000000000..7e58274aa --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-avs/CMakeLists.txt @@ -0,0 +1,91 @@ +cmake_minimum_required(VERSION 2.8) +include(CheckCSourceCompiles) + +set(SAMP lws-minimal-secure-streams-avs) + +# If we are being built as part of lws, confirm current build config supports +# reqconfig, else skip building ourselves. +# +# If we are being built externally, confirm installed lws was configured to +# support reqconfig, else error out with a helpful message about the problem. +# +MACRO(require_lws_config reqconfig _val result) + + if (DEFINED ${reqconfig}) + if (${reqconfig}) + set (rq 1) + else() + set (rq 0) + endif() + else() + set(rq 0) + endif() + + if (${_val} EQUAL ${rq}) + set(SAME 1) + else() + set(SAME 0) + endif() + + if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME}) + if (${_val}) + message("${SAMP}: skipping as lws being built without ${reqconfig}") + else() + message("${SAMP}: skipping as lws built with ${reqconfig}") + endif() + set(${result} 0) + else() + if (LWS_WITH_MINIMAL_EXAMPLES) + set(MET ${SAME}) + else() + CHECK_C_SOURCE_COMPILES("#include \nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig}) + if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig}) + set(HAS_${reqconfig} 0) + else() + set(HAS_${reqconfig} 1) + endif() + if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val})) + set(MET 1) + else() + set(MET 0) + endif() + endif() + if (NOT MET) + if (${_val}) + message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}") + else() + message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project") + endif() + endif() + endif() +ENDMACRO() + + +set(requirements 1) +require_lws_config(LWS_ROLE_H1 1 requirements) +require_lws_config(LWS_WITHOUT_CLIENT 0 requirements) +require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements) + +if (requirements) + add_executable(${SAMP} main.c avs.c) + + if (websockets_shared) + target_link_libraries(${SAMP} websockets_shared) + add_dependencies(${SAMP} websockets_shared) + else() + target_link_libraries(${SAMP} websockets) + endif() + + if (LWS_WITH_SECURE_STREAMS_PROXY_API) + add_compile_options(-DLWS_SS_USE_SSPC) + + add_executable(${SAMP}-client main-client.c avs.c) + if (websockets_shared) + target_link_libraries(${SAMP}-client websockets_shared) + add_dependencies(${SAMP}-client websockets_shared) + else() + target_link_libraries(${SAMP}-client websockets) + endif() + endif() + +endif() diff --git a/minimal-examples/secure-streams/minimal-secure-streams-avs/avs.c b/minimal-examples/secure-streams/minimal-secure-streams-avs/avs.c new file mode 100644 index 000000000..722b4a01f --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-avs/avs.c @@ -0,0 +1,418 @@ +/* + * lws-minimal-secure-streams-avs + * + * Written in 2019-2020 by Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * This sends a canned WAV and received (and discards) the mp3 response. + * However it rate-limits the response reception to manage a small ringbuffer + * using ss / h2 flow control apis, reflecting consumption at 64kbps and only + * and 8KB buffer, indtended to model optimizing rx buffering on mp3 playback + * on a constrained device. + */ + +#include +#include +#include +#include +#include +#include +#include + +extern int interrupted, bad; +static struct lws_ss_handle *hss_avs_event, *hss_avs_sync; +static uint8_t *wav; +static size_t wav_len; + +typedef struct ss_avs_event { + struct lws_ss_handle *ss; + void *opaque_data; + /* ... application specific state ... */ + struct lejp_ctx jctx; +} ss_avs_event_t; + +typedef struct ss_avs_metadata { + struct lws_ss_handle *ss; + void *opaque_data; + /* ... application specific state ... */ + struct lejp_ctx jctx; + size_t pos; + + /* + * We simulate a ringbuffer that is used up by a sul at 64Kbit/sec + * rate, and managed at the same rate using tx credit + */ + + lws_sorted_usec_list_t sul; + uint8_t buf[8192]; + int head; + int tail; + + char filled; + +} ss_avs_metadata_t; + +static const char *metadata = "{" + "\"event\": {" + "\"header\": {" + "\"namespace\": \"SpeechRecognizer\"," + "\"name\": \"Recognize\"," + "\"messageId\": \"message-123\"," + "\"dialogRequestId\": \"dialog-request-321\"" + "}," + "\"payload\": {" + "\"profile\":" "\"CLOSE_TALK\"," + "\"format\":" "\"AUDIO_L16_RATE_16000_CHANNELS_1\"" + "}" + "}" +"}"; + +/* + * avs metadata + */ + +static void +use_buffer_50ms(lws_sorted_usec_list_t *sul) +{ + ss_avs_metadata_t *m = lws_container_of(sul, ss_avs_metadata_t, sul); + struct lws_context *context = (struct lws_context *)m->opaque_data; + size_t n; + int e; + + /* + * Use up 50ms-worth (8KB / 20) == 401 bytes of buffered data + */ + + /* remaining data in buffer */ + n = ((m->head - m->tail) % sizeof(m->buf)); + lwsl_info("%s: avail %d\n", __func__, (int)n); + + if (n < 401) + lwsl_err("%s: underrun\n", __func__); + + m->tail = (m->tail + 401) % sizeof(m->buf); + n = ((m->head - m->tail) % sizeof(m->buf)); + + e = lws_ss_get_est_peer_tx_credit(m->ss); + + lwsl_info("%s: avail after: %d, curr est %d\n", __func__, (int)n, e); + + if (n < (sizeof(m->buf) * 2) / 3 && e < (int)(sizeof(m->buf) - 1 - n)) { + lwsl_info("%s: requesting additional %d\n", __func__, + (int)(sizeof(m->buf) - 1 - e - n)); + lws_ss_add_peer_tx_credit(m->ss, (sizeof(m->buf) - 1 - e - n)); + } + + lws_sul_schedule(context, 0, &m->sul, use_buffer_50ms, + 50 * LWS_US_PER_MS); +} + +static int +ss_avs_metadata_rx(void *userobj, const uint8_t *buf, size_t len, int flags) +{ + ss_avs_metadata_t *m = (ss_avs_metadata_t *)userobj; + struct lws_context *context = (struct lws_context *)m->opaque_data; + size_t n, n1; + + lwsl_notice("%s: rideshare %s, len %d, flags 0x%x\n", __func__, + lws_ss_rideshare(m->ss), (int)len, flags); + // lwsl_hexdump_warn(buf, len); + + n = sizeof(m->buf) - ((m->head - m->tail) % sizeof(m->buf)); + lwsl_info("%s: len %d, buf h %d, t %d, space %d\n", __func__, + (int)len, (int)m->head, (int)m->tail, (int)n); + lws_ss_get_est_peer_tx_credit(m->ss); + assert (len <= n); + + if (m->head < m->tail) /* |****h-------t**| */ + memcpy(&m->buf[m->head], buf, len); + else { /* |---t*****h-----| */ + n1 = sizeof(m->buf) - m->head; + if (len < n1) + n1 = len; + memcpy(&m->buf[m->head], buf, n1); + if (n1 != len) + memcpy(m->buf, buf, len - n1); + } + + m->head = (m->head + len) % sizeof(m->buf); + + lws_sul_schedule(context, 0, &m->sul, use_buffer_50ms, + 50 * LWS_US_PER_MS); + + return 0; +} + +static int +ss_avs_metadata_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, + size_t *len, int *flags) +{ + ss_avs_metadata_t *m = (ss_avs_metadata_t *)userobj; + //struct lws_context *context = (struct lws_context *)m->opaque_data; + size_t tot; + + if ((long)m->pos < 0) { + *len = 0; + lwsl_notice("%s: skip tx\n", __func__); + return 1; + } + + lwsl_notice("%s: rideshare '%s'\n", __func__, lws_ss_rideshare(m->ss)); + + if (!strcmp(lws_ss_rideshare(m->ss), "avs_audio")) { + /* audio rideshare */ + + if (!m->pos) + *flags |= LWSSS_FLAG_SOM; + + if (*len > wav_len - m->pos) + *len = wav_len - m->pos; + + memcpy(buf, wav + m->pos, *len); + m->pos += *len; + + if (m->pos == wav_len) { + *flags |= LWSSS_FLAG_EOM; + lwsl_info("%s: tx done\n", __func__); + m->pos = (long)-1l; /* ban subsequent until new stream */ + } else + lws_ss_request_tx(m->ss); + + lwsl_hexdump_info(buf, *len); + + return 0; + } + + /* metadata part */ + + tot = strlen(metadata); + + if (!m->pos) + *flags |= LWSSS_FLAG_SOM; + + if (*len > tot - m->pos) + *len = tot - m->pos; + + memcpy(buf, metadata + m->pos, *len); + + m->pos += *len; + + if (m->pos == tot) { + *flags |= LWSSS_FLAG_EOM; + m->pos = 0; /* for next time */ + lws_ss_request_tx(m->ss); + } + + lwsl_hexdump_info(buf, *len); + + return 0; +} + +static int +ss_avs_metadata_state(void *userobj, void *sh, + lws_ss_constate_t state, lws_ss_tx_ordinal_t ack) +{ + + ss_avs_metadata_t *m = (ss_avs_metadata_t *)userobj; + struct lws_context *context = (struct lws_context *)m->opaque_data; + + lwsl_user("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name(state), + (unsigned int)ack); + + switch (state) { + case LWSSSCS_CREATING: + lwsl_user("%s: CREATING\n", __func__); + lws_ss_client_connect(m->ss); + m->pos = 0; + break; + case LWSSSCS_CONNECTING: + break; + case LWSSSCS_CONNECTED: + lws_ss_request_tx(m->ss); + break; + case LWSSSCS_ALL_RETRIES_FAILED: + /* for this demo app, we want to exit on fail to connect */ + case LWSSSCS_DISCONNECTED: + /* for this demo app, we want to exit after complete flow */ + lws_sul_schedule(context, 0, &m->sul, use_buffer_50ms, + LWS_SET_TIMER_USEC_CANCEL); + interrupted = 1; + break; + case LWSSSCS_DESTROYING: + lws_sul_schedule(context, 0, &m->sul, use_buffer_50ms, + LWS_SET_TIMER_USEC_CANCEL); + break; + default: + break; + } + + return 0; +} + +/* + * avs event + */ + +static int +ss_avs_event_rx(void *userobj, const uint8_t *buf, size_t len, int flags) +{ + ss_avs_event_t *m = (ss_avs_event_t *)userobj; + // struct lws_context *context = (struct lws_context *)m->opaque_data; + + lwsl_notice("%s: rideshare %s, len %d, flags 0x%x\n", __func__, + lws_ss_rideshare(m->ss), (int)len, flags); + + //lwsl_hexdump_warn(buf, len); + + bad = 0; /* for this demo, receiving something here == success */ + + return 0; +} + +static int +ss_avs_event_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, + size_t *len, int *flags) +{ + ss_avs_event_t *m = (ss_avs_event_t *)userobj; + lwsl_notice("%s: rideshare %s\n", __func__, lws_ss_rideshare(m->ss)); + + return 1; /* don't transmit anything */ +} + +static int +ss_avs_event_state(void *userobj, void *sh, + lws_ss_constate_t state, lws_ss_tx_ordinal_t ack) +{ + ss_avs_event_t *m = (ss_avs_event_t *)userobj; + struct lws_context *context = (struct lws_context *)m->opaque_data; + lws_ss_info_t ssi; + + lwsl_user("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name(state), + (unsigned int)ack); + + switch (state) { + case LWSSSCS_CREATING: + case LWSSSCS_CONNECTING: + break; + case LWSSSCS_CONNECTED: + if (hss_avs_sync) + break; + + lwsl_notice("%s: starting the second avs stream\n", __func__); + + /* + * When we have established the event stream, we must POST + * on another stream within 10s + */ + + memset(&ssi, 0, sizeof(ssi)); + ssi.handle_offset = offsetof(ss_avs_metadata_t, ss); + ssi.opaque_user_data_offset = offsetof(ss_avs_metadata_t, + opaque_data); + ssi.rx = ss_avs_metadata_rx; + ssi.tx = ss_avs_metadata_tx; + ssi.state = ss_avs_metadata_state; + ssi.user_alloc = sizeof(ss_avs_metadata_t); + ssi.streamtype = "avs_metadata"; + + /* + * We want to allow the other side to fill our buffer, but no + * more. But it's a bit tricky when the payload is inside + * framing like multipart MIME and contains other parts + */ + + ssi.manual_initial_tx_credit = + sizeof(((ss_avs_metadata_t *)0)->buf) / 2; + + if (lws_ss_create(context, 0, &ssi, context, &hss_avs_sync, + NULL, NULL)) { + lwsl_err("%s: failed to create avs metadata secstream\n", + __func__); + } + break; + case LWSSSCS_ALL_RETRIES_FAILED: + /* for this demo app, we want to exit on fail to connect */ + interrupted = 1; + break; + case LWSSSCS_DISCONNECTED: + break; + case LWSSSCS_DESTROYING: + lwsl_notice("%s: DESTROYING\n", __func__); + if (wav) { + free(wav); + wav = NULL; + } + break; + default: + break; + } + + return 0; +} + +int +avs_example_start(struct lws_context *context) +{ + lws_ss_info_t ssi; + struct stat stat; + int fd; + + if (hss_avs_event) + return 0; + + fd = open("./year.wav", O_RDONLY); + if (fd < 0) { + lwsl_err("%s: failed to open wav file\n", __func__); + + return 1; + } + if (fstat(fd, &stat) < 0) { + lwsl_err("%s: failed to stat wav file\n", __func__); + + goto bail; + } + + wav_len = stat.st_size; + wav = malloc(wav_len); + if (!wav) { + lwsl_err("%s: failed to alloc wav buffer", __func__); + + goto bail; + } + if (read(fd, wav, wav_len) != (int)wav_len) { + lwsl_err("%s: failed to read wav\n", __func__); + + goto bail; + } + close(fd); + + lwsl_user("%s: Starting AVS stream\n", __func__); + + /* AVS wants us to establish the long poll event stream first */ + + memset(&ssi, 0, sizeof(ssi)); + ssi.handle_offset = offsetof(ss_avs_event_t, ss); + ssi.opaque_user_data_offset = offsetof(ss_avs_event_t, opaque_data); + ssi.rx = ss_avs_event_rx; + ssi.tx = ss_avs_event_tx; + ssi.state = ss_avs_event_state; + ssi.user_alloc = sizeof(ss_avs_event_t); + ssi.streamtype = "avs_event"; + + if (lws_ss_create(context, 0, &ssi, context, &hss_avs_event, NULL, NULL)) { + lwsl_err("%s: failed to create avs event secure stream\n", + __func__); + free(wav); + wav = NULL; + return 1; + } + + return 0; + +bail: + close(fd); + + return 1; +} diff --git a/minimal-examples/secure-streams/minimal-secure-streams-avs/main-client.c b/minimal-examples/secure-streams/minimal-secure-streams-avs/main-client.c new file mode 100644 index 000000000..bb9b8947b --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-avs/main-client.c @@ -0,0 +1,128 @@ +/* + * lws-minimal-secure-streams-avs + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + */ + +#include +#include +#include + +extern int +avs_example_start(struct lws_context *context); + +int interrupted, bad = 1; +static lws_state_notify_link_t nl; + +static const char *canned_root_token_payload = + "grant_type=refresh_token" + "&refresh_token=Atzr|IwEBIJedGXjDqsU_vMxykqOMg" + "SHfYe3CPcedueWEMWSDMaDnEmiW8RlR1Kns7Cb4B-TOSnqp7ifVsY4BMY2B8tpHfO39XP" + "zfu9HapGjTR458IyHX44FE71pWJkGZ79uVBpljP4sazJuk8XS3Oe_yLnm_DIO6fU1nU3Y" + "0flYmsOiOAQE_gRk_pdlmEtHnpMA-9rLw3mkY5L89Ty9kUygBsiFaYatouROhbsTn8-jW" + "k1zZLUDpT6ICtBXSnrCIg0pUbZevPFhTwdXd6eX-u4rq0W-XaDvPWFO7au-iPb4Zk5eZE" + "iX6sissYrtNmuEXc2uHu7MnQO1hHCaTdIO2CANVumf-PHSD8xseamyh04sLV5JgFzY45S" + "KvKMajiUZuLkMokOx86rjC2Hdkx5DO7G-dbG1ufBDG-N79pFMSs7Ck5pc283IdLoJkCQc" + "AGvTX8o8I29QqkcGou-9TKhOJmpX8As94T61ok0UqqEKPJ7RhfQHHYdCtsdwxgvfVr9qI" + "xL_hDCcTho8opCVX-6QhJHl6SQFlTw13" + "&client_id=" + "amzn1.application-oa2-client.4823334c434b4190a2b5a42c07938a2d"; + +static int +app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link, + int current, int target) +{ + struct lws_context *context = lws_system_context_from_system_mgr(mgr); + lws_system_blob_t *ab = lws_system_get_blob(context, + LWS_SYSBLOB_TYPE_AUTH, 1 /* AUTH_IDX_ROOT */); + size_t size; + + /* + * For the things we care about, let's notice if we are trying to get + * past them when we haven't solved them yet, and make the system + * state wait while we trigger the dependent action. + */ + switch (target) { + case LWS_SYSTATE_REGISTERED: + size = lws_system_blob_get_size(ab); + if (size) + break; + + /* let's register our canned root token so auth can use it */ + lws_system_blob_direct_set(ab, + (const uint8_t *)canned_root_token_payload, + strlen(canned_root_token_payload)); + break; + case LWS_SYSTATE_OPERATIONAL: + if (current == LWS_SYSTATE_OPERATIONAL) + avs_example_start(context); + break; + case LWS_SYSTATE_POLICY_INVALID: + /* + * This is a NOP since we used direct set... but in a real + * system this could easily change to be done on the heap, then + * this would be important + */ + lws_system_blob_destroy(lws_system_get_blob(context, + LWS_SYSBLOB_TYPE_AUTH, + 1 /* AUTH_IDX_ROOT */)); + break; + } + + return 0; +} + +static void +sigint_handler(int sig) +{ + interrupted = 1; +} + +static lws_state_notify_link_t * const app_notifier_list[] = { + &nl, NULL +}; + +int main(int argc, const char **argv) +{ + struct lws_context_creation_info info; + struct lws_context *context; + int n = 0; + + signal(SIGINT, sigint_handler); + memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_cmdline_option_handle_builtin(argc, argv, &info); + + lwsl_user("LWS secure streams - AVS test client [-d]\n"); + + info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + info.fd_limit_per_thread = 1 + 6 + 1; + info.protocols = lws_sspc_protocols; + info.port = CONTEXT_PORT_NO_LISTEN; + +#if defined(LWS_WITH_DETAILED_LATENCY) + info.detailed_latency_cb = lws_det_lat_plot_cb; + info.detailed_latency_filepath = "/tmp/lws-latency-ssproxy"; +#endif + + /* integrate us with lws system state management when context created */ + nl.name = "app"; + nl.notify_cb = app_system_state_nf; + info.register_notifier_list = app_notifier_list; + + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + /* the event loop */ + + while (n >= 0 && !interrupted) + n = lws_service(context, 0); + + lws_context_destroy(context); + lwsl_user("Completed: %s\n", bad ? "failed" : "OK"); + + return bad; +} diff --git a/minimal-examples/secure-streams/minimal-secure-streams-avs/main.c b/minimal-examples/secure-streams/minimal-secure-streams-avs/main.c new file mode 100644 index 000000000..fc154329d --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-avs/main.c @@ -0,0 +1,357 @@ +/* + * lws-minimal-secure-streams-avs + * + * Written in 2019-2020 by Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + */ + +#include +#include +#include + +extern int +avs_example_start(struct lws_context *context); + +int interrupted, bad = 1; +static lws_state_notify_link_t nl; +static const char * const default_ss_policy = + "{" + "\"release\":" "\"01234567\"," + "\"product\":" "\"myproduct\"," + "\"schema-version\":" "1," +// "\"via-socks5\":" "\"127.0.0.1:1080\"," + "\"retry\": [" /* named backoff / retry strategies */ + "{\"default\": {" + "\"backoff\": [" "1000," + "2000," + "3000," + "5000," + "10000" + "]," + "\"conceal\":" "5," + "\"jitterpc\":" "20," + "\"svalidping\":" "60," + "\"svalidhup\":" "64" + "}}" + "]," + "\"certs\": [" /* named individual certificates in BASE64 DER */ + "{\"digicert_global_root_g2\": \"" /* api.amazon.com 2038-01 */ + "MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh" + "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3" + "d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH" + "MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT" + "MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j" + "b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG" + "9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI" + "2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx" + "1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ" + "q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz" + "tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ" + "vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP" + "BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV" + "5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY" + "1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4" + "NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG" + "Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91" + "8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe" + "pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl" + "MrY=" + "\"}," + "{\"digicert_global_ca_g2\": \"" /* api.amazon.com 2028-08 */ + "MIIEizCCA3OgAwIBAgIQDI7gyQ1qiRWIBAYe4kH5rzANBgkqhkiG9w0BAQsFADBh" + "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3" + "d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH" + "MjAeFw0xMzA4MDExMjAwMDBaFw0yODA4MDExMjAwMDBaMEQxCzAJBgNVBAYTAlVT" + "MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxHjAcBgNVBAMTFURpZ2lDZXJ0IEdsb2Jh" + "bCBDQSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANNIfL7zBYZd" + "W9UvhU5L4IatFaxhz1uvPmoKR/uadpFgC4przc/cV35gmAvkVNlW7SHMArZagV+X" + "au4CLyMnuG3UsOcGAngLH1ypmTb+u6wbBfpXzYEQQGfWMItYNdSWYb7QjHqXnxr5" + "IuYUL6nG6AEfq/gmD6yOTSwyOR2Bm40cZbIc22GoiS9g5+vCShjEbyrpEJIJ7RfR" + "ACvmfe8EiRROM6GyD5eHn7OgzS+8LOy4g2gxPR/VSpAQGQuBldYpdlH5NnbQtwl6" + "OErXb4y/E3w57bqukPyV93t4CTZedJMeJfD/1K2uaGvG/w/VNfFVbkhJ+Pi474j4" + "8V4Rd6rfArMCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0P" + "AQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYYaHR0cDovL29j" + "c3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6Ly9jcmw0LmRp" + "Z2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RHMi5jcmwwN6A1oDOGMWh0dHA6" + "Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RHMi5jcmwwPQYD" + "VR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2lj" + "ZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFCRuKy3QapJRUSVpAaqaR6aJ50AgMB8GA1Ud" + "IwQYMBaAFE4iVCAYlebjbuYP+vq5Eu0GF485MA0GCSqGSIb3DQEBCwUAA4IBAQAL" + "OYSR+ZfrqoGvhOlaOJL84mxZvzbIRacxAxHhBsCsMsdaVSnaT0AC9aHesO3ewPj2" + "dZ12uYf+QYB6z13jAMZbAuabeGLJ3LhimnftiQjXS8X9Q9ViIyfEBFltcT8jW+rZ" + "8uckJ2/0lYDblizkVIvP6hnZf1WZUXoOLRg9eFhSvGNoVwvdRLNXSmDmyHBwW4co" + "atc7TlJFGa8kBpJIERqLrqwYElesA8u49L3KJg6nwd3jM+/AVTANlVlOnAM2BvjA" + "jxSZnE0qnsHhfTuvcqdFuhOWKU4Z0BqYBvQ3lBetoxi6PrABDJXWKTUgNX31EGDk" + "92hiHuwZ4STyhxGs6QiA" + "\"}," + "{\"starfield_services_root_ca\": \"" + "MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx" + "EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT" + "HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs" + "ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5" + "MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD" + "VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy" + "ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy" + "dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI" + "hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p" + "OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2" + "8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K" + "Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe" + "hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk" + "6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw" + "DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q" + "AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI" + "bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB" + "ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z" + "qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd" + "iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn" + "0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN" + "sSi6" + "\"}," + "{\"starfield_class_2_ca\": \"" + "MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl" + "MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp" + "U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw" + "NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE" + "ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp" + "ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3" + "DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf" + "8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN" + "+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0" + "X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa" + "K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA" + "1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G" + "A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR" + "zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0" + "YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD" + "bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w" + "DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3" + "L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D" + "eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl" + "xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp" + "VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY" + "WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q=" + "\"}" + "]," + "\"trust_stores\": [" /* named cert chains */ + "{" /* chain for alexa.na.gateway.devices.a2z.com */ + "\"name\": \"avs_via_starfield\"," + "\"stack\": [" + "\"starfield_class_2_ca\"," + "\"starfield_services_root_ca\"" + "]" + "}," + "{" /* chain for api.amazon.com */ + "\"name\": \"api_amazon_com\"," + "\"stack\": [" + "\"digicert_global_ca_g2\"," + "\"digicert_global_root_g2\"" + "]" + "}" + "]," + "\"s\": [" /* the supported stream types */ + "{\"api_amazon_com_auth\": {" + "\"endpoint\":" "\"api.amazon.com\"," + "\"port\":" "443," + "\"protocol\":" "\"h1\"," + "\"http_method\":" "\"POST\"," + "\"http_url\":" "\"auth/o2/token\"," + "\"plugins\":" "[]," + "\"opportunistic\":" "true," + "\"tls\":" "true," + "\"h2q_oflow_txcr\":" "true," + "\"http_www_form_urlencoded\":" "true," + "\"http_no_content_length\":" "true," + "\"retry\":" "\"default\"," + "\"tls_trust_store\":" "\"api_amazon_com\"" + "}}," + "{\"avs_event\": {" + "\"endpoint\":" "\"alexa.na.gateway.devices.a2z.com\"," + "\"port\":" "443," + "\"protocol\":" "\"h2\"," + "\"http_method\":" "\"GET\"," + "\"http_url\":" "\"v20160207/directives\"," + "\"h2q_oflow_txcr\":" "true," + "\"http_auth_header\":" "\"authorization:\"," + "\"http_auth_preamble\":" "\"Bearer \"," + "\"nailed_up\":" "true," + "\"long_poll\":" "true," + "\"retry\":" "\"default\"," + "\"plugins\":" "[]," + "\"tls\":" "true," + "\"tls_trust_store\":" "\"avs_via_starfield\"" + "}}," + "{\"avs_metadata\": {" + "\"endpoint\":" "\"alexa.na.gateway.devices.a2z.com\"," + "\"port\":" "443," + "\"protocol\":" "\"h2\"," + "\"http_method\":" "\"POST\"," + "\"http_url\":" "\"v20160207/events\"," + "\"http_no_content_length\":" "true," + "\"h2q_oflow_txcr\":" "true," + "\"http_auth_header\":" "\"authorization:\"," + "\"http_auth_preamble\":" "\"Bearer \"," + "\"http_multipart_name\":" "\"metadata\"," + "\"http_mime_content_type\":" "\"application/json; charset=UTF-8\"," + "\"rideshare\":" "\"avs_audio\"," + "\"retry\":" "\"default\"," + "\"plugins\":" "[]," + "\"tls\":" "true," + "\"tls_trust_store\":" "\"avs_via_starfield\"" + "}}," + "{\"avs_audio\": {" + "\"endpoint\":" "\"alexa.na.gateway.devices.a2z.com\"," + "\"port\":" "443," + "\"protocol\":" "\"h2\"," + "\"http_method\":" "\"POST\"," + "\"http_url\":" "\"v20160207/events\"," + "\"http_no_content_length\":" "true," + "\"plugins\":" "[]," + "\"tls\":" "true," + "\"h2q_oflow_txcr\":" "true," + "\"http_auth_header\":" "\"authorization:\"," + "\"http_auth_preamble\":" "\"Bearer \"," + "\"http_multipart_name\":" "\"audio\"," + "\"http_mime_content_type\":" "\"application/octet-stream\"," + "\"retry\":" "\"default\"," + "\"tls_trust_store\":" "\"avs_via_starfield\"" + "}}" + "]" + "}" +; + +static const char *canned_root_token_payload = + "grant_type=refresh_token" + "&refresh_token=Atzr|IwEBIJedGXjDqsU_vMxykqOMg" + "SHfYe3CPcedueWEMWSDMaDnEmiW8RlR1Kns7Cb4B-TOSnqp7ifVsY4BMY2B8tpHfO39XP" + "zfu9HapGjTR458IyHX44FE71pWJkGZ79uVBpljP4sazJuk8XS3Oe_yLnm_DIO6fU1nU3Y" + "0flYmsOiOAQE_gRk_pdlmEtHnpMA-9rLw3mkY5L89Ty9kUygBsiFaYatouROhbsTn8-jW" + "k1zZLUDpT6ICtBXSnrCIg0pUbZevPFhTwdXd6eX-u4rq0W-XaDvPWFO7au-iPb4Zk5eZE" + "iX6sissYrtNmuEXc2uHu7MnQO1hHCaTdIO2CANVumf-PHSD8xseamyh04sLV5JgFzY45S" + "KvKMajiUZuLkMokOx86rjC2Hdkx5DO7G-dbG1ufBDG-N79pFMSs7Ck5pc283IdLoJkCQc" + "AGvTX8o8I29QqkcGou-9TKhOJmpX8As94T61ok0UqqEKPJ7RhfQHHYdCtsdwxgvfVr9qI" + "xL_hDCcTho8opCVX-6QhJHl6SQFlTw13" + "&client_id=" + "amzn1.application-oa2-client.4823334c434b4190a2b5a42c07938a2d"; + +static int +app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link, + int current, int target) +{ + struct lws_context *context = lws_system_context_from_system_mgr(mgr); + lws_system_blob_t *ab = lws_system_get_blob(context, + LWS_SYSBLOB_TYPE_AUTH, 1 /* AUTH_IDX_ROOT */); + size_t size; + + /* + * For the things we care about, let's notice if we are trying to get + * past them when we haven't solved them yet, and make the system + * state wait while we trigger the dependent action. + */ + switch (target) { + case LWS_SYSTATE_REGISTERED: + size = lws_system_blob_get_size(ab); + if (size) + break; + + /* let's register our canned root token so auth can use it */ + lws_system_blob_direct_set(ab, + (const uint8_t *)canned_root_token_payload, + strlen(canned_root_token_payload)); + break; + case LWS_SYSTATE_OPERATIONAL: + if (current == LWS_SYSTATE_OPERATIONAL) + avs_example_start(context); + break; + case LWS_SYSTATE_POLICY_INVALID: + /* + * This is a NOP since we used direct set... but in a real + * system this could easily change to be done on the heap, then + * this would be important + */ + lws_system_blob_destroy(lws_system_get_blob(context, + LWS_SYSBLOB_TYPE_AUTH, + 1 /* AUTH_IDX_ROOT */)); + break; + } + + return 0; +} + +static void +sigint_handler(int sig) +{ + interrupted = 1; +} + +static lws_state_notify_link_t * const app_notifier_list[] = { + &nl, NULL +}; + +int main(int argc, const char **argv) +{ + struct lws_context_creation_info info; + struct lws_context *context; + int n = 0; + + signal(SIGINT, sigint_handler); + memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_cmdline_option_handle_builtin(argc, argv, &info); + + lwsl_user("LWS secure streams - AVS test [-d]\n"); + + info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS | + LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + info.fd_limit_per_thread = 1 + 6 + 1; + info.pss_policies_json = default_ss_policy; + info.port = CONTEXT_PORT_NO_LISTEN; + +#if defined(LWS_SS_USE_SSPC) + { + const char *p; + + /* connect to ssproxy via UDS by default, else via + * tcp connection to this port */ + if ((p = lws_cmdline_option(argc, argv, "-p"))) + info.ss_proxy_port = atoi(p); + + /* UDS "proxy.ss.lws" in abstract namespace, else this socket + * path; when -p given this can specify the network interface + * to bind to */ + if ((p = lws_cmdline_option(argc, argv, "-i"))) + info.ss_proxy_bind = p; + + /* if -p given, -a specifies the proxy address to connect to */ + if ((p = lws_cmdline_option(argc, argv, "-a"))) + info.ss_proxy_address = p; + } +#endif + +#if defined(LWS_WITH_DETAILED_LATENCY) + info.detailed_latency_cb = lws_det_lat_plot_cb; + info.detailed_latency_filepath = "/tmp/lws-latency-ssproxy"; +#endif + + /* integrate us with lws system state management when context created */ + nl.name = "app"; + nl.notify_cb = app_system_state_nf; + info.register_notifier_list = app_notifier_list; + + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + /* the event loop */ + + while (n >= 0 && !interrupted) + n = lws_service(context, 0); + + lws_context_destroy(context); + lwsl_user("Completed: %s\n", bad ? "failed" : "OK"); + + return bad; +} diff --git a/minimal-examples/secure-streams/minimal-secure-streams-avs/year.wav b/minimal-examples/secure-streams/minimal-secure-streams-avs/year.wav new file mode 100644 index 000000000..333b05742 Binary files /dev/null and b/minimal-examples/secure-streams/minimal-secure-streams-avs/year.wav differ diff --git a/minimal-examples/secure-streams/minimal-secure-streams-client-tx/CMakeLists.txt b/minimal-examples/secure-streams/minimal-secure-streams-client-tx/CMakeLists.txt new file mode 100644 index 000000000..38dd30066 --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-client-tx/CMakeLists.txt @@ -0,0 +1,81 @@ +cmake_minimum_required(VERSION 2.8) +include(CheckCSourceCompiles) + +set(SAMP lws-minimal-secure-streams-client-tx) +set(SRCS minimal-secure-streams-client-tx.c) + +# If we are being built as part of lws, confirm current build config supports +# reqconfig, else skip building ourselves. +# +# If we are being built externally, confirm installed lws was configured to +# support reqconfig, else error out with a helpful message about the problem. +# +MACRO(require_lws_config reqconfig _val result) + + if (DEFINED ${reqconfig}) + if (${reqconfig}) + set (rq 1) + else() + set (rq 0) + endif() + else() + set(rq 0) + endif() + + if (${_val} EQUAL ${rq}) + set(SAME 1) + else() + set(SAME 0) + endif() + + if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME}) + if (${_val}) + message("${SAMP}: skipping as lws being built without ${reqconfig}") + else() + message("${SAMP}: skipping as lws built with ${reqconfig}") + endif() + set(${result} 0) + else() + if (LWS_WITH_MINIMAL_EXAMPLES) + set(MET ${SAME}) + else() + CHECK_C_SOURCE_COMPILES("#include \nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig}) + if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig}) + set(HAS_${reqconfig} 0) + else() + set(HAS_${reqconfig} 1) + endif() + if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val})) + set(MET 1) + else() + set(MET 0) + endif() + endif() + if (NOT MET) + if (${_val}) + message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}") + else() + message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project") + endif() + endif() + endif() +ENDMACRO() + + +set(requirements 1) +require_lws_config(LWS_ROLE_H1 1 requirements) +require_lws_config(LWS_WITHOUT_CLIENT 0 requirements) +require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements) +require_lws_config(LWS_WITH_SECURE_STREAMS_PROXY_API 1 requirements) + + +if (requirements) + add_executable(${SAMP} ${SRCS}) + + if (websockets_shared) + target_link_libraries(${SAMP} websockets_shared) + add_dependencies(${SAMP} websockets_shared) + else() + target_link_libraries(${SAMP} websockets) + endif() +endif() diff --git a/minimal-examples/secure-streams/minimal-secure-streams-client-tx/README.md b/minimal-examples/secure-streams/minimal-secure-streams-client-tx/README.md new file mode 100644 index 000000000..80a1c6928 --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-client-tx/README.md @@ -0,0 +1,64 @@ +# lws minimal secure streams + +The application goes to https://warmcat.com and reads index.html there. + +It does it using Secure Streams... the main code in minimal-secure-streams.c +just sets up the context and opens a secure stream of type "mintest". + +The handler for state changes and payloads for "mintest" is in ss-myss.c + +The information about how a "mintest" stream should connect and the +protocol it uses is kept separated in policy-database.c + +## build + +``` + $ cmake . && make +``` + +## usage + +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 + +``` +[2019/08/12 07:16:11:0045] USR: LWS minimal secure streams [-d] [-f] +[2019/08/12 07:16:12:6102] USR: myss_state: LWSSSCS_CREATING, ord 0x0 +[2019/08/12 07:16:12:6107] USR: myss_state: LWSSSCS_POLL, ord 0x0 +[2019/08/12 07:16:12:6117] N: lws_ss_client_connect: connecting h1get warmcat.com / +[2019/08/12 07:16:12:6118] USR: myss_state: LWSSSCS_CONNECTING, ord 0x0 +[2019/08/12 07:16:13:4171] USR: myss_state: LWSSSCS_CONNECTED, ord 0x0 +[2019/08/12 07:16:13:4222] USR: myss_rx: len 1024, flags: 1 +[2019/08/12 07:16:13:4243] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4244] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4244] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4245] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4246] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4247] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4252] USR: myss_rx: len 1015, flags: 0 +[2019/08/12 07:16:13:4264] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4265] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4266] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4267] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4268] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4268] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4269] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4270] USR: myss_rx: len 1015, flags: 0 +[2019/08/12 07:16:13:4278] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4279] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4280] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4281] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4282] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4283] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4283] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4284] USR: myss_rx: len 1015, flags: 0 +[2019/08/12 07:16:13:4287] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4288] USR: myss_rx: len 947, flags: 0 +[2019/08/12 07:16:13:4293] USR: myss_rx: len 0, flags: 2 +[2019/08/12 07:16:13:4399] USR: myss_state: LWSSSCS_DISCONNECTED, ord 0x0 +[2019/08/12 07:16:13:4761] USR: myss_state: LWSSSCS_DESTROYING, ord 0x0 +[2019/08/12 07:16:13:4781] USR: Completed: OK +``` diff --git a/minimal-examples/secure-streams/minimal-secure-streams-client-tx/minimal-secure-streams-client-tx.c b/minimal-examples/secure-streams/minimal-secure-streams-client-tx/minimal-secure-streams-client-tx.c new file mode 100644 index 000000000..0eee1e917 --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-client-tx/minimal-secure-streams-client-tx.c @@ -0,0 +1,198 @@ +/* + * lws-minimal-secure-streams-tx + * + * Written in 2010-2020 by Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * + * This demonstrates tx from secure streams. + * + * It opens a stream and fires small 80-byte payloads on it at 50Hz (20ms) + */ + +#include +#include +#include + +#define PKT_SIZE 80 +#define RATE_US 50000 + +static int interrupted, bad = 1; + +typedef struct myss { + struct lws_sspc_handle *ss; + void *opaque_data; + /* ... application specific state ... */ + lws_sorted_usec_list_t sul; + + int count; + char due; +} myss_t; + +/* secure streams payload interface */ + +static int +myss_rx(void *userobj, const uint8_t *buf, size_t len, int flags) +{ +// myss_t *m = (myss_t *)userobj; + + //lwsl_user("%s: len %d, flags: %d\n", __func__, (int)len, flags); + //lwsl_hexdump_info(buf, len); + + return 0; +} + +static void +txcb(struct lws_sorted_usec_list *sul) +{ + myss_t *m = lws_container_of(sul, myss_t, sul); + + if (m->count == 1000) { + interrupted = 1; + return; + } + + m->due = 1; + lws_sspc_request_tx(m->ss); + + lws_sul_schedule(lws_sspc_get_context(m->ss), 0, &m->sul, txcb, RATE_US); + + +} + +static int +myss_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len, + int *flags) +{ + myss_t *m = (myss_t *)userobj; + + if (!m->due) + return 0; + + m->due = 0; + + if (lws_get_random(lws_sspc_get_context(m->ss), buf, PKT_SIZE) != PKT_SIZE) + return 1; + + *len = PKT_SIZE; + *flags = 0; + if (!m->count) + *flags |= LWSSS_FLAG_SOM; + if (m->count == 999) { + *flags |= LWSSS_FLAG_EOM; + lwsl_user("%s: sent final packet\n", __func__); + bad = 0; + } + + m->count++; + + lws_sul_schedule(lws_sspc_get_context(m->ss), 0, &m->sul, txcb, RATE_US); + + // lwsl_user("%s: sending pkt %d\n", __func__, m->count); + + return 0; +} + +static int +myss_state(void *userobj, void *sh, lws_ss_constate_t state, + lws_ss_tx_ordinal_t ack) +{ + myss_t *m = (myss_t *)userobj; + struct lws_context *context = lws_sspc_get_context(m->ss); + + lwsl_user("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name(state), + (unsigned int)ack); + + switch (state) { + case LWSSSCS_CREATING: + break; + case LWSSSCS_CONNECTED: + lws_sul_schedule(context, 0, &m->sul, txcb, RATE_US); + break; + case LWSSSCS_DISCONNECTED: + lws_sul_schedule(context, 0, &m->sul, txcb, + LWS_SET_TIMER_USEC_CANCEL); + break; + case LWSSSCS_ALL_RETRIES_FAILED: + /* if we're out of retries, we want to close the app and FAIL */ + interrupted = 1; + break; + default: + break; + } + + return 0; +} + +static void +sigint_handler(int sig) +{ + interrupted = 1; +} + +int main(int argc, const char **argv) +{ + int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + struct lws_context_creation_info info; + struct lws_context *context; + lws_ss_info_t ssi; + const char *p; + + signal(SIGINT, sigint_handler); + + if ((p = lws_cmdline_option(argc, argv, "-d"))) + logs = atoi(p); + + lws_set_log_level(logs, NULL); + lwsl_user("LWS secure streams client TX [-d]\n"); + + memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + + info.options = //LWS_SERVER_OPTION_EXPLICIT_VHOSTS | + LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + info.fd_limit_per_thread = 1 + 6 + 1; + info.port = CONTEXT_PORT_NO_LISTEN; + info.protocols = lws_sspc_protocols; +#if defined(LWS_WITH_DETAILED_LATENCY) + info.detailed_latency_cb = lws_det_lat_plot_cb; + info.detailed_latency_filepath = "/tmp/lws-latency-ssclient"; +#endif + + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + /* + * We're requesting a secure stream via proxy... where and how this + * connects are details managed by the proxy policy + */ + + memset(&ssi, 0, sizeof ssi); + ssi.handle_offset = offsetof(myss_t, ss); + ssi.opaque_user_data_offset = offsetof(myss_t, opaque_data); + ssi.rx = myss_rx; + ssi.tx = myss_tx; + ssi.state = myss_state; + ssi.user_alloc = sizeof(myss_t); + ssi.streamtype = "spam"; + + if (lws_sspc_create(context, 0, &ssi, NULL, NULL, NULL, NULL)) { + lwsl_err("%s: create secure stream failed\n", __func__); + goto bail; + } + + /* the event loop */ + + while (n >= 0 && !interrupted) + n = lws_service(context, 0); + +bail: + lws_context_destroy(context); + lwsl_user("Completed: %s\n", bad ? "failed" : "OK"); + + return bad; +} diff --git a/minimal-examples/secure-streams/minimal-secure-streams-proxy/CMakeLists.txt b/minimal-examples/secure-streams/minimal-secure-streams-proxy/CMakeLists.txt new file mode 100644 index 000000000..00c0c3d60 --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-proxy/CMakeLists.txt @@ -0,0 +1,80 @@ +cmake_minimum_required(VERSION 2.8) +include(CheckCSourceCompiles) + +set(SAMP lws-minimal-secure-streams-proxy) +set(SRCS main.c) + +# If we are being built as part of lws, confirm current build config supports +# reqconfig, else skip building ourselves. +# +# If we are being built externally, confirm installed lws was configured to +# support reqconfig, else error out with a helpful message about the problem. +# +MACRO(require_lws_config reqconfig _val result) + + if (DEFINED ${reqconfig}) + if (${reqconfig}) + set (rq 1) + else() + set (rq 0) + endif() + else() + set(rq 0) + endif() + + if (${_val} EQUAL ${rq}) + set(SAME 1) + else() + set(SAME 0) + endif() + + if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME}) + if (${_val}) + message("${SAMP}: skipping as lws being built without ${reqconfig}") + else() + message("${SAMP}: skipping as lws built with ${reqconfig}") + endif() + set(${result} 0) + else() + if (LWS_WITH_MINIMAL_EXAMPLES) + set(MET ${SAME}) + else() + CHECK_C_SOURCE_COMPILES("#include \nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig}) + if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig}) + set(HAS_${reqconfig} 0) + else() + set(HAS_${reqconfig} 1) + endif() + if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val})) + set(MET 1) + else() + set(MET 0) + endif() + endif() + if (NOT MET) + if (${_val}) + message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}") + else() + message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project") + endif() + endif() + endif() +ENDMACRO() + + +set(requirements 1) +require_lws_config(LWS_ROLE_H1 1 requirements) +require_lws_config(LWS_WITHOUT_CLIENT 0 requirements) +require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements) +require_lws_config(LWS_WITH_SECURE_STREAMS_PROXY_API 1 requirements) + +if (requirements) + add_executable(${SAMP} ${SRCS}) + + if (websockets_shared) + target_link_libraries(${SAMP} websockets_shared) + add_dependencies(${SAMP} websockets_shared) + else() + target_link_libraries(${SAMP} websockets) + endif() +endif() diff --git a/minimal-examples/secure-streams/minimal-secure-streams-proxy/README.md b/minimal-examples/secure-streams/minimal-secure-streams-proxy/README.md new file mode 100644 index 000000000..ab5cbcb8a --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-proxy/README.md @@ -0,0 +1,33 @@ +# lws minimal secure streams proxy + +Operates as a secure streams proxy, by default on a listening unix domain socket +"proxy.ss.lws" in the Linux abstract namespace. + +Give -p to have it listen on a specific tcp port instead. + +## build + +``` + $ cmake . && make +``` + +## usage + +Commandline option|Meaning +---|--- +-d |Debug verbosity in decimal, eg, -d15 +-f| Force connecting to the wrong endpoint to check backoff retry flow +-p |If not given, proxy listens on a Unix Domain Socket, if given listen on specified tcp port +-i |Optionally specify the UDS path (no -p) or network interface to bind to (if -p also given) + +``` +[2020/02/26 15:41:27:5768] U: LWS secure streams Proxy [-d] +[2020/02/26 15:41:27:5770] N: lws_ss_policy_set: 2.064KiB, pad 70%: hardcoded +[2020/02/26 15:41:27:5771] N: lws_tls_client_create_vhost_context: using mem client CA cert 1391 +[2020/02/26 15:41:27:8681] N: lws_ss_policy_set: 4.512KiB, pad 15%: updated +[2020/02/26 15:41:27:8682] N: lws_tls_client_create_vhost_context: using mem client CA cert 837 +[2020/02/26 15:41:27:8683] N: lws_tls_client_create_vhost_context: using mem client CA cert 1043 +[2020/02/26 15:41:27:8684] N: lws_tls_client_create_vhost_context: using mem client CA cert 1167 +[2020/02/26 15:41:27:8684] N: lws_tls_client_create_vhost_context: using mem client CA cert 1391 +[2020/02/26 15:41:28:4226] N: ss_api_amazon_auth_rx: acquired 567-byte api.amazon.com auth token, exp 3600s +``` diff --git a/minimal-examples/secure-streams/minimal-secure-streams-proxy/main.c b/minimal-examples/secure-streams/minimal-secure-streams-proxy/main.c new file mode 100644 index 000000000..33e0d461a --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-proxy/main.c @@ -0,0 +1,285 @@ +/* + * lws-minimal-secure-streams-proxy + * + * Written in 2010-2020 by Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * + * This is the proxy part for examples built to use it to connect to... it has + * the policy and the core SS function, but it doesn't contain any of the user + * code "business logic"... that's in the clients. + * + * The proxy side has the policy and performs the onward connection proxying + * fulfilment. The clients state the streamtype name they want and ask for the + * client to do the connection part. + * + * Rideshare information is being parsed out at the proxy side; the SSS RX part + * also brings with it rideshare names. + * + * Metadata is passed back over SSS from the client in the TX messages for the + * proxy to use per the policy. + */ + +#include +#include +#include + +static int interrupted, bad = 1, port = 0 /* unix domain socket */; +static const char *ibind = NULL; /* default to unix domain skt "proxy.ss.lws" */ +static lws_state_notify_link_t nl; + +/* + * We just define enough policy so it can fetch the latest one securely + */ + +static const char * const default_ss_policy = + "{" + "\"release\":" "\"01234567\"," + "\"product\":" "\"myproduct\"," + "\"schema-version\":" "1," + "\"retry\": [" /* named backoff / retry strategies */ + "{\"default\": {" + "\"backoff\": [" "1000," + "2000," + "3000," + "5000," + "10000" + "]," + "\"conceal\":" "5," + "\"jitterpc\":" "20," + "\"svalidping\":" "30," + "\"svalidhup\":" "35" + "}}" + "]," + "\"certs\": [" /* named individual certificates in BASE64 DER */ + /* + * Let's Encrypt certs for warmcat.com / libwebsockets.org + * + * We fetch the real policy from there using SS and switch to + * using that. + */ + "{\"isrg_root_x1\": \"" /* ISRG ROOT X1 */ + "MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw" + "TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh" + "cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4" + "WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu" + "ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY" + "MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc" + "h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+" + "0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U" + "A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW" + "T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH" + "B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC" + "B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv" + "KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn" + "OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn" + "jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw" + "qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI" + "rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV" + "HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq" + "hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL" + "ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ" + "3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK" + "NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5" + "ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur" + "TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC" + "jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc" + "oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq" + "4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA" + "mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d" + "emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=" + "\"}," + "{\"LEX3_isrg_root_x1\": \"" /* LE X3 signed by ISRG X1 root */ + "MIIFjTCCA3WgAwIBAgIRANOxciY0IzLc9AUoUSrsnGowDQYJKoZIhvcNAQELBQAw" + "TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh" + "cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTYxMDA2MTU0MzU1" + "WhcNMjExMDA2MTU0MzU1WjBKMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg" + "RW5jcnlwdDEjMCEGA1UEAxMaTGV0J3MgRW5jcnlwdCBBdXRob3JpdHkgWDMwggEi" + "MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCc0wzwWuUuR7dyXTeDs2hjMOrX" + "NSYZJeG9vjXxcJIvt7hLQQWrqZ41CFjssSrEaIcLo+N15Obzp2JxunmBYB/XkZqf" + "89B4Z3HIaQ6Vkc/+5pnpYDxIzH7KTXcSJJ1HG1rrueweNwAcnKx7pwXqzkrrvUHl" + "Npi5y/1tPJZo3yMqQpAMhnRnyH+lmrhSYRQTP2XpgofL2/oOVvaGifOFP5eGr7Dc" + "Gu9rDZUWfcQroGWymQQ2dYBrrErzG5BJeC+ilk8qICUpBMZ0wNAxzY8xOJUWuqgz" + "uEPxsR/DMH+ieTETPS02+OP88jNquTkxxa/EjQ0dZBYzqvqEKbbUC8DYfcOTAgMB" + "AAGjggFnMIIBYzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADBU" + "BgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEBATAwMC4GCCsGAQUFBwIB" + "FiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMB0GA1UdDgQWBBSo" + "SmpjBH3duubRObemRWXv86jsoTAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3Js" + "LnJvb3QteDEubGV0c2VuY3J5cHQub3JnMHIGCCsGAQUFBwEBBGYwZDAwBggrBgEF" + "BQcwAYYkaHR0cDovL29jc3Aucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcvMDAGCCsG" + "AQUFBzAChiRodHRwOi8vY2VydC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZy8wHwYD" + "VR0jBBgwFoAUebRZ5nu25eQBc4AIiMgaWPbpm24wDQYJKoZIhvcNAQELBQADggIB" + "ABnPdSA0LTqmRf/Q1eaM2jLonG4bQdEnqOJQ8nCqxOeTRrToEKtwT++36gTSlBGx" + "A/5dut82jJQ2jxN8RI8L9QFXrWi4xXnA2EqA10yjHiR6H9cj6MFiOnb5In1eWsRM" + "UM2v3e9tNsCAgBukPHAg1lQh07rvFKm/Bz9BCjaxorALINUfZ9DD64j2igLIxle2" + "DPxW8dI/F2loHMjXZjqG8RkqZUdoxtID5+90FgsGIfkMpqgRS05f4zPbCEHqCXl1" + "eO5HyELTgcVlLXXQDgAWnRzut1hFJeczY1tjQQno6f6s+nMydLN26WuU4s3UYvOu" + "OsUxRlJu7TSRHqDC3lSE5XggVkzdaPkuKGQbGpny+01/47hfXXNB7HntWNZ6N2Vw" + "p7G6OfY+YQrZwIaQmhrIqJZuigsrbe3W+gdn5ykE9+Ky0VgVUsfxo52mwFYs1JKY" + "2PGDuWx8M6DlS6qQkvHaRUo0FMd8TsSlbF0/v965qGFKhSDeQoMpYnwcmQilRh/0" + "ayLThlHLN81gSkJjVrPI0Y8xCVPB4twb1PFUd2fPM3sA1tJ83sZ5v8vgFv2yofKR" + "PB0t6JzUA81mSqM3kxl5e+IZwhYAyO0OTg3/fs8HqGTNKd9BqoUwSRBzp06JMg5b" + "rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt" + "\"}" + "]," + "\"trust_stores\": [" /* named cert chains */ + "{" + "\"name\": \"le_via_isrg\"," + "\"stack\": [" + "\"isrg_root_x1\"," + "\"LEX3_isrg_root_x1\"" + "]" + "}" + "]," + "\"s\": [" + "{\"fetch_policy\": {" + "\"endpoint\":" "\"warmcat.com\"," + "\"port\":" "443," + "\"protocol\":" "\"h1\"," + "\"http_method\":" "\"GET\"," + "\"http_url\":" "\"policy/minimal-proxy.json\"," + "\"tls\":" "true," + "\"opportunistic\":" "true," + "\"retry\":" "\"default\"," + "\"tls_trust_store\":" "\"le_via_isrg\"" + "}}" + "}" +; + +static const char *canned_root_token_payload = + "grant_type=refresh_token" + "&refresh_token=Atzr|IwEBIJedGXjDqsU_vMxykqOMg" + "SHfYe3CPcedueWEMWSDMaDnEmiW8RlR1Kns7Cb4B-TOSnqp7ifVsY4BMY2B8tpHfO39XP" + "zfu9HapGjTR458IyHX44FE71pWJkGZ79uVBpljP4sazJuk8XS3Oe_yLnm_DIO6fU1nU3Y" + "0flYmsOiOAQE_gRk_pdlmEtHnpMA-9rLw3mkY5L89Ty9kUygBsiFaYatouROhbsTn8-jW" + "k1zZLUDpT6ICtBXSnrCIg0pUbZevPFhTwdXd6eX-u4rq0W-XaDvPWFO7au-iPb4Zk5eZE" + "iX6sissYrtNmuEXc2uHu7MnQO1hHCaTdIO2CANVumf-PHSD8xseamyh04sLV5JgFzY45S" + "KvKMajiUZuLkMokOx86rjC2Hdkx5DO7G-dbG1ufBDG-N79pFMSs7Ck5pc283IdLoJkCQc" + "AGvTX8o8I29QqkcGou-9TKhOJmpX8As94T61ok0UqqEKPJ7RhfQHHYdCtsdwxgvfVr9qI" + "xL_hDCcTho8opCVX-6QhJHl6SQFlTw13" + "&client_id=" + "amzn1.application-oa2-client.4823334c434b4190a2b5a42c07938a2d"; + +static int +app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link, + int current, int target) +{ + struct lws_context *context = lws_system_context_from_system_mgr(mgr); + lws_system_blob_t *ab = lws_system_get_blob(context, + LWS_SYSBLOB_TYPE_AUTH, 1 /* AUTH_IDX_ROOT */); + size_t size; + + /* + * For the things we care about, let's notice if we are trying to get + * past them when we haven't solved them yet, and make the system + * state wait while we trigger the dependent action. + */ + switch (target) { + case LWS_SYSTATE_REGISTERED: + size = lws_system_blob_get_size(ab); + if (size) + break; + + /* let's register our canned root token so auth can use it */ + lws_system_blob_direct_set(ab, + (const uint8_t *)canned_root_token_payload, + strlen(canned_root_token_payload)); + break; + case LWS_SYSTATE_OPERATIONAL: + if (current == LWS_SYSTATE_OPERATIONAL) + /* + * At this point we have DHCP, ntp, system auth token + * and we can reasonably create the proxy + */ + if (lws_ss_proxy_create(context, ibind, port)) { + lwsl_err("%s: failed to create ss proxy\n", + __func__); + return -1; + } + break; + case LWS_SYSTATE_POLICY_INVALID: + /* + * This is a NOP since we used direct set... but in a real + * system this could easily change to be done on the heap, then + * this would be important + */ + lws_system_blob_destroy(lws_system_get_blob(context, + LWS_SYSBLOB_TYPE_AUTH, + 1 /* AUTH_IDX_ROOT */)); + break; + } + + return 0; +} + +static lws_state_notify_link_t * const app_notifier_list[] = { + &nl, NULL +}; + +static void +sigint_handler(int sig) +{ + interrupted = 1; +} + +int main(int argc, const char **argv) +{ + int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + struct lws_context_creation_info info; + struct lws_context *context; + const char *p; + + signal(SIGINT, sigint_handler); + + if ((p = lws_cmdline_option(argc, argv, "-d"))) + logs = atoi(p); + + /* connect to ssproxy via UDS by default, else via tcp with this port */ + if ((p = lws_cmdline_option(argc, argv, "-p"))) + port = atoi(p); + + /* UDS "proxy.ss.lws" in abstract namespace, else this socket path; + * when -p given this can specify the network interface to bind to */ + if ((p = lws_cmdline_option(argc, argv, "-i"))) + ibind = p; + + lws_set_log_level(logs, NULL); + lwsl_user("LWS secure streams Proxy [-d]\n"); + + memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + + info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS | + LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + info.fd_limit_per_thread = 1 + 6 + 1; + info.pss_policies_json = default_ss_policy; + info.port = CONTEXT_PORT_NO_LISTEN; +#if defined(LWS_WITH_DETAILED_LATENCY) + info.detailed_latency_cb = lws_det_lat_plot_cb; + info.detailed_latency_filepath = "/tmp/lws-latency-ssproxy"; +#endif + + /* integrate us with lws system state management when context created */ + nl.name = "app"; + nl.notify_cb = app_system_state_nf; + info.register_notifier_list = app_notifier_list; + + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + /* the event loop */ + + while (n >= 0 && !interrupted) + n = lws_service(context, 0); + + bad = 0; + + lws_context_destroy(context); + lwsl_user("Completed: %s\n", bad ? "failed" : "OK"); + + return bad; +} diff --git a/minimal-examples/secure-streams/minimal-secure-streams-seq/CMakeLists.txt b/minimal-examples/secure-streams/minimal-secure-streams-seq/CMakeLists.txt new file mode 100644 index 000000000..b55a3a00e --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-seq/CMakeLists.txt @@ -0,0 +1,79 @@ +cmake_minimum_required(VERSION 2.8) +include(CheckCSourceCompiles) + +set(SAMP lws-minimal-secure-streams-seq) +set(SRCS minimal-secure-streams.c) + +# If we are being built as part of lws, confirm current build config supports +# reqconfig, else skip building ourselves. +# +# If we are being built externally, confirm installed lws was configured to +# support reqconfig, else error out with a helpful message about the problem. +# +MACRO(require_lws_config reqconfig _val result) + + if (DEFINED ${reqconfig}) + if (${reqconfig}) + set (rq 1) + else() + set (rq 0) + endif() + else() + set(rq 0) + endif() + + if (${_val} EQUAL ${rq}) + set(SAME 1) + else() + set(SAME 0) + endif() + + if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME}) + if (${_val}) + message("${SAMP}: skipping as lws being built without ${reqconfig}") + else() + message("${SAMP}: skipping as lws built with ${reqconfig}") + endif() + set(${result} 0) + else() + if (LWS_WITH_MINIMAL_EXAMPLES) + set(MET ${SAME}) + else() + CHECK_C_SOURCE_COMPILES("#include \nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig}) + if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig}) + set(HAS_${reqconfig} 0) + else() + set(HAS_${reqconfig} 1) + endif() + if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val})) + set(MET 1) + else() + set(MET 0) + endif() + endif() + if (NOT MET) + if (${_val}) + message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}") + else() + message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project") + endif() + endif() + endif() +ENDMACRO() + + +set(requirements 1) +require_lws_config(LWS_ROLE_H1 1 requirements) +require_lws_config(LWS_WITHOUT_CLIENT 0 requirements) +require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements) + +if (requirements) + add_executable(${SAMP} ${SRCS}) + + if (websockets_shared) + target_link_libraries(${SAMP} websockets_shared) + add_dependencies(${SAMP} websockets_shared) + else() + target_link_libraries(${SAMP} websockets) + endif() +endif() diff --git a/minimal-examples/secure-streams/minimal-secure-streams-seq/README.md b/minimal-examples/secure-streams/minimal-secure-streams-seq/README.md new file mode 100644 index 000000000..6af2e25d6 --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-seq/README.md @@ -0,0 +1,65 @@ +# lws minimal sequre streams seq + +The application goes to https://warmcat.com and reads index.html there. + +It does it using Secure Streams... the main code in minimal-secure-streams.c +just sets up the context and opens a secure stream of type "mintest". + +The handler for state changes and payloads for "mintest" is in ss-myss.c + +The information about how a "mintest" stream should connect and the +protocol it uses is kept separated in policy-database.c + +## build + +``` + $ cmake . && make +``` + +## usage + +Commandline option|Meaning +---|--- +-d |Debug verbosity in decimal, eg, -d15 +-f| Force connecting to the wrong endpoint to check backoff retry flow + +``` + $ ./lws-minimal-secure-streams-seq +[2018/03/04 14:43:20:8562] USER: LWS minimal http client +[2018/03/04 14:43:20:8571] NOTICE: Creating Vhost 'default' port -1, 1 protocols, IPv6 on +[2018/03/04 14:43:20:8616] NOTICE: created client ssl context for default +[2018/03/04 14:43:20:8617] NOTICE: lws_client_connect_2: 0x1814dc0: address warmcat.com +[2018/03/04 14:43:21:1496] NOTICE: lws_client_connect_2: 0x1814dc0: address warmcat.com +[2018/03/04 14:43:22:0154] NOTICE: lws_client_interpret_server_handshake: incoming content length 26520 +[2018/03/04 14:43:22:0154] NOTICE: lws_client_interpret_server_handshake: client connection up +[2018/03/04 14:43:22:0169] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 +[2018/03/04 14:43:22:0169] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 +[2018/03/04 14:43:22:0169] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 +[2018/03/04 14:43:22:0169] USER: RECEIVE_CLIENT_HTTP_READ: read 1015 +[2018/03/04 14:43:22:0174] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 +[2018/03/04 14:43:22:0174] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 +[2018/03/04 14:43:22:0174] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 +[2018/03/04 14:43:22:0174] USER: RECEIVE_CLIENT_HTTP_READ: read 1015 +[2018/03/04 14:43:22:0179] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 +[2018/03/04 14:43:22:0179] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 +[2018/03/04 14:43:22:0179] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 +[2018/03/04 14:43:22:0179] USER: RECEIVE_CLIENT_HTTP_READ: read 1015 +[2018/03/04 14:43:22:3010] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 +[2018/03/04 14:43:22:3010] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 +[2018/03/04 14:43:22:3010] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 +[2018/03/04 14:43:22:3010] USER: RECEIVE_CLIENT_HTTP_READ: read 1015 +[2018/03/04 14:43:22:3015] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 +[2018/03/04 14:43:22:3015] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 +[2018/03/04 14:43:22:3015] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 +[2018/03/04 14:43:22:3015] USER: RECEIVE_CLIENT_HTTP_READ: read 1015 +[2018/03/04 14:43:22:3020] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 +[2018/03/04 14:43:22:3020] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 +[2018/03/04 14:43:22:3020] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 +[2018/03/04 14:43:22:3020] USER: RECEIVE_CLIENT_HTTP_READ: read 1015 +[2018/03/04 14:43:22:3022] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 +[2018/03/04 14:43:22:3022] USER: RECEIVE_CLIENT_HTTP_READ: read 974 +[2018/03/04 14:43:22:3022] NOTICE: lws_http_client_read: transaction completed says -1 +[2018/03/04 14:43:23:3042] USER: Completed +``` + + diff --git a/minimal-examples/secure-streams/minimal-secure-streams-seq/minimal-secure-streams.c b/minimal-examples/secure-streams/minimal-secure-streams-seq/minimal-secure-streams.c new file mode 100644 index 000000000..f648f0228 --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-seq/minimal-secure-streams.c @@ -0,0 +1,443 @@ +/* + * lws-minimal-secure-streams-seq + * + * Written in 2010-2020 by Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * + * This demonstrates the a minimal http client using secure streams api. + * + * It visits https://warmcat.com/ and receives the html page there. + * + * This is the "secure streams" api equivalent of minimal-http-client... + * it shows how to use a sequencer to make it easy to build more complex + * schemes on top of this example. + * + * The layering looks like this + * + * lifetime + * + * ------ app ------ process + * ---- sequencer ---- process + * --- secure stream --- process + * ------- wsi ------- connection + * + * see minimal-secure-streams for a similar example without the sequencer. + */ + +#include +#include +#include + +static int interrupted, bad = 1, flag_conn_fail, flag_h1post; +static const char * const default_ss_policy = + "{" + "\"release\":" "\"01234567\"," + "\"product\":" "\"myproduct\"," + "\"schema-version\":" "1," + "\"retry\": [" /* named backoff / retry strategies */ + "{\"default\": {" + "\"backoff\": [" "1000," + "2000," + "3000," + "5000," + "10000" + "]," + "\"conceal\":" "5," + "\"jitterpc\":" "20," + "\"svalidping\":" "300," + "\"svalidhup\":" "310" + "}}" + "]," + "\"certs\": [" /* named individual certificates in BASE64 DER */ + /* + * Need to be in order from root cert... notice sometimes as + * with Let's Encrypt there are multiple possible validation + * paths, all the pieces for one validation path must be + * given, excluding the server cert itself. Let's Encrypt + * intermediate is signed by their ISRG Root CA but also is + * cross-signed by an IdenTrust intermediate that's widely + * deployed in browsers. We use the ISRG path because that + * way we can skip the extra IdenTrust root cert. + */ + "{\"isrg_root_x1\": \"" /* ISRG ROOT X1 */ + "MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw" + "TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh" + "cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4" + "WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu" + "ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY" + "MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc" + "h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+" + "0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U" + "A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW" + "T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH" + "B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC" + "B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv" + "KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn" + "OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn" + "jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw" + "qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI" + "rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV" + "HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq" + "hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL" + "ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ" + "3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK" + "NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5" + "ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur" + "TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC" + "jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc" + "oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq" + "4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA" + "mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d" + "emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=" + "\"}," + "{\"LEX3_isrg_root_x1\": \"" /* LE X3 signed by ISRG X1 root */ + "MIIFjTCCA3WgAwIBAgIRANOxciY0IzLc9AUoUSrsnGowDQYJKoZIhvcNAQELBQAw" + "TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh" + "cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTYxMDA2MTU0MzU1" + "WhcNMjExMDA2MTU0MzU1WjBKMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg" + "RW5jcnlwdDEjMCEGA1UEAxMaTGV0J3MgRW5jcnlwdCBBdXRob3JpdHkgWDMwggEi" + "MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCc0wzwWuUuR7dyXTeDs2hjMOrX" + "NSYZJeG9vjXxcJIvt7hLQQWrqZ41CFjssSrEaIcLo+N15Obzp2JxunmBYB/XkZqf" + "89B4Z3HIaQ6Vkc/+5pnpYDxIzH7KTXcSJJ1HG1rrueweNwAcnKx7pwXqzkrrvUHl" + "Npi5y/1tPJZo3yMqQpAMhnRnyH+lmrhSYRQTP2XpgofL2/oOVvaGifOFP5eGr7Dc" + "Gu9rDZUWfcQroGWymQQ2dYBrrErzG5BJeC+ilk8qICUpBMZ0wNAxzY8xOJUWuqgz" + "uEPxsR/DMH+ieTETPS02+OP88jNquTkxxa/EjQ0dZBYzqvqEKbbUC8DYfcOTAgMB" + "AAGjggFnMIIBYzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADBU" + "BgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEBATAwMC4GCCsGAQUFBwIB" + "FiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMB0GA1UdDgQWBBSo" + "SmpjBH3duubRObemRWXv86jsoTAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3Js" + "LnJvb3QteDEubGV0c2VuY3J5cHQub3JnMHIGCCsGAQUFBwEBBGYwZDAwBggrBgEF" + "BQcwAYYkaHR0cDovL29jc3Aucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcvMDAGCCsG" + "AQUFBzAChiRodHRwOi8vY2VydC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZy8wHwYD" + "VR0jBBgwFoAUebRZ5nu25eQBc4AIiMgaWPbpm24wDQYJKoZIhvcNAQELBQADggIB" + "ABnPdSA0LTqmRf/Q1eaM2jLonG4bQdEnqOJQ8nCqxOeTRrToEKtwT++36gTSlBGx" + "A/5dut82jJQ2jxN8RI8L9QFXrWi4xXnA2EqA10yjHiR6H9cj6MFiOnb5In1eWsRM" + "UM2v3e9tNsCAgBukPHAg1lQh07rvFKm/Bz9BCjaxorALINUfZ9DD64j2igLIxle2" + "DPxW8dI/F2loHMjXZjqG8RkqZUdoxtID5+90FgsGIfkMpqgRS05f4zPbCEHqCXl1" + "eO5HyELTgcVlLXXQDgAWnRzut1hFJeczY1tjQQno6f6s+nMydLN26WuU4s3UYvOu" + "OsUxRlJu7TSRHqDC3lSE5XggVkzdaPkuKGQbGpny+01/47hfXXNB7HntWNZ6N2Vw" + "p7G6OfY+YQrZwIaQmhrIqJZuigsrbe3W+gdn5ykE9+Ky0VgVUsfxo52mwFYs1JKY" + "2PGDuWx8M6DlS6qQkvHaRUo0FMd8TsSlbF0/v965qGFKhSDeQoMpYnwcmQilRh/0" + "ayLThlHLN81gSkJjVrPI0Y8xCVPB4twb1PFUd2fPM3sA1tJ83sZ5v8vgFv2yofKR" + "PB0t6JzUA81mSqM3kxl5e+IZwhYAyO0OTg3/fs8HqGTNKd9BqoUwSRBzp06JMg5b" + "rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt" + "\"}" + "]," + "\"trust_stores\": [" /* named cert chains */ + "{" + "\"name\": \"le_via_isrg\"," + "\"stack\": [" + "\"isrg_root_x1\"," + "\"LEX3_isrg_root_x1\"" + "]" + "}" + "]," + "\"s\": [" /* the supported stream types */ + "{\"mintest\": {" + "\"endpoint\":" "\"warmcat.com\"," + "\"port\":" "443," + "\"protocol\":" "\"h1\"," + "\"http_method\":" "\"GET\"," + "\"http_url\":" "\"index.html\"," + "\"plugins\":" "[]," + "\"tls\":" "true," + "\"opportunistic\":" "true," + "\"retry\":" "\"default\"," + "\"tls_trust_store\":" "\"le_via_isrg\"" + "}}," + "{\"mintest-fail\": {" + "\"endpoint\":" "\"warmcat.com\"," + "\"port\":" "22," + "\"protocol\":" "\"h1\"," + "\"http_method\":" "\"GET\"," + "\"http_url\":" "\"index.html\"," + "\"plugins\":" "[]," + "\"tls\":" "true," + "\"opportunistic\":" "true," + "\"retry\":" "\"default\"," + "\"tls_trust_store\":" "\"le_via_isrg\"" + "}}," + "{\"minpost\": {" + "\"endpoint\":" "\"warmcat.com\"," + "\"port\":" "443," + "\"protocol\":" "\"h1\"," + "\"http_method\":" "\"POST\"," + "\"http_url\":" "\"testserver/formtest\"," + "\"plugins\":" "[]," + "\"tls\":" "true," + "\"opportunistic\":" "true," + "\"retry\":" "\"default\"," + "\"tls_trust_store\":" "\"le_via_isrg\"" + "}}" + "]" + "}" +; + +typedef struct myss { + struct lws_ss_handle *ss; + void *opaque_data; + /* ... application specific state ... */ +} myss_t; + +/* secure streams payload interface */ + +static int +myss_rx(void *userobj, const uint8_t *buf, size_t len, int flags) +{ +// myss_t *m = (myss_t *)userobj; + + lwsl_user("%s: len %d, flags: %d\n", __func__, (int)len, flags); + lwsl_hexdump_info(buf, len); + + /* + * If we received the whole message, we let the sequencer know it + * was a success + */ + if (flags & LWSSS_FLAG_EOM) { + bad = 0; + interrupted = 1; + } + + return 0; +} + +static int +myss_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len, + int *flags) +{ + // myss_t *m = (myss_t *)userobj; + + /* in this example, we don't send any payload */ + + return 0; +} + +static int +myss_state(void *userobj, void *sh, lws_ss_constate_t state, + lws_ss_tx_ordinal_t ack) +{ + myss_t *m = (myss_t *)userobj; + + lwsl_user("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name(state), + (unsigned int)ack); + + switch (state) { + case LWSSSCS_CREATING: + lws_ss_request_tx(m->ss); + break; + case LWSSSCS_ALL_RETRIES_FAILED: + /* if we're out of retries, we want to close the app and FAIL */ + interrupted = 1; + break; + default: + break; + } + + return 0; +} + +typedef enum { + SEQ_IDLE, + SEQ_TRY_CONNECT, + SEQ_RECONNECT_WAIT, + SEQ_CONNECTED, +} myseq_state_t; + +typedef struct myseq { + struct lws_ss_handle *ss; + + myseq_state_t state; + int http_resp; + + uint16_t try; +} myseq_t; + +/* + * This defines the sequence of things the test app does. + */ + +static lws_seq_cb_return_t +min_sec_str_sequencer_cb(struct lws_sequencer *seq, void *user, int event, + void *v, void *a) +{ + struct myseq *s = (struct myseq *)user; + lws_ss_info_t ssi; + + switch ((int)event) { + + /* these messages are created just by virtue of being a sequencer */ + + case LWSSEQ_CREATED: /* our sequencer just got started */ + s->state = SEQ_IDLE; + lwsl_notice("%s: LWSSEQ_CREATED\n", __func__); + + /* We're making an outgoing secure stream ourselves */ + + memset(&ssi, 0, sizeof(ssi)); + ssi.handle_offset = offsetof(myss_t, ss); + ssi.opaque_user_data_offset = offsetof(myss_t, opaque_data); + ssi.rx = myss_rx; + ssi.tx = myss_tx; + ssi.state = myss_state; + ssi.user_alloc = sizeof(myss_t); + + /* requested to fail (to check backoff)? */ + if (flag_conn_fail) + ssi.streamtype = "mintest-fail"; + else + /* request to check h1 POST */ + if (flag_h1post) + ssi.streamtype = "minpost"; + else + /* default to h1 GET */ + ssi.streamtype = "mintest"; + + if (lws_ss_create(lws_seq_get_context(seq), 0, &ssi, NULL, + &s->ss, seq, NULL)) { + lwsl_err("%s: failed to create secure stream\n", + __func__); + + return LWSSEQ_RET_DESTROY; + } + break; + + case LWSSEQ_DESTROYED: + lwsl_notice("%s: LWSSEQ_DESTROYED\n", __func__); + break; + + case LWSSEQ_TIMED_OUT: /* current step timed out */ + if (s->state == SEQ_RECONNECT_WAIT) + lws_ss_request_tx(s->ss); + break; + + /* + * These messages are created because we have a secure stream that was + * bound to this sequencer at creation time. It copies its state + * events to us as its sequencer parent. v is the lws_ss_handle_t * + */ + + case LWSSEQ_SS_STATE_BASE + LWSSSCS_CREATING: + lwsl_info("%s: seq LWSSSCS_CREATING\n", __func__); + lws_ss_request_tx(s->ss); + break; + case LWSSEQ_SS_STATE_BASE + LWSSSCS_DISCONNECTED: + lwsl_info("%s: seq LWSSSCS_DISCONNECTED\n", __func__); + break; + case LWSSEQ_SS_STATE_BASE + LWSSSCS_UNREACHABLE: + lwsl_info("%s: seq LWSSSCS_UNREACHABLE\n", __func__); + break; + case LWSSEQ_SS_STATE_BASE + LWSSSCS_AUTH_FAILED: + lwsl_info("%s: seq LWSSSCS_AUTH_FAILED\n", __func__); + break; + case LWSSEQ_SS_STATE_BASE + LWSSSCS_CONNECTED: + lwsl_info("%s: seq LWSSSCS_CONNECTED\n", __func__); + break; + case LWSSEQ_SS_STATE_BASE + LWSSSCS_CONNECTING: + lwsl_info("%s: seq LWSSSCS_CONNECTING\n", __func__); + break; + case LWSSEQ_SS_STATE_BASE + LWSSSCS_DESTROYING: + lwsl_info("%s: seq LWSSSCS_DESTROYING\n", __func__); + break; + case LWSSEQ_SS_STATE_BASE + LWSSSCS_POLL: + /* somebody called lws_ss_poll() on the stream */ + lwsl_info("%s: seq LWSSSCS_POLL\n", __func__); + break; + case LWSSEQ_SS_STATE_BASE + LWSSSCS_ALL_RETRIES_FAILED: + lwsl_info("%s: seq LWSSSCS_ALL_RETRIES_FAILED\n", __func__); + interrupted = 1; + break; + case LWSSEQ_SS_STATE_BASE + LWSSSCS_QOS_ACK_REMOTE: + lwsl_info("%s: seq LWSSSCS_QOS_ACK_REMOTE\n", __func__); + break; + case LWSSEQ_SS_STATE_BASE + LWSSSCS_QOS_ACK_LOCAL: + lwsl_info("%s: seq LWSSSCS_QOS_ACK_LOCAL\n", __func__); + break; + + /* + * This is the message we send from the ss handler to inform the + * sequencer we had the payload properly + */ + + case LWSSEQ_USER_BASE: + bad = 0; + interrupted = 1; + break; + + default: + break; + } + + return LWSSEQ_RET_CONTINUE; +} + +static void +sigint_handler(int sig) +{ + interrupted = 1; +} + +int main(int argc, const char **argv) +{ + int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + struct lws_context_creation_info info; + struct lws_context *context; + lws_seq_info_t i; + const char *p; + myseq_t *ms; + + signal(SIGINT, sigint_handler); + + if ((p = lws_cmdline_option(argc, argv, "-d"))) + logs = atoi(p); + + lws_set_log_level(logs, NULL); + lwsl_user("LWS minimal secure streams [-d][-f][--h1post]\n"); + + flag_conn_fail = !!lws_cmdline_option(argc, argv, "-f"); + flag_h1post = !!lws_cmdline_option(argc, argv, "--h1post"); + + memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + + info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS | + LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + info.fd_limit_per_thread = 1 + 1 + 1; + info.pss_policies_json = default_ss_policy; + info.port = CONTEXT_PORT_NO_LISTEN; + + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + /* + * Create the sequencer that performs the steps of the test action + * from inside the event loop. + */ + + memset(&i, 0, sizeof(i)); + i.context = context; + i.user_size = sizeof(myseq_t); + i.puser = (void **)&ms; + i.cb = min_sec_str_sequencer_cb; + i.name = "min-sec-stream-seq"; + + if (!lws_seq_create(&i)) { + lwsl_err("%s: failed to create sequencer\n", __func__); + goto bail; + } + + /* the event loop */ + + while (n >= 0 && !interrupted) + n = lws_service(context, 0); + +bail: + lws_context_destroy(context); + lwsl_user("Completed: %s\n", bad ? "failed" : "OK"); + + return bad; +} diff --git a/minimal-examples/secure-streams/minimal-secure-streams-sink/CMakeLists.txt b/minimal-examples/secure-streams/minimal-secure-streams-sink/CMakeLists.txt new file mode 100644 index 000000000..080ddeaa0 --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-sink/CMakeLists.txt @@ -0,0 +1,79 @@ +cmake_minimum_required(VERSION 2.8) +include(CheckCSourceCompiles) + +set(SAMP lws-minimal-secure-streams-sink) +set(SRCS main.c) + +# If we are being built as part of lws, confirm current build config supports +# reqconfig, else skip building ourselves. +# +# If we are being built externally, confirm installed lws was configured to +# support reqconfig, else error out with a helpful message about the problem. +# +MACRO(require_lws_config reqconfig _val result) + + if (DEFINED ${reqconfig}) + if (${reqconfig}) + set (rq 1) + else() + set (rq 0) + endif() + else() + set(rq 0) + endif() + + if (${_val} EQUAL ${rq}) + set(SAME 1) + else() + set(SAME 0) + endif() + + if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME}) + if (${_val}) + message("${SAMP}: skipping as lws being built without ${reqconfig}") + else() + message("${SAMP}: skipping as lws built with ${reqconfig}") + endif() + set(${result} 0) + else() + if (LWS_WITH_MINIMAL_EXAMPLES) + set(MET ${SAME}) + else() + CHECK_C_SOURCE_COMPILES("#include \nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig}) + if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig}) + set(HAS_${reqconfig} 0) + else() + set(HAS_${reqconfig} 1) + endif() + if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val})) + set(MET 1) + else() + set(MET 0) + endif() + endif() + if (NOT MET) + if (${_val}) + message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}") + else() + message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project") + endif() + endif() + endif() +ENDMACRO() + + +set(requirements 1) +require_lws_config(LWS_ROLE_H1 1 requirements) +require_lws_config(LWS_WITHOUT_CLIENT 0 requirements) +require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements) + +if (requirements) + add_executable(${SAMP} ${SRCS}) + + if (websockets_shared) + target_link_libraries(${SAMP} websockets_shared) + add_dependencies(${SAMP} websockets_shared) + else() + target_link_libraries(${SAMP} websockets) + endif() +endif() diff --git a/minimal-examples/secure-streams/minimal-secure-streams-sink/README.md b/minimal-examples/secure-streams/minimal-secure-streams-sink/README.md new file mode 100644 index 000000000..80a1c6928 --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-sink/README.md @@ -0,0 +1,64 @@ +# lws minimal secure streams + +The application goes to https://warmcat.com and reads index.html there. + +It does it using Secure Streams... the main code in minimal-secure-streams.c +just sets up the context and opens a secure stream of type "mintest". + +The handler for state changes and payloads for "mintest" is in ss-myss.c + +The information about how a "mintest" stream should connect and the +protocol it uses is kept separated in policy-database.c + +## build + +``` + $ cmake . && make +``` + +## usage + +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 + +``` +[2019/08/12 07:16:11:0045] USR: LWS minimal secure streams [-d] [-f] +[2019/08/12 07:16:12:6102] USR: myss_state: LWSSSCS_CREATING, ord 0x0 +[2019/08/12 07:16:12:6107] USR: myss_state: LWSSSCS_POLL, ord 0x0 +[2019/08/12 07:16:12:6117] N: lws_ss_client_connect: connecting h1get warmcat.com / +[2019/08/12 07:16:12:6118] USR: myss_state: LWSSSCS_CONNECTING, ord 0x0 +[2019/08/12 07:16:13:4171] USR: myss_state: LWSSSCS_CONNECTED, ord 0x0 +[2019/08/12 07:16:13:4222] USR: myss_rx: len 1024, flags: 1 +[2019/08/12 07:16:13:4243] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4244] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4244] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4245] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4246] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4247] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4252] USR: myss_rx: len 1015, flags: 0 +[2019/08/12 07:16:13:4264] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4265] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4266] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4267] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4268] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4268] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4269] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4270] USR: myss_rx: len 1015, flags: 0 +[2019/08/12 07:16:13:4278] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4279] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4280] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4281] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4282] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4283] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4283] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4284] USR: myss_rx: len 1015, flags: 0 +[2019/08/12 07:16:13:4287] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4288] USR: myss_rx: len 947, flags: 0 +[2019/08/12 07:16:13:4293] USR: myss_rx: len 0, flags: 2 +[2019/08/12 07:16:13:4399] USR: myss_state: LWSSSCS_DISCONNECTED, ord 0x0 +[2019/08/12 07:16:13:4761] USR: myss_state: LWSSSCS_DESTROYING, ord 0x0 +[2019/08/12 07:16:13:4781] USR: Completed: OK +``` diff --git a/minimal-examples/secure-streams/minimal-secure-streams-sink/main.c b/minimal-examples/secure-streams/minimal-secure-streams-sink/main.c new file mode 100644 index 000000000..792cad19a --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-sink/main.c @@ -0,0 +1,288 @@ +/* + * lws-minimal-secure-streams-sink + * + * Written in 2010-2020 by Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + */ + +#include +#include +#include + +static int interrupted, bad = 1; +static const char * const default_ss_policy = + "{" + "\"release\":" "\"01234567\"," + "\"product\":" "\"myproduct\"," + "\"schema-version\":" "1," + "\"retry\": [" /* named backoff / retry strategies */ + "{\"default\": {" + "\"backoff\": [" "1000," + "2000," + "3000," + "5000," + "10000" + "]," + "\"conceal\":" "5," + "\"jitterpc\":" "20," + "\"svalidping\":" "300," + "\"svalidhup\":" "310" + "}}" + "]," + "\"certs\": [" /* named individual certificates in BASE64 DER */ + /* + * Need to be in order from root cert... notice sometimes as + * with Let's Encrypt there are multiple possible validation + * paths, all the pieces for one validation path must be + * given, excluding the server cert itself. Let's Encrypt + * intermediate is signed by their ISRG Root CA but also is + * cross-signed by an IdenTrust intermediate that's widely + * deployed in browsers. We use the ISRG path because that + * way we can skip the extra IdenTrust root cert. + */ + "{\"isrg_root_x1\": \"" /* ISRG ROOT X1 */ + "MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw" + "TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh" + "cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4" + "WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu" + "ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY" + "MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc" + "h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+" + "0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U" + "A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW" + "T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH" + "B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC" + "B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv" + "KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn" + "OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn" + "jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw" + "qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI" + "rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV" + "HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq" + "hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL" + "ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ" + "3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK" + "NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5" + "ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur" + "TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC" + "jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc" + "oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq" + "4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA" + "mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d" + "emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=" + "\"}," + "{\"LEX3_isrg_root_x1\": \"" /* LE X3 signed by ISRG X1 root */ + "MIIFjTCCA3WgAwIBAgIRANOxciY0IzLc9AUoUSrsnGowDQYJKoZIhvcNAQELBQAw" + "TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh" + "cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTYxMDA2MTU0MzU1" + "WhcNMjExMDA2MTU0MzU1WjBKMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg" + "RW5jcnlwdDEjMCEGA1UEAxMaTGV0J3MgRW5jcnlwdCBBdXRob3JpdHkgWDMwggEi" + "MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCc0wzwWuUuR7dyXTeDs2hjMOrX" + "NSYZJeG9vjXxcJIvt7hLQQWrqZ41CFjssSrEaIcLo+N15Obzp2JxunmBYB/XkZqf" + "89B4Z3HIaQ6Vkc/+5pnpYDxIzH7KTXcSJJ1HG1rrueweNwAcnKx7pwXqzkrrvUHl" + "Npi5y/1tPJZo3yMqQpAMhnRnyH+lmrhSYRQTP2XpgofL2/oOVvaGifOFP5eGr7Dc" + "Gu9rDZUWfcQroGWymQQ2dYBrrErzG5BJeC+ilk8qICUpBMZ0wNAxzY8xOJUWuqgz" + "uEPxsR/DMH+ieTETPS02+OP88jNquTkxxa/EjQ0dZBYzqvqEKbbUC8DYfcOTAgMB" + "AAGjggFnMIIBYzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADBU" + "BgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEBATAwMC4GCCsGAQUFBwIB" + "FiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMB0GA1UdDgQWBBSo" + "SmpjBH3duubRObemRWXv86jsoTAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3Js" + "LnJvb3QteDEubGV0c2VuY3J5cHQub3JnMHIGCCsGAQUFBwEBBGYwZDAwBggrBgEF" + "BQcwAYYkaHR0cDovL29jc3Aucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcvMDAGCCsG" + "AQUFBzAChiRodHRwOi8vY2VydC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZy8wHwYD" + "VR0jBBgwFoAUebRZ5nu25eQBc4AIiMgaWPbpm24wDQYJKoZIhvcNAQELBQADggIB" + "ABnPdSA0LTqmRf/Q1eaM2jLonG4bQdEnqOJQ8nCqxOeTRrToEKtwT++36gTSlBGx" + "A/5dut82jJQ2jxN8RI8L9QFXrWi4xXnA2EqA10yjHiR6H9cj6MFiOnb5In1eWsRM" + "UM2v3e9tNsCAgBukPHAg1lQh07rvFKm/Bz9BCjaxorALINUfZ9DD64j2igLIxle2" + "DPxW8dI/F2loHMjXZjqG8RkqZUdoxtID5+90FgsGIfkMpqgRS05f4zPbCEHqCXl1" + "eO5HyELTgcVlLXXQDgAWnRzut1hFJeczY1tjQQno6f6s+nMydLN26WuU4s3UYvOu" + "OsUxRlJu7TSRHqDC3lSE5XggVkzdaPkuKGQbGpny+01/47hfXXNB7HntWNZ6N2Vw" + "p7G6OfY+YQrZwIaQmhrIqJZuigsrbe3W+gdn5ykE9+Ky0VgVUsfxo52mwFYs1JKY" + "2PGDuWx8M6DlS6qQkvHaRUo0FMd8TsSlbF0/v965qGFKhSDeQoMpYnwcmQilRh/0" + "ayLThlHLN81gSkJjVrPI0Y8xCVPB4twb1PFUd2fPM3sA1tJ83sZ5v8vgFv2yofKR" + "PB0t6JzUA81mSqM3kxl5e+IZwhYAyO0OTg3/fs8HqGTNKd9BqoUwSRBzp06JMg5b" + "rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt" + "\"}" + "]," + "\"trust_stores\": [" /* named cert chains */ + "{" + "\"name\": \"le_via_isrg\"," + "\"stack\": [" + "\"isrg_root_x1\"," + "\"LEX3_isrg_root_x1\"" + "]" + "}" + "]," + "\"s\": [" /* the supported stream types */ + "{\"\": {" + "\"endpoint\":" "\"warmcat.com\"," + "\"port\":" "443," + "\"protocol\":" "\"h2\"," + "\"http_method\":" "\"GET\"," + "\"http_url\":" "\"index.html\"," + "\"plugins\":" "[]," + "\"tls\":" "true," + "\"nailed_up\":" "true," + "\"long_poll\":" "true," + "\"retry\":" "\"default\"," + "\"tls_trust_store\":" "\"le_via_isrg\"" + "}}" + "]" + "}" +; + +typedef struct myss { + struct lws_ss_handle *ss; + void *opaque_data; + /* ... application specific state ... */ + lws_sorted_usec_list_t sul; + + int count; +} myss_t; + +/* secure streams payload interface */ + +static int +myss_rx(void *userobj, const uint8_t *buf, size_t len, int flags) +{ +// myss_t *m = (myss_t *)userobj; + + lwsl_user("%s: len %d, flags: %d\n", __func__, (int)len, flags); + lwsl_hexdump_info(buf, len); + + /* + * If we received the whole message, for our example it means + * we are done. + */ + if (flags & LWSSS_FLAG_EOM) { + bad = 0; + interrupted = 1; + } + + return 0; +} + +static int +myss_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len, + int *flags) +{ + //myss_t *m = (myss_t *)userobj; + + return 0; +} + +static int +myss_state(void *userobj, void *sh, lws_ss_constate_t state, + lws_ss_tx_ordinal_t ack) +{ + myss_t *m = (myss_t *)userobj; + + lwsl_user("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name(state), + (unsigned int)ack); + + switch (state) { + case LWSSSCS_CREATING: + lws_ss_request_tx(m->ss); + break; + case LWSSSCS_ALL_RETRIES_FAILED: + /* if we're out of retries, we want to close the app and FAIL */ + interrupted = 1; + break; + default: + break; + } + + return 0; +} + +static void +sigint_handler(int sig) +{ + interrupted = 1; +} + +int main(int argc, const char **argv) +{ + struct lws_context_creation_info info; + struct lws_context *context; + int n = 0; + + signal(SIGINT, sigint_handler); + + memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_cmdline_option_handle_builtin(argc, argv, &info); + lwsl_user("LWS secure streams [-d] [-f] [-p] [--h1post]\n"); + + info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS | + LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + info.fd_limit_per_thread = 1 + 6 + 1; + info.pss_policies_json = default_ss_policy; + info.port = CONTEXT_PORT_NO_LISTEN; + + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + // puts(default_ss_policy); + + if (lws_cmdline_option(argc, argv, "-p")) { + + /* we are being the proxy */ + +#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API) + if (lws_ss_proxy_create(context, NULL, 0)) { + lwsl_err("%s: failed to create ss proxy\n", __func__); + goto bail; + } + lwsl_notice("%s: secure streams proxy mode\n", __func__); +#else + lwsl_err("%s: needs cmake LWS_WITH_SECURE_STREAMS_PROXY_API\n", + __func__); +#endif + } else { + lws_ss_info_t ssi; + + /* We're making an outgoing secure stream ourselves */ + + memset(&ssi, 0, sizeof(ssi)); + ssi.handle_offset = offsetof(myss_t, ss); + ssi.opaque_user_data_offset = offsetof(myss_t, opaque_data); + ssi.rx = myss_rx; + ssi.tx = myss_tx; + ssi.state = myss_state; + ssi.user_alloc = sizeof(myss_t); + + /* requested to fail (to check backoff)? */ + if (lws_cmdline_option(argc, argv, "-f")) + ssi.streamtype = "mintest-fail"; + else + /* request to check h1 POST */ + if (lws_cmdline_option(argc, argv, "--h1post")) + ssi.streamtype = "minpost"; + else + /* default to h1 GET */ + ssi.streamtype = "mintest"; + + if (lws_ss_create(context, 0, &ssi, NULL, NULL, NULL, NULL)) { + lwsl_err("%s: failed to create secure stream\n", + __func__); + goto bail; + } + } + + /* the event loop */ + + while (n >= 0 && !interrupted) + n = lws_service(context, 0); + +bail: + + lws_context_destroy(context); + lwsl_user("Completed: %s\n", bad ? "failed" : "OK"); + + return bad; +} diff --git a/minimal-examples/secure-streams/minimal-secure-streams/CMakeLists.txt b/minimal-examples/secure-streams/minimal-secure-streams/CMakeLists.txt new file mode 100644 index 000000000..675e52388 --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams/CMakeLists.txt @@ -0,0 +1,91 @@ +cmake_minimum_required(VERSION 2.8) +include(CheckCSourceCompiles) + +set(SAMP lws-minimal-secure-streams) + +# If we are being built as part of lws, confirm current build config supports +# reqconfig, else skip building ourselves. +# +# If we are being built externally, confirm installed lws was configured to +# support reqconfig, else error out with a helpful message about the problem. +# +MACRO(require_lws_config reqconfig _val result) + + if (DEFINED ${reqconfig}) + if (${reqconfig}) + set (rq 1) + else() + set (rq 0) + endif() + else() + set(rq 0) + endif() + + if (${_val} EQUAL ${rq}) + set(SAME 1) + else() + set(SAME 0) + endif() + + if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME}) + if (${_val}) + message("${SAMP}: skipping as lws being built without ${reqconfig}") + else() + message("${SAMP}: skipping as lws built with ${reqconfig}") + endif() + set(${result} 0) + else() + if (LWS_WITH_MINIMAL_EXAMPLES) + set(MET ${SAME}) + else() + CHECK_C_SOURCE_COMPILES("#include \nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig}) + if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig}) + set(HAS_${reqconfig} 0) + else() + set(HAS_${reqconfig} 1) + endif() + if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val})) + set(MET 1) + else() + set(MET 0) + endif() + endif() + if (NOT MET) + if (${_val}) + message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}") + else() + message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project") + endif() + endif() + endif() +ENDMACRO() + + +set(requirements 1) +require_lws_config(LWS_ROLE_H1 1 requirements) +require_lws_config(LWS_WITHOUT_CLIENT 0 requirements) +require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements) + +if (requirements) + add_executable(${SAMP} minimal-secure-streams.c) + + if (websockets_shared) + target_link_libraries(${SAMP} websockets_shared) + add_dependencies(${SAMP} websockets_shared) + else() + target_link_libraries(${SAMP} websockets) + endif() + + if (LWS_WITH_SECURE_STREAMS_PROXY_API) + add_compile_options(-DLWS_SS_USE_SSPC) + + add_executable(${SAMP}-client minimal-secure-streams.c) + if (websockets_shared) + target_link_libraries(${SAMP}-client websockets_shared) + add_dependencies(${SAMP}-client websockets_shared) + else() + target_link_libraries(${SAMP}-client websockets) + endif() + endif() + +endif() diff --git a/minimal-examples/secure-streams/minimal-secure-streams/README.md b/minimal-examples/secure-streams/minimal-secure-streams/README.md new file mode 100644 index 000000000..80a1c6928 --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams/README.md @@ -0,0 +1,64 @@ +# lws minimal secure streams + +The application goes to https://warmcat.com and reads index.html there. + +It does it using Secure Streams... the main code in minimal-secure-streams.c +just sets up the context and opens a secure stream of type "mintest". + +The handler for state changes and payloads for "mintest" is in ss-myss.c + +The information about how a "mintest" stream should connect and the +protocol it uses is kept separated in policy-database.c + +## build + +``` + $ cmake . && make +``` + +## usage + +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 + +``` +[2019/08/12 07:16:11:0045] USR: LWS minimal secure streams [-d] [-f] +[2019/08/12 07:16:12:6102] USR: myss_state: LWSSSCS_CREATING, ord 0x0 +[2019/08/12 07:16:12:6107] USR: myss_state: LWSSSCS_POLL, ord 0x0 +[2019/08/12 07:16:12:6117] N: lws_ss_client_connect: connecting h1get warmcat.com / +[2019/08/12 07:16:12:6118] USR: myss_state: LWSSSCS_CONNECTING, ord 0x0 +[2019/08/12 07:16:13:4171] USR: myss_state: LWSSSCS_CONNECTED, ord 0x0 +[2019/08/12 07:16:13:4222] USR: myss_rx: len 1024, flags: 1 +[2019/08/12 07:16:13:4243] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4244] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4244] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4245] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4246] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4247] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4252] USR: myss_rx: len 1015, flags: 0 +[2019/08/12 07:16:13:4264] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4265] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4266] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4267] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4268] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4268] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4269] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4270] USR: myss_rx: len 1015, flags: 0 +[2019/08/12 07:16:13:4278] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4279] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4280] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4281] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4282] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4283] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4283] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4284] USR: myss_rx: len 1015, flags: 0 +[2019/08/12 07:16:13:4287] USR: myss_rx: len 1024, flags: 0 +[2019/08/12 07:16:13:4288] USR: myss_rx: len 947, flags: 0 +[2019/08/12 07:16:13:4293] USR: myss_rx: len 0, flags: 2 +[2019/08/12 07:16:13:4399] USR: myss_state: LWSSSCS_DISCONNECTED, ord 0x0 +[2019/08/12 07:16:13:4761] USR: myss_state: LWSSSCS_DESTROYING, ord 0x0 +[2019/08/12 07:16:13:4781] USR: Completed: OK +``` 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 new file mode 100644 index 000000000..fe44a437a --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams/minimal-secure-streams.c @@ -0,0 +1,421 @@ +/* + * lws-minimal-secure-streams + * + * Written in 2010-2020 by Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * + * This demonstrates a minimal http client using secure streams api. + * + * It visits https://warmcat.com/ and receives the html page there. + * + * This example is built two different ways from the same source... one includes + * the policy everything needed to fulfil the stream directly. The other -client + * variant has no policy itself and some other minor init changes, and connects + * to the -proxy example to actually get the connection done. + * + * In the -client build case, the example does not even init the tls libraries + * since the proxy part will take care of all that. + */ + +#include +#include +#include + +/* + * uncomment to force network traffic through 127.0.0.1:1080 + * + * On your local machine, you can run a SOCKS5 proxy like this + * + * $ ssh -N -D 0.0.0.0:1080 localhost -v + * + * If enabled, this also fetches a remote policy that also + * specifies that all traffic should go through the remote + * proxy. + */ +// #define VIA_LOCALHOST_SOCKS + +static int interrupted, bad = 1; +static lws_state_notify_link_t nl; + +/* + * If the -proxy app is fulfilling our connection, then we don't need to have + * the policy in the client. + * + * When we build with LWS_SS_USE_SSPC, the apis hook up to a proxy process over + * a Unix Domain Socket. To test that, you need to separately run the + * ./lws-minimal-secure-streams-proxy test app on the same machine. + */ + +#if !defined(LWS_SS_USE_SSPC) +static const char * const default_ss_policy = + "{" + "\"release\":" "\"01234567\"," + "\"product\":" "\"myproduct\"," + "\"schema-version\":" "1," +#if defined(VIA_LOCALHOST_SOCKS) + "\"via-socks5\":" "\"127.0.0.1:1080\"," +#endif + + "\"retry\": [" /* named backoff / retry strategies */ + "{\"default\": {" + "\"backoff\": [" "1000," + "2000," + "3000," + "5000," + "10000" + "]," + "\"conceal\":" "5," + "\"jitterpc\":" "20," + "\"svalidping\":" "30," + "\"svalidhup\":" "35" + "}}" + "]," + "\"certs\": [" /* named individual certificates in BASE64 DER */ + /* + * Let's Encrypt certs for warmcat.com / libwebsockets.org + * + * We fetch the real policy from there using SS and switch to + * using that. + */ + "{\"isrg_root_x1\": \"" /* ISRG ROOT X1 */ + "MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw" + "TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh" + "cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4" + "WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu" + "ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY" + "MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc" + "h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+" + "0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U" + "A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW" + "T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH" + "B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC" + "B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv" + "KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn" + "OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn" + "jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw" + "qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI" + "rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV" + "HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq" + "hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL" + "ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ" + "3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK" + "NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5" + "ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur" + "TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC" + "jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc" + "oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq" + "4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA" + "mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d" + "emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=" + "\"}," + "{\"LEX3_isrg_root_x1\": \"" /* LE X3 signed by ISRG X1 root */ + "MIIFjTCCA3WgAwIBAgIRANOxciY0IzLc9AUoUSrsnGowDQYJKoZIhvcNAQELBQAw" + "TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh" + "cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTYxMDA2MTU0MzU1" + "WhcNMjExMDA2MTU0MzU1WjBKMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg" + "RW5jcnlwdDEjMCEGA1UEAxMaTGV0J3MgRW5jcnlwdCBBdXRob3JpdHkgWDMwggEi" + "MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCc0wzwWuUuR7dyXTeDs2hjMOrX" + "NSYZJeG9vjXxcJIvt7hLQQWrqZ41CFjssSrEaIcLo+N15Obzp2JxunmBYB/XkZqf" + "89B4Z3HIaQ6Vkc/+5pnpYDxIzH7KTXcSJJ1HG1rrueweNwAcnKx7pwXqzkrrvUHl" + "Npi5y/1tPJZo3yMqQpAMhnRnyH+lmrhSYRQTP2XpgofL2/oOVvaGifOFP5eGr7Dc" + "Gu9rDZUWfcQroGWymQQ2dYBrrErzG5BJeC+ilk8qICUpBMZ0wNAxzY8xOJUWuqgz" + "uEPxsR/DMH+ieTETPS02+OP88jNquTkxxa/EjQ0dZBYzqvqEKbbUC8DYfcOTAgMB" + "AAGjggFnMIIBYzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADBU" + "BgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEBATAwMC4GCCsGAQUFBwIB" + "FiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMB0GA1UdDgQWBBSo" + "SmpjBH3duubRObemRWXv86jsoTAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3Js" + "LnJvb3QteDEubGV0c2VuY3J5cHQub3JnMHIGCCsGAQUFBwEBBGYwZDAwBggrBgEF" + "BQcwAYYkaHR0cDovL29jc3Aucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcvMDAGCCsG" + "AQUFBzAChiRodHRwOi8vY2VydC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZy8wHwYD" + "VR0jBBgwFoAUebRZ5nu25eQBc4AIiMgaWPbpm24wDQYJKoZIhvcNAQELBQADggIB" + "ABnPdSA0LTqmRf/Q1eaM2jLonG4bQdEnqOJQ8nCqxOeTRrToEKtwT++36gTSlBGx" + "A/5dut82jJQ2jxN8RI8L9QFXrWi4xXnA2EqA10yjHiR6H9cj6MFiOnb5In1eWsRM" + "UM2v3e9tNsCAgBukPHAg1lQh07rvFKm/Bz9BCjaxorALINUfZ9DD64j2igLIxle2" + "DPxW8dI/F2loHMjXZjqG8RkqZUdoxtID5+90FgsGIfkMpqgRS05f4zPbCEHqCXl1" + "eO5HyELTgcVlLXXQDgAWnRzut1hFJeczY1tjQQno6f6s+nMydLN26WuU4s3UYvOu" + "OsUxRlJu7TSRHqDC3lSE5XggVkzdaPkuKGQbGpny+01/47hfXXNB7HntWNZ6N2Vw" + "p7G6OfY+YQrZwIaQmhrIqJZuigsrbe3W+gdn5ykE9+Ky0VgVUsfxo52mwFYs1JKY" + "2PGDuWx8M6DlS6qQkvHaRUo0FMd8TsSlbF0/v965qGFKhSDeQoMpYnwcmQilRh/0" + "ayLThlHLN81gSkJjVrPI0Y8xCVPB4twb1PFUd2fPM3sA1tJ83sZ5v8vgFv2yofKR" + "PB0t6JzUA81mSqM3kxl5e+IZwhYAyO0OTg3/fs8HqGTNKd9BqoUwSRBzp06JMg5b" + "rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt" + "\"}" + "]," + "\"trust_stores\": [" /* named cert chains */ + "{" + "\"name\": \"le_via_isrg\"," + "\"stack\": [" + "\"isrg_root_x1\"," + "\"LEX3_isrg_root_x1\"" + "]" + "}" + "]," + "\"s\": [" + "{\"fetch_policy\": {" + "\"endpoint\":" "\"warmcat.com\"," + "\"port\":" "443," + "\"protocol\":" "\"h1\"," + "\"http_method\":" "\"GET\"," +#if defined(VIA_LOCALHOST_SOCKS) + "\"http_url\":" "\"policy/minimal-proxy-socks.json\"," +#else + "\"http_url\":" "\"policy/minimal-proxy.json\"," +#endif + "\"tls\":" "true," + "\"opportunistic\":" "true," + "\"retry\":" "\"default\"," + "\"tls_trust_store\":" "\"le_via_isrg\"" + "}}" + "}" +; + +#endif + +typedef struct myss { + struct lws_ss_handle *ss; + void *opaque_data; + /* ... application specific state ... */ + lws_sorted_usec_list_t sul; +} myss_t; + +static const char *canned_root_token_payload = + "grant_type=refresh_token" + "&refresh_token=Atzr|IwEBIJedGXjDqsU_vMxykqOMg" + "SHfYe3CPcedueWEMWSDMaDnEmiW8RlR1Kns7Cb4B-TOSnqp7ifVsY4BMY2B8tpHfO39XP" + "zfu9HapGjTR458IyHX44FE71pWJkGZ79uVBpljP4sazJuk8XS3Oe_yLnm_DIO6fU1nU3Y" + "0flYmsOiOAQE_gRk_pdlmEtHnpMA-9rLw3mkY5L89Ty9kUygBsiFaYatouROhbsTn8-jW" + "k1zZLUDpT6ICtBXSnrCIg0pUbZevPFhTwdXd6eX-u4rq0W-XaDvPWFO7au-iPb4Zk5eZE" + "iX6sissYrtNmuEXc2uHu7MnQO1hHCaTdIO2CANVumf-PHSD8xseamyh04sLV5JgFzY45S" + "KvKMajiUZuLkMokOx86rjC2Hdkx5DO7G-dbG1ufBDG-N79pFMSs7Ck5pc283IdLoJkCQc" + "AGvTX8o8I29QqkcGou-9TKhOJmpX8As94T61ok0UqqEKPJ7RhfQHHYdCtsdwxgvfVr9qI" + "xL_hDCcTho8opCVX-6QhJHl6SQFlTw13" + "&client_id=" + "amzn1.application-oa2-client.4823334c434b4190a2b5a42c07938a2d"; + +/* secure streams payload interface */ + +static int +myss_rx(void *userobj, const uint8_t *buf, size_t len, int flags) +{ +// myss_t *m = (myss_t *)userobj; + + lwsl_user("%s: len %d, flags: %d\n", __func__, (int)len, flags); + lwsl_hexdump_info(buf, len); + + /* + * If we received the whole message, for our example it means + * we are done. + */ + if (flags & LWSSS_FLAG_EOM) { + bad = 0; + interrupted = 1; + } + + return 0; +} + +static int +myss_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len, + int *flags) +{ + //myss_t *m = (myss_t *)userobj; + + return 0; +} + +static int +myss_state(void *userobj, void *sh, lws_ss_constate_t state, + lws_ss_tx_ordinal_t ack) +{ + myss_t *m = (myss_t *)userobj; + + lwsl_user("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name(state), + (unsigned int)ack); + + switch (state) { + case LWSSSCS_CREATING: + lws_ss_set_metadata(m->ss, "uptag", "myuptag123", 10); + lws_ss_set_metadata(m->ss, "ctype", "myctype", 7); + lws_ss_client_connect(m->ss); + break; + case LWSSSCS_ALL_RETRIES_FAILED: + /* if we're out of retries, we want to close the app and FAIL */ + interrupted = 1; + break; + case LWSSSCS_QOS_ACK_REMOTE: + lwsl_notice("%s: LWSSSCS_QOS_ACK_REMOTE\n", __func__); + break; + default: + break; + } + + return 0; +} + +static int +app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link, + int current, int target) +{ + struct lws_context *context = lws_system_context_from_system_mgr(mgr); + lws_system_blob_t *ab = lws_system_get_blob(context, + LWS_SYSBLOB_TYPE_AUTH, 1 /* AUTH_IDX_ROOT */); + size_t size; + + /* + * For the things we care about, let's notice if we are trying to get + * past them when we haven't solved them yet, and make the system + * state wait while we trigger the dependent action. + */ + switch (target) { + case LWS_SYSTATE_REGISTERED: + size = lws_system_blob_get_size(ab); + if (size) + break; + + /* let's register our canned root token so auth can use it */ + lws_system_blob_direct_set(ab, + (const uint8_t *)canned_root_token_payload, + strlen(canned_root_token_payload)); + break; + + case LWS_SYSTATE_OPERATIONAL: + if (current == LWS_SYSTATE_OPERATIONAL) { + lws_ss_info_t ssi; + + /* We're making an outgoing secure stream ourselves */ + + memset(&ssi, 0, sizeof(ssi)); + ssi.handle_offset = offsetof(myss_t, ss); + ssi.opaque_user_data_offset = offsetof(myss_t, + opaque_data); + ssi.rx = myss_rx; + ssi.tx = myss_tx; + ssi.state = myss_state; + ssi.user_alloc = sizeof(myss_t); + ssi.streamtype = "mintest"; + + if (lws_ss_create(context, 0, &ssi, NULL, NULL, + NULL, NULL)) { + lwsl_err("%s: failed to create secure stream\n", + __func__); + return -1; + } + } + break; + } + + return 0; +} + +static lws_state_notify_link_t * const app_notifier_list[] = { + &nl, NULL +}; + +static void +sigint_handler(int sig) +{ + interrupted = 1; +} + +int main(int argc, const char **argv) +{ + struct lws_context_creation_info info; + struct lws_context *context; + int n = 0; + + signal(SIGINT, sigint_handler); + + memset(&info, 0, sizeof info); + lws_cmdline_option_handle_builtin(argc, argv, &info); + + lwsl_user("LWS secure streams test client [-d]\n"); + + info.fd_limit_per_thread = 1 + 6 + 1; + info.port = CONTEXT_PORT_NO_LISTEN; +#if defined(LWS_SS_USE_SSPC) + info.protocols = lws_sspc_protocols; + { + const char *p; + + /* connect to ssproxy via UDS by default, else via + * tcp connection to this port */ + if ((p = lws_cmdline_option(argc, argv, "-p"))) + info.ss_proxy_port = atoi(p); + + /* UDS "proxy.ss.lws" in abstract namespace, else this socket + * path; when -p given this can specify the network interface + * to bind to */ + if ((p = lws_cmdline_option(argc, argv, "-i"))) + info.ss_proxy_bind = p; + + /* if -p given, -a specifies the proxy address to connect to */ + if ((p = lws_cmdline_option(argc, argv, "-a"))) + info.ss_proxy_address = p; + } +#else + info.pss_policies_json = default_ss_policy; + info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS | + LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; +#endif +#if defined(LWS_WITH_DETAILED_LATENCY) + info.detailed_latency_cb = lws_det_lat_plot_cb; + info.detailed_latency_filepath = "/tmp/lws-latency-ssproxy"; +#endif + + /* integrate us with lws system state management when context created */ + + nl.name = "app"; + nl.notify_cb = app_system_state_nf; + info.register_notifier_list = app_notifier_list; + + /* create the context */ + + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + /* + * Set the related lws_system blobs + * + * ...direct_set() sets a pointer, so the thing pointed to has to have + * a suitable lifetime, eg, something that already exists on the heap or + * a const string in .rodata like this + */ + + lws_system_blob_direct_set(lws_system_get_blob(context, + LWS_SYSBLOB_TYPE_DEVICE_SERIAL, 0), + (const uint8_t *)"SN12345678", 10); + lws_system_blob_direct_set(lws_system_get_blob(context, + LWS_SYSBLOB_TYPE_DEVICE_FW_VERSION, 0), + (const uint8_t *)"v0.01", 5); + + /* + * ..._heap_append() appends to a buflist kind of arrangement on heap, + * just one block is fine, otherwise it will concatenate the fragments + * in the order they were appended (and take care of freeing them at + * context destroy time). ..._heap_empty() is also available to remove + * everything that was already allocated. + * + * Here we use _heap_append() just so it's tested as well as direct set. + */ + + lws_system_blob_heap_append(lws_system_get_blob(context, + LWS_SYSBLOB_TYPE_DEVICE_TYPE, 0), + (const uint8_t *)"spacerocket", 11); + + /* the event loop */ + + while (n >= 0 && !interrupted) + n = lws_service(context, 0); + + lws_context_destroy(context); + + lwsl_user("Completed: %s\n", bad ? "failed" : "OK"); + + return bad; +} diff --git a/scripts/travis_control.sh b/scripts/travis_control.sh index e608f1804..04a66cd18 100755 --- a/scripts/travis_control.sh +++ b/scripts/travis_control.sh @@ -1,7 +1,7 @@ #!/bin/bash if [ "$COVERITY_SCAN_BRANCH" != 1 -a "$TRAVIS_OS_NAME" = "osx" ]; then - if [ "$LWS_METHOD" != "mbedtls" ] ; then + if [ "$LWS_METHOD" != "mbedtls" -a "$LWS_METHOD" != "ss+mbedtls" ] ; then mkdir build && cd build && cmake -DOPENSSL_ROOT_DIR="/usr/local/opt/openssl" $CMAKE_ARGS .. && cmake --build . @@ -34,7 +34,7 @@ else cmake --build . && ../scripts/h2load-smp.sh else - if [ "$LWS_METHOD" = "mbedtls" ] ; then + if [ "$LWS_METHOD" = "mbedtls" -o "$LWS_METHOD" = "ss+mbedtls" ] ; then cmake $CMAKE_ARGS .. && cmake --build . && sudo make install && diff --git a/scripts/travis_install.sh b/scripts/travis_install.sh index dffe9152d..9b84cb946 100755 --- a/scripts/travis_install.sh +++ b/scripts/travis_install.sh @@ -21,7 +21,7 @@ then sudo update-ca-certificates fi - if [ "$LWS_METHOD" == "mbedtls" ]; + if [ "$LWS_METHOD" == "mbedtls" -o "$LWS_METHOD" == "ss+mbedtls" ]; then sudo apt-get install -y -qq realpath libjemalloc1 libev4 libuv-dev valgrind wget https://libwebsockets.org/openssl-1.1.0-trusty.tar.bz2 -O/tmp/openssl.tar.bz2