tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

https-client.c (13591B)


      1 /*
      2  This is an example of how to hook up evhttp with bufferevent_ssl
      3 
      4  It just GETs an https URL given on the command-line and prints the response
      5  body to stdout.
      6 
      7  Actually, it also accepts plain http URLs to make it easy to compare http vs
      8  https code paths.
      9 
     10  Loosely based on le-proxy.c.
     11 */
     12 
     13 // Get rid of OSX 10.7 and greater deprecation warnings.
     14 #if defined(__APPLE__) && defined(__clang__)
     15 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
     16 #endif
     17 
     18 #include <stdio.h>
     19 #include <assert.h>
     20 #include <stdlib.h>
     21 #include <string.h>
     22 #include <errno.h>
     23 
     24 #ifdef _WIN32
     25 #include <winsock2.h>
     26 #include <ws2tcpip.h>
     27 
     28 #define snprintf _snprintf
     29 #define strcasecmp _stricmp 
     30 #else
     31 #include <sys/socket.h>
     32 #include <netinet/in.h>
     33 #endif
     34 
     35 #include <event2/bufferevent_ssl.h>
     36 #include <event2/bufferevent.h>
     37 #include <event2/buffer.h>
     38 #include <event2/listener.h>
     39 #include <event2/util.h>
     40 #include <event2/http.h>
     41 
     42 #include <openssl/ssl.h>
     43 #include <openssl/err.h>
     44 #include <openssl/rand.h>
     45 
     46 #include "openssl_hostname_validation.h"
     47 
     48 static int ignore_cert = 0;
     49 
     50 static void
     51 http_request_done(struct evhttp_request *req, void *ctx)
     52 {
     53 char buffer[256];
     54 int nread;
     55 
     56 if (!req || !evhttp_request_get_response_code(req)) {
     57 	/* If req is NULL, it means an error occurred, but
     58 	 * sadly we are mostly left guessing what the error
     59 	 * might have been.  We'll do our best... */
     60 	struct bufferevent *bev = (struct bufferevent *) ctx;
     61 	unsigned long oslerr;
     62 	int printed_err = 0;
     63 	int errcode = EVUTIL_SOCKET_ERROR();
     64 	fprintf(stderr, "some request failed - no idea which one though!\n");
     65 	/* Print out the OpenSSL error queue that libevent
     66 	 * squirreled away for us, if any. */
     67 	while ((oslerr = bufferevent_get_openssl_error(bev))) {
     68 		ERR_error_string_n(oslerr, buffer, sizeof(buffer));
     69 		fprintf(stderr, "%s\n", buffer);
     70 		printed_err = 1;
     71 	}
     72 	/* If the OpenSSL error queue was empty, maybe it was a
     73 	 * socket error; let's try printing that. */
     74 	if (! printed_err)
     75 		fprintf(stderr, "socket error = %s (%d)\n",
     76 			evutil_socket_error_to_string(errcode),
     77 			errcode);
     78 	return;
     79 }
     80 
     81 fprintf(stderr, "Response line: %d %s\n",
     82     evhttp_request_get_response_code(req),
     83     evhttp_request_get_response_code_line(req));
     84 
     85 while ((nread = evbuffer_remove(evhttp_request_get_input_buffer(req),
     86 	    buffer, sizeof(buffer)))
     87        > 0) {
     88 	/* These are just arbitrary chunks of 256 bytes.
     89 	 * They are not lines, so we can't treat them as such. */
     90 	fwrite(buffer, nread, 1, stdout);
     91 }
     92 }
     93 
     94 static void
     95 syntax(void)
     96 {
     97 fputs("Syntax:\n", stderr);
     98 fputs("   https-client -url <https-url> [-data data-file.bin] [-ignore-cert] [-retries num] [-timeout sec] [-crt crt]\n", stderr);
     99 fputs("Example:\n", stderr);
    100 fputs("   https-client -url https://ip.appspot.com/\n", stderr);
    101 }
    102 
    103 static void
    104 err(const char *msg)
    105 {
    106 fputs(msg, stderr);
    107 }
    108 
    109 static void
    110 err_openssl(const char *func)
    111 {
    112 fprintf (stderr, "%s failed:\n", func);
    113 
    114 /* This is the OpenSSL function that prints the contents of the
    115  * error stack to the specified file handle. */
    116 ERR_print_errors_fp (stderr);
    117 
    118 exit(1);
    119 }
    120 
    121 /* See http://archives.seul.org/libevent/users/Jan-2013/msg00039.html */
    122 static int cert_verify_callback(X509_STORE_CTX *x509_ctx, void *arg)
    123 {
    124 char cert_str[256];
    125 const char *host = (const char *) arg;
    126 const char *res_str = "X509_verify_cert failed";
    127 HostnameValidationResult res = Error;
    128 
    129 /* This is the function that OpenSSL would call if we hadn't called
    130  * SSL_CTX_set_cert_verify_callback().  Therefore, we are "wrapping"
    131  * the default functionality, rather than replacing it. */
    132 int ok_so_far = 0;
    133 
    134 X509 *server_cert = NULL;
    135 
    136 if (ignore_cert) {
    137 	return 1;
    138 }
    139 
    140 ok_so_far = X509_verify_cert(x509_ctx);
    141 
    142 server_cert = X509_STORE_CTX_get_current_cert(x509_ctx);
    143 
    144 if (ok_so_far) {
    145 	res = validate_hostname(host, server_cert);
    146 
    147 	switch (res) {
    148 	case MatchFound:
    149 		res_str = "MatchFound";
    150 		break;
    151 	case MatchNotFound:
    152 		res_str = "MatchNotFound";
    153 		break;
    154 	case NoSANPresent:
    155 		res_str = "NoSANPresent";
    156 		break;
    157 	case MalformedCertificate:
    158 		res_str = "MalformedCertificate";
    159 		break;
    160 	case Error:
    161 		res_str = "Error";
    162 		break;
    163 	default:
    164 		res_str = "WTF!";
    165 		break;
    166 	}
    167 }
    168 
    169 X509_NAME_oneline(X509_get_subject_name (server_cert),
    170 		  cert_str, sizeof (cert_str));
    171 
    172 if (res == MatchFound) {
    173 	printf("https server '%s' has this certificate, "
    174 	       "which looks good to me:\n%s\n",
    175 	       host, cert_str);
    176 	return 1;
    177 } else {
    178 	printf("Got '%s' for hostname '%s' and certificate:\n%s\n",
    179 	       res_str, host, cert_str);
    180 	return 0;
    181 }
    182 }
    183 
    184 #ifdef _WIN32
    185 static int
    186 add_cert_for_store(X509_STORE *store, const char *name)
    187 {
    188 HCERTSTORE sys_store = NULL;
    189 PCCERT_CONTEXT ctx = NULL;
    190 int r = 0;
    191 
    192 sys_store = CertOpenSystemStore(0, name);
    193 if (!sys_store) {
    194 	err("failed to open system certificate store");
    195 	return -1;
    196 }
    197 while ((ctx = CertEnumCertificatesInStore(sys_store, ctx))) {
    198 	X509 *x509 = d2i_X509(NULL, (unsigned char const **)&ctx->pbCertEncoded,
    199 		ctx->cbCertEncoded);
    200 	if (x509) {
    201 		X509_STORE_add_cert(store, x509);
    202 		X509_free(x509);
    203 	} else {
    204 		r = -1;
    205 		err_openssl("d2i_X509");
    206 		break;
    207 	}
    208 }
    209 CertCloseStore(sys_store, 0);
    210 return r;
    211 }
    212 #endif
    213 
    214 int
    215 main(int argc, char **argv)
    216 {
    217 int r;
    218 struct event_base *base = NULL;
    219 struct evhttp_uri *http_uri = NULL;
    220 const char *url = NULL, *data_file = NULL;
    221 const char *crt = NULL;
    222 const char *scheme, *host, *path, *query;
    223 char uri[256];
    224 int port;
    225 int retries = 0;
    226 int timeout = -1;
    227 
    228 SSL_CTX *ssl_ctx = NULL;
    229 SSL *ssl = NULL;
    230 struct bufferevent *bev;
    231 struct evhttp_connection *evcon = NULL;
    232 struct evhttp_request *req;
    233 struct evkeyvalq *output_headers;
    234 struct evbuffer *output_buffer;
    235 
    236 int i;
    237 int ret = 0;
    238 enum { HTTP, HTTPS } type = HTTP;
    239 
    240 for (i = 1; i < argc; i++) {
    241 	if (!strcmp("-url", argv[i])) {
    242 		if (i < argc - 1) {
    243 			url = argv[i + 1];
    244 		} else {
    245 			syntax();
    246 			goto error;
    247 		}
    248 	} else if (!strcmp("-crt", argv[i])) {
    249 		if (i < argc - 1) {
    250 			crt = argv[i + 1];
    251 		} else {
    252 			syntax();
    253 			goto error;
    254 		}
    255 	} else if (!strcmp("-ignore-cert", argv[i])) {
    256 		ignore_cert = 1;
    257 	} else if (!strcmp("-data", argv[i])) {
    258 		if (i < argc - 1) {
    259 			data_file = argv[i + 1];
    260 		} else {
    261 			syntax();
    262 			goto error;
    263 		}
    264 	} else if (!strcmp("-retries", argv[i])) {
    265 		if (i < argc - 1) {
    266 			retries = atoi(argv[i + 1]);
    267 		} else {
    268 			syntax();
    269 			goto error;
    270 		}
    271 	} else if (!strcmp("-timeout", argv[i])) {
    272 		if (i < argc - 1) {
    273 			timeout = atoi(argv[i + 1]);
    274 		} else {
    275 			syntax();
    276 			goto error;
    277 		}
    278 	} else if (!strcmp("-help", argv[i])) {
    279 		syntax();
    280 		goto error;
    281 	}
    282 }
    283 
    284 if (!url) {
    285 	syntax();
    286 	goto error;
    287 }
    288 
    289 #ifdef _WIN32
    290 {
    291 	WORD wVersionRequested;
    292 	WSADATA wsaData;
    293 	int err;
    294 
    295 	wVersionRequested = MAKEWORD(2, 2);
    296 
    297 	err = WSAStartup(wVersionRequested, &wsaData);
    298 	if (err != 0) {
    299 		printf("WSAStartup failed with error: %d\n", err);
    300 		goto error;
    301 	}
    302 }
    303 #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 
    508 error:
    509 ret = 1;
    510 cleanup:
    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 }