From 9f0cc5d09a89731f1cd9b111f12aa3ac9667b6a0 Mon Sep 17 00:00:00 2001 From: Matt Caswell Date: Wed, 14 Jun 2023 10:22:57 +0100 Subject: [PATCH] Add a tutorial on writing a simple blocking QUIC client This tutorial only covers a single stream client at this stage. A future PR will cover adding multi-stream support. Reviewed-by: Tomas Mraz Reviewed-by: Hugo Landau Reviewed-by: Paul Dale (Merged from https://github.com/openssl/openssl/pull/21204) --- doc/build.info | 12 + doc/man7/ossl-guide-quic-client-block.pod | 294 ++++++++++++++++++++++ doc/man7/ossl-guide-quic-introduction.pod | 135 ++++++++++ doc/man7/ossl-guide-tls-client-block.pod | 7 +- doc/man7/ossl-guide-tls-introduction.pod | 4 +- 5 files changed, 450 insertions(+), 2 deletions(-) create mode 100644 doc/man7/ossl-guide-quic-client-block.pod create mode 100644 doc/man7/ossl-guide-quic-introduction.pod diff --git a/doc/build.info b/doc/build.info index b8748d073de..24d4050a45a 100644 --- a/doc/build.info +++ b/doc/build.info @@ -4757,6 +4757,14 @@ DEPEND[man/man7/openssl_user_macros.7]=man7/openssl_user_macros.pod GENERATE[man/man7/openssl_user_macros.7]=man7/openssl_user_macros.pod DEPEND[man7/openssl_user_macros.pod]{pod}=man7/openssl_user_macros.pod.in GENERATE[man7/openssl_user_macros.pod]=man7/openssl_user_macros.pod.in +DEPEND[html/man7/ossl-guide-quic-client-block.html]=man7/ossl-guide-quic-client-block.pod +GENERATE[html/man7/ossl-guide-quic-client-block.html]=man7/ossl-guide-quic-client-block.pod +DEPEND[man/man7/ossl-guide-quic-client-block.7]=man7/ossl-guide-quic-client-block.pod +GENERATE[man/man7/ossl-guide-quic-client-block.7]=man7/ossl-guide-quic-client-block.pod +DEPEND[html/man7/ossl-guide-quic-introduction.html]=man7/ossl-guide-quic-introduction.pod +GENERATE[html/man7/ossl-guide-quic-introduction.html]=man7/ossl-guide-quic-introduction.pod +DEPEND[man/man7/ossl-guide-quic-introduction.7]=man7/ossl-guide-quic-introduction.pod +GENERATE[man/man7/ossl-guide-quic-introduction.7]=man7/ossl-guide-quic-introduction.pod DEPEND[html/man7/ossl-guide-tls-client-block.html]=man7/ossl-guide-tls-client-block.pod GENERATE[html/man7/ossl-guide-tls-client-block.html]=man7/ossl-guide-tls-client-block.pod DEPEND[man/man7/ossl-guide-tls-client-block.7]=man7/ossl-guide-tls-client-block.pod @@ -4973,6 +4981,8 @@ html/man7/openssl-glossary.html \ html/man7/openssl-quic.html \ html/man7/openssl-threads.html \ html/man7/openssl_user_macros.html \ +html/man7/ossl-guide-quic-client-block.html \ +html/man7/ossl-guide-quic-introduction.html \ html/man7/ossl-guide-tls-client-block.html \ html/man7/ossl-guide-tls-introduction.html \ html/man7/ossl_store-file.html \ @@ -5108,6 +5118,8 @@ man/man7/openssl-glossary.7 \ man/man7/openssl-quic.7 \ man/man7/openssl-threads.7 \ man/man7/openssl_user_macros.7 \ +man/man7/ossl-guide-quic-client-block.7 \ +man/man7/ossl-guide-quic-introduction.7 \ man/man7/ossl-guide-tls-client-block.7 \ man/man7/ossl-guide-tls-introduction.7 \ man/man7/ossl_store-file.7 \ diff --git a/doc/man7/ossl-guide-quic-client-block.pod b/doc/man7/ossl-guide-quic-client-block.pod new file mode 100644 index 00000000000..4506f885161 --- /dev/null +++ b/doc/man7/ossl-guide-quic-client-block.pod @@ -0,0 +1,294 @@ +=pod + +=begin comment + +NB: Changes to the source code samples in this file should also be reflected in +demos/guide/quic-client-block.c + +=end comment + +=head1 NAME + +ossl-guide-quic-client-block +- OpenSSL Guide: Writing a simple blocking QUIC client + +=head1 SIMPLE BLOCKING QUIC CLIENT EXAMPLE + +This page will present various source code samples demonstrating how to write +a simple blocking QUIC client application which connects to a server, sends an +HTTP/1.0 request to it, and reads back the response. Note that HTTP/1.0 over +QUIC is non-standard and will not typically be supported by real world servers. +This is for demonstration purposes only. + +We assume that you already have OpenSSL installed on your system; that you +already have some fundamental understanding of OpenSSL concepts, TLS and QUIC +(see L, L and +L); and that you know how to +write and build C code and link it against the libcrypto and libssl libraries +that are provided by OpenSSL. It also assumes that you have a basic +understanding of UDP/IP and sockets. The example code that we build in this +tutorial will amend the blocking TLS client example that is covered in +L. Only the differences between that client and +this one will be discussed so we also assume that you have run through and +understand that tutorial. + +For this tutorial our client will be using a single QUIC stream. A subsequent +tutorial will discuss how to write a multi-stream client. + +The complete source code for this example blocking QUIC client is available in +the C directory of the OpenSSL source distribution in the file +C. It is also available online at +L. + +=head2 Creating the SSL_CTX and SSL objects + +In the TLS tutorial (L) we created an B +object for our client and used it to create an B object to represent the +TLS connection. A QUIC connection works in exactly the same way. We first create +an B object and then use it to create an B object to represent the +QUIC connection. + +As in the TLS example the first step is to create an B object for our +client. This is done in the same way as before except that we use a different +"method". OpenSSL offers two different QUIC client methods, i.e. +L and L. + +The first one is the equivalent of L but for the QUIC +protocol. The second one is the same, but it will additionally create a +background thread for handling time based events (known as "thread assisted +mode", see L). For this tutorial we will be +using L because we will not be leaving the QUIC +connection idle in our application and so thread assisted mode is not needed. + + /* + * Create an SSL_CTX which we can use to create SSL objects from. We + * want an SSL_CTX for creating clients so we use OSSL_QUIC_client_method() + * here. + */ + ctx = SSL_CTX_new(OSSL_QUIC_client_method()); + if (ctx == NULL) { + printf("Failed to create the SSL_CTX\n"); + goto end; + } + +The other setup steps that we applied to the B for TLS also apply to +QUIC except for restricting the TLS versions that we are willing to accept. The +QUIC protocol implementation in OpenSSL currently only supports TLSv1.3. There +is no need to call L or +L in an OpenSSL QUIC application, and any such +call will be ignored. + +Once the B is created, the B object is constructed in exactly the +same way as for the TLS application. + +=head2 Creating the socket and BIO + +A major difference between TLS and QUIC is the underlying transport protocol. +TLS uses TCP while QUIC uses UDP. The way that the QUIC socket is created in our +example code is much the same as for TLS. We use the L and +L helper functions as we did in the previous tutorial except that +we pass B as an argument to indicate UDP (instead of B +for TCP). + + /* + * Lookup IP address info for the server. + */ + if (!BIO_lookup_ex(hostname, port, BIO_LOOKUP_CLIENT, 0, SOCK_DGRAM, 0, + &res)) + return NULL; + + /* + * Loop through all the possible addresses for the server and find one + * we can connect to. + */ + for (ai = res; ai != NULL; ai = BIO_ADDRINFO_next(ai)) { + /* + * Create a TCP socket. We could equally use non-OpenSSL calls such + * as "socket" here for this and the subsequent connect and close + * functions. But for portability reasons and also so that we get + * errors on the OpenSSL stack in the event of a failure we use + * OpenSSL's versions of these functions. + */ + sock = BIO_socket(BIO_ADDRINFO_family(ai), SOCK_DGRAM, 0, 0); + if (sock == -1) + continue; + + /* Connect the socket to the server's address */ + if (!BIO_connect(sock, BIO_ADDRINFO_address(ai), 0)) { + BIO_closesocket(sock); + sock = -1; + continue; + } + + /* Set to nonblocking mode */ + if (!BIO_socket_nbio(sock, 1)) { + sock = -1; + continue; + } + + break; + } + + if (sock != -1) { + *peer_addr = BIO_ADDR_dup(BIO_ADDRINFO_address(ai)); + if (*peer_addr == NULL) { + BIO_closesocket(sock); + return NULL; + } + } + + /* Free the address information resources we allocated earlier */ + BIO_ADDRINFO_free(res); + +You may notice a couple of other differences between this code and the version +that we used for TLS. + +Firstly, we set the socket into nonblocking mode. This must always be done for +an OpenSSL QUIC application. This may be surprising considering that we are +trying to write a blocking client. Despite this the B object will still +have blocking behaviour. See L for further +information on this. + +Secondly, we take note of the IP address of the peer that we are connecting to. +We store that information away. We will need it later. + +See L, L, L, +L, L, L, +L and L for further information on the +functions used here. In the above example code the B and B +variables are strings, e.g. "www.example.com" and "443". + +As for our TLS client, once the socket has been created and connected we need to +associate it with a BIO object: + + BIO *bio; + + /* Create a BIO to wrap the socket*/ + bio = BIO_new(BIO_s_datagram()); + if (bio == NULL) + BIO_closesocket(sock); + + /* + * Associate the newly created BIO with the underlying socket. By + * passing BIO_CLOSE here the socket will be automatically closed when + * the BIO is freed. Alternatively you can use BIO_NOCLOSE, in which + * case you must close the socket explicitly when it is no longer + * needed. + */ + BIO_set_fd(bio, sock, BIO_CLOSE); + +Note the use of L here as opposed to L that +we used for our TLS client. This is again due to the fact that QUIC uses UDP +instead of TCP for its transport layer. See L, L +and L for further information on these functions. + +=head2 Setting the server's hostname + +As in the TLS tutorial we need to set the server's hostname both for SNI (Server +Name Indication) and for certificate validation purposes. The steps for this are +identical to the TLS tutorial and won't be repeated here. + +=head2 Setting the ALPN + +ALPN (Application-Layer Protocol Negotiation) is a feature of TLS that enables +the application to negotiate which protocol will be used over the connection. +For example, if you intend to use HTTP/3 over the connection then the ALPN value +for that is "h3" (see +L). +OpenSSL provides the ability for a client to specify the ALPN to use via the +L function. This is optional for a TLS client and so our +simple client that we developed in L did not use +it. However QUIC mandates that the TLS handshake used in establishing a QUIC +connection must use ALPN. + + unsigned char alpn[] = { 8, 'h', 't', 't', 'p', '/', '1', '.', '0' }; + + /* SSL_set_alpn_protos returns 0 for success! */ + if (SSL_set_alpn_protos(ssl, alpn, sizeof(alpn)) != 0) { + printf("Failed to set the ALPN for the connection\n"); + goto end; + } + +The ALPN is specified using a length prefixed array of unsigned chars (it is not +a NUL terminated string). Our original TLS blocking client demo was using +HTTP/1.0. We will use the same for this example. Unlike most OpenSSL functions +L returns zero for success and nonzero for failure. + +=head2 Setting the peer address + +An OpenSSL QUIC application must specify the target address of the server that +is being connected to. In L above we saved that +address away for future use. Now we need to use it via the +L function. + + if (!SSL_set_initial_peer_addr(ssl, peer_addr)) { + printf("Failed to set the inital peer address\n"); + goto end; + } + +Note that we will need to free the B value that we allocated via +L earlier: + + BIO_ADDR_free(peer_addr); + +=head2 The handshake and application data transfer + +Once initial setup of the B object is complete then we perform the +handshake via L in exactly the same way as we did for the TLS +client, so we won't repeat it here. + +We can also perform data transfer using a default QUIC stream that is +automatically associated with the B object for us. We can transmit data +using L, and receive data using L in exactly +the same way as for TLS. Again, we won't repeat it here. + +=head2 Shutting down the connection + +As in the TLS tutorial, once we have finished reading data from the server then +we are ready to close the connection down. As before we do this via the +L function. This example for QUIC is very similar to the TLS +version. However the L function may need to be called more than +once: + + /* + * Repeatedly call SSL_shutdown() until the connection is fully + * closed. + */ + do { + ret = SSL_shutdown(ssl); + if (ret < 0) { + printf("Error shuting down: %d\n", ret); + goto end; + } + } while (ret != 1); + +The shutdown process is in two stages. First we gracefully shutdown the +connection for sending and secondly we shutdown the connection for receiving. +L returns 0 once the first stage has been completed, and 1 once +the second stage is finished. This two stage process applies to TLS as well. +However in our blocking TLS client example code we knew that the peer had +already partially closed down due to the B that we had +obtained via L after the final L call. Due to +that return value we knew that the connection was already closed down for +receiving data and hence L should only need to be called once. + +However, with QUIC, the B value only tells us that the +stream (not the connection) is partially closed down. Therefore we need to call +L more than once to ensure that we progress through both stages +of the shutdown process. + +=head1 SEE ALSO + +L, L, +L, L + +=head1 COPYRIGHT + +Copyright 2023 The OpenSSL Project Authors. All Rights Reserved. + +Licensed under the Apache License 2.0 (the "License"). You may not use +this file except in compliance with the License. You can obtain a copy +in the file LICENSE in the source distribution or at +L. + +=cut diff --git a/doc/man7/ossl-guide-quic-introduction.pod b/doc/man7/ossl-guide-quic-introduction.pod new file mode 100644 index 00000000000..33f9c3e0034 --- /dev/null +++ b/doc/man7/ossl-guide-quic-introduction.pod @@ -0,0 +1,135 @@ +=pod + +=head1 NAME + +ossl-guide-quic-introduction +- OpenSSL Guide: An introduction to QUIC in OpenSSL + +=head1 INTRODUCTION + +This page will provide an introduction to some basic QUIC concepts and +background and how it is used within OpenSSL. It assumes that you have a basic +understanding of UDP/IP and sockets. It also assumes that you are familiar with +some OpenSSL and TLS fundamentals (see L and +L). + +=head1 WHAT IS QUIC? + +QUIC is a general purpose protocol for enabling applications to securely +communicate over a network. It is defined in RFC9000 (see +L). QUIC integrates parts of the +TLS protocol for connection establishment but independently protects packets. +It provides similar security guarantees to TLS such as confidentiality, +integrity and authentication (see L). It +additionally provides multiplexing capabilities through the use of "streams" +(see L below). + +=head1 QUIC TIME BASED EVENTS + +A key difference between the TLS implementation and the QUIC implementation in +OpenSSL is how time is handled. The QUIC protocol requires various actions to be +performed on a regular basis regardless of whether application data is being +transmitted or received. + +OpenSSL introduces a new function L that will +automatically process any outstanding time based events that must be handled. +Alternatively calling any I/O function such as L or +L will also process these events. There is also +L which tells an application the amount of time that +remains until L (or any I/O function) must be called. + +Fortunately a blocking application that does not leave the QUIC connection idle, +and is regularly calling I/O functions does not typically need to worry about +this. However if you are developing a nonblocking application or one that may +leave the QUIC connection idle for a period of time then you will need to +arrange to call these functions. + +OpenSSL provides an optional "thread assisted mode" that will automatically +create a background thread and will regularly call L in a +thread safe manner. This provides a simple way for an application to satisfy the +QUIC requirements for time based events without having to implement special +logic to accomplish it. + +=head1 QUIC AND TLS + +QUIC reuses parts of the TLS protocol in its implementation. Specifically the +TLS handshake also exists in QUIC. The TLS handshake messages are wrapped up in +QUIC protocol messages in order to send them to the peer. Once the TLS handshake +is complete all application data is sent entirely using QUIC protocol messages +without using TLS - although some TLS handshake messages may still be sent in +some circumstances. + +This relationship between QUIC and TLS means that many of the API functions in +OpenSSL that apply to TLS connections also apply to QUIC connections and +applications can use them in exactly the same way. Some functions do not apply +to QUIC at all, and others have altered semantics. You should refer to the +documentation pages for each function for information on how it applies to QUIC. +Typically if QUIC is not mentioned in the manual pages then the functions apply +to both TLS and QUIC. + +=head1 QUIC STREAMS + +QUIC introduces the concept of "streams". A stream provides a reliable +mechanism for sending and receiving application data between the endpoints. The +bytes transmitted are guaranteed to be received in the same order they were sent +without any loss of data or reordering of the bytes. A TLS application +effectively has one bi-directional stream available to it per TLS connection. A +QUIC application can have multiple uni-directional or bi-directional streams +available to it for each connection. + +In OpenSSL an B object is used to represent both connections and streams. +A QUIC application creates an initial B object to represent the connection +(known as the connection B object). Once the connection is complete +additional B objects can be created to represent streams (known as stream +B objects). Unless configured otherwise, a "default" stream is also +associated with the connection B object so you can still write data and +read data to/from it. Some OpenSSL API functions can only be used with +connection B objects, and some can only be used with stream B objects. +Check the documentation for each function to confirm what type of B object +can be used in any particular context. A connection B object that has a +default stream attached to it can be used in contexts that require a connection +B object or in contexts that require a stream B object. + +=head1 SOCKETS AND BLOCKING + +TLS assumes "stream" type semantics for its underlying transport layer protocol +(usually achieved by using TCP). However QUIC assumes "datagram" type semantics +by using UDP. An OpenSSL application using QUIC is responsible for creating a +BIO to represent the underlying transport layer. This BIO must support datagrams +and is typically L, but other B choices are available. +See L for an introduction to OpenSSL's B concept. + +A significant difference between OpenSSL TLS applications and OpenSSL QUIC +applications is the way that blocking is implemented. In TLS if your application +expects blocking behaviour then you configure the underlying socket for +blocking. Conversely if your application wants nonblocking behaviour then the +underlying socket is configured to be nonblocking. + +With an OpenSSL QUIC application the underlying socket must always be configured +to be nonblocking. Howevever the B object will, by default, still operate +in blocking mode. So, from an application's perspective, calls to functions such +as L, L and other I/O functions will still +block. OpenSSL itself provides that blocking capability for QUIC instead of the +socket. If nonblocking behaviour is desired then the application must call +L. + +=head1 FURTHER READING + +See L to see an example of applying these +concepts in order to write a simple blocking QUIC client. + +=head1 SEE ALSO + +L, L, L, +L, L + +=head1 COPYRIGHT + +Copyright 2023 The OpenSSL Project Authors. All Rights Reserved. + +Licensed under the Apache License 2.0 (the "License"). You may not use +this file except in compliance with the License. You can obtain a copy +in the file LICENSE in the source distribution or at +L. + +=cut diff --git a/doc/man7/ossl-guide-tls-client-block.pod b/doc/man7/ossl-guide-tls-client-block.pod index aab5533f5e5..c4c19a98b54 100644 --- a/doc/man7/ossl-guide-tls-client-block.pod +++ b/doc/man7/ossl-guide-tls-client-block.pod @@ -135,7 +135,7 @@ to create an IPv4 TCP socket: int sock; - sock = socket(AF_INET, SOCK_STEAM, 0); + sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == -1) return NULL; @@ -543,6 +543,11 @@ of its intermediate CA certificates) in our trusted certificate store (e.g. because the trusted certificate store is misconfigured, or there are missing intermediate CAs, or the issuer is simply unrecognised). +=head1 FURTHER READING + +See L to read a tutorial on how to modify the +client developed on this page to support QUIC instead of TLS. + =head1 SEE ALSO L, L diff --git a/doc/man7/ossl-guide-tls-introduction.pod b/doc/man7/ossl-guide-tls-introduction.pod index 925cc69737d..307b5eec914 100644 --- a/doc/man7/ossl-guide-tls-introduction.pod +++ b/doc/man7/ossl-guide-tls-introduction.pod @@ -297,10 +297,12 @@ object. See L to see an example of applying these concepts in order to write a simple TLS client based on a blocking socket. +See L for an introduction to QUIC in OpenSSL. =head1 SEE ALSO -L, L +L, L, +L =head1 COPYRIGHT -- 2.47.2