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
34 static int interrupted, bad = 1, flag_conn_fail, flag_h1post;
35 static 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
146 typedef 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
154 static lws_ss_state_return_t
myss_rx(void * userobj,const uint8_t * buf,size_t len,int flags)155 myss_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
174 static lws_ss_state_return_t
myss_tx(void * userobj,lws_ss_tx_ordinal_t ord,uint8_t * buf,size_t * len,int * flags)175 myss_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
185 static lws_ss_state_return_t
myss_state(void * userobj,void * sh,lws_ss_constate_t state,lws_ss_tx_ordinal_t ack)186 myss_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
209 typedef enum {
210 SEQ_IDLE,
211 SEQ_TRY_CONNECT,
212 SEQ_RECONNECT_WAIT,
213 SEQ_CONNECTED,
214 } myseq_state_t;
215
216 typedef 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
229 static lws_seq_cb_return_t
min_sec_str_sequencer_cb(struct lws_sequencer * seq,void * user,int event,void * v,void * a)230 min_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
343 static void
sigint_handler(int sig)344 sigint_handler(int sig)
345 {
346 interrupted = 1;
347 }
348
main(int argc,const char ** argv)349 int 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
405 bail:
406 lws_context_destroy(context);
407 lwsl_user("Completed: %s\n", bad ? "failed" : "OK");
408
409 return bad;
410 }
411