1 /* 2 * Copyright 2020-2025 The OpenSSL Project Authors. All Rights Reserved. 3 * Copyright Siemens AG 2020 4 * 5 * Licensed under the Apache License 2.0 (the "License"). You may not use 6 * this file except in compliance with the License. You can obtain a copy 7 * in the file LICENSE in the source distribution or at 8 * https://www.openssl.org/source/license.html 9 */ 10 11 #include <openssl/http.h> 12 #include <openssl/pem.h> 13 #include <openssl/x509v3.h> 14 #include <string.h> 15 16 #include "testutil.h" 17 18 #define HTTP_STATUS_CODE_OK 200 19 #define HTTP_STATUS_CODES_FATAL_ERROR 399 20 #define HTTP_STATUS_CODES_NONFATAL_ERROR 400 21 22 static const ASN1_ITEM *x509_it = NULL; 23 static X509 *x509 = NULL; 24 #define RPATH "/path" 25 26 typedef struct { 27 BIO *out; 28 const char *content_type; 29 const char *txt; 30 char version; 31 int keep_alive; 32 } server_args; 33 34 /*- 35 * Pretty trivial HTTP mock server: 36 * For POST, copy request headers+body from mem BIO |in| as response to |out|. 37 * For GET, redirect to RPATH unless already there, else use |content_type| and 38 * respond with |txt| if not NULL, else with |rsp| of ASN1 type |it|. 39 * Take the status code suggsted by the client via special prefix of the path. 40 * On fatal status, respond with empty content. 41 * Response hdr has HTTP version 1.|version| and |keep_alive| (unless implicit). 42 */ 43 static int mock_http_server(BIO *in, BIO *out, char version, int keep_alive, 44 const char *content_type, const char *txt, 45 ASN1_VALUE *rsp, const ASN1_ITEM *it) 46 { 47 const char *req, *path; 48 long count = BIO_get_mem_data(in, (unsigned char **)&req); 49 const char *hdr = (char *)req, *suggested_status; 50 char status[4] = "200"; 51 int len; 52 int is_get = count >= 4 && CHECK_AND_SKIP_PREFIX(hdr, "GET "); 53 54 /* first line should contain "(GET|POST) (/<suggested status>)?/<path> HTTP/1.x" */ 55 if (!is_get 56 && !(TEST_true(count >= 5 && CHECK_AND_SKIP_PREFIX(hdr, "POST ")))) 57 return 0; 58 59 /* get any status code string to be returned suggested by test client */ 60 if (*hdr == '/') { 61 suggested_status = ++hdr; 62 while (*hdr >= '0' && *hdr <= '9') 63 hdr++; 64 if (hdr == suggested_status + sizeof(status) - 1) 65 strncpy(status, suggested_status, sizeof(status) - 1); 66 else 67 hdr = suggested_status - 1; 68 } 69 70 path = hdr; 71 hdr = strchr(hdr, ' '); 72 if (hdr == NULL) 73 return 0; 74 len = strlen("HTTP/1."); 75 if (!TEST_strn_eq(++hdr, "HTTP/1.", len)) 76 return 0; 77 hdr += len; 78 /* check for HTTP version 1.0 .. 1.1 */ 79 if (!TEST_char_le('0', *hdr) || !TEST_char_le(*hdr++, '1')) 80 return 0; 81 if (!TEST_char_eq(*hdr++, '\r') || !TEST_char_eq(*hdr++, '\n')) 82 return 0; 83 84 count -= (hdr - req); 85 if (count < 0 || out == NULL) 86 return 0; 87 88 if (!HAS_PREFIX(path, RPATH)) { 89 if (!is_get) 90 return 0; 91 return BIO_printf(out, "HTTP/1.%c 301 Moved Permanently\r\n" 92 "Location: %s\r\n\r\n", 93 version, RPATH) 94 > 0; /* same server */ 95 } 96 if (BIO_printf(out, "HTTP/1.%c %s %s\r\n", version, status, 97 /* mock some reason string: */ 98 strcmp(status, "200") == 0 ? "OK" : strcmp(status, "400") >= 0 ? "error" 99 : "fatal") 100 <= 0) 101 return 0; 102 if ((version == '0') == keep_alive) /* otherwise, default */ 103 if (BIO_printf(out, "Connection: %s\r\n", 104 version == '0' ? "keep-alive" : "close") 105 <= 0) 106 return 0; 107 108 if (strcmp(status, "399") == 0) /* HTTP_STATUS_CODES_FATAL_ERROR */ 109 return BIO_puts(out, "\r\n") == 2; /* empty content */ 110 111 if (is_get) { /* construct new header and body */ 112 if (txt != NULL) 113 len = strlen(txt); 114 else if ((len = ASN1_item_i2d(rsp, NULL, it)) <= 0) 115 return 0; 116 if (BIO_printf(out, "Content-Type: %s\r\n" 117 "Content-Length: %d\r\n\r\n", 118 content_type, len) 119 <= 0) 120 return 0; 121 if (txt != NULL) 122 return BIO_puts(out, txt); 123 return ASN1_item_i2d_bio(it, out, rsp); 124 } else { /* respond on POST request */ 125 if (CHECK_AND_SKIP_PREFIX(hdr, "Connection: ")) { 126 /* skip req Connection header */ 127 hdr = strstr(hdr, "\r\n"); 128 if (hdr == NULL) 129 return 0; 130 hdr += 2; 131 } 132 /* echo remaining request header and body */ 133 return BIO_write(out, hdr, count) == count; 134 } 135 } 136 137 static long http_bio_cb_ex(BIO *bio, int oper, const char *argp, size_t len, 138 int cmd, long argl, int ret, size_t *processed) 139 { 140 server_args *args = (server_args *)BIO_get_callback_arg(bio); 141 142 if (oper == (BIO_CB_CTRL | BIO_CB_RETURN) && cmd == BIO_CTRL_FLUSH) 143 ret = mock_http_server(bio, args->out, args->version, args->keep_alive, 144 args->content_type, args->txt, 145 (ASN1_VALUE *)x509, x509_it); 146 return ret; 147 } 148 149 #define text1 "test\n" 150 #define text2 "more\n" 151 #define REAL_SERVER_URL "http://httpbin.org/" 152 #define DOCTYPE_HTML "<!DOCTYPE html>\n" 153 154 /* do_get > 1 used for testing redirection */ 155 static int test_http_method(int do_get, int do_txt, int suggested_status) 156 { 157 BIO *wbio = BIO_new(BIO_s_mem()); 158 BIO *rbio = BIO_new(BIO_s_mem()); 159 server_args mock_args = { NULL, NULL, NULL, '0', 0 }; 160 BIO *req, *rsp; 161 char path[80]; 162 STACK_OF(CONF_VALUE) *headers = NULL; 163 const char *content_type; 164 int res = 0; 165 int real_server = do_txt && 0; /* remove "&& 0" for using real server */ 166 167 BIO_snprintf(path, sizeof(path), "/%d%s", suggested_status, 168 do_get > 1 ? "/will-be-redirected" : RPATH); 169 if (do_txt) { 170 content_type = "text/plain"; 171 req = BIO_new(BIO_s_mem()); 172 if (req == NULL 173 || BIO_puts(req, text1) != sizeof(text1) - 1 174 || BIO_puts(req, text2) != sizeof(text2) - 1) { 175 BIO_free(req); 176 req = NULL; 177 } 178 mock_args.txt = text1; 179 } else { 180 content_type = "application/x-x509-ca-cert"; 181 req = ASN1_item_i2d_mem_bio(x509_it, (ASN1_VALUE *)x509); 182 mock_args.txt = NULL; 183 } 184 if (wbio == NULL || rbio == NULL || req == NULL) 185 goto err; 186 187 mock_args.out = rbio; 188 mock_args.content_type = content_type; 189 BIO_set_callback_ex(wbio, http_bio_cb_ex); 190 BIO_set_callback_arg(wbio, (char *)&mock_args); 191 192 rsp = do_get ? OSSL_HTTP_get(real_server ? REAL_SERVER_URL : path, 193 NULL /* proxy */, NULL /* no_proxy */, 194 real_server ? NULL : wbio, 195 real_server ? NULL : rbio, 196 NULL /* bio_update_fn */, NULL /* arg */, 197 0 /* buf_size */, headers, 198 real_server ? "text/html; charset=utf-8" : content_type, 199 !do_txt /* expect_asn1 */, 200 OSSL_HTTP_DEFAULT_MAX_RESP_LEN, 0 /* timeout */) 201 : OSSL_HTTP_transfer(NULL, NULL /* host */, NULL /* port */, path, 202 0 /* use_ssl */, NULL /* proxy */, NULL /* no_pr */, 203 wbio, rbio, NULL /* bio_fn */, NULL /* arg */, 204 0 /* buf_size */, headers, content_type, 205 req, content_type, !do_txt /* expect_asn1 */, 206 OSSL_HTTP_DEFAULT_MAX_RESP_LEN, 0 /* timeout */, 207 0 /* keep_alive */); 208 if (!TEST_int_eq(suggested_status == HTTP_STATUS_CODES_FATAL_ERROR, rsp == NULL)) 209 goto err; 210 if (suggested_status == HTTP_STATUS_CODES_FATAL_ERROR) 211 res = 1; 212 if (rsp != NULL) { 213 if (do_get && real_server) { 214 char rtext[sizeof(DOCTYPE_HTML)]; 215 216 res = TEST_int_eq(BIO_gets(rsp, rtext, sizeof(rtext)), 217 sizeof(DOCTYPE_HTML) - 1) 218 && TEST_str_eq(rtext, DOCTYPE_HTML); 219 } else if (do_txt) { 220 char rtext[sizeof(text1) + 1 /* more space than needed */]; 221 222 res = TEST_int_eq(BIO_gets(rsp, rtext, sizeof(rtext)), 223 sizeof(text1) - 1) 224 && TEST_str_eq(rtext, text1); 225 } else { 226 X509 *rcert = d2i_X509_bio(rsp, NULL); 227 228 res = TEST_ptr(rcert) && TEST_int_eq(X509_cmp(x509, rcert), 0); 229 X509_free(rcert); 230 } 231 BIO_free(rsp); 232 } 233 234 err: 235 BIO_free(req); 236 BIO_free(wbio); 237 BIO_free(rbio); 238 sk_CONF_VALUE_pop_free(headers, X509V3_conf_free); 239 return res; 240 } 241 242 static int test_http_keep_alive(char version, int keep_alive, int kept_alive) 243 { 244 BIO *wbio = BIO_new(BIO_s_mem()); 245 BIO *rbio = BIO_new(BIO_s_mem()); 246 BIO *rsp; 247 const char *const content_type = "application/x-x509-ca-cert"; 248 server_args mock_args = { NULL, NULL, NULL, '0', 0 }; 249 OSSL_HTTP_REQ_CTX *rctx = NULL; 250 int i, res = 0; 251 252 if (wbio == NULL || rbio == NULL) 253 goto err; 254 mock_args.out = rbio; 255 mock_args.content_type = content_type; 256 mock_args.version = version; 257 mock_args.keep_alive = kept_alive; 258 BIO_set_callback_ex(wbio, http_bio_cb_ex); 259 BIO_set_callback_arg(wbio, (char *)&mock_args); 260 261 for (res = 1, i = 1; res && i <= 2; i++) { 262 rsp = OSSL_HTTP_transfer(&rctx, NULL /* server */, NULL /* port */, 263 RPATH, 0 /* use_ssl */, 264 NULL /* proxy */, NULL /* no_proxy */, 265 wbio, rbio, NULL /* bio_update_fn */, NULL, 266 0 /* buf_size */, NULL /* headers */, 267 NULL /* content_type */, NULL /* req => GET */, 268 content_type, 0 /* ASN.1 not expected */, 269 0 /* max_resp_len */, 0 /* timeout */, 270 keep_alive); 271 if (keep_alive == 2 && kept_alive == 0) 272 res = res && TEST_ptr_null(rsp) 273 && TEST_int_eq(OSSL_HTTP_is_alive(rctx), 0); 274 else 275 res = res && TEST_ptr(rsp) 276 && TEST_int_eq(OSSL_HTTP_is_alive(rctx), keep_alive > 0); 277 BIO_free(rsp); 278 (void)BIO_reset(rbio); /* discard response contents */ 279 keep_alive = 0; 280 } 281 OSSL_HTTP_close(rctx, res); 282 283 err: 284 BIO_free(wbio); 285 BIO_free(rbio); 286 return res; 287 } 288 289 static int test_http_url_ok(const char *url, int exp_ssl, const char *exp_host, 290 const char *exp_port, const char *exp_path) 291 { 292 char *user, *host, *port, *path, *query, *frag; 293 int exp_num, num, ssl; 294 int res; 295 296 if (!TEST_int_eq(sscanf(exp_port, "%d", &exp_num), 1)) 297 return 0; 298 res = TEST_true(OSSL_HTTP_parse_url(url, &ssl, &user, &host, &port, &num, 299 &path, &query, &frag)) 300 && TEST_str_eq(host, exp_host) 301 && TEST_str_eq(port, exp_port) 302 && TEST_int_eq(num, exp_num) 303 && TEST_str_eq(path, exp_path) 304 && TEST_int_eq(ssl, exp_ssl); 305 if (res && *user != '\0') 306 res = TEST_str_eq(user, "user:pass"); 307 if (res && *frag != '\0') 308 res = TEST_str_eq(frag, "fr"); 309 if (res && *query != '\0') 310 res = TEST_str_eq(query, "q"); 311 OPENSSL_free(user); 312 OPENSSL_free(host); 313 OPENSSL_free(port); 314 OPENSSL_free(path); 315 OPENSSL_free(query); 316 OPENSSL_free(frag); 317 return res; 318 } 319 320 static int test_http_url_path_query_ok(const char *url, const char *exp_path_qu) 321 { 322 char *host, *path; 323 int res; 324 325 res = TEST_true(OSSL_HTTP_parse_url(url, NULL, NULL, &host, NULL, NULL, 326 &path, NULL, NULL)) 327 && TEST_str_eq(host, "host") 328 && TEST_str_eq(path, exp_path_qu); 329 OPENSSL_free(host); 330 OPENSSL_free(path); 331 return res; 332 } 333 334 static int test_http_url_dns(void) 335 { 336 return test_http_url_ok("host:65535/path", 0, "host", "65535", "/path"); 337 } 338 339 static int test_http_url_timestamp(void) 340 { 341 return test_http_url_ok("host/p/2017-01-03T00:00:00", 0, "host", "80", 342 "/p/2017-01-03T00:00:00") 343 && test_http_url_ok("http://host/p/2017-01-03T00:00:00", 0, "host", 344 "80", "/p/2017-01-03T00:00:00") 345 && test_http_url_ok("https://host/p/2017-01-03T00:00:00", 1, "host", 346 "443", "/p/2017-01-03T00:00:00"); 347 } 348 349 static int test_http_url_path_query(void) 350 { 351 return test_http_url_path_query_ok("http://usr@host:1/p?q=x#frag", "/p?q=x") 352 && test_http_url_path_query_ok("http://host?query#frag", "/?query") 353 && test_http_url_path_query_ok("http://host:9999#frag", "/"); 354 } 355 356 static int test_http_url_userinfo_query_fragment(void) 357 { 358 return test_http_url_ok("user:pass@host/p?q#fr", 0, "host", "80", "/p"); 359 } 360 361 static int test_http_url_ipv4(void) 362 { 363 return test_http_url_ok("https://1.2.3.4/p/q", 1, "1.2.3.4", "443", "/p/q"); 364 } 365 366 static int test_http_url_ipv6(void) 367 { 368 return test_http_url_ok("http://[FF01::101]:6", 0, "[FF01::101]", "6", "/"); 369 } 370 371 static int test_http_url_invalid(const char *url) 372 { 373 char *host = "1", *port = "1", *path = "1"; 374 int num = 1, ssl = 1; 375 int res; 376 377 res = TEST_false(OSSL_HTTP_parse_url(url, &ssl, NULL, &host, &port, &num, 378 &path, NULL, NULL)) 379 && TEST_ptr_null(host) 380 && TEST_ptr_null(port) 381 && TEST_ptr_null(path); 382 if (!res) { 383 OPENSSL_free(host); 384 OPENSSL_free(port); 385 OPENSSL_free(path); 386 } 387 return res; 388 } 389 390 static int test_http_url_invalid_prefix(void) 391 { 392 return test_http_url_invalid("htttps://1.2.3.4:65535/pkix"); 393 } 394 395 static int test_http_url_invalid_port(void) 396 { 397 return test_http_url_invalid("https://1.2.3.4:65536/pkix") 398 && test_http_url_invalid("https://1.2.3.4:"); 399 } 400 401 static int test_http_url_invalid_path(void) 402 { 403 return test_http_url_invalid("https://[FF01::101]pkix"); 404 } 405 406 static int test_http_get_txt(void) 407 { 408 return test_http_method(1 /* GET */, 1, HTTP_STATUS_CODE_OK); 409 } 410 411 static int test_http_get_txt_redirected(void) 412 { 413 return test_http_method(2 /* GET with redirection */, 1, HTTP_STATUS_CODE_OK); 414 } 415 416 static int test_http_get_txt_fatal_status(void) 417 { 418 return test_http_method(1 /* GET */, 1, HTTP_STATUS_CODES_FATAL_ERROR); 419 } 420 421 static int test_http_get_txt_error_status(void) 422 { 423 return test_http_method(1 /* GET */, 1, HTTP_STATUS_CODES_NONFATAL_ERROR); 424 } 425 426 static int test_http_post_txt(void) 427 { 428 return test_http_method(0 /* POST */, 1, HTTP_STATUS_CODE_OK); 429 } 430 431 static int test_http_get_x509(void) 432 { 433 return test_http_method(1 /* GET */, 0, HTTP_STATUS_CODE_OK); 434 } 435 436 static int test_http_get_x509_redirected(void) 437 { 438 return test_http_method(2 /* GET with redirection */, 0, HTTP_STATUS_CODE_OK); 439 } 440 441 static int test_http_post_x509(void) 442 { 443 return test_http_method(0 /* POST */, 0, HTTP_STATUS_CODE_OK); 444 } 445 446 static int test_http_post_x509_fatal_status(void) 447 { 448 return test_http_method(0 /* POST */, 0, HTTP_STATUS_CODES_FATAL_ERROR); 449 } 450 451 static int test_http_post_x509_error_status(void) 452 { 453 return test_http_method(0 /* POST */, 0, HTTP_STATUS_CODES_NONFATAL_ERROR); 454 } 455 456 static int test_http_keep_alive_0_no_no(void) 457 { 458 return test_http_keep_alive('0', 0, 0); 459 } 460 461 static int test_http_keep_alive_1_no_no(void) 462 { 463 return test_http_keep_alive('1', 0, 0); 464 } 465 466 static int test_http_keep_alive_0_prefer_yes(void) 467 { 468 return test_http_keep_alive('0', 1, 1); 469 } 470 471 static int test_http_keep_alive_1_prefer_yes(void) 472 { 473 return test_http_keep_alive('1', 1, 1); 474 } 475 476 static int test_http_keep_alive_0_require_yes(void) 477 { 478 return test_http_keep_alive('0', 2, 1); 479 } 480 481 static int test_http_keep_alive_1_require_yes(void) 482 { 483 return test_http_keep_alive('1', 2, 1); 484 } 485 486 static int test_http_keep_alive_0_require_no(void) 487 { 488 return test_http_keep_alive('0', 2, 0); 489 } 490 491 static int test_http_keep_alive_1_require_no(void) 492 { 493 return test_http_keep_alive('1', 2, 0); 494 } 495 496 static int test_http_resp_hdr_limit(size_t limit) 497 { 498 BIO *wbio = BIO_new(BIO_s_mem()); 499 BIO *rbio = BIO_new(BIO_s_mem()); 500 BIO *mem = NULL; 501 server_args mock_args = { NULL, NULL, NULL, '0', 0 }; 502 int res = 0; 503 OSSL_HTTP_REQ_CTX *rctx = NULL; 504 505 if (TEST_ptr(wbio) == 0 || TEST_ptr(rbio) == 0) 506 goto err; 507 508 mock_args.txt = text1; 509 mock_args.content_type = "text/plain"; 510 mock_args.version = '1'; 511 mock_args.out = rbio; 512 513 BIO_set_callback_ex(wbio, http_bio_cb_ex); 514 BIO_set_callback_arg(wbio, (char *)&mock_args); 515 516 rctx = OSSL_HTTP_REQ_CTX_new(wbio, rbio, 8192); 517 if (TEST_ptr(rctx) == 0) 518 goto err; 519 520 if (!TEST_true(OSSL_HTTP_REQ_CTX_set_request_line(rctx, 0 /* GET */, 521 NULL, NULL, RPATH))) 522 goto err; 523 524 OSSL_HTTP_REQ_CTX_set_max_response_hdr_lines(rctx, limit); 525 mem = OSSL_HTTP_REQ_CTX_exchange(rctx); 526 527 /* 528 * Note the server sends 4 http response headers, thus we expect to 529 * see failure here when we set header limit in http response to 1. 530 */ 531 if (limit == 1) 532 res = TEST_ptr_null(mem); 533 else 534 res = TEST_ptr(mem); 535 536 err: 537 BIO_free(wbio); 538 BIO_free(rbio); 539 OSSL_HTTP_REQ_CTX_free(rctx); 540 541 return res; 542 } 543 544 static int test_hdr_resp_hdr_limit_none(void) 545 { 546 return test_http_resp_hdr_limit(0); 547 } 548 549 static int test_hdr_resp_hdr_limit_short(void) 550 { 551 return (test_http_resp_hdr_limit(1)); 552 } 553 554 static int test_hdr_resp_hdr_limit_256(void) 555 { 556 return test_http_resp_hdr_limit(256); 557 } 558 559 void cleanup_tests(void) 560 { 561 X509_free(x509); 562 } 563 564 OPT_TEST_DECLARE_USAGE("cert.pem\n") 565 566 int setup_tests(void) 567 { 568 if (!test_skip_common_options()) 569 return 0; 570 571 x509_it = ASN1_ITEM_rptr(X509); 572 if (!TEST_ptr((x509 = load_cert_pem(test_get_argument(0), NULL)))) 573 return 0; 574 575 ADD_TEST(test_http_url_dns); 576 ADD_TEST(test_http_url_timestamp); 577 ADD_TEST(test_http_url_path_query); 578 ADD_TEST(test_http_url_userinfo_query_fragment); 579 ADD_TEST(test_http_url_ipv4); 580 ADD_TEST(test_http_url_ipv6); 581 ADD_TEST(test_http_url_invalid_prefix); 582 ADD_TEST(test_http_url_invalid_port); 583 ADD_TEST(test_http_url_invalid_path); 584 585 ADD_TEST(test_http_get_txt); 586 ADD_TEST(test_http_get_txt_redirected); 587 ADD_TEST(test_http_get_txt_fatal_status); 588 ADD_TEST(test_http_get_txt_error_status); 589 ADD_TEST(test_http_post_txt); 590 ADD_TEST(test_http_get_x509); 591 ADD_TEST(test_http_get_x509_redirected); 592 ADD_TEST(test_http_post_x509); 593 ADD_TEST(test_http_post_x509_fatal_status); 594 ADD_TEST(test_http_post_x509_error_status); 595 596 ADD_TEST(test_http_keep_alive_0_no_no); 597 ADD_TEST(test_http_keep_alive_1_no_no); 598 ADD_TEST(test_http_keep_alive_0_prefer_yes); 599 ADD_TEST(test_http_keep_alive_1_prefer_yes); 600 ADD_TEST(test_http_keep_alive_0_require_yes); 601 ADD_TEST(test_http_keep_alive_1_require_yes); 602 ADD_TEST(test_http_keep_alive_0_require_no); 603 ADD_TEST(test_http_keep_alive_1_require_no); 604 605 ADD_TEST(test_hdr_resp_hdr_limit_none); 606 ADD_TEST(test_hdr_resp_hdr_limit_short); 607 ADD_TEST(test_hdr_resp_hdr_limit_256); 608 return 1; 609 } 610