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