1/*
2 * lws-minimal-secure-streams-seq
3 *
4 * Written in 2010-2020 by Andy Green <andy@warmcat.com>
5 *
6 * This file is made available under the Creative Commons CC0 1.0
7 * Universal Public Domain Dedication.
8 *
9 *
10 * This demonstrates the a minimal http client using secure streams api.
11 *
12 * It visits https://warmcat.com/ and receives the html page there.
13 *
14 * This is the "secure streams" api equivalent of minimal-http-client...
15 * it shows how to use a sequencer to make it easy to build more complex
16 * schemes on top of this example.
17 *
18 * The layering looks like this
19 *
20 *                        lifetime
21 *
22 * ------   app   ------  process
23 * ----  sequencer  ----  process
24 * --- secure stream ---  process
25 * -------  wsi  -------  connection
26 *
27 * see minimal-secure-streams for a similar example without the sequencer.
28 */
29
30#include <libwebsockets.h>
31#include <string.h>
32#include <signal.h>
33
34static int interrupted, bad = 1, flag_conn_fail, flag_h1post;
35static const char * const default_ss_policy =
36	"{"
37	  "\"release\":"			"\"01234567\","
38	  "\"product\":"			"\"myproduct\","
39	  "\"schema-version\":"			"1,"
40	  "\"retry\": ["	/* named backoff / retry strategies */
41		"{\"default\": {"
42			"\"backoff\": ["	 "1000,"
43						 "2000,"
44						 "3000,"
45						 "5000,"
46						"10000"
47				"],"
48			"\"conceal\":"		"5,"
49			"\"jitterpc\":"		"20,"
50			"\"svalidping\":"	"300,"
51			"\"svalidhup\":"	"310"
52		"}}"
53	  "],"
54	  "\"certs\": [" /* named individual certificates in BASE64 DER */
55		/*
56		 * Need to be in order from root cert... notice sometimes as
57		 * with Let's Encrypt there are multiple possible validation
58		 * paths, all the pieces for one validation path must be
59		 * given, excluding the server cert itself.  Let's Encrypt
60		 * intermediate is signed by their ISRG Root CA but also is
61		 * cross-signed by an IdenTrust intermediate that's widely
62		 * deployed in browsers.  We use the ISRG path because that
63		 * way we can skip the extra IdenTrust root cert.
64		 */
65		"{\"isrg_root_x1\": \""
66	"MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw"
67	"TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh"
68	"cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4"
69	"WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu"
70	"ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY"
71	"MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc"
72	"h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+"
73	"0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U"
74	"A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW"
75	"T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH"
76	"B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC"
77	"B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv"
78	"KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn"
79	"OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn"
80	"jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw"
81	"qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI"
82	"rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV"
83	"HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq"
84	"hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL"
85	"ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ"
86	"3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK"
87	"NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5"
88	"ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur"
89	"TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC"
90	"jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc"
91	"oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq"
92	"4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA"
93	"mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d"
94	"emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc="
95		  "\"}"
96	  "],"
97	  "\"trust_stores\": [" /* named cert chains */
98		"{"
99			"\"name\": \"le_via_isrg\","
100			"\"stack\": ["
101				"\"isrg_root_x1\""
102			"]"
103		"}"
104	  "],"
105	  "\"s\": [" /* the supported stream types */
106		"{\"mintest\": {"
107			"\"endpoint\":"		"\"warmcat.com\","
108			"\"port\":"		"443,"
109			"\"protocol\":"		"\"h1\","
110			"\"http_method\":"	"\"GET\","
111			"\"http_url\":"		"\"index.html\","
112			"\"plugins\":"		"[],"
113			"\"tls\":"		"true,"
114			"\"opportunistic\":"	"true,"
115			"\"retry\":"		"\"default\","
116			"\"tls_trust_store\":"	"\"le_via_isrg\""
117		"}},"
118		"{\"mintest-fail\": {"
119			"\"endpoint\":"		"\"warmcat.com\","
120			"\"port\":"		"22,"
121			"\"protocol\":"		"\"h1\","
122			"\"http_method\":"	"\"GET\","
123			"\"http_url\":"		"\"index.html\","
124			"\"plugins\":"		"[],"
125			"\"tls\":"		"true,"
126			"\"opportunistic\":"	"true,"
127			"\"retry\":"		"\"default\","
128			"\"tls_trust_store\":"	"\"le_via_isrg\""
129		"}},"
130		"{\"minpost\": {"
131			"\"endpoint\":"		"\"warmcat.com\","
132			"\"port\":"		"443,"
133			"\"protocol\":"		"\"h1\","
134			"\"http_method\":"	"\"POST\","
135			"\"http_url\":"		"\"testserver/formtest\","
136			"\"plugins\":"		"[],"
137			"\"tls\":"		"true,"
138			"\"opportunistic\":"	"true,"
139			"\"retry\":"		"\"default\","
140			"\"tls_trust_store\":"	"\"le_via_isrg\""
141		"}}"
142	  "]"
143	"}"
144;
145
146typedef struct myss {
147	struct lws_ss_handle 	*ss;
148	void			*opaque_data;
149	/* ... application specific state ... */
150} myss_t;
151
152/* secure streams payload interface */
153
154static lws_ss_state_return_t
155myss_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
156{
157//	myss_t *m = (myss_t *)userobj;
158
159	lwsl_user("%s: len %d, flags: %d\n", __func__, (int)len, flags);
160	lwsl_hexdump_info(buf, len);
161
162	/*
163	 * If we received the whole message, we let the sequencer know it
164	 * was a success
165	 */
166	if (flags & LWSSS_FLAG_EOM) {
167		bad = 0;
168		interrupted = 1;
169	}
170
171	return 0;
172}
173
174static lws_ss_state_return_t
175myss_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len,
176	int *flags)
177{
178	// myss_t *m = (myss_t *)userobj;
179
180	/* in this example, we don't send any payload */
181
182	return 0;
183}
184
185static lws_ss_state_return_t
186myss_state(void *userobj, void *sh, lws_ss_constate_t state,
187		lws_ss_tx_ordinal_t ack)
188{
189	myss_t *m = (myss_t *)userobj;
190
191	lwsl_user("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name(state),
192		  (unsigned int)ack);
193
194	switch (state) {
195	case LWSSSCS_CREATING:
196		return lws_ss_request_tx(m->ss);
197
198	case LWSSSCS_ALL_RETRIES_FAILED:
199		/* if we're out of retries, we want to close the app and FAIL */
200		interrupted = 1;
201		break;
202	default:
203		break;
204	}
205
206	return 0;
207}
208
209typedef enum {
210	SEQ_IDLE,
211	SEQ_TRY_CONNECT,
212	SEQ_RECONNECT_WAIT,
213	SEQ_CONNECTED,
214} myseq_state_t;
215
216typedef struct myseq {
217	struct lws_ss_handle	*ss;
218
219	myseq_state_t		state;
220	int			http_resp;
221
222	uint16_t		try;
223} myseq_t;
224
225/*
226 * This defines the sequence of things the test app does.
227 */
228
229static lws_seq_cb_return_t
230min_sec_str_sequencer_cb(struct lws_sequencer *seq, void *user, int event,
231			 void *v, void *a)
232{
233	struct myseq *s = (struct myseq *)user;
234	lws_ss_info_t ssi;
235
236	switch ((int)event) {
237
238	/* these messages are created just by virtue of being a sequencer */
239
240	case LWSSEQ_CREATED: /* our sequencer just got started */
241		s->state = SEQ_IDLE;
242		lwsl_notice("%s: LWSSEQ_CREATED\n", __func__);
243
244		/* We're making an outgoing secure stream ourselves */
245
246		memset(&ssi, 0, sizeof(ssi));
247		ssi.handle_offset = offsetof(myss_t, ss);
248		ssi.opaque_user_data_offset = offsetof(myss_t, opaque_data);
249		ssi.rx = myss_rx;
250		ssi.tx = myss_tx;
251		ssi.state = myss_state;
252		ssi.user_alloc = sizeof(myss_t);
253
254		/* requested to fail (to check backoff)? */
255		if (flag_conn_fail)
256			ssi.streamtype = "mintest-fail";
257		else
258			/* request to check h1 POST */
259			if (flag_h1post)
260				ssi.streamtype = "minpost";
261			else
262				/* default to h1 GET */
263				ssi.streamtype = "mintest";
264
265		if (lws_ss_create(lws_seq_get_context(seq), 0, &ssi, NULL,
266				  &s->ss, seq, NULL)) {
267			lwsl_err("%s: failed to create secure stream\n",
268				 __func__);
269
270			return LWSSEQ_RET_DESTROY;
271		}
272		break;
273
274	case LWSSEQ_DESTROYED:
275		lwsl_notice("%s: LWSSEQ_DESTROYED\n", __func__);
276		break;
277
278	case LWSSEQ_TIMED_OUT: /* current step timed out */
279		if (s->state == SEQ_RECONNECT_WAIT)
280			return lws_ss_request_tx(s->ss);
281		break;
282
283	/*
284	 * These messages are created because we have a secure stream that was
285	 * bound to this sequencer at creation time.  It copies its state
286	 * events to us as its sequencer parent.  v is the lws_ss_handle_t *
287	 */
288
289	case LWSSEQ_SS_STATE_BASE + LWSSSCS_CREATING:
290		lwsl_info("%s: seq LWSSSCS_CREATING\n", __func__);
291		return lws_ss_request_tx(s->ss);
292
293	case LWSSEQ_SS_STATE_BASE + LWSSSCS_DISCONNECTED:
294		lwsl_info("%s: seq LWSSSCS_DISCONNECTED\n", __func__);
295		break;
296	case LWSSEQ_SS_STATE_BASE + LWSSSCS_UNREACHABLE:
297		lwsl_info("%s: seq LWSSSCS_UNREACHABLE\n", __func__);
298		break;
299	case LWSSEQ_SS_STATE_BASE + LWSSSCS_AUTH_FAILED:
300		lwsl_info("%s: seq LWSSSCS_AUTH_FAILED\n", __func__);
301		break;
302	case LWSSEQ_SS_STATE_BASE + LWSSSCS_CONNECTED:
303		lwsl_info("%s: seq LWSSSCS_CONNECTED\n", __func__);
304		break;
305	case LWSSEQ_SS_STATE_BASE + LWSSSCS_CONNECTING:
306		lwsl_info("%s: seq LWSSSCS_CONNECTING\n", __func__);
307		break;
308	case LWSSEQ_SS_STATE_BASE + LWSSSCS_DESTROYING:
309		lwsl_info("%s: seq LWSSSCS_DESTROYING\n", __func__);
310		break;
311	case LWSSEQ_SS_STATE_BASE + LWSSSCS_POLL:
312		/* somebody called lws_ss_poll() on the stream */
313		lwsl_info("%s: seq LWSSSCS_POLL\n", __func__);
314		break;
315	case LWSSEQ_SS_STATE_BASE + LWSSSCS_ALL_RETRIES_FAILED:
316		lwsl_info("%s: seq LWSSSCS_ALL_RETRIES_FAILED\n", __func__);
317		interrupted = 1;
318		break;
319	case LWSSEQ_SS_STATE_BASE + LWSSSCS_QOS_ACK_REMOTE:
320		lwsl_info("%s: seq LWSSSCS_QOS_ACK_REMOTE\n", __func__);
321		break;
322	case LWSSEQ_SS_STATE_BASE + LWSSSCS_QOS_ACK_LOCAL:
323		lwsl_info("%s: seq LWSSSCS_QOS_ACK_LOCAL\n", __func__);
324		break;
325
326	/*
327	 * This is the message we send from the ss handler to inform the
328	 * sequencer we had the payload properly
329	 */
330
331	case LWSSEQ_USER_BASE:
332		bad = 0;
333		interrupted = 1;
334		break;
335
336	default:
337		break;
338	}
339
340	return LWSSEQ_RET_CONTINUE;
341}
342
343static void
344sigint_handler(int sig)
345{
346	interrupted = 1;
347}
348
349int main(int argc, const char **argv)
350{
351	int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
352	struct lws_context_creation_info info;
353	struct lws_context *context;
354	lws_seq_info_t i;
355	const char *p;
356	myseq_t *ms;
357
358	signal(SIGINT, sigint_handler);
359
360	if ((p = lws_cmdline_option(argc, argv, "-d")))
361		logs = atoi(p);
362
363	lws_set_log_level(logs, NULL);
364	lwsl_user("LWS minimal secure streams [-d<verbosity>][-f][--h1post]\n");
365
366	flag_conn_fail = !!lws_cmdline_option(argc, argv, "-f");
367	flag_h1post = !!lws_cmdline_option(argc, argv, "--h1post");
368
369	memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
370
371	info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
372		       LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
373	info.fd_limit_per_thread = 1 + 1 + 1;
374	info.pss_policies_json = default_ss_policy;
375	info.port = CONTEXT_PORT_NO_LISTEN;
376
377	context = lws_create_context(&info);
378	if (!context) {
379		lwsl_err("lws init failed\n");
380		return 1;
381	}
382
383	/*
384	 * Create the sequencer that performs the steps of the test action
385	 * from inside the event loop.
386	 */
387
388	memset(&i, 0, sizeof(i));
389	i.context	= context;
390	i.user_size	= sizeof(myseq_t);
391	i.puser		= (void **)&ms;
392	i.cb		= min_sec_str_sequencer_cb;
393	i.name		= "min-sec-stream-seq";
394
395	if (!lws_seq_create(&i)) {
396		lwsl_err("%s: failed to create sequencer\n", __func__);
397		goto bail;
398	}
399
400	/* the event loop */
401
402	while (n >= 0 && !interrupted)
403		n = lws_service(context, 0);
404
405bail:
406	lws_context_destroy(context);
407	lwsl_user("Completed: %s\n", bad ? "failed" : "OK");
408
409	return bad;
410}
411