190792Sgshapiro/* 2261363Sgshapiro This is an example of how to hook up evhttp with bufferevent_ssl 390792Sgshapiro 490792Sgshapiro It just GETs an https URL given on the command-line and prints the response 590792Sgshapiro body to stdout. 690792Sgshapiro 790792Sgshapiro Actually, it also accepts plain http URLs to make it easy to compare http vs 890792Sgshapiro https code paths. 990792Sgshapiro 1090792Sgshapiro Loosely based on le-proxy.c. 1190792Sgshapiro */ 1290792Sgshapiro 1390792Sgshapiro// Get rid of OSX 10.7 and greater deprecation warnings. 1490792Sgshapiro#if defined(__APPLE__) && defined(__clang__) 1590792Sgshapiro#pragma clang diagnostic ignored "-Wdeprecated-declarations" 16266692Sgshapiro#endif 1790792Sgshapiro 1890792Sgshapiro#include <stdio.h> 1990792Sgshapiro#include <assert.h> 2090792Sgshapiro#include <stdlib.h> 2190792Sgshapiro#include <string.h> 22157001Sgshapiro#include <errno.h> 2390792Sgshapiro 2490792Sgshapiro#ifdef _WIN32 2590792Sgshapiro#include <winsock2.h> 2690792Sgshapiro#include <ws2tcpip.h> 2790792Sgshapiro 28285303Sgshapiro#define snprintf _snprintf 2990792Sgshapiro#define strcasecmp _stricmp 3090792Sgshapiro#else 3190792Sgshapiro#include <sys/socket.h> 3290792Sgshapiro#include <netinet/in.h> 3390792Sgshapiro#endif 3490792Sgshapiro 3590792Sgshapiro#include <event2/bufferevent_ssl.h> 3690792Sgshapiro#include <event2/bufferevent.h> 3790792Sgshapiro#include <event2/buffer.h> 3890792Sgshapiro#include <event2/listener.h> 3990792Sgshapiro#include <event2/util.h> 4090792Sgshapiro#include <event2/http.h> 4190792Sgshapiro 4290792Sgshapiro#include <openssl/ssl.h> 4390792Sgshapiro#include <openssl/err.h> 4490792Sgshapiro#include <openssl/rand.h> 4590792Sgshapiro 4690792Sgshapiro#include "openssl_hostname_validation.h" 4790792Sgshapiro 4890792Sgshapirostatic int ignore_cert = 0; 4990792Sgshapiro 5090792Sgshapirostatic void 5190792Sgshapirohttp_request_done(struct evhttp_request *req, void *ctx) 5290792Sgshapiro{ 5390792Sgshapiro char buffer[256]; 5490792Sgshapiro int nread; 5590792Sgshapiro 5690792Sgshapiro if (!req || !evhttp_request_get_response_code(req)) { 5790792Sgshapiro /* If req is NULL, it means an error occurred, but 5890792Sgshapiro * sadly we are mostly left guessing what the error 5990792Sgshapiro * might have been. We'll do our best... */ 6090792Sgshapiro struct bufferevent *bev = (struct bufferevent *) ctx; 6190792Sgshapiro unsigned long oslerr; 6290792Sgshapiro int printed_err = 0; 6390792Sgshapiro int errcode = EVUTIL_SOCKET_ERROR(); 6490792Sgshapiro fprintf(stderr, "some request failed - no idea which one though!\n"); 6590792Sgshapiro /* Print out the OpenSSL error queue that libevent 6690792Sgshapiro * squirreled away for us, if any. */ 6790792Sgshapiro while ((oslerr = bufferevent_get_openssl_error(bev))) { 6890792Sgshapiro ERR_error_string_n(oslerr, buffer, sizeof(buffer)); 69285303Sgshapiro fprintf(stderr, "%s\n", buffer); 70110560Sgshapiro printed_err = 1; 71110560Sgshapiro } 72110560Sgshapiro /* If the OpenSSL error queue was empty, maybe it was a 73110560Sgshapiro * socket error; let's try printing that. */ 7490792Sgshapiro if (! printed_err) 7590792Sgshapiro fprintf(stderr, "socket error = %s (%d)\n", 7690792Sgshapiro evutil_socket_error_to_string(errcode), 7790792Sgshapiro errcode); 7890792Sgshapiro return; 7990792Sgshapiro } 80157001Sgshapiro 81157001Sgshapiro fprintf(stderr, "Response line: %d %s\n", 82157001Sgshapiro evhttp_request_get_response_code(req), 83157001Sgshapiro evhttp_request_get_response_code_line(req)); 84157001Sgshapiro 8590792Sgshapiro while ((nread = evbuffer_remove(evhttp_request_get_input_buffer(req), 8690792Sgshapiro buffer, sizeof(buffer))) 8790792Sgshapiro > 0) { 8890792Sgshapiro /* These are just arbitrary chunks of 256 bytes. 8990792Sgshapiro * They are not lines, so we can't treat them as such. */ 9090792Sgshapiro fwrite(buffer, nread, 1, stdout); 9190792Sgshapiro } 9290792Sgshapiro} 9390792Sgshapiro 9490792Sgshapirostatic void 9590792Sgshapirosyntax(void) 9690792Sgshapiro{ 9790792Sgshapiro fputs("Syntax:\n", stderr); 9890792Sgshapiro fputs(" https-client -url <https-url> [-data data-file.bin] [-ignore-cert] [-retries num] [-timeout sec] [-crt crt]\n", stderr); 9990792Sgshapiro fputs("Example:\n", stderr); 10090792Sgshapiro fputs(" https-client -url https://ip.appspot.com/\n", stderr); 101157001Sgshapiro} 10290792Sgshapiro 10390792Sgshapirostatic void 10490792Sgshapiroerr(const char *msg) 10590792Sgshapiro{ 10690792Sgshapiro fputs(msg, stderr); 10790792Sgshapiro} 10890792Sgshapiro 10990792Sgshapirostatic void 11090792Sgshapiroerr_openssl(const char *func) 11190792Sgshapiro{ 11290792Sgshapiro fprintf (stderr, "%s failed:\n", func); 11390792Sgshapiro 11490792Sgshapiro /* This is the OpenSSL function that prints the contents of the 11590792Sgshapiro * error stack to the specified file handle. */ 11690792Sgshapiro ERR_print_errors_fp (stderr); 11790792Sgshapiro 11890792Sgshapiro exit(1); 11990792Sgshapiro} 12090792Sgshapiro 12190792Sgshapiro/* See http://archives.seul.org/libevent/users/Jan-2013/msg00039.html */ 12290792Sgshapirostatic int cert_verify_callback(X509_STORE_CTX *x509_ctx, void *arg) 12390792Sgshapiro{ 12490792Sgshapiro char cert_str[256]; 12590792Sgshapiro const char *host = (const char *) arg; 12690792Sgshapiro const char *res_str = "X509_verify_cert failed"; 12790792Sgshapiro HostnameValidationResult res = Error; 12890792Sgshapiro 12990792Sgshapiro /* This is the function that OpenSSL would call if we hadn't called 13090792Sgshapiro * SSL_CTX_set_cert_verify_callback(). Therefore, we are "wrapping" 13190792Sgshapiro * the default functionality, rather than replacing it. */ 13290792Sgshapiro int ok_so_far = 0; 13390792Sgshapiro 13490792Sgshapiro X509 *server_cert = NULL; 13590792Sgshapiro 13690792Sgshapiro if (ignore_cert) { 13790792Sgshapiro return 1; 13890792Sgshapiro } 13990792Sgshapiro 14090792Sgshapiro ok_so_far = X509_verify_cert(x509_ctx); 14190792Sgshapiro 14290792Sgshapiro server_cert = X509_STORE_CTX_get_current_cert(x509_ctx); 14390792Sgshapiro 14490792Sgshapiro if (ok_so_far) { 14590792Sgshapiro res = validate_hostname(host, server_cert); 14690792Sgshapiro 14790792Sgshapiro switch (res) { 14890792Sgshapiro case MatchFound: 14990792Sgshapiro res_str = "MatchFound"; 15090792Sgshapiro break; 15190792Sgshapiro case MatchNotFound: 15290792Sgshapiro res_str = "MatchNotFound"; 15390792Sgshapiro break; 15490792Sgshapiro case NoSANPresent: 15590792Sgshapiro res_str = "NoSANPresent"; 15690792Sgshapiro break; 15790792Sgshapiro case MalformedCertificate: 15890792Sgshapiro res_str = "MalformedCertificate"; 15990792Sgshapiro break; 16090792Sgshapiro case Error: 16190792Sgshapiro res_str = "Error"; 16290792Sgshapiro break; 16390792Sgshapiro default: 16490792Sgshapiro res_str = "WTF!"; 16590792Sgshapiro break; 16690792Sgshapiro } 16790792Sgshapiro } 16890792Sgshapiro 16990792Sgshapiro X509_NAME_oneline(X509_get_subject_name (server_cert), 17090792Sgshapiro cert_str, sizeof (cert_str)); 17190792Sgshapiro 17290792Sgshapiro if (res == MatchFound) { 17390792Sgshapiro printf("https server '%s' has this certificate, " 17490792Sgshapiro "which looks good to me:\n%s\n", 17590792Sgshapiro host, cert_str); 17690792Sgshapiro return 1; 17790792Sgshapiro } else { 17890792Sgshapiro printf("Got '%s' for hostname '%s' and certificate:\n%s\n", 17990792Sgshapiro res_str, host, cert_str); 18090792Sgshapiro return 0; 18190792Sgshapiro } 18290792Sgshapiro} 18390792Sgshapiro 18490792Sgshapiro#ifdef _WIN32 18590792Sgshapirostatic int 18690792Sgshapiroadd_cert_for_store(X509_STORE *store, const char *name) 18790792Sgshapiro{ 18890792Sgshapiro HCERTSTORE sys_store = NULL; 18990792Sgshapiro PCCERT_CONTEXT ctx = NULL; 19090792Sgshapiro int r = 0; 19190792Sgshapiro 19290792Sgshapiro sys_store = CertOpenSystemStore(0, name); 19390792Sgshapiro if (!sys_store) { 19490792Sgshapiro err("failed to open system certificate store"); 19590792Sgshapiro return -1; 19690792Sgshapiro } 19790792Sgshapiro while ((ctx = CertEnumCertificatesInStore(sys_store, ctx))) { 19890792Sgshapiro X509 *x509 = d2i_X509(NULL, (unsigned char const **)&ctx->pbCertEncoded, 19990792Sgshapiro ctx->cbCertEncoded); 20090792Sgshapiro if (x509) { 20190792Sgshapiro X509_STORE_add_cert(store, x509); 20290792Sgshapiro X509_free(x509); 20390792Sgshapiro } else { 20490792Sgshapiro r = -1; 20590792Sgshapiro err_openssl("d2i_X509"); 20690792Sgshapiro break; 20790792Sgshapiro } 20890792Sgshapiro } 20990792Sgshapiro CertCloseStore(sys_store, 0); 21090792Sgshapiro return r; 21190792Sgshapiro} 21290792Sgshapiro#endif 21390792Sgshapiro 21490792Sgshapiroint 21590792Sgshapiromain(int argc, char **argv) 21690792Sgshapiro{ 21790792Sgshapiro int r; 21890792Sgshapiro struct event_base *base = NULL; 21990792Sgshapiro struct evhttp_uri *http_uri = NULL; 22090792Sgshapiro const char *url = NULL, *data_file = NULL; 22190792Sgshapiro const char *crt = NULL; 22290792Sgshapiro const char *scheme, *host, *path, *query; 22390792Sgshapiro char uri[256]; 22490792Sgshapiro int port; 22590792Sgshapiro int retries = 0; 22690792Sgshapiro int timeout = -1; 22790792Sgshapiro 22890792Sgshapiro SSL_CTX *ssl_ctx = NULL; 22990792Sgshapiro SSL *ssl = NULL; 23090792Sgshapiro struct bufferevent *bev; 23190792Sgshapiro struct evhttp_connection *evcon = NULL; 23290792Sgshapiro struct evhttp_request *req; 23390792Sgshapiro struct evkeyvalq *output_headers; 23490792Sgshapiro struct evbuffer *output_buffer; 23590792Sgshapiro 23690792Sgshapiro int i; 23790792Sgshapiro int ret = 0; 23890792Sgshapiro enum { HTTP, HTTPS } type = HTTP; 23990792Sgshapiro 24090792Sgshapiro for (i = 1; i < argc; i++) { 24190792Sgshapiro if (!strcmp("-url", argv[i])) { 24290792Sgshapiro if (i < argc - 1) { 24390792Sgshapiro url = argv[i + 1]; 24490792Sgshapiro } else { 24590792Sgshapiro syntax(); 24690792Sgshapiro goto error; 24790792Sgshapiro } 24890792Sgshapiro } else if (!strcmp("-crt", argv[i])) { 24990792Sgshapiro if (i < argc - 1) { 25090792Sgshapiro crt = argv[i + 1]; 25190792Sgshapiro } else { 25290792Sgshapiro syntax(); 25390792Sgshapiro goto error; 25490792Sgshapiro } 25590792Sgshapiro } else if (!strcmp("-ignore-cert", argv[i])) { 25690792Sgshapiro ignore_cert = 1; 25790792Sgshapiro } else if (!strcmp("-data", argv[i])) { 25890792Sgshapiro if (i < argc - 1) { 25990792Sgshapiro data_file = argv[i + 1]; 26090792Sgshapiro } else { 26190792Sgshapiro syntax(); 26290792Sgshapiro goto error; 26390792Sgshapiro } 26490792Sgshapiro } else if (!strcmp("-retries", argv[i])) { 26590792Sgshapiro if (i < argc - 1) { 26690792Sgshapiro retries = atoi(argv[i + 1]); 26790792Sgshapiro } else { 26890792Sgshapiro syntax(); 26990792Sgshapiro goto error; 27090792Sgshapiro } 27190792Sgshapiro } else if (!strcmp("-timeout", argv[i])) { 27290792Sgshapiro if (i < argc - 1) { 27390792Sgshapiro timeout = atoi(argv[i + 1]); 27490792Sgshapiro } else { 27590792Sgshapiro syntax(); 27690792Sgshapiro goto error; 27790792Sgshapiro } 27890792Sgshapiro } else if (!strcmp("-help", argv[i])) { 27990792Sgshapiro syntax(); 28090792Sgshapiro goto error; 28190792Sgshapiro } 28290792Sgshapiro } 28390792Sgshapiro 28490792Sgshapiro if (!url) { 28590792Sgshapiro syntax(); 28690792Sgshapiro goto error; 28790792Sgshapiro } 28890792Sgshapiro 28990792Sgshapiro#ifdef _WIN32 29090792Sgshapiro { 29190792Sgshapiro WORD wVersionRequested; 29290792Sgshapiro WSADATA wsaData; 29390792Sgshapiro int err; 29490792Sgshapiro 29590792Sgshapiro wVersionRequested = MAKEWORD(2, 2); 29690792Sgshapiro 29790792Sgshapiro err = WSAStartup(wVersionRequested, &wsaData); 29890792Sgshapiro if (err != 0) { 29990792Sgshapiro printf("WSAStartup failed with error: %d\n", err); 30090792Sgshapiro goto error; 30190792Sgshapiro } 30290792Sgshapiro } 30390792Sgshapiro#endif // _WIN32 304 305 http_uri = evhttp_uri_parse(url); 306 if (http_uri == NULL) { 307 err("malformed url"); 308 goto error; 309 } 310 311 scheme = evhttp_uri_get_scheme(http_uri); 312 if (scheme == NULL || (strcasecmp(scheme, "https") != 0 && 313 strcasecmp(scheme, "http") != 0)) { 314 err("url must be http or https"); 315 goto error; 316 } 317 318 host = evhttp_uri_get_host(http_uri); 319 if (host == NULL) { 320 err("url must have a host"); 321 goto error; 322 } 323 324 port = evhttp_uri_get_port(http_uri); 325 if (port == -1) { 326 port = (strcasecmp(scheme, "http") == 0) ? 80 : 443; 327 } 328 329 path = evhttp_uri_get_path(http_uri); 330 if (strlen(path) == 0) { 331 path = "/"; 332 } 333 334 query = evhttp_uri_get_query(http_uri); 335 if (query == NULL) { 336 snprintf(uri, sizeof(uri) - 1, "%s", path); 337 } else { 338 snprintf(uri, sizeof(uri) - 1, "%s?%s", path, query); 339 } 340 uri[sizeof(uri) - 1] = '\0'; 341 342#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || \ 343 (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000L) 344 // Initialize OpenSSL 345 SSL_library_init(); 346 ERR_load_crypto_strings(); 347 SSL_load_error_strings(); 348 OpenSSL_add_all_algorithms(); 349#endif 350 351 /* This isn't strictly necessary... OpenSSL performs RAND_poll 352 * automatically on first use of random number generator. */ 353 r = RAND_poll(); 354 if (r == 0) { 355 err_openssl("RAND_poll"); 356 goto error; 357 } 358 359 /* Create a new OpenSSL context */ 360 ssl_ctx = SSL_CTX_new(SSLv23_method()); 361 if (!ssl_ctx) { 362 err_openssl("SSL_CTX_new"); 363 goto error; 364 } 365 366 if (crt == NULL) { 367 X509_STORE *store; 368 /* Attempt to use the system's trusted root certificates. */ 369 store = SSL_CTX_get_cert_store(ssl_ctx); 370#ifdef _WIN32 371 if (add_cert_for_store(store, "CA") < 0 || 372 add_cert_for_store(store, "AuthRoot") < 0 || 373 add_cert_for_store(store, "ROOT") < 0) { 374 goto error; 375 } 376#else // _WIN32 377 if (X509_STORE_set_default_paths(store) != 1) { 378 err_openssl("X509_STORE_set_default_paths"); 379 goto error; 380 } 381#endif // _WIN32 382 } else { 383 if (SSL_CTX_load_verify_locations(ssl_ctx, crt, NULL) != 1) { 384 err_openssl("SSL_CTX_load_verify_locations"); 385 goto error; 386 } 387 } 388 /* Ask OpenSSL to verify the server certificate. Note that this 389 * does NOT include verifying that the hostname is correct. 390 * So, by itself, this means anyone with any legitimate 391 * CA-issued certificate for any website, can impersonate any 392 * other website in the world. This is not good. See "The 393 * Most Dangerous Code in the World" article at 394 * https://crypto.stanford.edu/~dabo/pubs/abstracts/ssl-client-bugs.html 395 */ 396 SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL); 397 /* This is how we solve the problem mentioned in the previous 398 * comment. We "wrap" OpenSSL's validation routine in our 399 * own routine, which also validates the hostname by calling 400 * the code provided by iSECPartners. Note that even though 401 * the "Everything You've Always Wanted to Know About 402 * Certificate Validation With OpenSSL (But Were Afraid to 403 * Ask)" paper from iSECPartners says very explicitly not to 404 * call SSL_CTX_set_cert_verify_callback (at the bottom of 405 * page 2), what we're doing here is safe because our 406 * cert_verify_callback() calls X509_verify_cert(), which is 407 * OpenSSL's built-in routine which would have been called if 408 * we hadn't set the callback. Therefore, we're just 409 * "wrapping" OpenSSL's routine, not replacing it. */ 410 SSL_CTX_set_cert_verify_callback(ssl_ctx, cert_verify_callback, 411 (void *) host); 412 413 // Create event base 414 base = event_base_new(); 415 if (!base) { 416 perror("event_base_new()"); 417 goto error; 418 } 419 420 // Create OpenSSL bufferevent and stack evhttp on top of it 421 ssl = SSL_new(ssl_ctx); 422 if (ssl == NULL) { 423 err_openssl("SSL_new()"); 424 goto error; 425 } 426 427 #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME 428 // Set hostname for SNI extension 429 SSL_set_tlsext_host_name(ssl, host); 430 #endif 431 432 if (strcasecmp(scheme, "http") == 0) { 433 bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE); 434 } else { 435 type = HTTPS; 436 bev = bufferevent_openssl_socket_new(base, -1, ssl, 437 BUFFEREVENT_SSL_CONNECTING, 438 BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS); 439 } 440 441 if (bev == NULL) { 442 fprintf(stderr, "bufferevent_openssl_socket_new() failed\n"); 443 goto error; 444 } 445 446 bufferevent_openssl_set_allow_dirty_shutdown(bev, 1); 447 448 // For simplicity, we let DNS resolution block. Everything else should be 449 // asynchronous though. 450 evcon = evhttp_connection_base_bufferevent_new(base, NULL, bev, 451 host, port); 452 if (evcon == NULL) { 453 fprintf(stderr, "evhttp_connection_base_bufferevent_new() failed\n"); 454 goto error; 455 } 456 457 if (retries > 0) { 458 evhttp_connection_set_retries(evcon, retries); 459 } 460 if (timeout >= 0) { 461 evhttp_connection_set_timeout(evcon, timeout); 462 } 463 464 // Fire off the request 465 req = evhttp_request_new(http_request_done, bev); 466 if (req == NULL) { 467 fprintf(stderr, "evhttp_request_new() failed\n"); 468 goto error; 469 } 470 471 output_headers = evhttp_request_get_output_headers(req); 472 evhttp_add_header(output_headers, "Host", host); 473 evhttp_add_header(output_headers, "Connection", "close"); 474 475 if (data_file) { 476 /* NOTE: In production code, you'd probably want to use 477 * evbuffer_add_file() or evbuffer_add_file_segment(), to 478 * avoid needless copying. */ 479 FILE * f = fopen(data_file, "rb"); 480 char buf[1024]; 481 size_t s; 482 size_t bytes = 0; 483 484 if (!f) { 485 syntax(); 486 goto error; 487 } 488 489 output_buffer = evhttp_request_get_output_buffer(req); 490 while ((s = fread(buf, 1, sizeof(buf), f)) > 0) { 491 evbuffer_add(output_buffer, buf, s); 492 bytes += s; 493 } 494 evutil_snprintf(buf, sizeof(buf)-1, "%lu", (unsigned long)bytes); 495 evhttp_add_header(output_headers, "Content-Length", buf); 496 fclose(f); 497 } 498 499 r = evhttp_make_request(evcon, req, data_file ? EVHTTP_REQ_POST : EVHTTP_REQ_GET, uri); 500 if (r != 0) { 501 fprintf(stderr, "evhttp_make_request() failed\n"); 502 goto error; 503 } 504 505 event_base_dispatch(base); 506 goto cleanup; 507 508error: 509 ret = 1; 510cleanup: 511 if (evcon) 512 evhttp_connection_free(evcon); 513 if (http_uri) 514 evhttp_uri_free(http_uri); 515 if (base) 516 event_base_free(base); 517 518 if (ssl_ctx) 519 SSL_CTX_free(ssl_ctx); 520 if (type == HTTP && ssl) 521 SSL_free(ssl); 522#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || \ 523 (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000L) 524 EVP_cleanup(); 525 ERR_free_strings(); 526 527#if OPENSSL_VERSION_NUMBER < 0x10000000L 528 ERR_remove_state(0); 529#else 530 ERR_remove_thread_state(NULL); 531#endif 532 533 CRYPTO_cleanup_all_ex_data(); 534 535 sk_SSL_COMP_free(SSL_COMP_get_compression_methods()); 536#endif /* (OPENSSL_VERSION_NUMBER < 0x10100000L) || \ 537 (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000L) */ 538 539#ifdef _WIN32 540 WSACleanup(); 541#endif 542 543 return ret; 544} 545