diff --git a/doc-assets/ss-operation-modes.png b/doc-assets/ss-operation-modes.png new file mode 100644 index 000000000..8c0f5d3b7 Binary files /dev/null and b/doc-assets/ss-operation-modes.png differ diff --git a/doc-assets/ss-state-flow-server.png b/doc-assets/ss-state-flow-server.png new file mode 100644 index 000000000..9d8cfd5bb Binary files /dev/null and b/doc-assets/ss-state-flow-server.png differ diff --git a/doc-assets/ss-state-flow.png b/doc-assets/ss-state-flow.png index 88dfca83d..73f04fc1c 100644 Binary files a/doc-assets/ss-state-flow.png and b/doc-assets/ss-state-flow.png differ diff --git a/include/libwebsockets/lws-secure-streams-policy.h b/include/libwebsockets/lws-secure-streams-policy.h index 31f0d7a53..00063c337 100644 --- a/include/libwebsockets/lws-secure-streams-policy.h +++ b/include/libwebsockets/lws-secure-streams-policy.h @@ -51,6 +51,7 @@ typedef int (*plugin_auth_status_cb)(struct lws_ss_handle *ss, int status); * has the LWSSSPOLF_NAILED_UP flag. */ +#if defined(LWS_WITH_SSPLUGINS) typedef struct lws_ss_plugin { struct lws_ss_plugin *next; const char *name; /**< auth plugin name */ @@ -74,13 +75,14 @@ typedef struct lws_ss_plugin { add http headers) this callback will give it the opportunity to do so */ } lws_ss_plugin_t; - +#endif 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 */ + uint8_t keep:1; /**< ie, if used in server tls */ } lws_ss_x509_t; enum { @@ -120,6 +122,8 @@ enum { /**< this stream's idle validity checks are critical enough we * should arrange to wake from suspend to perform them */ + LWSSSPOLF_SERVER = (1 << 15), + /**< we listen on a socket as a server */ }; typedef struct lws_ss_trust_store { @@ -135,6 +139,7 @@ enum { LWSSSP_H2, LWSSSP_WS, LWSSSP_MQTT, + LWSSSP_RAW, LWSSS_HBI_AUTH = 0, @@ -242,12 +247,29 @@ typedef struct lws_ss_policy { /* details for non-http related protocols... */ } u; +#if defined(LWS_WITH_SSPLUGINS) const struct lws_ss_plugin *plugins[2]; /**< NULL or auth plugin */ const void *plugins_info[2]; /**< plugin-specific data */ +#endif - const lws_ss_trust_store_t *trust_store; /**< CA certs needed for conn - validation, only set between policy parsing and vhost creation */ + /* + * We're either a client connection policy that wants a trust store, + * or we're a server policy that wants a mem cert and key... Hold + * these mutually-exclusive things in a union. + */ + + union { + const lws_ss_trust_store_t *store; + /**< CA certs needed for conn validation, only set between + * policy parsing and vhost creation */ + struct { + const lws_ss_x509_t *cert; + /**< the server's signed cert with the pubkey */ + const lws_ss_x509_t *key; + /**< the server's matching private key */ + } server; + } trust; const lws_retry_bo_t *retry_bo; /**< retry policy to use */ diff --git a/include/libwebsockets/lws-secure-streams.h b/include/libwebsockets/lws-secure-streams.h index 52fe74d42..3b6ddf8fd 100644 --- a/include/libwebsockets/lws-secure-streams.h +++ b/include/libwebsockets/lws-secure-streams.h @@ -180,6 +180,9 @@ typedef enum { LWSSSCS_QOS_NACK_LOCAL, /* local proxy refused our tx */ LWSSSCS_TIMEOUT, /* optional timeout timer fired */ + LWSSSCS_SERVER_TXN, + LWSSSCS_SERVER_UPGRADE, /* the server protocol upgraded */ + LWSSSCS_SINK_JOIN, /* sinks get this when a new source * stream joins the sink */ LWSSSCS_SINK_PART, /* sinks get this when a new source @@ -281,6 +284,13 @@ enum { */ LWSSSINFLAGS_PROXIED = (1 << 1), /**< Set if the stream is being created as a stand-in at the proxy */ + LWSSSINFLAGS_SERVER = (1 << 2), + /**< Set on the server object copy of the ssi / info to indicate that + * stream creation using this ssi is for Accepted connections belonging + * to a server */ + LWSSSINFLAGS_ACCEPTED = (1 << 3), + /**< Set on the accepted object copy of the ssi / info to indicate that + * we are an accepted connection from a server's listening socket */ }; typedef struct lws_ss_info { @@ -546,6 +556,27 @@ LWS_VISIBLE LWS_EXTERN int lws_ss_set_metadata(struct lws_ss_handle *h, const char *name, const void *value, size_t len); +/* + * lws_ss_server_ack() - indicate how we feel about what the server has sent + * + * \param h: ss handle of accepted connection + * \param nack: 0 means we are OK with it, else some problem + * + * For SERVER secure streams + * + * Depending on the protocol, the server sending us something may be + * transactional, ie, built into it sending something is the idea we will + * respond somehow out-of-band; HTTP is like this with, eg, 200 response code. + * + * Calling this with nack=0 indicates that when we later respond, we want to + * acknowledge the transaction (eg, it means a 200 if http underneath), if + * nonzero that the transaction should act like it failed. + * + * If the underlying protocol doesn't understand transactions (eg, ws) then this + * has no effect either way. + */ +LWS_VISIBLE LWS_EXTERN void +lws_ss_server_ack(struct lws_ss_handle *h, int nack); /** * lws_ss_change_handlers() - helper for dynamically changing stream handlers diff --git a/lib/core-net/adopt.c b/lib/core-net/adopt.c index 5fd3aff62..2444ff69b 100644 --- a/lib/core-net/adopt.c +++ b/lib/core-net/adopt.c @@ -193,6 +193,99 @@ bail: return NULL; } +#if defined(LWS_WITH_SERVER) && defined(LWS_WITH_SECURE_STREAMS) + +/* + * If the incoming wsi is bound to a vhost that is a ss server, this creates + * an accepted ss bound to the wsi. + * + * For h1 or raw, we can do the binding here, but for muxed protocols like h2 + * or mqtt we have to do it not on the nwsi but on the stream. And for h2 we + * start off bound to h1 role, since we don't know if we will upgrade to h2 + * until we meet the server. + * + * 1) No tls is assumed to mean no muxed protocol so can do it at adopt. + * + * 2) After alpn if not muxed we can do it. + * + * 3) For muxed, do it at the nwsi migration and on new stream + */ + +int +lws_adopt_ss_server_accept(struct lws *new_wsi) +{ + lws_ss_handle_t *h; + void *pv, **ppv; + + if (!new_wsi->a.vhost->ss_handle) + return 0; + + pv = (char *)&new_wsi->a.vhost->ss_handle[1]; + + /* + * Yes... the vhost is pointing to its secure stream representing the + * server... we want to create an accepted SS and bind it to new_wsi, + * the info/ssi from the server SS (so the SS callbacks defined there), + * the opaque_user_data of the server object and the policy of it. + */ + + ppv = (void **)((char *)pv + + new_wsi->a.vhost->ss_handle->info.opaque_user_data_offset); + + /* + * indicate we are an accepted connection referencing the + * server object + */ + + new_wsi->a.vhost->ss_handle->info.flags |= LWSSSINFLAGS_SERVER; + + if (lws_ss_create(new_wsi->a.context, new_wsi->tsi, + &new_wsi->a.vhost->ss_handle->info, + *ppv, &h, NULL, NULL)) { + lwsl_err("%s: accept ss creation failed\n", __func__); + goto fail1; + } + + /* + * We made a fresh accepted SS conn from the server pieces, + * now bind the wsi... the problem is, this is the nwsi if it's + * h2. + */ + + h->wsi = new_wsi; + new_wsi->a.opaque_user_data = h; + h->info.flags |= LWSSSINFLAGS_ACCEPTED; + + // lwsl_notice("%s: opaq %p, role %s\n", __func__, + // new_wsi->a.opaque_user_data, new_wsi->role_ops->name); + + h->policy = new_wsi->a.vhost->ss_handle->policy; + + /* + * Let's give it appropriate state notifications + */ + + if (lws_ss_event_helper(h, LWSSSCS_CREATING)) + goto fail; + if (lws_ss_event_helper(h, LWSSSCS_CONNECTING)) + goto fail; + if (lws_ss_event_helper(h, LWSSSCS_CONNECTED)) + goto fail; + + // lwsl_notice("%s: accepted ss complete, pcol %s\n", __func__, + // new_wsi->a.protocol->name); + + return 0; + +fail: + lws_ss_destroy(&h); +fail1: + return 1; +} + +#endif + + static struct lws * lws_adopt_descriptor_vhost2(struct lws *new_wsi, lws_adoption_type type, lws_sock_file_fd_type fd) @@ -287,6 +380,22 @@ lws_adopt_descriptor_vhost2(struct lws *new_wsi, lws_adoption_type type, lws_role_call_adoption_bind(new_wsi, type | _LWS_ADOPT_FINISH, new_wsi->a.protocol->name); +#if defined(LWS_WITH_SERVER) && defined(LWS_WITH_SECURE_STREAMS) + /* + * Did we come from an accepted client connection to a ss server? + * + * !!! For mux protocols, this will cause an additional inactive ss + * representing the nwsi. Doing that allows us to support both h1 + * (here) and h2 (at lws_wsi_server_new()) + */ + + lwsl_notice("%s: wsi %p, vhost %s ss_handle %p\n", __func__, new_wsi, + new_wsi->a.vhost->name, new_wsi->a.vhost->ss_handle); + + if (lws_adopt_ss_server_accept(new_wsi)) + goto fail; +#endif + #if LWS_MAX_SMP > 1 /* its actual pt can service it now */ diff --git a/lib/core-net/output.c b/lib/core-net/output.c index 269a6e59d..a9f9082e6 100644 --- a/lib/core-net/output.c +++ b/lib/core-net/output.c @@ -35,8 +35,15 @@ lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len) size_t real_len = len; unsigned int n, m; - // lwsl_notice("%s: len %d\n", __func__, (int)len); - // lwsl_hexdump_level(LLL_NOTICE, buf, len); +#if 0 + /* + * This is the last place data going out over tls tunnels can be dumped + * before encryption, in order to understand what's being sent "raw" + */ + + lwsl_notice("%s: wsi: %p, len: %d\n", __func__, wsi, (int)len); + lwsl_hexdump_notice(buf, len); +#endif /* * Detect if we got called twice without going through the diff --git a/lib/core-net/private-lib-core-net.h b/lib/core-net/private-lib-core-net.h index d6384501f..eacdeed28 100644 --- a/lib/core-net/private-lib-core-net.h +++ b/lib/core-net/private-lib-core-net.h @@ -544,6 +544,10 @@ struct lws_vhost { const lws_retry_bo_t *retry_policy; +#if defined(LWS_WITH_SERVER) && defined(LWS_WITH_SECURE_STREAMS) + lws_ss_handle_t *ss_handle; /* ss handle for the server obj */ +#endif + struct lws *lserv_wsi; const char *name; const char *iface; @@ -1321,6 +1325,10 @@ int lws_plat_change_pollfd(struct lws_context *context, struct lws *wsi, struct lws_pollfd *pfd); +#if defined(LWS_WITH_SERVER) && defined(LWS_WITH_SECURE_STREAMS) +int +lws_adopt_ss_server_accept(struct lws *new_wsi); +#endif int lws_plat_pipe_create(struct lws *wsi); diff --git a/lib/core/context.c b/lib/core/context.c index 10bca9fb2..66636022a 100644 --- a/lib/core/context.c +++ b/lib/core/context.c @@ -464,7 +464,9 @@ lws_create_context(const struct lws_context_creation_info *info) #if !defined(LWS_WITH_SECURE_STREAMS_STATIC_POLICY_ONLY) context->pss_policies_json = info->pss_policies_json; #endif +#if defined(LWS_WITH_SSPLUGINS) context->pss_plugins = info->pss_plugins; +#endif #endif /* if he gave us names, set the uid / gid */ @@ -1269,6 +1271,15 @@ lws_context_destroy3(struct lws_context *context) lws_mutex_refcount_destroy(&context->mr); #endif + /* drop any lingering deferred vhost frees */ + + while (context->deferred_free_list) { + struct lws_deferred_free *df = context->deferred_free_list; + + context->deferred_free_list = df->next; + lws_free(df); + }; + lws_free(context); lwsl_debug("%s: ctx %p freed\n", __func__, context); @@ -1311,6 +1322,14 @@ lws_context_destroy2(struct lws_context *context) #if defined(LWS_WITH_SECURE_STREAMS) lws_dll2_foreach_safe(&pt->ss_owner, NULL, lws_ss_destroy_dll); #if !defined(LWS_WITH_SECURE_STREAMS_STATIC_POLICY_ONLY) + + while (context->server_der_list) { + struct lws_ss_x509 *x = context->server_der_list; + + context->server_der_list = x->next; + lws_free((void *)x->ca_der); + } + if (context->ac_policy) lwsac_free(&context->ac_policy); #endif diff --git a/lib/core/private-lib-core.h b/lib/core/private-lib-core.h index 973a752cd..be9fd449c 100644 --- a/lib/core/private-lib-core.h +++ b/lib/core/private-lib-core.h @@ -401,6 +401,9 @@ struct lws_context { lws_sorted_usec_list_t sul_api_amazon_com; lws_sorted_usec_list_t sul_api_amazon_com_kick; #endif +#if !defined(LWS_WITH_SECURE_STREAMS_STATIC_POLICY_ONLY) + struct lws_ss_x509 *server_der_list; +#endif #endif #if defined(LWS_WITH_SYS_STATE) @@ -494,7 +497,9 @@ struct lws_context { void *pol_args; #endif const lws_ss_policy_t *pss_policies; +#if defined(LWS_WITH_SSPLUGINS) const lws_ss_plugin_t **pss_plugins; +#endif #endif void *external_baggage_free_on_destroy; diff --git a/lib/roles/h2/http2.c b/lib/roles/h2/http2.c index 2975646e9..52d6c6a4f 100644 --- a/lib/roles/h2/http2.c +++ b/lib/roles/h2/http2.c @@ -269,6 +269,11 @@ lws_wsi_server_new(struct lws_vhost *vh, struct lws *parent_wsi, wsi->a.vhost->conn_stats.h2_subs++; #endif +#if defined(LWS_WITH_SERVER) && defined(LWS_WITH_SECURE_STREAMS) + if (lws_adopt_ss_server_accept(wsi)) + goto bail1; +#endif + /* get the ball rolling */ lws_validity_confirmed(wsi); @@ -2512,10 +2517,48 @@ lws_h2_ws_handshake(struct lws *wsi) if (lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL) && /* - it is not an empty string */ wsi->a.protocol->name && wsi->a.protocol->name[0]) { + +#if defined(LWS_WITH_SECURE_STREAMS) && defined(LWS_WITH_SERVER) + + /* + * This is the h2 version of server-ws.c understanding that it + * did the ws upgrade on a ss server object, therefore it needs + * to pass back to the peer the policy ws-protocol name, not + * the generic ss-ws.c protocol name + */ + + if (wsi->a.vhost->ss_handle->policy->u.http.u.ws.subprotocol) { + lws_ss_handle_t *h = + (lws_ss_handle_t *)wsi->a.opaque_user_data; + + lwsl_notice("%s: Server SS %p .wsi %p switching to ws protocol\n", + __func__, h, h->wsi); + + wsi->a.protocol = &protocol_secstream_ws; + + /* + * inform the SS user code that this has done a one-way + * upgrade to some other protocol... it will likely + * want to treat subsequent payloads differently + */ + + lws_ss_event_helper(h, LWSSSCS_SERVER_UPGRADE); + + lws_mux_mark_immortal(wsi); + + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_PROTOCOL, + (unsigned char *)wsi->a.vhost->ss_handle->policy-> + u.http.u.ws.subprotocol, + (int)strlen(wsi->a.vhost->ss_handle->policy-> + u.http.u.ws.subprotocol), &p, end)) + return -1; + } else +#endif + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_PROTOCOL, (unsigned char *)wsi->a.protocol->name, (int)strlen(wsi->a.protocol->name), &p, end)) - return -1; + return -1; } } diff --git a/lib/roles/http/header.c b/lib/roles/http/header.c index f505fd759..02c3a9f5b 100644 --- a/lib/roles/http/header.c +++ b/lib/roles/http/header.c @@ -157,7 +157,8 @@ lws_add_http_common_headers(struct lws *wsi, unsigned int code, if (lws_add_http_header_status(wsi, code, p, end)) return 1; - if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, + if (content_type && + lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)content_type, (int)strlen(content_type), p, end)) return 1; diff --git a/lib/roles/ws/server-ws.c b/lib/roles/ws/server-ws.c index 66e1b0866..1eeba0546 100644 --- a/lib/roles/ws/server-ws.c +++ b/lib/roles/ws/server-ws.c @@ -297,10 +297,37 @@ lws_process_ws_upgrade2(struct lws *wsi) lws_pt_lock(pt, __func__); - if (!wsi->h2_stream_carries_ws) + /* + * Switch roles if we're upgrading away from http + */ + + if (!wsi->h2_stream_carries_ws) { lws_role_transition(wsi, LWSIFR_SERVER, LRS_ESTABLISHED, &role_ops_ws); +#if defined(LWS_WITH_SECURE_STREAMS) && defined(LWS_WITH_SERVER) + + /* + * If we're a SS server object, we have to switch to ss-ws + * protocol handler too + */ + if (wsi->a.vhost->ss_handle) { + lwsl_info("%s: Server SS %p switching to ws protocol\n", + __func__, wsi->a.vhost->ss_handle); + wsi->a.protocol = &protocol_secstream_ws; + + /* + * inform the SS user code that this has done a one-way + * upgrade to some other protocol... it will likely + * want to treat subsequent payloads differently + */ + + lws_ss_event_helper(wsi->a.vhost->ss_handle, + LWSSSCS_SERVER_UPGRADE); + } +#endif + } + lws_pt_unlock(pt); /* allocate the ws struct for the wsi */ @@ -535,6 +562,33 @@ lws_process_ws_upgrade(struct lws *wsi) goto alloc_ws; } +#if defined(LWS_WITH_SECURE_STREAMS) && defined(LWS_WITH_SERVER) + if (wsi->a.vhost->ss_handle) { + lws_ss_handle_t *sssh = wsi->a.vhost->ss_handle; + + /* + * At the moment, once we see it's a ss ws server, whatever + * he asked for we bind him to the ss-ws protocol handler. + * + * In the response subprotocol header, we need to name + * + * sssh->policy->u.http.u.ws.subprotocol + * + * though... + */ + + if (sssh->policy->u.http.u.ws.subprotocol) { + pcol = lws_vhost_name_to_protocol(wsi->a.vhost, + "lws-secstream-ws"); + if (pcol) { + lws_bind_protocol(wsi, pcol, "ss ws upg pcol"); + + goto alloc_ws; + } + } + } +#endif + /* otherwise go through the user-provided protocol list */ do { @@ -651,6 +705,26 @@ handshake_0405(struct lws_context *context, struct lws *wsi) prot = wsi->child_list->ws->actual_protocol; #endif +#if defined(LWS_WITH_SECURE_STREAMS) && defined(LWS_WITH_SERVER) + { + lws_ss_handle_t *sssh = wsi->a.vhost->ss_handle; + + /* + * At the moment, once we see it's a ss ws server, whatever + * he asked for we bind him to the ss-ws protocol handler. + * + * In the response subprotocol header, we need to name + * + * sssh->policy->u.http.u.ws.subprotocol + * + * though... + */ + + if (sssh->policy->u.http.u.ws.subprotocol) + prot = sssh->policy->u.http.u.ws.subprotocol; + } +#endif + LWS_CPYAPP(p, "\x0d\x0aSec-WebSocket-Protocol: "); p += lws_snprintf(p, 128, "%s", prot); } @@ -945,7 +1019,6 @@ lws_parse_ws(struct lws *wsi, unsigned char **buf, size_t len) * We dealt with it by trimming the existing * rxflow cache HEAD to account for what we used. * - * indicate we didn't use anything to the caller * so he doesn't do any consumed processing */ lwsl_info("%s: trimming inside rxflow cache\n", diff --git a/lib/secure-streams/CMakeLists.txt b/lib/secure-streams/CMakeLists.txt index 1569943d2..ba3d13c48 100644 --- a/lib/secure-streams/CMakeLists.txt +++ b/lib/secure-streams/CMakeLists.txt @@ -123,7 +123,7 @@ if (LWS_WITH_CLIENT) list(APPEND SS_PLUGINS_LIST ${NAME}) endmacro() - create_ss_plugin(ssp-h1url "h1url.c" "" "" "" "") + # create_ss_plugin(ssp-h1url "h1url.c" "" "" "" "") endif() # diff --git a/lib/secure-streams/README.md b/lib/secure-streams/README.md index 1930164cf..887f2dd58 100644 --- a/lib/secure-streams/README.md +++ b/lib/secure-streams/README.md @@ -1,30 +1,21 @@ # 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. +Secure Streams is a networking api that strictly separates payload from any +metadata. That includes the client endpoint address for the connection, the tls +trust chain 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 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) +Both client and server networking can be handled using Secure Streams APIS. -## Convention for rx and tx callback return +![overview](../doc-assets/ss-operation-modes.png) -Function|Return|Meaning ----|---|--- -tx|`LWSSSSRET_OK`|Send the amount of `buf` stored in `*len` -tx|`LWSSSSRET_TX_DONT_SEND`|Do not send anything -tx|`LWSSSSRET_DISCONNECT_ME`|Close the current connection -tx|`LWSSSSRET_DESTROY_ME`|Destroy the Secure Stream -rx|>=0|accepted -rx|<0|Close the current connection - -## Secure Streams State lifecycle +## Secure Streams CLIENT State lifecycle ![overview](../doc-assets/ss-state-flow.png) @@ -47,6 +38,63 @@ State callbacks and tx() can indicate they want to drop the connection (`LWSSSRET_DISCONNECT_ME`) or destroy the whole logical Secure Stream (`LWSSSRET_DESTROY_ME`). +## Secure Streams SERVER State lifecycle + +![overview](../doc-assets/ss-state-flow-server.png) + +You can also run servers defined using Secure Streams, the main difference is +that the user code must assertively create a secure stream of the server type +in order to create the vhost and listening socket. When this stream is +destroyed, the vhost is destroyed and the listen socket closed, otherwise it +does not perform any rx or tx, it just represents the server lifecycle. + +When client connections randomly arrive at the listen socket, new Secure Stream +objects are created along with accept sockets to represent each client +connection. As they represent the incoming connection, their lifecycle is the +same as that of the underlying connection. There is no retry concept since as +with eg, http servers, the clients may typically not be routable for new +connections initiated by the server. + +Since connections at socket level are already established, new connections are +immediately taken through CREATING, CONNECTING, CONNECTED states for +consistency. + +Some underlying protocols like http are "transactional", the server receives +a logical request and must reply with a logical response. The additional +state `LWSSSCS_SERVER_TXN` provides a point where the user code can set +transaction metadata before or in place of sending any payload. It's also +possible to defer this until any rx related to the transaction was received, +but commonly with http requests, there is no rx / body. Configuring the +response there may look like + +``` + /* + * We do want to ack the transaction... + */ + lws_ss_server_ack(m->ss, 0); + /* + * ... it's going to be text/html... + */ + lws_ss_set_metadata(m->ss, "mime", "text/html", 9); + /* + * ...it's going to be 128 byte (and request tx) + */ + lws_ss_request_tx_len(m->ss, 128); +``` + +Otherwise the general api usage is very similar to client usage. + +## Convention for rx and tx callback return + +Function|Return|Meaning +---|---|--- +tx|`LWSSSSRET_OK`|Send the amount of `buf` stored in `*len` +tx|`LWSSSSRET_TX_DONT_SEND`|Do not send anything +tx|`LWSSSSRET_DISCONNECT_ME`|Close the current connection +tx|`LWSSSSRET_DESTROY_ME`|Destroy the Secure Stream +rx|>=0|accepted +rx|<0|Close the current connection + # JSON Policy Database Example JSON policy... formatting is shown for clarity but whitespace can be @@ -151,9 +199,14 @@ Entries should be named using "name" and the stack array defined using "stack" These are an array of policies for the supported stream type names. +### `server` + +**SERVER ONLY**: if set to `true`, the policy describes a secure streams +server. + ### `endpoint` -The DNS address the secure stream should connect to. +**CLIENT**: The DNS address the secure stream should connect to. This may contain string symbols which will be replaced with the corresponding streamtype metadata value at runtime. Eg, if the @@ -169,14 +222,19 @@ Namespace and doesn't have a filesystem footprint. This is only supported on unix-type and windows platforms and when lws was configured with `-DLWS_UNIX_SOCK=1` +**SERVER**: If given, the network interface name or IP address the listen socket +should bind to. + ### `port` -The port number as an integer on the endpoint to connect to +**CLIENT**: The port number as an integer on the endpoint to connect to + +**SERVER**: The port number the server will listen on ### `protocol` -The wire protocol to connect to the endpoint with. Currently supported -streamtypes are +**CLIENT**: The wire protocol to connect to the endpoint with. Currently +supported streamtypes are |Wire protocol|Description| |---|---| @@ -234,6 +292,16 @@ policy is applied. The name of the trust store described in the `trust_stores` section to apply to validate the remote server cert. +### `server_cert` + +**SERVER ONLY**: subject to change... the name of the x.509 cert that is the +server's tls certificate + +### `server_key` + +**SERVER ONLY**: subject to change... the name of the x.509 cert that is the +server's tls key + ### `swake_validity` Set to `true` if this streamtype is important enough for the functioning of the @@ -361,7 +429,9 @@ protocol. Eg, a single multipart mime transaction carries content from two or m ### `ws_subprotocol` -Name of the ws subprotocol to use. +** CLIENT **: Name of the ws subprotocol to request from the server + +** SERVER **: Name of the subprotocol we will accept ### `ws_binary` @@ -620,3 +690,18 @@ you're not otherwise using it, saving an additional couple of KB). Notice policy2c example tool must be built with `LWS_ROLE_H1`, `LWS_ROLE_H2`, `LWS_ROLE_WS` and `LWS_ROLE_MQTT` enabled so it can handle any kind of policy. +## HTTP and ws serving + +All ws servers start out as http servers... for that reason ws serving is +handled as part of http serving, if you give the `ws_subprotocol` entry to the +streamtype additionally, the server will also accept upgrades to ws. + +To help the user code understand if the upgrade occurred, there's a special +state `LWSSSCS_SERVER_UPGRADE`, so subsequent rx and tx can be understood to +have come from the upgraded protocol. To allow separation of rx and tx +handling between http and ws, there's a ss api `lws_ss_change_handlers()` +which allows dynamically setting SS handlers. + +Since the http and ws upgrade identity is encapsulated in one streamtype, the +user object for the server streamtype should contain related user data for both +http and ws underlying protocol identity. diff --git a/lib/secure-streams/policy-common.c b/lib/secure-streams/policy-common.c index 193c2fd3e..84dc234bc 100644 --- a/lib/secure-streams/policy-common.c +++ b/lib/secure-streams/policy-common.c @@ -142,7 +142,7 @@ lws_ss_policy_ref_trust_store(struct lws_context *context, memset(&i, 0, sizeof(i)); - if (!pol->trust_store) { + if (!pol->trust.store) { v = lws_get_vhost_by_name(context, "_ss_default"); if (!v) { /* corner case... there's no trust store used */ @@ -160,23 +160,23 @@ lws_ss_policy_ref_trust_store(struct lws_context *context, goto accepted; } - v = lws_get_vhost_by_name(context, pol->trust_store->name); + v = lws_get_vhost_by_name(context, pol->trust.store->name); if (v) { lwsl_notice("%s: vh already exists\n", __func__); goto accepted; } i.options = context->options; - i.vhost_name = pol->trust_store->name; + i.vhost_name = pol->trust.store->name; lwsl_debug("%s: %s\n", __func__, i.vhost_name); #if defined(LWS_WITH_TLS) && defined(LWS_WITH_CLIENT) - i.client_ssl_ca_mem = pol->trust_store->ssx509[0]->ca_der; + i.client_ssl_ca_mem = pol->trust.store->ssx509[0]->ca_der; i.client_ssl_ca_mem_len = (unsigned int) - pol->trust_store->ssx509[0]->ca_der_len; + pol->trust.store->ssx509[0]->ca_der_len; #endif i.port = CONTEXT_PORT_NO_LISTEN; lwsl_notice("%s: %s trust store initial '%s'\n", __func__, - i.vhost_name, pol->trust_store->ssx509[0]->vhost_name); + i.vhost_name, pol->trust.store->ssx509[0]->vhost_name); v = lws_create_vhost(context, &i); if (!v) { @@ -186,13 +186,13 @@ lws_ss_policy_ref_trust_store(struct lws_context *context, } else v->from_ss_policy = 1; - for (n = 1; v && n < pol->trust_store->count; n++) { + for (n = 1; v && n < pol->trust.store->count; n++) { lwsl_info("%s: add '%s' to trust store\n", __func__, - pol->trust_store->ssx509[n]->vhost_name); + pol->trust.store->ssx509[n]->vhost_name); #if defined(LWS_WITH_TLS) if (lws_tls_client_vhost_extra_cert_mem(v, - pol->trust_store->ssx509[n]->ca_der, - pol->trust_store->ssx509[n]->ca_der_len)) { + pol->trust.store->ssx509[n]->ca_der, + pol->trust.store->ssx509[n]->ca_der_len)) { lwsl_err("%s: add extra cert failed\n", __func__); return NULL; @@ -217,8 +217,8 @@ lws_ss_policy_unref_trust_store(struct lws_context *context, struct lws_vhost *v; const char *name = "_ss_default"; - if (pol->trust_store) - name = pol->trust_store->name; + if (pol->trust.store) + name = pol->trust.store->name; v = lws_get_vhost_by_name(context, name); if (!v || !v->from_ss_policy) @@ -321,17 +321,19 @@ lws_ss_policy_set(struct lws_context *context, const char *name) * We get called from context creation... instantiates * vhosts with client tls contexts set up for each unique CA. * - * For compatibility with static policy, we create the vhosts - * by walking streamtype list and create vhosts using trust - * store name if it doesn't already exist. + * We create the vhosts by walking streamtype list and create vhosts + * using trust store name if it's a client connection that doesn't + * already exist. */ pol = context->pss_policies; while (pol) { - v = lws_ss_policy_ref_trust_store(context, pol, + if (!(pol->flags & LWSSSPOLF_SERVER)) { + v = lws_ss_policy_ref_trust_store(context, pol, 0 /* no refcount inc */); - if (!v) - ret = 1; + if (!v) + ret = 1; + } pol = pol->next; } @@ -365,11 +367,14 @@ lws_ss_policy_set(struct lws_context *context, const char *name) x = args->heads[LTY_X509].x; while (x) { /* - * Free all the DER buffers now they have been parsed into - * tls library X.509 objects + * Free all the client DER buffers now they have been parsed + * into tls library X.509 objects */ - lws_free((void *)x->ca_der); - x->ca_der = NULL; + if (!x->keep) { /* used for server */ + lws_free((void *)x->ca_der); + x->ca_der = NULL; + } + x = x->next; } diff --git a/lib/secure-streams/policy-json.c b/lib/secure-streams/policy-json.c index c3f074676..d220d7f4f 100644 --- a/lib/secure-streams/policy-json.c +++ b/lib/secure-streams/policy-json.c @@ -82,6 +82,9 @@ static const char * const lejp_tokens_policy[] = { "s[].*.ws_subprotocol", "s[].*.ws_binary", "s[].*.local_sink", + "s[].*.server", + "s[].*.server_cert", + "s[].*.server_key", "s[].*.mqtt_topic", "s[].*.mqtt_subscribe", "s[].*.mqtt_qos", @@ -149,6 +152,9 @@ typedef enum { LSSPPT_WS_SUBPROTOCOL, LSSPPT_WS_BINARY, LSSPPT_LOCAL_SINK, + LSSPPT_SERVER, + LSSPPT_SERVER_CERT, + LSSPPT_SERVER_KEY, LSSPPT_MQTT_TOPIC, LSSPPT_MQTT_SUBSCRIBE, LSSPPT_MQTT_QOS, @@ -159,12 +165,13 @@ typedef enum { LSSPPT_MQTT_WILL_QOS, LSSPPT_MQTT_WILL_RETAIN, LSSPPT_SWAKE_VALIDITY, - LSSPPT_STREAMTYPES + LSSPPT_STREAMTYPES, + } policy_token_t; #define POL_AC_INITIAL 2048 #define POL_AC_GRAIN 800 -#define MAX_CERT_TEMP 2048 /* used to discover actual cert size for realloc */ +#define MAX_CERT_TEMP 3072 /* used to discover actual cert size for realloc */ static uint8_t sizes[] = { sizeof(backoff_t), @@ -185,14 +192,16 @@ 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; +#if defined(LWS_WITH_SSPLUGINS) const lws_ss_plugin_t **pin; +#endif char **pp, dotstar[32], *q; lws_ss_trust_store_t *ts; lws_ss_metadata_t *pmd; + lws_ss_x509_t *x, **py; lws_ss_policy_t *p2; lws_retry_bo_t *b; size_t inl, outl; - lws_ss_x509_t *x; uint8_t *extant; backoff_t *bot; int n = -1; @@ -232,7 +241,10 @@ lws_ss_policy_parser_cb(struct lejp_ctx *ctx, char reason) /* * 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 + * size. + * + * The struct *x is in the lwsac... the ca_der it points to + * is individually allocated from the heap */ a->curr[LTY_X509].x->ca_der = lws_malloc(a->count, "ssx509"); if (!a->curr[LTY_X509].x->ca_der) @@ -416,6 +428,56 @@ lws_ss_policy_parser_cb(struct lejp_ctx *ctx, char reason) dotstar); goto oom; + case LSSPPT_SERVER_CERT: + case LSSPPT_SERVER_KEY: + + /* iterate through the certs */ + + py = &a->heads[LTY_X509].x; + x = a->heads[LTY_X509].x; + while (x) { + if (!strncmp(x->vhost_name, ctx->buf, ctx->npos) && + !x->vhost_name[ctx->npos]) { + if ((ctx->path_match - 1) == LSSPPT_SERVER_CERT) + a->curr[LTY_POLICY].p->trust.server.cert = x; + else + a->curr[LTY_POLICY].p->trust.server.key = x; + /* + * Certs that are for servers need to stick + * around in DER form, so the vhost can be + * instantiated when the server is brought up + */ + x->keep = 1; + lwsl_notice("%s: server '%s' keep %d %p\n", + __func__, x->vhost_name, + ctx->path_match - 1, x); + + /* + * Server DER we need to move it to another + * list just for destroying it when the context + * is destroyed... snip us out of the live + * X.509 list + */ + + *py = x->next; + + /* + * ... and instead put us on the list of things + * to keep hold of for context destruction + */ + + x->next = a->context->server_der_list; + a->context->server_der_list = x; + + return 0; + } + py = &x->next; + x = x->next; + } + lws_strnncpy(dotstar, ctx->buf, ctx->npos, sizeof(dotstar)); + lwsl_err("%s: unknown cert / key %s\n", __func__, dotstar); + goto oom; + case LSSPPT_ENDPOINT: pp = (char **)&a->curr[LTY_POLICY].p->endpoint; goto string2; @@ -445,6 +507,7 @@ lws_ss_policy_parser_cb(struct lejp_ctx *ctx, char reason) goto string2; case LSSPPT_PLUGINS: +#if defined(LWS_WITH_SSPLUGINS) pin = a->context->pss_plugins; if (a->count == (int)LWS_ARRAY_SIZE(a->curr[LTY_POLICY].p->plugins)) { @@ -463,6 +526,9 @@ lws_ss_policy_parser_cb(struct lejp_ctx *ctx, char reason) } lwsl_err("%s: unknown plugin\n", __func__); goto oom; +#else + break; +#endif case LSSPPT_TLS: if (reason == LEJPCB_VAL_TRUE) @@ -530,7 +596,7 @@ lws_ss_policy_parser_cb(struct lejp_ctx *ctx, char reason) ts = a->heads[LTY_TRUSTSTORE].t; while (ts) { if (!strncmp(ctx->buf, ts->name, ctx->npos)) { - a->curr[LTY_POLICY].p->trust_store = ts; + a->curr[LTY_POLICY].p->trust.store = ts; return 0; } ts = ts->next; @@ -631,6 +697,11 @@ lws_ss_policy_parser_cb(struct lejp_ctx *ctx, char reason) a->curr[LTY_POLICY].p->flags |= LWSSSPOLF_LOCAL_SINK; break; + case LSSPPT_SERVER: + if (reason == LEJPCB_VAL_TRUE) + a->curr[LTY_POLICY].p->flags |= LWSSSPOLF_SERVER; + break; + #if defined(LWS_ROLE_MQTT) case LSSPPT_MQTT_TOPIC: pp = (char **)&a->curr[LTY_POLICY].p->u.mqtt.topic; @@ -783,9 +854,10 @@ lws_ss_policy_parse(struct lws_context *context, const uint8_t *buf, size_t len) if (m == LEJP_CONTINUE || m >= 0) return m; - lwsl_err("%s: parse failed: %d: %s\n", __func__, m, - lejp_error_to_string(m)); + lwsl_err("%s: parse failed line %u: %d: %s\n", __func__, + args->jctx.line, m, lejp_error_to_string(m)); lws_ss_policy_parse_abandon(context); + assert(0); return m; } diff --git a/lib/secure-streams/private-lib-secure-streams.h b/lib/secure-streams/private-lib-secure-streams.h index d0262fe34..9332d03d4 100644 --- a/lib/secure-streams/private-lib-secure-streams.h +++ b/lib/secure-streams/private-lib-secure-streams.h @@ -54,14 +54,16 @@ typedef struct lws_ss_handle { struct lws_sequencer *seq; /**< owning sequencer if any */ struct lws *wsi; /**< the stream wsi if any */ +#if defined(LWS_WITH_SSPLUGINS) void *nauthi; /**< the nauth plugin instance data */ void *sauthi; /**< the sauth plugin instance data */ +#endif 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 */ + //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_timeout; lws_sorted_usec_list_t sul; @@ -135,6 +137,10 @@ typedef struct lws_ss_handle { lws_ss_constate_t connstate;/**< public connection state */ lws_ss_seq_state_t seqstate; /**< private connection state */ +#if defined(LWS_WITH_SERVER) + int txn_resp; +#endif + uint16_t retry; /**< retry / backoff tracking */ int16_t temp16; @@ -142,6 +148,8 @@ typedef struct lws_ss_handle { uint8_t subseq; /**< emulate SOM tracking */ uint8_t txn_ok; /**< 1 = transaction was OK */ + uint8_t txn_resp_set:1; /**< user code set one */ + uint8_t txn_resp_pending:1; /**< we have yet to send */ uint8_t hanging_som:1; uint8_t inside_msg:1; uint8_t being_serialized:1; /* we are not the consumer */ @@ -407,7 +415,7 @@ 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 struct lws_protocols *protocol; secstream_protocol_connect_munge_t munge; secstream_protocol_add_txcr_t tx_cr_add; secstream_protocol_get_txcr_t tx_cr_est; diff --git a/lib/secure-streams/protocols/ss-h1.c b/lib/secure-streams/protocols/ss-h1.c index 3eab00470..ef071b8d6 100644 --- a/lib/secure-streams/protocols/ss-h1.c +++ b/lib/secure-streams/protocols/ss-h1.c @@ -152,6 +152,55 @@ around: } #endif +static int +lws_apply_metadata(lws_ss_handle_t *h, struct lws *wsi, uint8_t *buf, + uint8_t **pp, uint8_t *end) +{ + int m; + + 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, + (int)h->metadata[m].length, pp, end)) + return -1; + } + } + + /* + * Content-length on POST / PUT if we have the length information + */ + + if (h->policy->u.http.method && ( + (!strcmp(h->policy->u.http.method, "POST") || + !strcmp(h->policy->u.http.method, "PUT"))) && + 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, pp, end)) + return -1; + } + lws_client_http_body_pending(wsi, 1); + } + + return 0; +} + static const uint8_t blob_idx[] = { LWS_SYSBLOB_TYPE_AUTH, LWS_SYSBLOB_TYPE_DEVICE_SERIAL, @@ -165,6 +214,9 @@ secstream_h1(struct lws *wsi, enum lws_callback_reasons reason, void *user, { 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], +#if defined(LWS_WITH_SERVER) + *start = p, +#endif *end = &buf[sizeof(buf) - 1]; int f = 0, m, status; char conceal_eom = 0; @@ -196,6 +248,7 @@ secstream_h1(struct lws *wsi, enum lws_callback_reasons reason, void *user, /* don't follow it */ return 1; + case LWS_CALLBACK_CLOSED_HTTP: /* server */ case LWS_CALLBACK_CLOSED_CLIENT_HTTP: if (!h) break; @@ -208,6 +261,9 @@ secstream_h1(struct lws *wsi, enum lws_callback_reasons reason, void *user, //bad = status != 200; //lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */ if (h->policy && !(h->policy->flags & LWSSSPOLF_OPPORTUNISTIC) && +#if defined(LWS_WITH_SERVER) + !(h->info.flags & LWSSSINFLAGS_ACCEPTED) && /* not server */ +#endif !h->txn_ok && !wsi->a.context->being_destroyed) { if (lws_ss_backoff(h)) break; @@ -363,44 +419,8 @@ malformed: * 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, - (int)h->metadata[m].length, p, end)) - return -1; - } - } - - /* - * Content-length on POST / PUT if we have the length information - */ - - if ((!strcmp(h->policy->u.http.method, "POST") || - !strcmp(h->policy->u.http.method, "PUT")) && - 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); - } + if (lws_apply_metadata(h, wsi, buf, p, end)) + return -1; (void)oin; // if (*p != oin) @@ -411,6 +431,7 @@ malformed: break; /* chunks of chunked content, with header removed */ + case LWS_CALLBACK_HTTP_BODY: case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: lwsl_debug("%s: RECEIVE_CLIENT_HTTP_READ: read %d\n", __func__, (int)len); @@ -476,16 +497,62 @@ malformed: lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */ break; + case LWS_CALLBACK_HTTP_WRITEABLE: case LWS_CALLBACK_CLIENT_HTTP_WRITEABLE: - lwsl_info("%s: LWS_CALLBACK_CLIENT_HTTP_WRITEABLE\n", __func__); - if (!h || !h->info.tx) + //lwsl_info("%s: wsi %p, par %p, HTTP_WRITEABLE\n", __func__, + // wsi, wsi->mux.parent_wsi); + if (!h || !h->info.tx) { + lwsl_notice("%s: no handle / tx %p\n", __func__, h); return 0; + } + +#if defined(LWS_WITH_SERVER) + if (h->txn_resp_pending) { + /* + * If we're going to start sending something, we need to + * to take care of the http response header for it first + */ + h->txn_resp_pending = 0; + + if (lws_add_http_common_headers(wsi, + h->txn_resp_set ? + (h->txn_resp ? h->txn_resp : 200) : + HTTP_STATUS_NOT_FOUND, + NULL, h->wsi->http.writeable_len, + &p, end)) + return 1; + + /* + * metadata-based headers + */ + + if (lws_apply_metadata(h, wsi, buf, &p, end)) + return -1; + + if (lws_finalize_write_http_header(wsi, start, &p, end)) + return 1; + + /* write the body separately */ + lws_callback_on_writable(wsi); + + return 0; + } +#endif + + if ( +#if defined(LWS_WITH_SERVER) + !(h->info.flags & LWSSSINFLAGS_ACCEPTED) && /* not accepted */ +#endif + !h->rideshare) - if (!h->rideshare) h->rideshare = h->policy; #if defined(LWS_WITH_SS_RIDESHARE) - if (!h->inside_msg && h->rideshare->u.http.multipart_name) + if ( +#if defined(LWS_WITH_SERVER) + !(h->info.flags & LWSSSINFLAGS_ACCEPTED) && /* not accepted */ +#endif + !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, @@ -498,7 +565,6 @@ malformed: #else buflen = lws_ptr_diff(end, p); #endif - switch(h->info.tx(ss_to_userobj(h), h->txord++, p, &buflen, &f)) { case LWSSSSRET_DISCONNECT_ME: lwsl_debug("%s: tx handler asked to close conn\n", __func__); @@ -519,13 +585,15 @@ do_destroy_me: break; } - lwsl_info("%s: WRITEABLE: user tx says len %d fl 0x%x\n", - __func__, (int)buflen, (int)f); + // lwsl_notice("%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_WITH_SERVER) + if (!(h->info.flags & LWSSSINFLAGS_ACCEPTED)) { +#endif conceal_eom = 1; /* end of rideshares */ if (!h->rideshare->rideshare_streamtype) { @@ -541,6 +609,9 @@ do_destroy_me: h->rideshare->rideshare_streamtype); lws_callback_on_writable(wsi); } +#if defined(LWS_WITH_SERVER) + } +#endif h->inside_msg = 0; } else { @@ -562,9 +633,64 @@ do_destroy_me: return -1; } +#if defined(LWS_WITH_SERVER) + if (!(h->info.flags & LWSSSINFLAGS_ACCEPTED) && + (f & LWSSS_FLAG_EOM) && + lws_http_transaction_completed(wsi)) + return -1; +#else lws_set_timeout(wsi, 0, 0); +#endif break; +#if defined(LWS_WITH_SERVER) + case LWS_CALLBACK_HTTP: + + lwsl_notice("%s: LWS_CALLBACK_HTTP\n", __func__); + { + + h->txn_resp_set = 0; + h->txn_resp_pending = 1; + h->writeable_len = 0; + +#if defined(LWS_ROLE_H2) + m = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COLON_METHOD); + if (m) { + lws_ss_set_metadata(h, "method", + lws_hdr_simple_ptr(wsi, + WSI_TOKEN_HTTP_COLON_METHOD), m); + m = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COLON_PATH); + lws_ss_set_metadata(h, "path", + lws_hdr_simple_ptr(wsi, + WSI_TOKEN_HTTP_COLON_PATH), m); + } else +#endif + { + m = lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI); + if (m) { + lws_ss_set_metadata(h, "path", + lws_hdr_simple_ptr(wsi, + WSI_TOKEN_GET_URI), m); + lws_ss_set_metadata(h, "method", "GET", 3); + } else { + m = lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI); + if (m) { + lws_ss_set_metadata(h, "path", + lws_hdr_simple_ptr(wsi, + WSI_TOKEN_POST_URI), m); + lws_ss_set_metadata(h, "method", "POST", 4); + } + } + } + } + + if (lws_ss_event_helper(h, LWSSSCS_SERVER_TXN)) + /* was destroyed */ + return -1; + + return 0; +#endif + default: break; } @@ -631,7 +757,7 @@ secstream_connect_munge_h1(lws_ss_handle_t *h, char *buf, size_t len, const struct ss_pcols ss_pcol_h1 = { "h1", "http/1.1", - "lws-secstream-h1", + &protocol_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 index 3158df50a..0dae22740 100644 --- a/lib/secure-streams/protocols/ss-h2.c +++ b/lib/secure-streams/protocols/ss-h2.c @@ -198,7 +198,7 @@ secstream_tx_credit_est_h2(lws_ss_handle_t *h) const struct ss_pcols ss_pcol_h2 = { "h2", NULL, - "lws-secstream-h2", + &protocol_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 index 66dc39fb1..e3c912c04 100644 --- a/lib/secure-streams/protocols/ss-mqtt.c +++ b/lib/secure-streams/protocols/ss-mqtt.c @@ -345,6 +345,6 @@ secstream_connect_munge_mqtt(lws_ss_handle_t *h, char *buf, size_t len, const struct ss_pcols ss_pcol_mqtt = { "MQTT", "x-amzn-mqtt-ca", //"mqtt/3.1.1", - "lws-secstream-mqtt", + &protocol_secstream_mqtt, secstream_connect_munge_mqtt }; diff --git a/lib/secure-streams/protocols/ss-raw.c b/lib/secure-streams/protocols/ss-raw.c index fc0d5b91a..604883e63 100644 --- a/lib/secure-streams/protocols/ss-raw.c +++ b/lib/secure-streams/protocols/ss-raw.c @@ -155,7 +155,7 @@ const struct lws_protocols protocol_secstream_raw = { const struct ss_pcols ss_pcol_raw = { "raw", "", - "lws-secstream-raw", + &protocol_secstream_raw, secstream_connect_munge_raw, NULL }; diff --git a/lib/secure-streams/protocols/ss-ws.c b/lib/secure-streams/protocols/ss-ws.c index 0c612d462..96b8a72c8 100644 --- a/lib/secure-streams/protocols/ss-ws.c +++ b/lib/secure-streams/protocols/ss-ws.c @@ -30,7 +30,7 @@ secstream_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user, { lws_ss_handle_t *h = (lws_ss_handle_t *)lws_get_opaque_user_data(wsi); uint8_t buf[LWS_PRE + 1400]; - int f = 0, f1; + int f = 0, f1, n; size_t buflen; switch (reason) { @@ -50,6 +50,7 @@ secstream_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user, /* may have been destroyed */ break; + case LWS_CALLBACK_CLOSED: /* server */ case LWS_CALLBACK_CLIENT_CLOSED: if (!h) break; @@ -61,12 +62,19 @@ secstream_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user, lws_set_opaque_user_data(h->wsi, NULL); h->wsi = NULL; - if (h->policy && !(h->policy->flags & LWSSSPOLF_OPPORTUNISTIC) && - !h->txn_ok && !wsi->a.context->being_destroyed) - lws_ss_backoff(h); + if (reason == LWS_CALLBACK_CLIENT_CLOSED) { + if (h->policy && + !(h->policy->flags & LWSSSPOLF_OPPORTUNISTIC) && +#if defined(LWS_WITH_SERVER) + !(h->info.flags & LWSSSINFLAGS_ACCEPTED) && /* not server */ +#endif + !h->txn_ok && !wsi->a.context->being_destroyed) + lws_ss_backoff(h); + } /* may have been destroyed */ break; + case LWS_CALLBACK_ESTABLISHED: case LWS_CALLBACK_CLIENT_ESTABLISHED: h->retry = 0; h->seqstate = SSSEQ_CONNECTED; @@ -75,6 +83,7 @@ secstream_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user, return -1; break; + case LWS_CALLBACK_RECEIVE: case LWS_CALLBACK_CLIENT_RECEIVE: // lwsl_user("LWS_CALLBACK_CLIENT_RECEIVE: read %d\n", (int)len); if (!h || !h->info.rx) @@ -92,10 +101,11 @@ secstream_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user, return 0; /* don't passthru */ + case LWS_CALLBACK_SERVER_WRITEABLE: case LWS_CALLBACK_CLIENT_WRITEABLE: + // lwsl_notice("%s: ss %p: WRITEABLE\n", __func__, h); if (!h || !h->info.tx) 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); @@ -103,8 +113,10 @@ secstream_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user, } buflen = sizeof(buf) - LWS_PRE; - switch(h->info.tx(ss_to_userobj(h), h->txord++, buf + LWS_PRE, - &buflen, &f)) { + n = h->info.tx(ss_to_userobj(h), h->txord++, buf + LWS_PRE, + &buflen, &f); + + switch (n) { case LWSSSSRET_DISCONNECT_ME: lwsl_debug("%s: tx handler asked to close conn\n", __func__); return -1; /* close connection */ @@ -128,8 +140,11 @@ secstream_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user, !!(f & LWSSS_FLAG_SOM), !!(f & LWSSS_FLAG_EOM)); - if (lws_write(wsi, buf + LWS_PRE, buflen, f1) < (int)buflen) { - lwsl_info("%s: write failed\n", __func__); + n = lws_write(wsi, buf + LWS_PRE, buflen, f1); + if (n < (int)buflen) { + lwsl_info("%s: write failed %d %d\n", __func__, + n, (int)buflen); + return -1; } @@ -197,5 +212,5 @@ secstream_connect_munge_ws(lws_ss_handle_t *h, char *buf, size_t len, } const struct ss_pcols ss_pcol_ws = { - "ws", "http/1.1", "lws-secstream-ws", secstream_connect_munge_ws + "ws", "http/1.1", &protocol_secstream_ws, secstream_connect_munge_ws }; diff --git a/lib/secure-streams/secure-streams.c b/lib/secure-streams/secure-streams.c index 48dda68e4..24fe673ea 100644 --- a/lib/secure-streams/secure-streams.c +++ b/lib/secure-streams/secure-streams.c @@ -64,6 +64,8 @@ static const char *state_names[] = { "LWSSSCS_QOS_ACK_LOCAL", "LWSSSCS_QOS_NACK_LOCAL", "LWSSSCS_TIMEOUT", + "LWSSSCS_SERVER_TXN", + "LWSSSCS_SERVER_UPGRADE", }; const char * @@ -93,6 +95,7 @@ lws_ss_event_helper(lws_ss_handle_t *h, lws_ss_constate_t cs) (void *)h, NULL); #endif +#if 0 if (h->h_sink && h->h_sink->info.state) { n = h->h_sink->info.state(h->sink_obj, h->h_sink, cs, 0); if (n) { @@ -105,9 +108,15 @@ lws_ss_event_helper(lws_ss_handle_t *h, lws_ss_constate_t cs) } } } +#endif if (h->info.state) { n = h->info.state(ss_to_userobj(h), NULL, cs, 0); +#if defined(LWS_WITH_SERVER) + if ((h->info.flags & LWSSSINFLAGS_ACCEPTED) && + cs == LWSSSCS_DISCONNECTED) + n = LWSSSSRET_DESTROY_ME; +#endif if (n) { if (cs == LWSSSCS_CREATING) /* just let caller handle it */ @@ -321,8 +330,8 @@ _lws_ss_client_connect(lws_ss_handle_t *h, int is_retry) * We are already bound to a sink? */ - if (h->h_sink) - return 0; +// if (h->h_sink) +// return 0; if (!is_retry) h->retry = 0; @@ -405,15 +414,16 @@ _lws_ss_client_connect(lws_ss_handle_t *h, int is_retry) lwsl_info("%s: using tls\n", __func__); i.ssl_connection = LCCSCF_USE_SSL; - if (!h->policy->trust_store) + if (!h->policy->trust.store) lwsl_info("%s: using platform trust store\n", __func__); else { i.vhost = lws_get_vhost_by_name(h->context, - h->policy->trust_store->name); + h->policy->trust.store->name); if (!i.vhost) { lwsl_err("%s: missing vh for policy %s\n", - __func__, h->policy->trust_store->name); + __func__, + h->policy->trust.store->name); return -1; } @@ -450,7 +460,7 @@ _lws_ss_client_connect(lws_ss_handle_t *h, int is_retry) * 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.protocol = ssp->protocol->name; /* lws protocol name */ i.local_protocol_name = i.protocol; if (ssp->munge) /* eg, raw doesn't use; endpoint strexp already done */ @@ -458,8 +468,10 @@ _lws_ss_client_connect(lws_ss_handle_t *h, int is_retry) i.pwsi = &h->wsi; +#if defined(LWS_WITH_SSPLUGINS) if (h->policy->plugins[0] && h->policy->plugins[0]->munge) h->policy->plugins[0]->munge(h, path, sizeof(path)); +#endif lwsl_info("%s: connecting %s, '%s' '%s' %s\n", __func__, i.method, i.alpn, i.address, i.path); @@ -557,10 +569,12 @@ lws_ss_create(struct lws_context *context, int tsi, const lws_ss_info_t *ssi, */ size = sizeof(*h) + ssi->user_alloc + strlen(ssi->streamtype) + 1; +#if defined(LWS_WITH_SSPLUGINS) if (pol->plugins[0]) size += pol->plugins[0]->alloc; if (pol->plugins[1]) size += pol->plugins[1]->alloc; +#endif size += pol->metadata_count * sizeof(lws_ss_metadata_t); h = lws_zalloc(size, __func__); @@ -586,6 +600,7 @@ lws_ss_create(struct lws_context *context, int tsi, const lws_ss_info_t *ssi, p += ssi->user_alloc; +#if defined(LWS_WITH_SSPLUGINS) if (pol->plugins[0]) { h->nauthi = p; p += pol->plugins[0]->alloc; @@ -594,6 +609,7 @@ lws_ss_create(struct lws_context *context, int tsi, const lws_ss_info_t *ssi, h->sauthi = p; p += pol->plugins[1]->alloc; } +#endif if (pol->metadata_count) { h->metadata = (lws_ss_metadata_t *)p; @@ -614,6 +630,9 @@ lws_ss_create(struct lws_context *context, int tsi, const lws_ss_info_t *ssi, } memcpy(p, ssi->streamtype, strlen(ssi->streamtype) + 1); + /* don't mark accepted ss as being the server */ + if (ssi->flags & LWSSSINFLAGS_SERVER) + h->info.flags &= ~LWSSSINFLAGS_SERVER; h->info.streamtype = p; lws_pt_lock(pt, __func__); @@ -626,6 +645,12 @@ lws_ss_create(struct lws_context *context, int tsi, const lws_ss_info_t *ssi, if (ppayload_fmt) *ppayload_fmt = pol->payload_fmt; + if (ssi->flags & LWSSSINFLAGS_SERVER) + /* + * return early for accepted connection flow + */ + return 0; + #if defined(LWS_WITH_SYS_SMD) /* * For a local Secure Streams connection @@ -653,11 +678,72 @@ lws_ss_create(struct lws_context *context, int tsi, const lws_ss_info_t *ssi, } #endif - if (ssi->flags & LWSSSINFLAGS_REGISTER_SINK) { +#if defined(LWS_WITH_SERVER) + if (h->policy->flags & LWSSSPOLF_SERVER) { + const struct lws_protocols *pprot[3], **ppp = &pprot[0]; + struct lws_context_creation_info i; + struct lws_vhost *vho; + + lwsl_info("%s: creating server\n", __func__); + /* - * + * This streamtype represents a server, we're being asked to + * instantiate a corresponding vhost for it */ + + memset(&i, 0, sizeof i); + + i.iface = h->policy->endpoint; + i.vhost_name = h->policy->streamtype; + i.port = h->policy->port; + + if (!ss_pcols[h->policy->protocol]) { + lwsl_err("%s: unsupp protocol", __func__); + goto late_bail; + } + + *ppp++ = ss_pcols[h->policy->protocol]->protocol; +#if defined(LWS_ROLE_WS) + if (h->policy->u.http.u.ws.subprotocol) + /* + * He names a ws subprotocol, ie, we want to support + * ss-ws protocol in this vhost + */ + *ppp++ = &protocol_secstream_ws; +#endif + *ppp = NULL; + i.pprotocols = pprot; + + if (h->policy->flags & LWSSSPOLF_TLS) { + i.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + i.server_ssl_cert_mem = + h->policy->trust.server.cert->ca_der; + i.server_ssl_cert_mem_len = (unsigned int) + h->policy->trust.server.cert->ca_der_len; + i.server_ssl_private_key_mem = + h->policy->trust.server.key->ca_der; + i.server_ssl_private_key_mem_len = (unsigned int) + h->policy->trust.server.key->ca_der_len; + } + + vho = lws_create_vhost(context, &i); + if (!vho) { + lwsl_err("%s: failed to create vh", __func__); + goto late_bail; + } + + /* + * Mark this vhost as having to apply ss server semantics to + * any incoming accepted connection + */ + vho->ss_handle = h; + + lwsl_notice("%s: created server %s\n", __func__, + h->policy->streamtype); + + return 0; } +#endif #if defined(LWS_WITH_SECURE_STREAMS_STATIC_POLICY_ONLY) @@ -777,6 +863,21 @@ lws_ss_destroy(lws_ss_handle_t **ppss) lws_ss_policy_unref_trust_store(h->context, h->policy); #endif +#if defined(LWS_WITH_SERVER) + if (h->policy->flags & LWSSSPOLF_SERVER) { + struct lws_vhost *v = lws_get_vhost_by_name(h->context, + h->policy->streamtype); + + /* + * For server, the policy describes a vhost that implements the + * server, when we take down the ss, we take down the related + * vhost (if it got that far) + */ + if (v) + lws_vhost_destroy(v); + } +#endif + /* confirm no sul left scheduled in handle or user allocation object */ lws_sul_debug_zombies(h->context, h, sizeof(*h) + h->info.user_alloc, __func__); @@ -784,12 +885,19 @@ lws_ss_destroy(lws_ss_handle_t **ppss) lws_free_set_NULL(h); } +void +lws_ss_server_ack(struct lws_ss_handle *h, int nack) +{ + h->txn_resp = nack; + h->txn_resp_set = 1; +} + void lws_ss_request_tx(lws_ss_handle_t *h) { int n; - lwsl_info("%s: wsi %p\n", __func__, h->wsi); + // lwsl_notice("%s: h %p, wsi %p\n", __func__, h, h->wsi); if (h->wsi) { lws_callback_on_writable(h->wsi); @@ -797,6 +905,10 @@ lws_ss_request_tx(lws_ss_handle_t *h) return; } + /* + * there's currently no wsi / connection associated with the ss handle + */ + #if defined(LWS_WITH_SYS_SMD) if (h->policy == &pol_smd) { /* diff --git a/lib/tls/openssl/openssl-ssl.c b/lib/tls/openssl/openssl-ssl.c index 2392822e7..00e5e62f1 100644 --- a/lib/tls/openssl/openssl-ssl.c +++ b/lib/tls/openssl/openssl-ssl.c @@ -59,6 +59,8 @@ int lws_ssl_get_error(struct lws *wsi, int n) m = SSL_get_error(wsi->tls.ssl, n); lwsl_debug("%s: %p %d -> %d (errno %d)\n", __func__, wsi->tls.ssl, n, m, errno); + if (m == SSL_ERROR_SSL) + lws_tls_err_describe_clear(); // assert (errno != 9); diff --git a/minimal-examples/secure-streams/minimal-secure-streams-policy2c/minimal-secure-streams.c b/minimal-examples/secure-streams/minimal-secure-streams-policy2c/minimal-secure-streams.c index 272f02854..f45f4a95a 100644 --- a/minimal-examples/secure-streams/minimal-secure-streams-policy2c/minimal-secure-streams.c +++ b/minimal-examples/secure-streams/minimal-secure-streams-policy2c/minimal-secure-streams.c @@ -256,10 +256,10 @@ int main(int argc, const char **argv) * How about his trust store, it's new to us? */ - if (pol->trust_store) { + if (pol->trust.store) { a = trustmap; while (a) { - if (a->orig == (const char *)pol->trust_store) + if (a->orig == (const char *)pol->trust.store) break; a = a->next; @@ -274,7 +274,7 @@ int main(int argc, const char **argv) goto bail; a->next = trustmap; a->offset = 0; /* don't care, just track seen */ - a->orig = (const char *)pol->trust_store; + a->orig = (const char *)pol->trust.store; trustmap = a; /* @@ -282,12 +282,12 @@ int main(int argc, const char **argv) * any that're new to us? */ - for (n = 0; n < pol->trust_store->count; n++) { - if (!pol->trust_store->ssx509[n]) + for (n = 0; n < pol->trust.store->count; n++) { + if (!pol->trust.store->ssx509[n]) continue; a1 = certmap; while (a1) { - if (a1->orig == (const char *)pol->trust_store->ssx509[n]) + if (a1->orig == (const char *)pol->trust.store->ssx509[n]) break; a1 = a1->next; } @@ -303,48 +303,48 @@ int main(int argc, const char **argv) goto bail; a1->next = certmap; a1->offset = 0; /* don't care, just track seen */ - a1->orig = (const char *)pol->trust_store->ssx509[n]; + a1->orig = (const char *)pol->trust.store->ssx509[n]; certmap = a1; printf("static const uint8_t _ss_der_%s[] = {\n", - purify_csymbol(pol->trust_store->ssx509[n]->vhost_name, + purify_csymbol(pol->trust.store->ssx509[n]->vhost_name, buf, sizeof(buf))); - for (m = 0; m < (int)pol->trust_store->ssx509[n]->ca_der_len; m++) { + for (m = 0; m < (int)pol->trust.store->ssx509[n]->ca_der_len; m++) { if ((m & 7) == 0) printf("\t/* 0x%3x */ ", m); - printf("0x%02X, ", pol->trust_store->ssx509[n]->ca_der[m]); + printf("0x%02X, ", pol->trust.store->ssx509[n]->ca_der[m]); if ((m & 7) == 7) printf("\n"); } printf("\n};\nstatic const lws_ss_x509_t _ss_x509_%s = {\n", - purify_csymbol(pol->trust_store->ssx509[n]->vhost_name, + purify_csymbol(pol->trust.store->ssx509[n]->vhost_name, buf, sizeof(buf))); - printf("\t.vhost_name = \"%s\",\n", pol->trust_store->ssx509[n]->vhost_name); + printf("\t.vhost_name = \"%s\",\n", pol->trust.store->ssx509[n]->vhost_name); printf("\t.ca_der = _ss_der_%s,\n", - purify_csymbol(pol->trust_store->ssx509[n]->vhost_name, + purify_csymbol(pol->trust.store->ssx509[n]->vhost_name, buf, sizeof(buf))); - printf("\t.ca_der_len = %zu,\n", pol->trust_store->ssx509[n]->ca_der_len); + printf("\t.ca_der_len = %zu,\n", pol->trust.store->ssx509[n]->ca_der_len); printf("};\n"); - est += sizeof(lws_ss_x509_t) + pol->trust_store->ssx509[n]->ca_der_len; + est += sizeof(lws_ss_x509_t) + pol->trust.store->ssx509[n]->ca_der_len; } } printf("static const lws_ss_trust_store_t _ss_ts_%s = {\n", - purify_csymbol(pol->trust_store->name, + purify_csymbol(pol->trust.store->name, buf, sizeof(buf))); - printf("\t.name = \"%s\",\n", pol->trust_store->name); + printf("\t.name = \"%s\",\n", pol->trust.store->name); printf("\t.ssx509 = {\n"); - for (n = pol->trust_store->count - 1; n >= 0 ; n--) + for (n = pol->trust.store->count - 1; n >= 0 ; n--) printf("\t\t&_ss_x509_%s,\n", - pol->trust_store->ssx509[n]->vhost_name); + pol->trust.store->ssx509[n]->vhost_name); printf("\t}\n};\n"); @@ -532,9 +532,10 @@ int main(int argc, const char **argv) if (pol->client_cert) printf("\t.client_cert = %u,\n", pol->client_cert); - if (pol->trust_store) - printf("\t.trust_store = &_ss_ts_%s,\n", - purify_csymbol(pol->trust_store->name, buf, sizeof(buf))); + if (pol->trust.store) + printf("\t.trust = {.store = &_ss_ts_%s},\n", + purify_csymbol(pol->trust.store->name, + buf, sizeof(buf))); printf("}"); diff --git a/minimal-examples/secure-streams/minimal-secure-streams-sink/CMakeLists.txt b/minimal-examples/secure-streams/minimal-secure-streams-server/CMakeLists.txt similarity index 74% rename from minimal-examples/secure-streams/minimal-secure-streams-sink/CMakeLists.txt rename to minimal-examples/secure-streams/minimal-secure-streams-server/CMakeLists.txt index 0725d601e..a2170a6d4 100644 --- a/minimal-examples/secure-streams/minimal-secure-streams-sink/CMakeLists.txt +++ b/minimal-examples/secure-streams/minimal-secure-streams-server/CMakeLists.txt @@ -1,16 +1,17 @@ -project(lws-minimal-secure-streams-sink C) +project(lws-minimal-secure-streams-server C) cmake_minimum_required(VERSION 2.8) find_package(libwebsockets CONFIG REQUIRED) list(APPEND CMAKE_MODULE_PATH ${LWS_CMAKE_DIR}) include(CheckCSourceCompiles) include(LwsCheckRequirements) -set(SAMP lws-minimal-secure-streams-sink) -set(SRCS main.c) +set(SAMP lws-minimal-secure-streams-server) +set(SRCS main.c ss-client.c ss-server.c) set(requirements 1) require_lws_config(LWS_ROLE_H1 1 requirements) -require_lws_config(LWS_WITHOUT_CLIENT 0 requirements) +require_lws_config(LWS_WITH_CLIENT 1 requirements) +require_lws_config(LWS_WITH_SERVER 1 requirements) require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements) require_lws_config(LWS_WITH_SECURE_STREAMS_STATIC_POLICY_ONLY 0 requirements) diff --git a/minimal-examples/secure-streams/minimal-secure-streams-server/README.md b/minimal-examples/secure-streams/minimal-secure-streams-server/README.md new file mode 100644 index 000000000..6e98f1180 --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-server/README.md @@ -0,0 +1,72 @@ +# lws minimal secure streams server + +The application sets up a tls + ws server on https://localhost:7681 + +It does it using Secure Streams... information about how the server should +operate is held in JSON policy in main.c + +Visiting the server in a modern browser will fetch some html + JS, the JS will +create a ws link back to the server and the server will spam an incrementing +number that is displayed in the browser every 100ms. + +The app also has a SS client that works, but it's disabled by default since +we're interested in server. + +## build + +``` + $ cmake . && make +``` + +## usage + +Commandline option|Meaning +---|--- +-d |Debug verbosity in decimal, eg, -d15 + +``` +[2020/07/27 10:51:04:8994] U: LWS Secure Streams Server +[2020/07/27 10:51:04:9440] N: LWS: 4.0.99-v4.0.0-245-ge6eb4417a, loglevel 1031 +[2020/07/27 10:51:04:9444] N: NET CLI SRV H1 H2 WS MQTT SS-JSON-POL SSPROX ASYNC_DNS IPv6-absent +[2020/07/27 10:51:05:1685] N: lws_adopt_descriptor_vhost2: wsi 0x5317d30, vhost system ss_handle (nil) +[2020/07/27 10:51:05:1753] N: lws_adopt_descriptor_vhost2: wsi 0x53182c0, vhost system ss_handle (nil) +[2020/07/27 10:51:05:2129] N: lws_ss_policy_parser_cb: server 'self_localhost' keep 52 0x5318cc0 +[2020/07/27 10:51:05:2134] N: lws_ss_policy_parser_cb: server 'self_localhost_key' keep 53 0x5318cf8 +[2020/07/27 10:51:05:2192] N: lws_ss_policy_ref_trust_store: le_via_isrg trust store initial 'isrg_root_x1' +[2020/07/27 10:51:05:7804] N: smd_cb: creating server stream +[2020/07/27 10:51:05:7851] N: Vhost 'myserver' using TLS mode +[2020/07/27 10:51:05:8660] N: SSL ECDH curve 'prime256v1' +[2020/07/27 10:51:06:1035] N: vhost myserver: cert expiry: 729599d +[2020/07/27 10:51:06:1039] N: lws_ss_create: created server myserver +[2020/07/27 10:51:11:8650] N: lws_adopt_descriptor_vhost2: wsi 0x5b046e0, vhost myserver ss_handle 0x56e2be0 +[2020/07/27 10:51:11:8672] U: myss_srv_state: 0x5b52f60 LWSSSCS_CREATING, ord 0x0 +[2020/07/27 10:51:11:8693] U: myss_srv_state: 0x5b52f60 LWSSSCS_CONNECTING, ord 0x0 +[2020/07/27 10:51:11:8696] U: myss_srv_state: 0x5b52f60 LWSSSCS_CONNECTED, ord 0x0 +[2020/07/27 10:51:11:9743] U: myss_srv_state: 0x5ba2bd0 LWSSSCS_CREATING, ord 0x0 +[2020/07/27 10:51:11:9747] U: myss_srv_state: 0x5ba2bd0 LWSSSCS_CONNECTING, ord 0x0 +[2020/07/27 10:51:11:9747] U: myss_srv_state: 0x5ba2bd0 LWSSSCS_CONNECTED, ord 0x0 +[2020/07/27 10:51:12:0192] U: myss_srv_state: 0x5bad0a0 LWSSSCS_CREATING, ord 0x0 +[2020/07/27 10:51:12:0193] U: myss_srv_state: 0x5bad0a0 LWSSSCS_CONNECTING, ord 0x0 +[2020/07/27 10:51:12:0194] U: myss_srv_state: 0x5bad0a0 LWSSSCS_CONNECTED, ord 0x0 +[2020/07/27 10:51:12:0306] N: secstream_h1: LWS_CALLBACK_HTTP +[2020/07/27 10:51:12:0329] U: myss_srv_state: 0x5bad0a0 LWSSSCS_SERVER_TXN, ord 0x0 +[2020/07/27 10:51:12:0481] N: lws_h2_ws_handshake: Server SS 0x5ba2bd0 .wsi 0x5ba27b0 switching to ws protocol +[2020/07/27 10:51:12:0484] U: myss_srv_state: 0x5ba2bd0 LWSSSCS_SERVER_UPGRADE, ord 0x0 +[2020/07/27 10:51:12:0541] U: myss_srv_state: 0x5ba2bd0 LWSSSCS_CONNECTED, ord 0x0 +[2020/07/27 10:51:12:1222] U: myss_srv_state: 0x5bd1100 LWSSSCS_CREATING, ord 0x0 +[2020/07/27 10:51:12:1222] U: myss_srv_state: 0x5bd1100 LWSSSCS_CONNECTING, ord 0x0 +[2020/07/27 10:51:12:1223] U: myss_srv_state: 0x5bd1100 LWSSSCS_CONNECTED, ord 0x0 +[2020/07/27 10:51:12:1242] N: lws_h2_ws_handshake: Server SS 0x5bd1100 .wsi 0x5bd0ce0 switching to ws protocol +[2020/07/27 10:51:12:1243] U: myss_srv_state: 0x5bd1100 LWSSSCS_SERVER_UPGRADE, ord 0x0 +[2020/07/27 10:51:12:1246] U: myss_srv_state: 0x5bd1100 LWSSSCS_CONNECTED, ord 0x0 +^C[2020/07/27 10:51:15:2809] U: myss_srv_state: 0x5bad0a0 LWSSSCS_DISCONNECTED, ord 0x0 +[2020/07/27 10:51:15:2838] U: myss_srv_state: 0x5bad0a0 LWSSSCS_DESTROYING, ord 0x0 +[2020/07/27 10:51:15:2938] U: myss_srv_state: 0x5ba2bd0 LWSSSCS_DISCONNECTED, ord 0x0 +[2020/07/27 10:51:15:2946] U: myss_srv_state: 0x5ba2bd0 LWSSSCS_DESTROYING, ord 0x0 +[2020/07/27 10:51:15:2952] U: myss_srv_state: 0x5bd1100 LWSSSCS_DISCONNECTED, ord 0x0 +[2020/07/27 10:51:15:2953] U: myss_srv_state: 0x5bd1100 LWSSSCS_DESTROYING, ord 0x0 +[2020/07/27 10:51:15:2960] U: myss_srv_state: 0x5b52f60 LWSSSCS_DISCONNECTED, ord 0x0 +[2020/07/27 10:51:15:2961] U: myss_srv_state: 0x5b52f60 LWSSSCS_DESTROYING, ord 0x0 +[2020/07/27 10:51:15:3042] U: myss_srv_state: 0x56e2be0 LWSSSCS_DESTROYING, ord 0x0 +[2020/07/27 10:51:15:3378] U: Completed: OK +``` diff --git a/minimal-examples/secure-streams/minimal-secure-streams-server/main.c b/minimal-examples/secure-streams/minimal-secure-streams-server/main.c new file mode 100644 index 000000000..78363e7b6 --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-server/main.c @@ -0,0 +1,332 @@ +/* + * lws-minimal-secure-streams-server + * + * 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 + +extern const lws_ss_info_t ssi_client, ssi_server; + +static struct lws_context *context; +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" + "\"}," + /* + * a selfsigned cert for localhost for 100 years + */ + "{\"self_localhost\": \"" + "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" + "e2Hl/7M0c/lbZ4FvXgARlex2rkgS0Ka06HE=" + "\"}," + /* + * the private key for above + */ + "{\"self_localhost_key\": \"" + "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" + "fBrpEY1IATtPq1taBZZogRqI3rOkkPk=" + "\"}" + "]," + "\"trust_stores\": [" /* named cert chains */ + "{" + "\"name\": \"le_via_isrg\"," + "\"stack\": [" + "\"isrg_root_x1\"," + "\"LEX3_isrg_root_x1\"" + "]" + "}" + "]," + "\"s\": [" + /* + * Client streamtypes + */ + + "{\"mintest\": {" + "\"endpoint\":" "\"warmcat.com\"," + "\"port\":" "443," + "\"protocol\":" "\"h2\"," + "\"http_method\":" "\"GET\"," + "\"http_url\":" "\"index.html\"," + "\"tls\":" "true," + "\"retry\":" "\"default\"," + "\"tls_trust_store\":" "\"le_via_isrg\"" + "}}," + + /* + * This streamtype represents an h2 server listening on :7681, + * using a 100-y self-signed tls cert + */ + + "{\"myserver\": {" + /* if given, "endpoint" is network if to bind to */ + "\"server\":" "true," + "\"port\":" "7681," + "\"protocol\":" "\"h1\"," + "\"metadata\": [{" + "\"mime\": \"Content-Type:\"," + "\"method\": \"\"," + "\"path\": \"\"" + "}]," + "\"tls\":" "true," + /* + * A ws server is an http server, if you give a + * ws_subprotocol here it's understood we also serve + * that ove ws or wss according to tls + */ + "\"ws_subprotocol\":" "\"mywsprotocol\"," + "\"server_cert\":" "\"self_localhost\"," + "\"server_key\":" "\"self_localhost_key\"" + "}}," + + "]" + "}" +; + +static int +smd_cb(void *opaque, lws_smd_class_t c, lws_usec_t ts, void *buf, size_t len) +{ + if ((c & LWSSMDCL_SYSTEM_STATE) && + !lws_json_simple_strcmp(buf, len, "\"state\":", "OPERATIONAL")) { + + /* create the secure streams */ + + lwsl_notice("%s: creating server stream\n", __func__); + + if (lws_ss_create(context, 0, &ssi_server, NULL, NULL, + NULL, NULL)) { + lwsl_err("%s: failed to create secure stream\n", + __func__); + return -1; + } +#if 0 + lwsl_notice("%s: creating client stream\n", __func__); + + if (lws_ss_create(context, 0, &ssi_client, NULL, NULL, + NULL, NULL)) { + lwsl_err("%s: failed to create secure stream\n", + __func__); + return -1; + } +#endif + } + + return 0; +} + +static void +sigint_handler(int sig) +{ + interrupted = 1; +} + +int main(int argc, const char **argv) +{ + struct lws_context_creation_info info; + 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 Server\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; + info.early_smd_cb = smd_cb; + info.early_smd_class_filter = LWSSMDCL_SYSTEM_STATE; + + 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-server/ss-client.c b/minimal-examples/secure-streams/minimal-secure-streams-server/ss-client.c new file mode 100644 index 000000000..f1d262442 --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-server/ss-client.c @@ -0,0 +1,86 @@ +/* + * lws-minimal-secure-streams-server + * + * Written in 2010-2020 by Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + */ + +#include + +extern int interrupted, bad; + +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 LWSSSSRET_TX_DONT_SEND; /* don't want to write */ +} + +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: %p %s, ord 0x%x\n", __func__, m->ss, + 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; +} + +const lws_ss_info_t ssi_client = { + .handle_offset = offsetof(myss_t, ss), + .opaque_user_data_offset = offsetof(myss_t, opaque_data), + .streamtype = "mintest", + .rx = myss_rx, + .tx = myss_tx, + .state = myss_state, + .user_alloc = sizeof(myss_t), +}; diff --git a/minimal-examples/secure-streams/minimal-secure-streams-server/ss-server.c b/minimal-examples/secure-streams/minimal-secure-streams-server/ss-server.c new file mode 100644 index 000000000..716f3e101 --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-server/ss-server.c @@ -0,0 +1,208 @@ +/* + * lws-minimal-secure-streams-server + * + * 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 + +extern int interrupted, bad; + +static const char *html = + "" + "Hello from the web server
" + "
" + ""; + + +typedef struct myss { + struct lws_ss_handle *ss; + void *opaque_data; + /* ... application specific state ... */ + + lws_sorted_usec_list_t sul; + int count; + char upgraded; + +} myss_srv_t; + +/* + * This is the Secure Streams Server RX and TX for HTTP(S) + */ + +static int +myss_srv_rx(void *userobj, const uint8_t *buf, size_t len, int flags) +{ +// myss_srv_t *m = (myss_srv_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_srv_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len, + int *flags) +{ + myss_srv_t *m = (myss_srv_t *)userobj; + + if (m->upgraded) + return LWSSSSRET_TX_DONT_SEND; + + *flags = LWSSS_FLAG_SOM | LWSSS_FLAG_EOM; + + lws_strncpy((char *)buf, html, *len); + *len = strlen(html); + + return 0; +} + +/* + * This is the Secure Streams Server RX and TX for WS(S)... when we get a + * state that the underlying connection upgraded protocol, we switch the stream + * rx and tx handlers to here. + */ + +static int +myss_ws_rx(void *userobj, const uint8_t *buf, size_t len, int flags) +{ +// myss_srv_t *m = (myss_srv_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; +} + +/* this is the callback that mediates sending the incrementing number */ + +static void +spam_sul_cb(struct lws_sorted_usec_list *sul) +{ + myss_srv_t *m = lws_container_of(sul, myss_srv_t, sul); + + lws_ss_request_tx(m->ss); + + lws_sul_schedule(lws_ss_get_context(m->ss), 0, &m->sul, spam_sul_cb, + 100 * LWS_US_PER_MS); +} + +static int +myss_ws_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len, + int *flags) +{ + myss_srv_t *m = (myss_srv_t *)userobj; + + *flags = LWSSS_FLAG_SOM | LWSSS_FLAG_EOM; + + *len = lws_snprintf((char *)buf, *len, "hello from ws %d", m->count++); + + lws_sul_schedule(lws_ss_get_context(m->ss), 0, &m->sul, spam_sul_cb, + 100 * LWS_US_PER_MS); + + return 0; +} + +static int +myss_srv_state(void *userobj, void *sh, lws_ss_constate_t state, + lws_ss_tx_ordinal_t ack) +{ + myss_srv_t *m = (myss_srv_t *)userobj; + + lwsl_user("%s: %p %s, ord 0x%x\n", __func__, m->ss, + lws_ss_state_name(state), (unsigned int)ack); + + switch (state) { + case LWSSSCS_DISCONNECTED: + lws_sul_cancel(&m->sul); + break; + 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; + + case LWSSSCS_SERVER_TXN: + /* + * The underlying protocol started a transaction, let's + * describe how we want to complete it. We can defer this until + * later, eg, after we have consumed any rx that's coming with + * the client's transaction initiation phase, but in this + * example we know what we want to do already. + * + * We do want to ack the transaction... + */ + lws_ss_server_ack(m->ss, 0); + /* + * ... it's going to be text/html... + */ + lws_ss_set_metadata(m->ss, "mime", "text/html", 9); + /* + * ...it's going to be 128 byte (and request tx) + */ + lws_ss_request_tx_len(m->ss, strlen(html)); + break; + + case LWSSSCS_SERVER_UPGRADE: + + /* + * This is sent when the underlying protocol has experienced + * an upgrade, eg, http->ws... it's a one-way upgrade on this + * stream, change the handlers to deal with the kind of + * messages we send on ws + */ + + m->upgraded = 1; + lws_ss_change_handlers(m->ss, myss_ws_rx, myss_ws_tx, NULL); + lws_ss_request_tx(m->ss); /* we want to start sending numbers */ + break; + default: + break; + } + + return 0; +} + +const lws_ss_info_t ssi_server = { + .handle_offset = offsetof(myss_srv_t, ss), + .opaque_user_data_offset = offsetof(myss_srv_t, opaque_data), + .streamtype = "myserver", + .rx = myss_srv_rx, + .tx = myss_srv_tx, + .state = myss_srv_state, + .user_alloc = sizeof(myss_srv_t), +}; diff --git a/minimal-examples/secure-streams/minimal-secure-streams-sink/README.md b/minimal-examples/secure-streams/minimal-secure-streams-sink/README.md deleted file mode 100644 index 80a1c6928..000000000 --- a/minimal-examples/secure-streams/minimal-secure-streams-sink/README.md +++ /dev/null @@ -1,64 +0,0 @@ -# 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 deleted file mode 100644 index 792cad19a..000000000 --- a/minimal-examples/secure-streams/minimal-secure-streams-sink/main.c +++ /dev/null @@ -1,288 +0,0 @@ -/* - * 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; -}