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