1/* 2 * lws-api-test-secure-streams 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 * Let's exercise some basic SS / h1 functionality against httpbin.org 10 */ 11 12#include <libwebsockets.h> 13#include <string.h> 14#include <signal.h> 15 16static int interrupted, bad = 1; 17static lws_state_notify_link_t nl; 18static struct lws_context *context; 19 20static const char * const default_ss_policy = 21 "{" 22 "\"release\":" "\"01234567\"," 23 "\"product\":" "\"myproduct\"," 24 "\"schema-version\":" "1," 25#if defined(VIA_LOCALHOST_SOCKS) 26 "\"via-socks5\":" "\"127.0.0.1:1080\"," 27#endif 28 29 "\"retry\": [" /* named backoff / retry strategies */ 30 "{\"default\": {" 31 "\"backoff\": [" "1000," 32 "2000," 33 "3000," 34 "5000," 35 "10000" 36 "]," 37 "\"conceal\":" "5," 38 "\"jitterpc\":" "20," 39 "\"svalidping\":" "30," 40 "\"svalidhup\":" "35" 41 "}}" 42 "]," 43 "\"certs\": [" /* named individual certificates in BASE64 DER */ 44 /* 45 * Let's Encrypt certs for warmcat.com / libwebsockets.org 46 * 47 * We fetch the real policy from there using SS and switch to 48 * using that. 49 */ 50 51 "{\"amz_root_ca1\": \"" 52 "MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF" 53 "ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6" 54 "b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL" 55 "MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv" 56 "b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj" 57 "ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM" 58 "9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw" 59 "IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6" 60 "VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L" 61 "93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm" 62 "jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC" 63 "AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA" 64 "A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI" 65 "U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs" 66 "N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv" 67 "o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU" 68 "5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy" 69 "rqXRfboQnoZsG4q5WTP468SQvvG5" 70 "\"}" 71 "]," 72 "\"trust_stores\": [" /* named cert chains */ 73 "{" 74 "\"name\": \"amz\"," 75 "\"stack\": [" 76 "\"amz_root_ca1\"" 77 "]" 78 "}" 79 "]," 80 "\"s\": [" 81 /* 82 * "fetch_policy" decides from where the real policy 83 * will be fetched, if present. Otherwise the initial 84 * policy is treated as the whole, hardcoded, policy. 85 */ 86 "{\"httpbin_get\": {" 87 "\"endpoint\":" "\"httpbin.org\"," 88 "\"port\":" "443," 89 "\"protocol\":" "\"h1\"," 90 "\"http_method\":" "\"GET\"," 91 "\"http_url\":" "\"/get\"," 92 "\"tls\":" "true," 93 "\"opportunistic\":" "true," 94 "\"retry\":" "\"default\"," 95 "\"tls_trust_store\":" "\"amz\"" 96 "}}," 97 "{\"httpbin_get404\": {" 98 "\"endpoint\":" "\"httpbin.org\"," 99 "\"port\":" "443," 100 "\"protocol\":" "\"h1\"," 101 "\"http_method\":" "\"GET\"," 102 "\"http_url\":" "\"/status/404\"," 103 "\"tls\":" "true," 104 "\"opportunistic\":" "true," 105 "\"retry\":" "\"default\"," 106 "\"tls_trust_store\":" "\"amz\"" 107 "}}," 108 "{\"httpbin_post\": {" 109 "\"endpoint\":" "\"httpbin.org\"," 110 "\"port\":" "443," 111 "\"protocol\":" "\"h1\"," 112 "\"http_method\":" "\"POST\"," 113 "\"http_url\":" "\"/post\"," 114 "\"tls\":" "true," 115 "\"opportunistic\":" "true," 116 "\"retry\":" "\"default\"," 117 "\"tls_trust_store\":" "\"amz\"" 118 "}}" 119 "}" 120 "]}" 121; 122 123typedef struct atss { 124 const lws_ss_info_t *ssi; 125 size_t send; 126 char expect_nack; 127} atss_t; 128 129static const atss_t *next_test; 130 131typedef struct myss { 132 struct lws_ss_handle *ss; 133 void *opaque_data; 134 /* ... application specific state ... */ 135 lws_sorted_usec_list_t sul; 136 size_t payload; 137 size_t sent; 138 char seen_eom; 139 char ended_well; 140} myss_t; 141 142/* secure streams payload interface */ 143 144static lws_ss_state_return_t 145myss_rx(void *userobj, const uint8_t *buf, size_t len, int flags) 146{ 147 myss_t *m = (myss_t *)userobj; 148 149 lwsl_hexdump_info(buf, len); 150 151 m->payload += len; 152 153 if (!(flags & LWSSS_FLAG_EOM)) 154 m->seen_eom = 1; 155 156 return 0; 157} 158 159static lws_ss_state_return_t 160myss_tx_get(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len, 161 int *flags) 162{ 163 return 1; /* nothing to send */ 164} 165 166static lws_ss_state_return_t 167myss_tx_post(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len, 168 int *flags) 169{ 170 myss_t *m = (myss_t *)userobj; 171 size_t budget = (next_test->send - m->sent); 172 173 if (!budget) 174 return 1; 175 176 if (*len < budget) 177 budget = *len; 178 179 if (!m->sent) 180 *flags |= LWSSS_FLAG_SOM; 181 182 memset(buf, 0x55, budget); 183 *len = budget; 184 m->sent += budget; 185 if (m->sent != next_test->send) 186 return lws_ss_request_tx(m->ss); 187 188 *flags |= LWSSS_FLAG_EOM; 189 190 return LWSSSSRET_OK; 191} 192 193static lws_ss_state_return_t 194myss_state(void *userobj, void *sh, lws_ss_constate_t state, 195 lws_ss_tx_ordinal_t ack) 196{ 197 myss_t *m = (myss_t *)userobj; 198 lws_ss_state_return_t r; 199 200 lwsl_notice("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name((int)state), 201 (unsigned int)ack); 202 203 switch (state) { 204 case LWSSSCS_CREATING: 205 r = lws_ss_client_connect(m->ss); 206 if (r) 207 return r; 208 if (next_test->send) 209 return lws_ss_request_tx_len(m->ss, (unsigned long)next_test->send); 210 break; 211 case LWSSSCS_ALL_RETRIES_FAILED: 212 lwsl_notice("%s: Connection failed\n", __func__); 213 interrupted = 1; 214 break; 215 case LWSSSCS_QOS_NACK_REMOTE: 216 if (next_test->expect_nack) 217 goto happy; 218 lwsl_notice("%s: remote NACK\n", __func__); 219 interrupted = 1; 220 break; 221 case LWSSSCS_QOS_ACK_REMOTE: 222 /* 223 * To be satisfied, we want to see the ACK_REMOTE indicating 224 * that the transaction went through; that we had the payload 225 * EOM; and that we saw at least 200 + posted bytes response 226 */ 227 228 if (!m->seen_eom || m->payload < 200 + next_test->send) { 229 lwsl_warn("%s: ACK_REMOTE but eom %d, payload %d\n", 230 __func__, m->seen_eom, (int)m->payload); 231 interrupted = 1; 232 return -1; 233 } 234 235happy: 236 /* when we disconnect, we can go happily */ 237 m->ended_well = 1; 238 239 if (!(++next_test)->ssi) { 240 lwsl_notice("%s: completed all tests\n", __func__); 241 bad = 0; 242 interrupted = 1; 243 break; 244 } 245 if (lws_ss_create(context, 0, next_test->ssi, 246 NULL, NULL, NULL, NULL)) { 247 lwsl_err("%s: failed to create secure stream\n", 248 __func__); 249 return -1; 250 } 251 break; 252 253 case LWSSSCS_DISCONNECTED: 254 if (!m->ended_well) { 255 lwsl_warn("%s: DISCONNECTED without good end\n", 256 __func__); 257 interrupted = 1; 258 } 259 break; 260 default: 261 break; 262 } 263 264 return LWSSSSRET_OK; 265} 266 267static const lws_ss_info_t ssi_get = { 268 .handle_offset = offsetof(myss_t, ss), 269 .opaque_user_data_offset = offsetof(myss_t, opaque_data), 270 .rx = myss_rx, 271 .tx = myss_tx_get, 272 .state = myss_state, 273 .user_alloc = sizeof(myss_t), 274 .streamtype = "httpbin_get" 275}, ssi_get404 = { 276 .handle_offset = offsetof(myss_t, ss), 277 .opaque_user_data_offset = offsetof(myss_t, opaque_data), 278 .rx = myss_rx, 279 .tx = myss_tx_get, 280 .state = myss_state, 281 .user_alloc = sizeof(myss_t), 282 .streamtype = "httpbin_get404" 283}, ssi_post = { 284 .handle_offset = offsetof(myss_t, ss), 285 .opaque_user_data_offset = offsetof(myss_t, opaque_data), 286 .rx = myss_rx, 287 .tx = myss_tx_post, 288 .state = myss_state, 289 .user_alloc = sizeof(myss_t), 290 .streamtype = "httpbin_post" 291}; 292 293static const atss_t test_list[] = { 294 { .ssi = &ssi_get }, 295 { .ssi = &ssi_get404, .expect_nack = 1 }, 296 { .ssi = &ssi_post, .send = 4096 }, 297 { .ssi = NULL } 298}; 299 300 301static int 302app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link, 303 int current, int target) 304{ 305 struct lws_context *context = lws_system_context_from_system_mgr(mgr); 306 307 /* 308 * For the things we care about, let's notice if we are trying to get 309 * past them when we haven't solved them yet, and make the system 310 * state wait while we trigger the dependent action. 311 */ 312 switch (target) { 313 314 case LWS_SYSTATE_OPERATIONAL: 315 if (current == LWS_SYSTATE_OPERATIONAL) { 316 317 next_test = &test_list[0]; 318 319 if (lws_ss_create(context, 0, next_test->ssi, 320 NULL, NULL, NULL, NULL)) { 321 lwsl_err("%s: failed to create secure stream\n", 322 __func__); 323 return -1; 324 } 325 } 326 break; 327 } 328 329 return 0; 330} 331 332static lws_state_notify_link_t * const app_notifier_list[] = { 333 &nl, NULL 334}; 335 336static void 337sigint_handler(int sig) 338{ 339 interrupted = 1; 340} 341 342int main(int argc, const char **argv) 343{ 344 struct lws_context_creation_info info; 345 int n = 0; 346 347 signal(SIGINT, sigint_handler); 348 349 memset(&info, 0, sizeof info); 350 lws_cmdline_option_handle_builtin(argc, argv, &info); 351 352 lwsl_user("LWS secure streams test client [-d<verb>]\n"); 353 354 /* these options are mutually exclusive if given */ 355 356 info.fd_limit_per_thread = 1 + 6 + 1; 357 info.port = CONTEXT_PORT_NO_LISTEN; 358 info.pss_policies_json = default_ss_policy; 359 info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS | 360 LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | 361 LWS_SERVER_OPTION_H2_JUST_FIX_WINDOW_UPDATE_OVERFLOW; 362 363 /* integrate us with lws system state management when context created */ 364 365 nl.name = "app"; 366 nl.notify_cb = app_system_state_nf; 367 info.register_notifier_list = app_notifier_list; 368 369 /* create the context */ 370 371 context = lws_create_context(&info); 372 if (!context) { 373 lwsl_err("lws init failed\n"); 374 return 1; 375 } 376 377 /* the event loop */ 378 379 while (n >= 0 && !interrupted) 380 n = lws_service(context, 0); 381 382 lws_context_destroy(context); 383 384 lwsl_user("Completed: %s\n", bad ? "failed" : "OK"); 385 386 return bad; 387} 388