https-client.c revision 1.1.1.1 1 1.1 christos /* $NetBSD: https-client.c,v 1.1.1.1 2017/01/31 21:14:53 christos Exp $ */
2 1.1 christos /*
3 1.1 christos This is an example of how to hook up evhttp with bufferevent_ssl
4 1.1 christos
5 1.1 christos It just GETs an https URL given on the command-line and prints the response
6 1.1 christos body to stdout.
7 1.1 christos
8 1.1 christos Actually, it also accepts plain http URLs to make it easy to compare http vs
9 1.1 christos https code paths.
10 1.1 christos
11 1.1 christos Loosely based on le-proxy.c.
12 1.1 christos */
13 1.1 christos
14 1.1 christos // Get rid of OSX 10.7 and greater deprecation warnings.
15 1.1 christos #if defined(__APPLE__) && defined(__clang__)
16 1.1 christos #pragma clang diagnostic ignored "-Wdeprecated-declarations"
17 1.1 christos #endif
18 1.1 christos
19 1.1 christos #include <stdio.h>
20 1.1 christos #include <assert.h>
21 1.1 christos #include <stdlib.h>
22 1.1 christos #include <string.h>
23 1.1 christos #include <errno.h>
24 1.1 christos
25 1.1 christos #ifdef _WIN32
26 1.1 christos #include <winsock2.h>
27 1.1 christos #include <ws2tcpip.h>
28 1.1 christos
29 1.1 christos #define snprintf _snprintf
30 1.1 christos #define strcasecmp _stricmp
31 1.1 christos #else
32 1.1 christos #include <sys/socket.h>
33 1.1 christos #include <netinet/in.h>
34 1.1 christos #endif
35 1.1 christos
36 1.1 christos #include <event2/bufferevent_ssl.h>
37 1.1 christos #include <event2/bufferevent.h>
38 1.1 christos #include <event2/buffer.h>
39 1.1 christos #include <event2/listener.h>
40 1.1 christos #include <event2/util.h>
41 1.1 christos #include <event2/http.h>
42 1.1 christos
43 1.1 christos #include <openssl/ssl.h>
44 1.1 christos #include <openssl/err.h>
45 1.1 christos #include <openssl/rand.h>
46 1.1 christos
47 1.1 christos #include "openssl_hostname_validation.h"
48 1.1 christos
49 1.1 christos static struct event_base *base;
50 1.1 christos static int ignore_cert = 0;
51 1.1 christos
52 1.1 christos static void
53 1.1 christos http_request_done(struct evhttp_request *req, void *ctx)
54 1.1 christos {
55 1.1 christos char buffer[256];
56 1.1 christos int nread;
57 1.1 christos
58 1.1 christos if (req == NULL) {
59 1.1 christos /* If req is NULL, it means an error occurred, but
60 1.1 christos * sadly we are mostly left guessing what the error
61 1.1 christos * might have been. We'll do our best... */
62 1.1 christos struct bufferevent *bev = (struct bufferevent *) ctx;
63 1.1 christos unsigned long oslerr;
64 1.1 christos int printed_err = 0;
65 1.1 christos int errcode = EVUTIL_SOCKET_ERROR();
66 1.1 christos fprintf(stderr, "some request failed - no idea which one though!\n");
67 1.1 christos /* Print out the OpenSSL error queue that libevent
68 1.1 christos * squirreled away for us, if any. */
69 1.1 christos while ((oslerr = bufferevent_get_openssl_error(bev))) {
70 1.1 christos ERR_error_string_n(oslerr, buffer, sizeof(buffer));
71 1.1 christos fprintf(stderr, "%s\n", buffer);
72 1.1 christos printed_err = 1;
73 1.1 christos }
74 1.1 christos /* If the OpenSSL error queue was empty, maybe it was a
75 1.1 christos * socket error; let's try printing that. */
76 1.1 christos if (! printed_err)
77 1.1 christos fprintf(stderr, "socket error = %s (%d)\n",
78 1.1 christos evutil_socket_error_to_string(errcode),
79 1.1 christos errcode);
80 1.1 christos return;
81 1.1 christos }
82 1.1 christos
83 1.1 christos fprintf(stderr, "Response line: %d %s\n",
84 1.1 christos evhttp_request_get_response_code(req),
85 1.1 christos evhttp_request_get_response_code_line(req));
86 1.1 christos
87 1.1 christos while ((nread = evbuffer_remove(evhttp_request_get_input_buffer(req),
88 1.1 christos buffer, sizeof(buffer)))
89 1.1 christos > 0) {
90 1.1 christos /* These are just arbitrary chunks of 256 bytes.
91 1.1 christos * They are not lines, so we can't treat them as such. */
92 1.1 christos fwrite(buffer, nread, 1, stdout);
93 1.1 christos }
94 1.1 christos }
95 1.1 christos
96 1.1 christos static void
97 1.1 christos syntax(void)
98 1.1 christos {
99 1.1 christos fputs("Syntax:\n", stderr);
100 1.1 christos fputs(" https-client -url <https-url> [-data data-file.bin] [-ignore-cert] [-retries num] [-timeout sec] [-crt crt]\n", stderr);
101 1.1 christos fputs("Example:\n", stderr);
102 1.1 christos fputs(" https-client -url https://ip.appspot.com/\n", stderr);
103 1.1 christos }
104 1.1 christos
105 1.1 christos static void
106 1.1 christos err(const char *msg)
107 1.1 christos {
108 1.1 christos fputs(msg, stderr);
109 1.1 christos }
110 1.1 christos
111 1.1 christos static void
112 1.1 christos err_openssl(const char *func)
113 1.1 christos {
114 1.1 christos fprintf (stderr, "%s failed:\n", func);
115 1.1 christos
116 1.1 christos /* This is the OpenSSL function that prints the contents of the
117 1.1 christos * error stack to the specified file handle. */
118 1.1 christos ERR_print_errors_fp (stderr);
119 1.1 christos
120 1.1 christos exit(1);
121 1.1 christos }
122 1.1 christos
123 1.1 christos #ifndef _WIN32
124 1.1 christos /* See http://archives.seul.org/libevent/users/Jan-2013/msg00039.html */
125 1.1 christos static int cert_verify_callback(X509_STORE_CTX *x509_ctx, void *arg)
126 1.1 christos {
127 1.1 christos char cert_str[256];
128 1.1 christos const char *host = (const char *) arg;
129 1.1 christos const char *res_str = "X509_verify_cert failed";
130 1.1 christos HostnameValidationResult res = Error;
131 1.1 christos
132 1.1 christos /* This is the function that OpenSSL would call if we hadn't called
133 1.1 christos * SSL_CTX_set_cert_verify_callback(). Therefore, we are "wrapping"
134 1.1 christos * the default functionality, rather than replacing it. */
135 1.1 christos int ok_so_far = 0;
136 1.1 christos
137 1.1 christos X509 *server_cert = NULL;
138 1.1 christos
139 1.1 christos if (ignore_cert) {
140 1.1 christos return 1;
141 1.1 christos }
142 1.1 christos
143 1.1 christos ok_so_far = X509_verify_cert(x509_ctx);
144 1.1 christos
145 1.1 christos server_cert = X509_STORE_CTX_get_current_cert(x509_ctx);
146 1.1 christos
147 1.1 christos if (ok_so_far) {
148 1.1 christos res = validate_hostname(host, server_cert);
149 1.1 christos
150 1.1 christos switch (res) {
151 1.1 christos case MatchFound:
152 1.1 christos res_str = "MatchFound";
153 1.1 christos break;
154 1.1 christos case MatchNotFound:
155 1.1 christos res_str = "MatchNotFound";
156 1.1 christos break;
157 1.1 christos case NoSANPresent:
158 1.1 christos res_str = "NoSANPresent";
159 1.1 christos break;
160 1.1 christos case MalformedCertificate:
161 1.1 christos res_str = "MalformedCertificate";
162 1.1 christos break;
163 1.1 christos case Error:
164 1.1 christos res_str = "Error";
165 1.1 christos break;
166 1.1 christos default:
167 1.1 christos res_str = "WTF!";
168 1.1 christos break;
169 1.1 christos }
170 1.1 christos }
171 1.1 christos
172 1.1 christos X509_NAME_oneline(X509_get_subject_name (server_cert),
173 1.1 christos cert_str, sizeof (cert_str));
174 1.1 christos
175 1.1 christos if (res == MatchFound) {
176 1.1 christos printf("https server '%s' has this certificate, "
177 1.1 christos "which looks good to me:\n%s\n",
178 1.1 christos host, cert_str);
179 1.1 christos return 1;
180 1.1 christos } else {
181 1.1 christos printf("Got '%s' for hostname '%s' and certificate:\n%s\n",
182 1.1 christos res_str, host, cert_str);
183 1.1 christos return 0;
184 1.1 christos }
185 1.1 christos }
186 1.1 christos #endif
187 1.1 christos
188 1.1 christos int
189 1.1 christos main(int argc, char **argv)
190 1.1 christos {
191 1.1 christos int r;
192 1.1 christos
193 1.1 christos struct evhttp_uri *http_uri = NULL;
194 1.1 christos const char *url = NULL, *data_file = NULL;
195 1.1 christos const char *crt = "/etc/ssl/certs/ca-certificates.crt";
196 1.1 christos const char *scheme, *host, *path, *query;
197 1.1 christos char uri[256];
198 1.1 christos int port;
199 1.1 christos int retries = 0;
200 1.1 christos int timeout = -1;
201 1.1 christos
202 1.1 christos SSL_CTX *ssl_ctx = NULL;
203 1.1 christos SSL *ssl = NULL;
204 1.1 christos struct bufferevent *bev;
205 1.1 christos struct evhttp_connection *evcon = NULL;
206 1.1 christos struct evhttp_request *req;
207 1.1 christos struct evkeyvalq *output_headers;
208 1.1 christos struct evbuffer *output_buffer;
209 1.1 christos
210 1.1 christos int i;
211 1.1 christos int ret = 0;
212 1.1 christos enum { HTTP, HTTPS } type = HTTP;
213 1.1 christos
214 1.1 christos for (i = 1; i < argc; i++) {
215 1.1 christos if (!strcmp("-url", argv[i])) {
216 1.1 christos if (i < argc - 1) {
217 1.1 christos url = argv[i + 1];
218 1.1 christos } else {
219 1.1 christos syntax();
220 1.1 christos goto error;
221 1.1 christos }
222 1.1 christos } else if (!strcmp("-crt", argv[i])) {
223 1.1 christos if (i < argc - 1) {
224 1.1 christos crt = argv[i + 1];
225 1.1 christos } else {
226 1.1 christos syntax();
227 1.1 christos goto error;
228 1.1 christos }
229 1.1 christos } else if (!strcmp("-ignore-cert", argv[i])) {
230 1.1 christos ignore_cert = 1;
231 1.1 christos } else if (!strcmp("-data", argv[i])) {
232 1.1 christos if (i < argc - 1) {
233 1.1 christos data_file = argv[i + 1];
234 1.1 christos } else {
235 1.1 christos syntax();
236 1.1 christos goto error;
237 1.1 christos }
238 1.1 christos } else if (!strcmp("-retries", argv[i])) {
239 1.1 christos if (i < argc - 1) {
240 1.1 christos retries = atoi(argv[i + 1]);
241 1.1 christos } else {
242 1.1 christos syntax();
243 1.1 christos goto error;
244 1.1 christos }
245 1.1 christos } else if (!strcmp("-timeout", argv[i])) {
246 1.1 christos if (i < argc - 1) {
247 1.1 christos timeout = atoi(argv[i + 1]);
248 1.1 christos } else {
249 1.1 christos syntax();
250 1.1 christos goto error;
251 1.1 christos }
252 1.1 christos } else if (!strcmp("-help", argv[i])) {
253 1.1 christos syntax();
254 1.1 christos goto error;
255 1.1 christos }
256 1.1 christos }
257 1.1 christos
258 1.1 christos if (!url) {
259 1.1 christos syntax();
260 1.1 christos goto error;
261 1.1 christos }
262 1.1 christos
263 1.1 christos #ifdef _WIN32
264 1.1 christos {
265 1.1 christos WORD wVersionRequested;
266 1.1 christos WSADATA wsaData;
267 1.1 christos int err;
268 1.1 christos
269 1.1 christos wVersionRequested = MAKEWORD(2, 2);
270 1.1 christos
271 1.1 christos err = WSAStartup(wVersionRequested, &wsaData);
272 1.1 christos if (err != 0) {
273 1.1 christos printf("WSAStartup failed with error: %d\n", err);
274 1.1 christos goto error;
275 1.1 christos }
276 1.1 christos }
277 1.1 christos #endif // _WIN32
278 1.1 christos
279 1.1 christos http_uri = evhttp_uri_parse(url);
280 1.1 christos if (http_uri == NULL) {
281 1.1 christos err("malformed url");
282 1.1 christos goto error;
283 1.1 christos }
284 1.1 christos
285 1.1 christos scheme = evhttp_uri_get_scheme(http_uri);
286 1.1 christos if (scheme == NULL || (strcasecmp(scheme, "https") != 0 &&
287 1.1 christos strcasecmp(scheme, "http") != 0)) {
288 1.1 christos err("url must be http or https");
289 1.1 christos goto error;
290 1.1 christos }
291 1.1 christos
292 1.1 christos host = evhttp_uri_get_host(http_uri);
293 1.1 christos if (host == NULL) {
294 1.1 christos err("url must have a host");
295 1.1 christos goto error;
296 1.1 christos }
297 1.1 christos
298 1.1 christos port = evhttp_uri_get_port(http_uri);
299 1.1 christos if (port == -1) {
300 1.1 christos port = (strcasecmp(scheme, "http") == 0) ? 80 : 443;
301 1.1 christos }
302 1.1 christos
303 1.1 christos path = evhttp_uri_get_path(http_uri);
304 1.1 christos if (strlen(path) == 0) {
305 1.1 christos path = "/";
306 1.1 christos }
307 1.1 christos
308 1.1 christos query = evhttp_uri_get_query(http_uri);
309 1.1 christos if (query == NULL) {
310 1.1 christos snprintf(uri, sizeof(uri) - 1, "%s", path);
311 1.1 christos } else {
312 1.1 christos snprintf(uri, sizeof(uri) - 1, "%s?%s", path, query);
313 1.1 christos }
314 1.1 christos uri[sizeof(uri) - 1] = '\0';
315 1.1 christos
316 1.1 christos #if OPENSSL_VERSION_NUMBER < 0x10100000L
317 1.1 christos // Initialize OpenSSL
318 1.1 christos SSL_library_init();
319 1.1 christos ERR_load_crypto_strings();
320 1.1 christos SSL_load_error_strings();
321 1.1 christos OpenSSL_add_all_algorithms();
322 1.1 christos #endif
323 1.1 christos
324 1.1 christos /* This isn't strictly necessary... OpenSSL performs RAND_poll
325 1.1 christos * automatically on first use of random number generator. */
326 1.1 christos r = RAND_poll();
327 1.1 christos if (r == 0) {
328 1.1 christos err_openssl("RAND_poll");
329 1.1 christos goto error;
330 1.1 christos }
331 1.1 christos
332 1.1 christos /* Create a new OpenSSL context */
333 1.1 christos ssl_ctx = SSL_CTX_new(SSLv23_method());
334 1.1 christos if (!ssl_ctx) {
335 1.1 christos err_openssl("SSL_CTX_new");
336 1.1 christos goto error;
337 1.1 christos }
338 1.1 christos
339 1.1 christos #ifndef _WIN32
340 1.1 christos /* TODO: Add certificate loading on Windows as well */
341 1.1 christos
342 1.1 christos /* Attempt to use the system's trusted root certificates.
343 1.1 christos * (This path is only valid for Debian-based systems.) */
344 1.1 christos if (1 != SSL_CTX_load_verify_locations(ssl_ctx, crt, NULL)) {
345 1.1 christos err_openssl("SSL_CTX_load_verify_locations");
346 1.1 christos goto error;
347 1.1 christos }
348 1.1 christos /* Ask OpenSSL to verify the server certificate. Note that this
349 1.1 christos * does NOT include verifying that the hostname is correct.
350 1.1 christos * So, by itself, this means anyone with any legitimate
351 1.1 christos * CA-issued certificate for any website, can impersonate any
352 1.1 christos * other website in the world. This is not good. See "The
353 1.1 christos * Most Dangerous Code in the World" article at
354 1.1 christos * https://crypto.stanford.edu/~dabo/pubs/abstracts/ssl-client-bugs.html
355 1.1 christos */
356 1.1 christos SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
357 1.1 christos /* This is how we solve the problem mentioned in the previous
358 1.1 christos * comment. We "wrap" OpenSSL's validation routine in our
359 1.1 christos * own routine, which also validates the hostname by calling
360 1.1 christos * the code provided by iSECPartners. Note that even though
361 1.1 christos * the "Everything You've Always Wanted to Know About
362 1.1 christos * Certificate Validation With OpenSSL (But Were Afraid to
363 1.1 christos * Ask)" paper from iSECPartners says very explicitly not to
364 1.1 christos * call SSL_CTX_set_cert_verify_callback (at the bottom of
365 1.1 christos * page 2), what we're doing here is safe because our
366 1.1 christos * cert_verify_callback() calls X509_verify_cert(), which is
367 1.1 christos * OpenSSL's built-in routine which would have been called if
368 1.1 christos * we hadn't set the callback. Therefore, we're just
369 1.1 christos * "wrapping" OpenSSL's routine, not replacing it. */
370 1.1 christos SSL_CTX_set_cert_verify_callback(ssl_ctx, cert_verify_callback,
371 1.1 christos (void *) host);
372 1.1 christos #else // _WIN32
373 1.1 christos (void)crt;
374 1.1 christos #endif // _WIN32
375 1.1 christos
376 1.1 christos // Create event base
377 1.1 christos base = event_base_new();
378 1.1 christos if (!base) {
379 1.1 christos perror("event_base_new()");
380 1.1 christos goto error;
381 1.1 christos }
382 1.1 christos
383 1.1 christos // Create OpenSSL bufferevent and stack evhttp on top of it
384 1.1 christos ssl = SSL_new(ssl_ctx);
385 1.1 christos if (ssl == NULL) {
386 1.1 christos err_openssl("SSL_new()");
387 1.1 christos goto error;
388 1.1 christos }
389 1.1 christos
390 1.1 christos #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
391 1.1 christos // Set hostname for SNI extension
392 1.1 christos SSL_set_tlsext_host_name(ssl, host);
393 1.1 christos #endif
394 1.1 christos
395 1.1 christos if (strcasecmp(scheme, "http") == 0) {
396 1.1 christos bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
397 1.1 christos } else {
398 1.1 christos type = HTTPS;
399 1.1 christos bev = bufferevent_openssl_socket_new(base, -1, ssl,
400 1.1 christos BUFFEREVENT_SSL_CONNECTING,
401 1.1 christos BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS);
402 1.1 christos }
403 1.1 christos
404 1.1 christos if (bev == NULL) {
405 1.1 christos fprintf(stderr, "bufferevent_openssl_socket_new() failed\n");
406 1.1 christos goto error;
407 1.1 christos }
408 1.1 christos
409 1.1 christos bufferevent_openssl_set_allow_dirty_shutdown(bev, 1);
410 1.1 christos
411 1.1 christos // For simplicity, we let DNS resolution block. Everything else should be
412 1.1 christos // asynchronous though.
413 1.1 christos evcon = evhttp_connection_base_bufferevent_new(base, NULL, bev,
414 1.1 christos host, port);
415 1.1 christos if (evcon == NULL) {
416 1.1 christos fprintf(stderr, "evhttp_connection_base_bufferevent_new() failed\n");
417 1.1 christos goto error;
418 1.1 christos }
419 1.1 christos
420 1.1 christos if (retries > 0) {
421 1.1 christos evhttp_connection_set_retries(evcon, retries);
422 1.1 christos }
423 1.1 christos if (timeout >= 0) {
424 1.1 christos evhttp_connection_set_timeout(evcon, timeout);
425 1.1 christos }
426 1.1 christos
427 1.1 christos // Fire off the request
428 1.1 christos req = evhttp_request_new(http_request_done, bev);
429 1.1 christos if (req == NULL) {
430 1.1 christos fprintf(stderr, "evhttp_request_new() failed\n");
431 1.1 christos goto error;
432 1.1 christos }
433 1.1 christos
434 1.1 christos output_headers = evhttp_request_get_output_headers(req);
435 1.1 christos evhttp_add_header(output_headers, "Host", host);
436 1.1 christos evhttp_add_header(output_headers, "Connection", "close");
437 1.1 christos
438 1.1 christos if (data_file) {
439 1.1 christos /* NOTE: In production code, you'd probably want to use
440 1.1 christos * evbuffer_add_file() or evbuffer_add_file_segment(), to
441 1.1 christos * avoid needless copying. */
442 1.1 christos FILE * f = fopen(data_file, "rb");
443 1.1 christos char buf[1024];
444 1.1 christos size_t s;
445 1.1 christos size_t bytes = 0;
446 1.1 christos
447 1.1 christos if (!f) {
448 1.1 christos syntax();
449 1.1 christos goto error;
450 1.1 christos }
451 1.1 christos
452 1.1 christos output_buffer = evhttp_request_get_output_buffer(req);
453 1.1 christos while ((s = fread(buf, 1, sizeof(buf), f)) > 0) {
454 1.1 christos evbuffer_add(output_buffer, buf, s);
455 1.1 christos bytes += s;
456 1.1 christos }
457 1.1 christos evutil_snprintf(buf, sizeof(buf)-1, "%lu", (unsigned long)bytes);
458 1.1 christos evhttp_add_header(output_headers, "Content-Length", buf);
459 1.1 christos fclose(f);
460 1.1 christos }
461 1.1 christos
462 1.1 christos r = evhttp_make_request(evcon, req, data_file ? EVHTTP_REQ_POST : EVHTTP_REQ_GET, uri);
463 1.1 christos if (r != 0) {
464 1.1 christos fprintf(stderr, "evhttp_make_request() failed\n");
465 1.1 christos goto error;
466 1.1 christos }
467 1.1 christos
468 1.1 christos event_base_dispatch(base);
469 1.1 christos goto cleanup;
470 1.1 christos
471 1.1 christos error:
472 1.1 christos ret = 1;
473 1.1 christos cleanup:
474 1.1 christos if (evcon)
475 1.1 christos evhttp_connection_free(evcon);
476 1.1 christos if (http_uri)
477 1.1 christos evhttp_uri_free(http_uri);
478 1.1 christos event_base_free(base);
479 1.1 christos
480 1.1 christos if (ssl_ctx)
481 1.1 christos SSL_CTX_free(ssl_ctx);
482 1.1 christos if (type == HTTP && ssl)
483 1.1 christos SSL_free(ssl);
484 1.1 christos #if OPENSSL_VERSION_NUMBER < 0x10100000L
485 1.1 christos EVP_cleanup();
486 1.1 christos ERR_free_strings();
487 1.1 christos
488 1.1 christos #ifdef EVENT__HAVE_ERR_REMOVE_THREAD_STATE
489 1.1 christos ERR_remove_thread_state(NULL);
490 1.1 christos #else
491 1.1 christos ERR_remove_state(0);
492 1.1 christos #endif
493 1.1 christos CRYPTO_cleanup_all_ex_data();
494 1.1 christos
495 1.1 christos sk_SSL_COMP_free(SSL_COMP_get_compression_methods());
496 1.1 christos #endif /*OPENSSL_VERSION_NUMBER < 0x10100000L */
497 1.1 christos
498 1.1 christos #ifdef _WIN32
499 1.1 christos WSACleanup();
500 1.1 christos #endif
501 1.1 christos
502 1.1 christos return ret;
503 1.1 christos }
504