1 /*
2 * Copyright 2024 The OpenSSL Project Authors. All Rights Reserved.
3 *
4 * Licensed under the Apache License 2.0 (the "License"). You may not use
5 * this file except in compliance with the License. You can obtain a copy
6 * in the file LICENSE in the source distribution or at
7 * https://www.openssl.org/source/license.html
8 */
9
10 /**
11 * @file quic-hq-interop.c
12 * @brief QUIC client interoperability demo using OpenSSL.
13 *
14 * This file contains the implementation of a QUIC client that demonstrates
15 * interoperability with hq-interop servers. It handles connection setup,
16 * session caching, key logging, and sending HTTP GET requests over QUIC.
17 *
18 * The file includes functions for setting up SSL/TLS contexts, managing session
19 * caches, and handling I/O failures. It supports both IPv4 and IPv6 connections
20 * and uses non-blocking mode for QUIC operations.
21 *
22 * The client sends HTTP/1.0 GET requests for specified files and saves the
23 * responses to disk. It demonstrates OpenSSL's QUIC API, including ALPN protocol
24 * settings, peer address management, and SSL handshake and shutdown processes.
25 *
26 * @note This client is intended for demonstration purposes and may require
27 * additional error handling and robustness improvements for production use.
28 *
29 * USAGE
30 * quic-hq-interop <host> <port> <reqfile>
31 * host - The hostname of the server to contact
32 * port - The port that the server is listening on
33 * reqfile - a text file containing a space separated list of paths to fetch
34 * via http 1.0 GET requests
35 *
36 * ENVIRONMENT VARIABLES
37 * SSLKEYLOGFILE - set to a file path to record keylog exchange with server
38 * SSL_SESSION_FILE - set to a file path to record ssl sessions and restore
39 * said sessions on next invocation
40 * SSL_CERT_FILE - The ca file to use when validating a server
41 * SSL_CIPHER_SUITES - The list of cipher suites to use (see openssl-ciphers)
42 */
43 #include <string.h>
44
45 /* Include the appropriate header file for SOCK_DGRAM */
46 #ifdef _WIN32 /* Windows */
47 # include <winsock2.h>
48 #else /* Linux/Unix */
49 # include <sys/socket.h>
50 # include <sys/select.h>
51 #endif
52
53 #include <openssl/bio.h>
54 #include <openssl/ssl.h>
55 #include <openssl/err.h>
56
57 static int handle_io_failure(SSL *ssl, int res);
58
59 #define REQ_STRING_SZ 1024
60
61 /**
62 * @brief A static pointer to a BIO object representing the session's BIO.
63 *
64 * This variable holds a reference to a BIO object used for network
65 * communication during the session. It is initialized to NULL and should
66 * be assigned a valid BIO object before use. The BIO object pointed to by
67 * this variable may be used throughout the session for reading and writing
68 * data.
69 *
70 * @note This variable is static, meaning it is only accessible within the
71 * file in which it is declared.
72 */
73 static BIO *session_bio = NULL;
74
75 /**
76 * @brief Creates a BIO object for a UDP socket connection to a server.
77 *
78 * This function attempts to create a UDP socket and connect it to the server
79 * specified by the given hostname and port. The socket is wrapped in a BIO
80 * object, which allows for network communication via OpenSSL's BIO API.
81 * The function also returns the address of the connected peer.
82 *
83 * @param hostname The hostname of the server to connect to.
84 * @param port The port number of the server to connect to.
85 * @param family The desired address family (e.g., AF_INET for IPv4,
86 * AF_INET6 for IPv6).
87 * @param peer_addr A pointer to a BIO_ADDR pointer that will hold the address
88 * of the connected peer on success. The caller is responsible
89 * for freeing this memory using BIO_ADDR_free().
90 * @return A pointer to a BIO object on success, or NULL on failure.
91 * The returned BIO object will be associated with the connected socket.
92 * If the BIO object is successfully created, it will take ownership of
93 * the socket and automatically close it when the BIO is freed.
94 *
95 * @note The function uses OpenSSL's socket-related functions (e.g., BIO_socket,
96 * BIO_connect) or portability and to integrate with OpenSSL's error
97 * handling mechanisms.
98 * @note If no valid connection is established, the function returns NULL and
99 * ensures that any resources allocated during the process are properly
100 * freed.
101 */
create_socket_bio(const char * hostname,const char * port,int family,BIO_ADDR ** peer_addr)102 static BIO *create_socket_bio(const char *hostname, const char *port,
103 int family, BIO_ADDR **peer_addr)
104 {
105 int sock = -1;
106 BIO_ADDRINFO *res;
107 const BIO_ADDRINFO *ai = NULL;
108 BIO *bio;
109
110 /*
111 * Lookup IP address info for the server.
112 */
113 if (!BIO_lookup_ex(hostname, port, BIO_LOOKUP_CLIENT, family, SOCK_DGRAM, 0,
114 &res))
115 return NULL;
116
117 /*
118 * Loop through all the possible addresses for the server and find one
119 * we can connect to.
120 */
121 for (ai = res; ai != NULL; ai = BIO_ADDRINFO_next(ai)) {
122 /*
123 * Create a UDP socket. We could equally use non-OpenSSL calls such
124 * as "socket" here for this and the subsequent connect and close
125 * functions. But for portability reasons and also so that we get
126 * errors on the OpenSSL stack in the event of a failure we use
127 * OpenSSL's versions of these functions.
128 */
129 sock = BIO_socket(BIO_ADDRINFO_family(ai), SOCK_DGRAM, 0, 0);
130 if (sock == -1)
131 continue;
132
133 /* Connect the socket to the server's address */
134 if (!BIO_connect(sock, BIO_ADDRINFO_address(ai), 0)) {
135 BIO_closesocket(sock);
136 sock = -1;
137 continue;
138 }
139
140 /*
141 * Set to nonblocking mode
142 * Note: This function returns a range of errors
143 * <= 0 if something goes wrong, so catch them all here
144 */
145 if (BIO_socket_nbio(sock, 1) <= 0) {
146 BIO_closesocket(sock);
147 sock = -1;
148 continue;
149 }
150
151 break;
152 }
153
154 if (sock != -1) {
155 *peer_addr = BIO_ADDR_dup(BIO_ADDRINFO_address(ai));
156 if (*peer_addr == NULL) {
157 BIO_closesocket(sock);
158 return NULL;
159 }
160 }
161
162 /* Free the address information resources we allocated earlier */
163 BIO_ADDRINFO_free(res);
164
165 /* If sock is -1 then we've been unable to connect to the server */
166 if (sock == -1)
167 return NULL;
168
169 /* Create a BIO to wrap the socket */
170 bio = BIO_new(BIO_s_datagram());
171 if (bio == NULL) {
172 BIO_closesocket(sock);
173 return NULL;
174 }
175
176 /*
177 * Associate the newly created BIO with the underlying socket. By
178 * passing BIO_CLOSE here the socket will be automatically closed when
179 * the BIO is freed. Alternatively you can use BIO_NOCLOSE, in which
180 * case you must close the socket explicitly when it is no longer
181 * needed.
182 */
183 if (BIO_set_fd(bio, sock, BIO_CLOSE) <= 0) {
184 BIO_closesocket(sock);
185 BIO_free(bio);
186 return NULL;
187 }
188
189 return bio;
190 }
191
192 /**
193 * @brief Waits for activity on the SSL socket, either for reading or writing.
194 *
195 * This function monitors the underlying file descriptor of the given SSL
196 * connection to determine when it is ready for reading or writing, or both.
197 * It uses the select function to wait until the socket is either readable
198 * or writable, depending on what the SSL connection requires.
199 *
200 * @param ssl A pointer to the SSL object representing the connection.
201 *
202 * @note This function blocks until there is activity on the socket or
203 * until the timeout specified by OpenSSL is reached. In a real application,
204 * you might want to perform other tasks while waiting, such as updating a
205 * GUI or handling other connections.
206 *
207 * @note This function uses select for simplicity and portability. Depending
208 * on your application's requirements, you might consider using other
209 * mechanisms like poll or epoll for handling multiple file descriptors.
210 */
wait_for_activity(SSL * ssl)211 static void wait_for_activity(SSL *ssl)
212 {
213 fd_set wfds, rfds;
214 int width, sock, isinfinite;
215 struct timeval tv;
216 struct timeval *tvp = NULL;
217
218 /* Get hold of the underlying file descriptor for the socket */
219 sock = SSL_get_fd(ssl);
220
221 FD_ZERO(&wfds);
222 FD_ZERO(&rfds);
223
224 /*
225 * Find out if we would like to write to the socket, or read from it (or
226 * both)
227 */
228 if (SSL_net_write_desired(ssl))
229 FD_SET(sock, &wfds);
230 if (SSL_net_read_desired(ssl))
231 FD_SET(sock, &rfds);
232 width = sock + 1;
233
234 /*
235 * Find out when OpenSSL would next like to be called, regardless of
236 * whether the state of the underlying socket has changed or not.
237 */
238 if (SSL_get_event_timeout(ssl, &tv, &isinfinite) && !isinfinite)
239 tvp = &tv;
240
241 /*
242 * Wait until the socket is writeable or readable. We use select here
243 * for the sake of simplicity and portability, but you could equally use
244 * poll/epoll or similar functions
245 *
246 * NOTE: For the purposes of this demonstration code this effectively
247 * makes this demo block until it has something more useful to do. In a
248 * real application you probably want to go and do other work here (e.g.
249 * update a GUI, or service other connections).
250 *
251 * Let's say for example that you want to update the progress counter on
252 * a GUI every 100ms. One way to do that would be to use the timeout in
253 * the last parameter to "select" below. If the tvp value is greater
254 * than 100ms then use 100ms instead. Then, when select returns, you
255 * check if it did so because of activity on the file descriptors or
256 * because of the timeout. If the 100ms GUI timeout has expired but the
257 * tvp timeout has not then go and update the GUI and then restart the
258 * "select" (with updated timeouts).
259 */
260
261 select(width, &rfds, &wfds, NULL, tvp);
262 }
263
264 /**
265 * @brief Handles I/O failures on an SSL connection based on the result code.
266 *
267 * This function processes the result of an SSL I/O operation and handles
268 * different types of errors that may occur during the operation. It takes
269 * appropriate actions such as retrying the operation, reporting errors, or
270 * returning specific status codes based on the error type.
271 *
272 * @param ssl A pointer to the SSL object representing the connection.
273 * @param res The result code from the SSL I/O operation.
274 * @return An integer indicating the outcome:
275 * - 1: Temporary failure, the operation should be retried.
276 * - 0: EOF, indicating the connection has been closed.
277 * - -1: A fatal error occurred or the connection has been reset.
278 *
279 * @note This function may block if a temporary failure occurs and
280 * wait_for_activity() is called.
281 *
282 * @note If the failure is due to an SSL verification error, additional
283 * information will be logged to stderr.
284 */
handle_io_failure(SSL * ssl,int res)285 static int handle_io_failure(SSL *ssl, int res)
286 {
287 switch (SSL_get_error(ssl, res)) {
288 case SSL_ERROR_WANT_READ:
289 case SSL_ERROR_WANT_WRITE:
290 /* Temporary failure. Wait until we can read/write and try again */
291 wait_for_activity(ssl);
292 return 1;
293
294 case SSL_ERROR_ZERO_RETURN:
295 /* EOF */
296 return 0;
297
298 case SSL_ERROR_SYSCALL:
299 return -1;
300
301 case SSL_ERROR_SSL:
302 /*
303 * Some stream fatal error occurred. This could be because of a
304 * stream reset - or some failure occurred on the underlying
305 * connection.
306 */
307 switch (SSL_get_stream_read_state(ssl)) {
308 case SSL_STREAM_STATE_RESET_REMOTE:
309 fprintf(stderr, "Stream reset occurred\n");
310 /*
311 * The stream has been reset but the connection is still
312 * healthy.
313 */
314 break;
315
316 case SSL_STREAM_STATE_CONN_CLOSED:
317 fprintf(stderr, "Connection closed\n");
318 /* Connection is already closed. */
319 break;
320
321 default:
322 fprintf(stderr, "Unknown stream failure\n");
323 break;
324 }
325 /*
326 * If the failure is due to a verification error we can get more
327 * information about it from SSL_get_verify_result().
328 */
329 if (SSL_get_verify_result(ssl) != X509_V_OK)
330 fprintf(stderr, "Verify error: %s\n",
331 X509_verify_cert_error_string(SSL_get_verify_result(ssl)));
332 return -1;
333
334 default:
335 return -1;
336 }
337 }
338
339 /**
340 * @brief A static integer indicating whether the session is cached.
341 *
342 * This variable is used to track the state of session caching. It is
343 * initialized to 0, meaning no session is cached. The value may be updated
344 * to indicate that a session has been successfully cached.
345 *
346 * @note This variable is static, meaning it is only accessible within the
347 * file in which it is declared.
348 */
349 static int session_cached = 0;
350
351 /**
352 * @brief Caches a new SSL session if one is not already cached.
353 *
354 * This function writes a new SSL session to the session BIO and caches it.
355 * It ensures that only one session is cached at a time by checking the
356 * `session_cached` flag. If a session is already cached, the function
357 * returns without caching the new session.
358 *
359 * @param ssl A pointer to the SSL object associated with the session.
360 * @param sess A pointer to the SSL_SESSION object to be cached.
361 * @return 1 if the session is successfully cached, 0 otherwise.
362 *
363 * @note This function only allows one session to be cached. Subsequent
364 * sessions will not be cached unless `session_cached` is reset.
365 */
cache_new_session(struct ssl_st * ssl,SSL_SESSION * sess)366 static int cache_new_session(struct ssl_st *ssl, SSL_SESSION *sess)
367 {
368
369 if (session_cached == 1)
370 return 0;
371
372 /* Just write the new session to our bio */
373 if (!PEM_write_bio_SSL_SESSION(session_bio, sess))
374 return 0;
375
376 (void)BIO_flush(session_bio);
377 /* only cache one session */
378 session_cached = 1;
379 return 1;
380 }
381
382 /**
383 * @brief Sets up the session cache for the SSL connection.
384 *
385 * This function configures session caching for the given SSL connection
386 * and context. It attempts to load a session from the specified cache file
387 * or creates a new one if the file does not exist. It also configures the
388 * session cache mode and disables stateless session tickets.
389 *
390 * @param ssl A pointer to the SSL object for the connection.
391 * @param ctx A pointer to the SSL_CTX object representing the context.
392 * @param filename The name of the file used to store the session cache.
393 * @return 1 on success, 0 on failure.
394 *
395 * @note If the cache file does not exist, a new file is created and the
396 * session cache is initialized. If a session is successfully loaded from
397 * the file, it is added to the context and set for the SSL connection.
398 * If an error occurs during setup, the session BIO is freed.
399 */
setup_session_cache(SSL * ssl,SSL_CTX * ctx,const char * filename)400 static int setup_session_cache(SSL *ssl, SSL_CTX *ctx, const char *filename)
401 {
402 SSL_SESSION *sess = NULL;
403 int rc = 0;
404 int new_cache = 0;
405
406 /*
407 * Because we cache sessions to a file in this client, we don't
408 * actualy need to internally store sessions, because we restore them
409 * from the file with SSL_set_session below, but we want to ensure
410 * that caching is enabled so that the session cache callbacks get called
411 * properly. The documentation is a bit unclear under what conditions
412 * the callback is made, so play it safe here, by enforcing enablement
413 */
414 if (!SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_CLIENT |
415 SSL_SESS_CACHE_NO_INTERNAL_STORE |
416 SSL_SESS_CACHE_NO_AUTO_CLEAR))
417 return rc;
418
419 /* open our cache file */
420 session_bio = BIO_new_file(filename, "r+");
421 if (session_bio == NULL) {
422 /* file might need to be created */
423 session_bio = BIO_new_file(filename, "w+");
424 if (session_bio == NULL)
425 return rc;
426 new_cache = 1;
427 }
428
429 if (new_cache == 0) {
430 /* read in our cached session */
431 if (PEM_read_bio_SSL_SESSION(session_bio, &sess, NULL, NULL)) {
432 /* set our session */
433 if (!SSL_set_session(ssl, sess))
434 goto err;
435 }
436 } else {
437 /* Set the callback to store new sessions */
438 SSL_CTX_sess_set_new_cb(ctx, cache_new_session);
439 }
440
441 rc = 1;
442
443 err:
444 if (rc == 0)
445 BIO_free(session_bio);
446 return rc;
447 }
448
449 /**
450 * @brief Pointer to a list of SSL polling items.
451 *
452 * This static variable holds the reference to a dynamically allocated list
453 * of SSL_POLL_ITEM structures used for SSL polling operations. It is
454 * initialized to NULL and will be populated as needed.
455 */
456 static SSL_POLL_ITEM *poll_list = NULL;
457
458 /**
459 * @brief Pointer to an array of BIO objects for output.
460 *
461 * This static variable holds the reference to a dynamically allocated array
462 * of BIO structures, which are used for handling output in SSL operations.
463 * It is initialized to NULL and will be set when needed. This array holds
464 * the out bio's for all received data from GET requests
465 */
466 static BIO **outbiolist = NULL;
467
468 /**
469 * @brief Pointer to an array of output names.
470 *
471 * This static variable holds the reference to a dynamically allocated array
472 * of strings, representing output names. It is initialized to NULL and
473 * populated as required during operation. This array holds the names of the
474 * output files from http GET requests. Indicies are correlated with the
475 * corresponding outbiolist and poll_list arrays
476 */
477 static char **outnames = NULL;
478
479 /**
480 * @brief Counter for the number of poll items.
481 *
482 * This static variable holds the count of items in the poll_list. It is
483 * initialized to 0 and updated as items are added or removed from the list.
484 */
485 static size_t poll_count = 0;
486
487 /**
488 * @brief Pointer to an array of request strings.
489 *
490 * This static variable holds the reference to a dynamically allocated array
491 * of strings, representing requests. It is initialized to NULL and populated
492 * as requests are added during execution.
493 */
494 static char **req_array = NULL;
495
496 /**
497 * @brief Counter for the total number of requests.
498 *
499 * This static variable tracks the total number of parsed from reqfile. It is
500 * initialized to 0 and incremented as new requests are processed.
501 */
502 static size_t total_requests = 0;
503
504 /**
505 * @brief Index for the current request in the request array.
506 *
507 * This static variable keeps track of the index of the current request being
508 * processed in the request array. It is initialized to 0 and updated as
509 * requests are handled.
510 */
511 static size_t req_idx = 0;
512
513 /**
514 * @brief Builds and processes a set of SSL poll requests.
515 *
516 * This function creates a new set of SSL poll requests based on the current
517 * request array. It allocates and manages memory for poll lists, BIO output
518 * files, and associated request names. Each request sends an HTTP GET to the
519 * corresponding peer. The function processes the requests until a batch limit
520 * or error is encountered.
521 *
522 * @param ssl A pointer to the SSL object to use for creating new streams.
523 *
524 * @return The number of poll requests successfully built, or 0 on error.
525 */
build_request_set(SSL * ssl)526 static size_t build_request_set(SSL *ssl)
527 {
528 size_t poll_idx;
529 char *req;
530 char outfilename[REQ_STRING_SZ];
531 char req_string[REQ_STRING_SZ];
532 SSL *new_stream;
533 size_t written;
534
535 /*
536 * Free any previous poll list
537 */
538 for (poll_idx = 0; poll_idx < poll_count; poll_idx++) {
539 (void)BIO_flush(outbiolist[poll_idx]);
540 BIO_free(outbiolist[poll_idx]);
541 SSL_free(poll_list[poll_idx].desc.value.ssl);
542 }
543
544 /*
545 * Reset out lists and poll_count
546 */
547 OPENSSL_free(outbiolist);
548 OPENSSL_free(outnames);
549 OPENSSL_free(poll_list);
550 outnames = NULL;
551 poll_list = NULL;
552 outbiolist = NULL;
553
554 poll_count = 0;
555
556 /*
557 * Iterate through our parsed lists of requests
558 * note req_idx may start at a non-zero value if
559 * multiple calls to build_request_list are made
560 */
561 while (req_idx < total_requests) {
562 req = req_array[req_idx];
563 /* Up our poll count and set our poll_list index */
564 poll_count++;
565 poll_idx = poll_count - 1;
566
567 /*
568 * Expand our poll_list, outbiolist, and outnames arrays
569 */
570 poll_list = OPENSSL_realloc(poll_list,
571 sizeof(SSL_POLL_ITEM) * poll_count);
572 if (poll_list == NULL) {
573 fprintf(stderr, "Unable to realloc poll_list\n");
574 goto err;
575 }
576
577 outbiolist = OPENSSL_realloc(outbiolist,
578 sizeof(BIO *) * poll_count);
579 if (outbiolist == NULL) {
580 fprintf(stderr, "Unable to realloc outbiolist\n");
581 goto err;
582 }
583
584 outnames = OPENSSL_realloc(outnames, sizeof(char *) * poll_count);
585 if (outnames == NULL) {
586 fprintf(stderr, "Unable to realloc outnames\n");
587 goto err;
588 }
589
590 /* set the output file name for this index */
591 outnames[poll_idx] = req;
592
593 /* Format the http request */
594 BIO_snprintf(req_string, REQ_STRING_SZ, "GET /%s\r\n", req);
595
596 /* build the outfile request path */
597 memset(outfilename, 0, REQ_STRING_SZ);
598 BIO_snprintf(outfilename, REQ_STRING_SZ, "/downloads/%s", req);
599
600 /* open a bio to write the file */
601 outbiolist[poll_idx] = BIO_new_file(outfilename, "w+");
602 if (outbiolist[poll_idx] == NULL) {
603 fprintf(stderr, "Failed to open outfile %s\n", outfilename);
604 goto err;
605 }
606
607 /* create a request stream */
608 new_stream = NULL;
609
610 /*
611 * We don't strictly have to do this check, but our quic client limits
612 * our max data streams to 100, so we're just batching in groups of 100
613 * for now
614 */
615 if (poll_count <= 99)
616 new_stream = SSL_new_stream(ssl, 0);
617
618 if (new_stream == NULL) {
619 /*
620 * We ran out of new streams to allocate
621 * return and process this batch before getting more
622 */
623 poll_count--;
624 return poll_count;
625 }
626
627 /*
628 * Create a poll descriptor for this stream
629 */
630 poll_list[poll_idx].desc = SSL_as_poll_descriptor(new_stream);
631 poll_list[poll_idx].revents = 0;
632 poll_list[poll_idx].events = SSL_POLL_EVENT_R;
633
634 /* Write an HTTP GET request to the peer */
635 while (!SSL_write_ex2(poll_list[poll_idx].desc.value.ssl,
636 req_string, strlen(req_string),
637 SSL_WRITE_FLAG_CONCLUDE, &written)) {
638 if (handle_io_failure(poll_list[poll_idx].desc.value.ssl, 0) == 1)
639 continue; /* Retry */
640 fprintf(stderr, "Failed to write start of HTTP request\n");
641 goto err; /* Cannot retry: error */
642 }
643
644 req_idx++;
645 }
646 return poll_count;
647
648 err:
649 for (poll_idx = 0; poll_idx < poll_count; poll_idx++) {
650 BIO_free(outbiolist[poll_idx]);
651 SSL_free(poll_list[poll_idx].desc.value.ssl);
652 }
653 OPENSSL_free(poll_list);
654 OPENSSL_free(outbiolist);
655 poll_list = NULL;
656 outbiolist = NULL;
657 poll_count = 0;
658 return poll_count;
659 }
660
661 /**
662 * @brief Static pointer to a BIO_ADDR structure representing the peer's address.
663 *
664 * This variable is used to store the address of a peer for network communication.
665 * It is statically allocated and should be initialized appropriately.
666 */
667 static BIO_ADDR *peer_addr = NULL;
668
669 /**
670 * @brief Set up a TLS/QUIC connection to the specified hostname and port.
671 *
672 * This function creates and configures an SSL context for a client connection
673 * using the QUIC client method. It sets up the necessary certificates,
674 * performs host verification, configures ALPN, and establishes a non-blocking
675 * connection.
676 *
677 * @param hostname Hostname to connect to.
678 * @param port Port to connect to.
679 * @param ipv6 Whether to use IPv6 (non-zero for IPv6, zero for IPv4).
680 * @param ctx Pointer to an SSL_CTX object, which will be created.
681 * @param ssl Pointer to an SSL object, which will be created.
682 *
683 * @return Returns 0 on success, 1 on error.
684 */
setup_connection(char * hostname,char * port,int ipv6,SSL_CTX ** ctx,SSL ** ssl)685 static int setup_connection(char *hostname, char *port, int ipv6,
686 SSL_CTX **ctx, SSL **ssl)
687 {
688 unsigned char alpn[] = {10, 'h', 'q', '-', 'i', 'n', 't', 'e', 'r', 'o', 'p'};
689 int ret = 0;
690 BIO *bio = NULL;
691
692 /*
693 * Create an SSL_CTX which we can use to create SSL objects from. We
694 * want an SSL_CTX for creating clients so we use
695 * OSSL_QUIC_client_method() here.
696 */
697 *ctx = SSL_CTX_new(OSSL_QUIC_client_method());
698 if (*ctx == NULL) {
699 fprintf(stderr, "Failed to create the SSL_CTX\n");
700 goto end;
701 }
702
703 /*
704 * Configure the client to abort the handshake if certificate
705 * verification fails. Virtually all clients should do this unless you
706 * really know what you are doing.
707 */
708 SSL_CTX_set_verify(*ctx, SSL_VERIFY_PEER, NULL);
709
710 /*
711 * Use the default trusted certificate store
712 * Note: The store is read from SSL_CERT_DIR and SSL_CERT_FILE
713 * environment variables in the default case, so users can set those
714 * When running this application to direct where the store is loaded from
715 */
716 if (!SSL_CTX_set_default_verify_paths(*ctx)) {
717 fprintf(stderr, "Failed to set the default trusted certificate store\n");
718 goto end;
719 }
720
721 /*
722 * If the SSL_CIPHER_SUITES env variable is set, assign those
723 * ciphers to the context
724 */
725 if (getenv("SSL_CIPHER_SUITES") != NULL) {
726 if (!SSL_CTX_set_ciphersuites(*ctx, getenv("SSL_CIPHER_SUITES"))) {
727 fprintf(stderr, "Failed to set cipher suites for connection\n");
728 goto end;
729 }
730 }
731
732 /* Create an SSL object to represent the TLS connection */
733 *ssl = SSL_new(*ctx);
734 if (*ssl == NULL) {
735 fprintf(stderr, "Failed to create the SSL object\n");
736 goto end;
737 }
738
739 if (getenv("SSL_SESSION_FILE") != NULL) {
740 if (!setup_session_cache(*ssl, *ctx, getenv("SSL_SESSION_FILE"))) {
741 fprintf(stderr, "Unable to setup session cache\n");
742 goto end;
743 }
744 }
745
746 /*
747 * Create the underlying transport socket/BIO and associate it with the
748 * connection.
749 */
750 bio = create_socket_bio(hostname, port, ipv6 ? AF_INET6 : AF_INET,
751 &peer_addr);
752 if (bio == NULL) {
753 fprintf(stderr, "Failed to crete the BIO\n");
754 goto end;
755 }
756 SSL_set_bio(*ssl, bio, bio);
757
758 /*
759 * Tell the server during the handshake which hostname we are attempting
760 * to connect to in case the server supports multiple hosts.
761 */
762 if (!SSL_set_tlsext_host_name(*ssl, hostname)) {
763 fprintf(stderr, "Failed to set the SNI hostname\n");
764 goto end;
765 }
766
767 /*
768 * Ensure we check during certificate verification that the server has
769 * supplied a certificate for the hostname that we were expecting.
770 * Virtually all clients should do this unless you really know what you
771 * are doing.
772 */
773 if (!SSL_set1_host(*ssl, hostname)) {
774 fprintf(stderr, "Failed to set the certificate verification hostname");
775 goto end;
776 }
777
778 /* SSL_set_alpn_protos returns 0 for success! */
779 if (SSL_set_alpn_protos(*ssl, alpn, sizeof(alpn)) != 0) {
780 fprintf(stderr, "Failed to set the ALPN for the connection\n");
781 goto end;
782 }
783
784 /* Set the IP address of the remote peer */
785 if (!SSL_set1_initial_peer_addr(*ssl, peer_addr)) {
786 fprintf(stderr, "Failed to set the initial peer address\n");
787 goto end;
788 }
789
790 /*
791 * The underlying socket is always nonblocking with QUIC, but the default
792 * behaviour of the SSL object is still to block. We set it for nonblocking
793 * mode in this demo.
794 */
795 if (!SSL_set_blocking_mode(*ssl, 0)) {
796 fprintf(stderr, "Failed to turn off blocking mode\n");
797 goto end;
798 }
799
800 /* Do the handshake with the server */
801 while ((ret = SSL_connect(*ssl)) != 1) {
802 if (handle_io_failure(*ssl, ret) == 1)
803 continue; /* Retry */
804 fprintf(stderr, "Failed to connect to server\n");
805 goto end; /* Cannot retry: error */
806 }
807
808 return 1;
809 end:
810 SSL_CTX_free(*ctx);
811 SSL_free(*ssl);
812 BIO_ADDR_free(peer_addr);
813 return 0;
814 }
815
816 /**
817 * @brief Entry point for the QUIC hq-interop client demo application.
818 *
819 * This function sets up an SSL/TLS connection using QUIC, sends HTTP GET
820 * requests for files specified in the command-line arguments, and saves
821 * the responses to disk. It handles various configurations such as IPv6
822 * support, session caching, and key logging.
823 *
824 * @param argc The number of command-line arguments.
825 * @param argv The array of command-line arguments. The expected format is
826 * "[-6] hostname port file".
827 * @return EXIT_SUCCESS on success, or EXIT_FAILURE on error.
828 *
829 * @note The function performs the following main tasks:
830 * - Parses command-line arguments and configures IPv6 if specified.
831 * - Reads the list of requests from the specified file.
832 * - Sets up the SSL context and configures certificate verification.
833 * - Optionally enables key logging and session caching.
834 * - Establishes a non-blocking QUIC connection to the server.
835 * - Sends an HTTP GET request for each file and writes the response
836 * to the corresponding output file.
837 * - Gracefully shuts down the SSL connection and frees resources.
838 * - Prints any OpenSSL error stack information on failure.
839 */
main(int argc,char * argv[])840 int main(int argc, char *argv[])
841 {
842 SSL_CTX *ctx = NULL;
843 SSL *ssl = NULL;
844 BIO *req_bio = NULL;
845 int res = EXIT_FAILURE;
846 int ret;
847 size_t readbytes = 0;
848 char buf[160];
849 int eof = 0;
850 int argnext = 1;
851 char *reqfile = NULL;
852 char *reqnames = OPENSSL_zalloc(1025);
853 size_t read_offset = 0;
854 size_t bytes_read = 0;
855 size_t poll_idx = 0;
856 size_t poll_done = 0;
857 size_t result_count = 0;
858 struct timeval poll_timeout;
859 size_t this_poll_count = 0;
860 char *req = NULL;
861 char *hostname, *port;
862 int ipv6 = 0;
863
864 if (argc < 4) {
865 fprintf(stderr, "Usage: quic-hq-interop [-6] hostname port reqfile\n");
866 goto end;
867 }
868
869 if (!strcmp(argv[argnext], "-6")) {
870 if (argc < 5) {
871 fprintf(stderr, "Usage: quic-hq-interop [-6] hostname port reqfile\n");
872 goto end;
873 }
874 ipv6 = 1;
875 argnext++;
876 }
877 hostname = argv[argnext++];
878 port = argv[argnext++];
879 reqfile = argv[argnext];
880
881 req_bio = BIO_new_file(reqfile, "r");
882 if (req_bio == NULL) {
883 fprintf(stderr, "Failed to open request file %s\n", reqfile);
884 goto end;
885 }
886
887 /* Get the list of requests */
888 while (!BIO_eof(req_bio)) {
889 if (!BIO_read_ex(req_bio, &reqnames[read_offset], REQ_STRING_SZ, &bytes_read)) {
890 fprintf(stderr, "Failed to read some data from request file\n");
891 goto end;
892 }
893 read_offset += bytes_read;
894 reqnames = OPENSSL_realloc(reqnames, read_offset + REQ_STRING_SZ);
895 if (reqnames == NULL) {
896 fprintf(stderr, "Realloc failure\n");
897 goto end;
898 }
899 }
900 BIO_free(req_bio);
901 req_bio = NULL;
902 reqnames[read_offset + 1] = '\0';
903
904 if (!setup_connection(hostname, port, ipv6, &ctx, &ssl)) {
905 fprintf(stderr, "Unable to establish connection\n");
906 goto end;
907 }
908
909 req = strtok(reqnames, " ");
910
911 while (req != NULL) {
912 total_requests++;
913 req_array = OPENSSL_realloc(req_array, sizeof(char *) * total_requests);
914 req_array[total_requests - 1] = req;
915 req = strtok(NULL, " ");
916 }
917
918 /* get a list of requests to poll */
919 this_poll_count = build_request_set(ssl);
920
921 /*
922 * Now poll all our descriptors for events
923 */
924 while (this_poll_count != 0 && poll_done < this_poll_count) {
925 result_count = 0;
926 poll_timeout.tv_sec = 0;
927 poll_timeout.tv_usec = 0;
928 if (!SSL_poll(poll_list, this_poll_count, sizeof(SSL_POLL_ITEM),
929 &poll_timeout, 0, &result_count)) {
930 fprintf(stderr, "Failed to poll\n");
931 goto end;
932 }
933
934 /* Iterate over our poll array looking for ready SSL's */
935 for (poll_idx = 0; poll_idx < this_poll_count; poll_idx++) {
936 /*
937 * If we have visited the number of SSL's that SSL_poll
938 * indicated were ready, we can go poll again
939 */
940 if (result_count == 0)
941 break;
942
943 if (poll_list[poll_idx].revents == SSL_POLL_EVENT_R) {
944 /*
945 * We found an SSL that we can read, drop our result count
946 */
947 result_count--;
948
949 /* And clear the revents for the next poll */
950 poll_list[poll_idx].revents = 0;
951
952 /*
953 * Get up to sizeof(buf) bytes of the response. We keep reading until
954 * the server closes the connection.
955 */
956 eof = 0;
957
958 /* Read our data, and handle any errors/eof conditions */
959 if (!SSL_read_ex(poll_list[poll_idx].desc.value.ssl, buf,
960 sizeof(buf), &readbytes)) {
961 switch (handle_io_failure(poll_list[poll_idx].desc.value.ssl,
962 0)) {
963 case 1:
964 eof = 0;
965 break; /* Retry on next poll */
966 case 0:
967 eof = 1;
968 break;
969 case -1:
970 default:
971 fprintf(stderr, "Failed reading remaining data\n");
972 goto end; /* Cannot retry: error */
973 }
974 }
975
976 /*
977 * If error handling indicated that this SSL is in an EOF state
978 * we mark the SSL as not needing any more polling, and up our
979 * poll_done count. Otherwise, just write to the outbio
980 */
981 if (!eof) {
982 BIO_write(outbiolist[poll_idx], buf, readbytes);
983 } else {
984 fprintf(stderr, "completed %s\n", outnames[poll_idx]);
985 /* This file is done, take it out of polling contention */
986 poll_list[poll_idx].events = 0;
987 poll_done++;
988 }
989 }
990 }
991
992 /*
993 * If we've completed this poll set, try get another one
994 */
995 if (poll_done == this_poll_count) {
996 this_poll_count = build_request_set(ssl);
997 poll_done = 0;
998 }
999 }
1000
1001 /*
1002 * Repeatedly call SSL_shutdown() until the connection is fully
1003 * closed.
1004 */
1005 fprintf(stderr, "Shutting down\n");
1006 while ((ret = SSL_shutdown(ssl)) != 1) {
1007 if (ret < 0 && handle_io_failure(ssl, ret) == 1)
1008 continue; /* Retry */
1009 }
1010
1011 /* Success! */
1012 res = EXIT_SUCCESS;
1013 end:
1014 /*
1015 * If something bad happened then we will dump the contents of the
1016 * OpenSSL error stack to stderr. There might be some useful diagnostic
1017 * information there.
1018 */
1019 if (res == EXIT_FAILURE)
1020 ERR_print_errors_fp(stderr);
1021
1022 /*
1023 * Free the resources we allocated. We do not free the BIO object here
1024 * because ownership of it was immediately transferred to the SSL object
1025 * via SSL_set_bio(). The BIO will be freed when we free the SSL object.
1026 */
1027 BIO_ADDR_free(peer_addr);
1028 OPENSSL_free(reqnames);
1029 BIO_free(session_bio);
1030 for (poll_idx = 0; poll_idx < poll_count; poll_idx++) {
1031 BIO_free(outbiolist[poll_idx]);
1032 SSL_free(poll_list[poll_idx].desc.value.ssl);
1033 }
1034 SSL_free(ssl);
1035 SSL_CTX_free(ctx);
1036 OPENSSL_free(outbiolist);
1037 OPENSSL_free(poll_list);
1038 return res;
1039 }
1040