xref: /libevent-2.1.12/sample/https-client.c (revision 19222e52)
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 #include <stdio.h>
14 #include <assert.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <errno.h>
18 
19 #ifdef WIN32
20 #include <winsock2.h>
21 #include <ws2tcpip.h>
22 
23 #define snprintf _snprintf
24 #define strcasecmp _stricmp
25 #else
26 #include <sys/socket.h>
27 #include <netinet/in.h>
28 #endif
29 
30 #include <event2/bufferevent_ssl.h>
31 #include <event2/bufferevent.h>
32 #include <event2/buffer.h>
33 #include <event2/listener.h>
34 #include <event2/util.h>
35 #include <event2/http.h>
36 
37 #include <openssl/ssl.h>
38 #include <openssl/err.h>
39 #include <openssl/rand.h>
40 
41 #include "openssl_hostname_validation.h"
42 
43 static struct event_base *base;
44 
45 static void
46 http_request_done(struct evhttp_request *req, void *ctx)
47 {
48 	char buffer[256];
49 	int nread;
50 
51 	if (req == NULL) {
52 		/* If req is NULL, it means an error occurred, but
53 		 * sadly we are mostly left guessing what the error
54 		 * might have been.  We'll do our best... */
55 		struct bufferevent *bev = (struct bufferevent *) ctx;
56 		unsigned long oslerr;
57 		int printed_err = 0;
58 		int errcode = EVUTIL_SOCKET_ERROR();
59 		fprintf(stderr, "some request failed - no idea which one though!\n");
60 		/* Print out the OpenSSL error queue that libevent
61 		 * squirreled away for us, if any. */
62 		while ((oslerr = bufferevent_get_openssl_error(bev))) {
63 			ERR_error_string_n(oslerr, buffer, sizeof(buffer));
64 			fprintf(stderr, "%s\n", buffer);
65 			printed_err = 1;
66 		}
67 		/* If the OpenSSL error queue was empty, maybe it was a
68 		 * socket error; let's try printing that. */
69 		if (! printed_err)
70 			fprintf(stderr, "socket error = %s (%d)\n",
71 				evutil_socket_error_to_string(errcode),
72 				errcode);
73 		return;
74 	}
75 
76 	fprintf(stderr, "Response line: %d %s\n",
77 	    evhttp_request_get_response_code(req),
78 	    evhttp_request_get_response_code_line(req));
79 
80 	while ((nread = evbuffer_remove(evhttp_request_get_input_buffer(req),
81 		    buffer, sizeof(buffer)))
82 	       > 0) {
83 		/* These are just arbitrary chunks of 256 bytes.
84 		 * They are not lines, so we can't treat them as such. */
85 		fwrite(buffer, nread, 1, stdout);
86 	}
87 }
88 
89 static void
90 syntax(void)
91 {
92 	fputs("Syntax:\n", stderr);
93 	fputs("   https-client <https-url>\n", stderr);
94 	fputs("Example:\n", stderr);
95 	fputs("   https-client https://ip.appspot.com/\n", stderr);
96 
97 	exit(1);
98 }
99 
100 static void
101 die(const char *msg)
102 {
103 	fputs(msg, stderr);
104 	exit(1);
105 }
106 
107 static void
108 die_openssl(const char *func)
109 {
110 	fprintf (stderr, "%s failed:\n", func);
111 
112 	/* This is the OpenSSL function that prints the contents of the
113 	 * error stack to the specified file handle. */
114 	ERR_print_errors_fp (stderr);
115 
116 	exit(1);
117 }
118 
119 /* See http://archives.seul.org/libevent/users/Jan-2013/msg00039.html */
120 static int cert_verify_callback(X509_STORE_CTX *x509_ctx, void *arg)
121 {
122 	char cert_str[256];
123 	const char *host = (const char *) arg;
124 	const char *res_str = "X509_verify_cert failed";
125 	HostnameValidationResult res = Error;
126 
127 	/* This is the function that OpenSSL would call if we hadn't called
128 	 * SSL_CTX_set_cert_verify_callback().  Therefore, we are "wrapping"
129 	 * the default functionality, rather than replacing it. */
130 	int ok_so_far = X509_verify_cert(x509_ctx);
131 
132 	X509 *server_cert = X509_STORE_CTX_get_current_cert(x509_ctx);
133 
134 	if (ok_so_far) {
135 		res = validate_hostname(host, server_cert);
136 
137 		switch (res) {
138 		case MatchFound:
139 			res_str = "MatchFound";
140 			break;
141 		case MatchNotFound:
142 			res_str = "MatchNotFound";
143 			break;
144 		case NoSANPresent:
145 			res_str = "NoSANPresent";
146 			break;
147 		case MalformedCertificate:
148 			res_str = "MalformedCertificate";
149 			break;
150 		case Error:
151 			res_str = "Error";
152 			break;
153 		default:
154 			res_str = "WTF!";
155 			break;
156 		}
157 	}
158 
159 	X509_NAME_oneline(X509_get_subject_name (server_cert),
160 			  cert_str, sizeof (cert_str));
161 
162 	if (res == MatchFound) {
163 		printf("https server '%s' has this certificate, "
164 		       "which looks good to me:\n%s\n",
165 		       host, cert_str);
166 		return 1;
167 	} else {
168 		printf("Got '%s' for hostname '%s' and certificate:\n%s\n",
169 		       res_str, host, cert_str);
170 		return 0;
171 	}
172 }
173 
174 int
175 main(int argc, char **argv)
176 {
177 	int r;
178 
179 	struct evhttp_uri *http_uri;
180 	const char *url, *scheme, *host, *path, *query;
181 	char uri[256];
182 	int port;
183 
184 	SSL_CTX *ssl_ctx;
185 	SSL *ssl;
186 	struct bufferevent *bev;
187 	struct evhttp_connection *evcon;
188 	struct evhttp_request *req;
189 	struct evkeyvalq *output_headers;
190 
191 	if (argc != 2)
192 		syntax();
193 
194 #ifdef WIN32
195 	{
196 		WORD wVersionRequested;
197 		WSADATA wsaData;
198 		int err;
199 
200 		wVersionRequested = MAKEWORD(2, 2);
201 
202 		err = WSAStartup(wVersionRequested, &wsaData);
203 		if (err != 0) {
204 			printf("WSAStartup failed with error: %d\n", err);
205 			return 1;
206 		}
207 	}
208 #endif
209 
210 	url = argv[1];
211 	http_uri = evhttp_uri_parse(url);
212 	if (http_uri == NULL) {
213 		die("malformed url");
214 	}
215 
216 	scheme = evhttp_uri_get_scheme(http_uri);
217 	if (scheme == NULL || (strcasecmp(scheme, "https") != 0 &&
218 	                       strcasecmp(scheme, "http") != 0)) {
219 		die("url must be http or https");
220 	}
221 
222 	host = evhttp_uri_get_host(http_uri);
223 	if (host == NULL) {
224 		die("url must have a host");
225 	}
226 
227 	port = evhttp_uri_get_port(http_uri);
228 	if (port == -1) {
229 		port = (strcasecmp(scheme, "http") == 0) ? 80 : 443;
230 	}
231 
232 	path = evhttp_uri_get_path(http_uri);
233 	if (path == NULL) {
234 		path = "/";
235 	}
236 
237 	query = evhttp_uri_get_query(http_uri);
238 	if (query == NULL) {
239 		snprintf(uri, sizeof(uri) - 1, "%s", path);
240 	} else {
241 		snprintf(uri, sizeof(uri) - 1, "%s?%s", path, query);
242 	}
243 	uri[sizeof(uri) - 1] = '\0';
244 
245 	// Initialize OpenSSL
246 	SSL_library_init();
247 	ERR_load_crypto_strings();
248 	SSL_load_error_strings();
249 	OpenSSL_add_all_algorithms();
250 
251 	/* This isn't strictly necessary... OpenSSL performs RAND_poll
252 	 * automatically on first use of random number generator. */
253 	r = RAND_poll();
254 	if (r == 0) {
255 		die_openssl("RAND_poll");
256 	}
257 
258 	/* Create a new OpenSSL context */
259 	ssl_ctx = SSL_CTX_new(SSLv23_method());
260 	if (!ssl_ctx)
261 		die_openssl("SSL_CTX_new");
262 
263 	#ifndef WIN32
264 	/* TODO: Add certificate loading on Windows as well */
265 
266 	/* Attempt to use the system's trusted root certificates.
267 	 * (This path is only valid for Debian-based systems.) */
268 	if (1 != SSL_CTX_load_verify_locations(ssl_ctx,
269 					       "/etc/ssl/certs/ca-certificates.crt",
270 					       NULL))
271 		die_openssl("SSL_CTX_load_verify_locations");
272 	/* Ask OpenSSL to verify the server certificate.  Note that this
273 	 * does NOT include verifying that the hostname is correct.
274 	 * So, by itself, this means anyone with any legitimate
275 	 * CA-issued certificate for any website, can impersonate any
276 	 * other website in the world.  This is not good.  See "The
277 	 * Most Dangerous Code in the World" article at
278 	 * https://crypto.stanford.edu/~dabo/pubs/abstracts/ssl-client-bugs.html
279 	 */
280 	SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
281 	/* This is how we solve the problem mentioned in the previous
282 	 * comment.  We "wrap" OpenSSL's validation routine in our
283 	 * own routine, which also validates the hostname by calling
284 	 * the code provided by iSECPartners.  Note that even though
285 	 * the "Everything You've Always Wanted to Know About
286 	 * Certificate Validation With OpenSSL (But Were Afraid to
287 	 * Ask)" paper from iSECPartners says very explicitly not to
288 	 * call SSL_CTX_set_cert_verify_callback (at the bottom of
289 	 * page 2), what we're doing here is safe because our
290 	 * cert_verify_callback() calls X509_verify_cert(), which is
291 	 * OpenSSL's built-in routine which would have been called if
292 	 * we hadn't set the callback.  Therefore, we're just
293 	 * "wrapping" OpenSSL's routine, not replacing it. */
294 	SSL_CTX_set_cert_verify_callback (ssl_ctx, cert_verify_callback,
295 					  (void *) host);
296 	#endif // not WIN32
297 
298 	// Create event base
299 	base = event_base_new();
300 	if (!base) {
301 		perror("event_base_new()");
302 		return 1;
303 	}
304 
305 	// Create OpenSSL bufferevent and stack evhttp on top of it
306 	ssl = SSL_new(ssl_ctx);
307 	if (ssl == NULL) {
308 		die_openssl("SSL_new()");
309 	}
310 
311 	if (strcasecmp(scheme, "http") == 0) {
312 		bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
313 	} else {
314 		bev = bufferevent_openssl_socket_new(base, -1, ssl,
315 			BUFFEREVENT_SSL_CONNECTING,
316 			BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS);
317 	}
318 
319 	if (bev == NULL) {
320 		fprintf(stderr, "bufferevent_openssl_socket_new() failed\n");
321 		return 1;
322 	}
323 
324 	bufferevent_openssl_set_allow_dirty_shutdown(bev, 1);
325 
326 	// For simplicity, we let DNS resolution block. Everything else should be
327 	// asynchronous though.
328 	evcon = evhttp_connection_base_bufferevent_new(base, NULL, bev,
329 		host, port);
330 	if (evcon == NULL) {
331 		fprintf(stderr, "evhttp_connection_base_bufferevent_new() failed\n");
332 		return 1;
333 	}
334 
335 	// Fire off the request
336 	req = evhttp_request_new(http_request_done, bev);
337 	if (req == NULL) {
338 		fprintf(stderr, "evhttp_request_new() failed\n");
339 		return 1;
340 	}
341 
342 	output_headers = evhttp_request_get_output_headers(req);
343 	evhttp_add_header(output_headers, "Host", host);
344 	evhttp_add_header(output_headers, "Connection", "close");
345 
346 	r = evhttp_make_request(evcon, req, EVHTTP_REQ_GET, uri);
347 	if (r != 0) {
348 		fprintf(stderr, "evhttp_make_request() failed\n");
349 		return 1;
350 	}
351 
352 	event_base_dispatch(base);
353 
354 	evhttp_connection_free(evcon);
355 	event_base_free(base);
356 
357 #ifdef WIN32
358 	WSACleanup();
359 #endif
360 
361 	return 0;
362 }
363