dwww Home | Manual pages | Find package

OSSL-GUIDE-TLS-CLIENT-BLOCK(7SSL)   OpenSSL   OSSL-GUIDE-TLS-CLIENT-BLOCK(7SSL)

NAME
       ossl-guide-tls-client-block - OpenSSL Guide: Writing a simple blocking
       TLS client

SIMPLE BLOCKING TLS CLIENT EXAMPLE
       This page will present various source code samples demonstrating how to
       write a simple TLS client application which connects to a server, sends
       an HTTP/1.0 request to it, and reads back the response.

       We use a blocking socket for the purposes of this example. This means
       that attempting to read data from a socket that has no data available on
       it to read will block (and the function will not return), until data
       becomes available.  For example, this can happen if we have sent our
       request, but we are still waiting for the server's response. Similarly
       any attempts to write to a socket that is not able to write at the
       moment will block until writing is possible.

       This blocking behaviour simplifies the implementation of a client
       because you do not have to worry about what happens if data is not yet
       available. The application will simply wait until it is available.

       The complete source code for this example blocking TLS client is
       available in the demos/guide directory of the OpenSSL source
       distribution in the file tls-client-block.c. It is also available online
       at
       <https://github.com/openssl/openssl/blob/master/demos/guide/tls-client-block.c>.

       We assume that you already have OpenSSL installed on your system; that
       you already have some fundamental understanding of OpenSSL concepts and
       TLS (see ossl-guide-libraries-introduction(7) and
       ossl-guide-tls-introduction(7)); 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 TCP/IP and sockets.

   Creating the SSL_CTX and SSL objects
       The first step is to create an SSL_CTX object for our client. We use the
       SSL_CTX_new(3) function for this purpose. We could alternatively use
       SSL_CTX_new_ex(3) if we want to associate the SSL_CTX with a particular
       OSSL_LIB_CTX (see ossl-guide-libraries-introduction(7) to learn about
       OSSL_LIB_CTX). We pass as an argument the return value of the function
       TLS_client_method(3). You should use this method whenever you are
       writing a TLS client. This method will automatically use TLS version
       negotiation to select the highest version of the protocol that is
       mutually supported by both the client and the server.

           /*
            * Create an SSL_CTX which we can use to create SSL objects from. We
            * want an SSL_CTX for creating clients so we use TLS_client_method()
            * here.
            */
           ctx = SSL_CTX_new(TLS_client_method());
           if (ctx == NULL) {
               printf("Failed to create the SSL_CTX\n");
               goto end;
           }

       Since we are writing a client we must ensure that we verify the server's
       certificate. We do this by calling the SSL_CTX_set_verify(3) function
       and pass the SSL_VERIFY_PEER value to it. The final argument to this
       function is a callback that you can optionally supply to override the
       default handling for certificate verification. Most applications do not
       need to do this so this can safely be set to NULL to get the default
       handling.

           /*
            * Configure the client to abort the handshake if certificate
            * verification fails. Virtually all clients should do this unless you
            * really know what you are doing.
            */
           SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);

       In order for certificate verification to be successful you must have
       configured where the trusted certificate store to be used is located
       (see ossl-guide-tls-introduction(7)). In most cases you just want to use
       the default store so we call SSL_CTX_set_default_verify_paths(3).

           /* Use the default trusted certificate store */
           if (!SSL_CTX_set_default_verify_paths(ctx)) {
               printf("Failed to set the default trusted certificate store\n");
               goto end;
           }

       We would also like to restrict the TLS versions that we are willing to
       accept to TLSv1.2 or above. TLS protocol versions earlier than that are
       generally to be avoided where possible. We can do that using
       SSL_CTX_set_min_proto_version(3):

           /*
            * TLSv1.1 or earlier are deprecated by IETF and are generally to be
            * avoided if possible. We require a minimum TLS version of TLSv1.2.
            */
           if (!SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION)) {
               printf("Failed to set the minimum TLS protocol version\n");
               goto end;
           }

       That is all the setup that we need to do for the SSL_CTX, so next we
       need to create an SSL object to represent the TLS connection. In a real
       application we might expect to be creating more than one TLS connection
       over time. In that case we would expect to reuse the SSL_CTX that we
       already created each time.  There is no need to repeat those steps. In
       fact it is best not to since certain internal resources are cached in
       the SSL_CTX. You will get better performance by reusing an existing
       SSL_CTX instead of creating a new one each time.

       Creating the SSL object is a simple matter of calling the SSL_new(3)
       function and passing the SSL_CTX we created as an argument.

           /* Create an SSL object to represent the TLS connection */
           ssl = SSL_new(ctx);
           if (ssl == NULL) {
               printf("Failed to create the SSL object\n");
               goto end;
           }

   Creating the socket and BIO
       TLS data is transmitted over an underlying transport layer. Normally a
       TCP socket. It is the application's responsibility for ensuring that the
       socket is created and associated with an SSL object (via a BIO).

       Socket creation for use by a client is typically a 2 step process, i.e.
       constructing the socket; and connecting the socket.

       How to construct a socket is platform specific - but most platforms
       (including Windows) provide a POSIX compatible interface via the socket
       function, e.g.  to create an IPv4 TCP socket:

           int sock;

           sock = socket(AF_INET, SOCK_STREAM, 0);
           if (sock == -1)
               return NULL;

       Once the socket is constructed it must be connected to the remote
       server. Again the details are platform specific but most platforms
       (including Windows) provide the POSIX compatible connect function. For
       example:

           struct sockaddr_in serveraddr;
           struct hostent *server;

           server = gethostbyname("www.openssl.org");
           if (server == NULL) {
               close(sock);
               return NULL;
           }

           memset(&serveraddr, 0, sizeof(serveraddr));
           serveraddr.sin_family = server->h_addrtype;
           serveraddr.sin_port = htons(443);
           memcpy(&serveraddr.sin_addr.s_addr, server->h_addr, server->h_length);

           if (connect(sock, (struct sockaddr *)&serveraddr,
                       sizeof(serveraddr)) == -1) {
               close(sock);
               return NULL;
           }

       OpenSSL provides portable helper functions to do these tasks which also
       integrate into the OpenSSL error system to log error data, e.g.

           int sock = -1;
           BIO_ADDRINFO *res;
           const BIO_ADDRINFO *ai = NULL;

           /*
            * Lookup IP address info for the server.
            */
           if (!BIO_lookup_ex(hostname, port, BIO_LOOKUP_CLIENT, family, SOCK_STREAM, 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_STREAM, 0, 0);
               if (sock == -1)
                   continue;

               /* Connect the socket to the server's address */
               if (!BIO_connect(sock, BIO_ADDRINFO_address(ai), BIO_SOCK_NODELAY)) {
                   BIO_closesocket(sock);
                   sock = -1;
                   continue;
               }

               /* We have a connected socket so break out of the loop */
               break;
           }

           /* Free the address information resources we allocated earlier */
           BIO_ADDRINFO_free(res);

       See BIO_lookup_ex(3), BIO_socket(3), BIO_connect(3), BIO_closesocket(3),
       BIO_ADDRINFO_next(3), BIO_ADDRINFO_address(3) and BIO_ADDRINFO_free(3)
       for further information on the functions used here. In the above example
       code the hostname and port variables are strings, e.g.
       "www.example.com" and "443".  Note also the use of the family variable,
       which can take the values of AF_INET or AF_INET6 based on the command
       line -6 option, to allow specific connections to an ipv4 or ipv6 enabled
       host.

       Sockets created using the methods described above will automatically be
       blocking sockets - which is exactly what we want for this example.

       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_socket());
           if (bio == NULL) {
               BIO_closesocket(sock);
               return NULL;
           }

           /*
            * 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);

       See BIO_new(3), BIO_s_socket(3) and BIO_set_fd(3) for further
       information on these functions.

       Finally we associate the SSL object we created earlier with the BIO
       using the SSL_set_bio(3) function. Note that this passes ownership of
       the BIO object to the SSL object. Once ownership is passed the SSL
       object is responsible for its management and will free it automatically
       when the SSL is freed. So, once SSL_set_bio(3) has been been called, you
       should not call BIO_free(3) on the BIO.

           SSL_set_bio(ssl, bio, bio);

   Setting the server's hostname
       We have already connected our underlying socket to the server, but the
       client still needs to know the server's hostname. It uses this
       information for 2 key purposes and we need to set the hostname for each
       one.

       Firstly, the server's hostname is included in the initial ClientHello
       message sent by the client. This is known as the Server Name Indication
       (SNI). This is important because it is common for multiple hostnames to
       be fronted by a single server that handles requests for all of them. In
       other words a single server may have multiple hostnames associated with
       it and it is important to indicate which one we want to connect to.
       Without this information we may get a handshake failure, or we may get
       connected to the "default" server which may not be the one we were
       expecting.

       To set the SNI hostname data we call the SSL_set_tlsext_host_name(3)
       function like this:

           /*
            * Tell the server during the handshake which hostname we are attempting
            * to connect to in case the server supports multiple hosts.
            */
           if (!SSL_set_tlsext_host_name(ssl, hostname)) {
               printf("Failed to set the SNI hostname\n");
               goto end;
           }

       Here the "hostname" argument is a string representing the hostname of
       the server, e.g. "www.example.com".

       Secondly, we need to tell OpenSSL what hostname we expect to see in the
       certificate coming back from the server. This is almost always the same
       one that we asked for in the original request. This is important
       because, without this, we do not verify that the hostname in the
       certificate is what we expect it to be and any certificate is acceptable
       unless your application explicitly checks this itself. We do this via
       the SSL_set1_host(3) function:

           /*
            * Ensure we check during certificate verification that the server has
            * supplied a certificate for the hostname that we were expecting.
            * Virtually all clients should do this unless you really know what you
            * are doing.
            */
           if (!SSL_set1_host(ssl, hostname)) {
               printf("Failed to set the certificate verification hostname");
               goto end;
           }

       All of the above steps must happen before we attempt to perform the
       handshake otherwise they will have no effect.

   Performing the handshake
       Before we can start sending or receiving application data over a TLS
       connection the TLS handshake must be performed. We can do this
       explicitly via the SSL_connect(3) function.

           /* Do the handshake with the server */
           if (SSL_connect(ssl) < 1) {
               printf("Failed to connect to the server\n");
               /*
                * If the failure is due to a verification error we can get more
                * information about it from SSL_get_verify_result().
                */
               if (SSL_get_verify_result(ssl) != X509_V_OK)
                   printf("Verify error: %s\n",
                       X509_verify_cert_error_string(SSL_get_verify_result(ssl)));
               goto end;
           }

       The SSL_connect(3) function can return 1, 0 or less than 0. Only a
       return value of 1 is considered a success. For a simple blocking client
       we only need to concern ourselves with whether the call was successful
       or not. Anything else indicates that we have failed to connect to the
       server.

       A common cause of failures at this stage is due to a problem verifying
       the server's certificate. For example if the certificate has expired, or
       it is not signed by a CA in our trusted certificate store. We can use
       the SSL_get_verify_result(3) function to find out more information about
       the verification failure. A return value of X509_V_OK indicates that the
       verification was successful (so the connection error must be due to some
       other cause). Otherwise we use the X509_verify_cert_error_string(3)
       function to get a human readable error message.

   Sending and receiving data
       Once the handshake is complete we are able to send and receive
       application data.  Exactly what data is sent and in what order is
       usually controlled by some application level protocol. In this example
       we are using HTTP 1.0 which is a very simple request and response
       protocol. The client sends a request to the server. The server sends the
       response data and then immediately closes down the connection.

       To send data to the server we use the SSL_write_ex(3) function and to
       receive data from the server we use the SSL_read_ex(3) function. In HTTP
       1.0 the client always writes data first. Our HTTP request will include
       the hostname that we are connecting to. For simplicity, we write the
       HTTP request in three chunks. First we write the start of the request.
       Secondly we write the hostname we are sending the request to. Finally we
       send the end of the request.

           size_t written;
           const char *request_start = "GET / HTTP/1.0\r\nConnection: close\r\nHost: ";
           const char *request_end = "\r\n\r\n";

           /* Write an HTTP GET request to the peer */
           if (!SSL_write_ex(ssl, request_start, strlen(request_start), &written)) {
               printf("Failed to write start of HTTP request\n");
               goto end;
           }
           if (!SSL_write_ex(ssl, hostname, strlen(hostname), &written)) {
               printf("Failed to write hostname in HTTP request\n");
               goto end;
           }
           if (!SSL_write_ex(ssl, request_end, strlen(request_end), &written)) {
               printf("Failed to write end of HTTP request\n");
               goto end;
           }

       The SSL_write_ex(3) function returns 0 if it fails and 1 if it is
       successful.  If it is successful then we can proceed to waiting for a
       response from the server.

           size_t readbytes;
           char buf[160];

           /*
            * Get up to sizeof(buf) bytes of the response. We keep reading until the
            * server closes the connection.
            */
           while (SSL_read_ex(ssl, buf, sizeof(buf), &readbytes)) {
               /*
               * OpenSSL does not guarantee that the returned data is a string or
               * that it is NUL terminated so we use fwrite() to write the exact
               * number of bytes that we read. The data could be non-printable or
               * have NUL characters in the middle of it. For this simple example
               * we're going to print it to stdout anyway.
               */
               fwrite(buf, 1, readbytes, stdout);
           }
           /* In case the response didn't finish with a newline we add one now */
           printf("\n");

       We use the SSL_read_ex(3) function to read the response. We don't know
       exactly how much data we are going to receive back so we enter a loop
       reading blocks of data from the server and printing each block that we
       receive to the screen. The loop ends as soon as SSL_read_ex(3) returns 0
       - meaning that it failed to read any data.

       A failure to read data could mean that there has been some error, or it
       could simply mean that server has sent all the data that it wants to
       send and has indicated that it has finished by sending a "close_notify"
       alert. This alert is a TLS protocol level message indicating that the
       endpoint has finished sending all of its data and it will not send any
       more. Both of these conditions result in a 0 return value from
       SSL_read_ex(3) and we need to use the function SSL_get_error(3) to
       determine the cause of the 0 return value.

           /*
            * Check whether we finished the while loop above normally or as the
            * result of an error. The 0 argument to SSL_get_error() is the return
            * code we received from the SSL_read_ex() call. It must be 0 in order
            * to get here. Normal completion is indicated by SSL_ERROR_ZERO_RETURN.
            */
           if (SSL_get_error(ssl, 0) != SSL_ERROR_ZERO_RETURN) {
               /*
                * Some error occurred other than a graceful close down by the
                * peer
                */
               printf ("Failed reading remaining data\n");
               goto end;
           }

       If SSL_get_error(3) returns SSL_ERROR_ZERO_RETURN then we know that the
       server has finished sending its data. Otherwise an error has occurred.

   Shutting down the connection
       Once we have finished reading data from the server then we are ready to
       close the connection down. We do this via the SSL_shutdown(3) function
       which has the effect of sending a TLS protocol level message (a
       "close_notify" alert) to the server saying that we have finished writing
       data:

           /*
            * The peer already shutdown gracefully (we know this because of the
            * SSL_ERROR_ZERO_RETURN above). We should do the same back.
            */
           ret = SSL_shutdown(ssl);
           if (ret < 1) {
               /*
                * ret < 0 indicates an error. ret == 0 would be unexpected here
                * because that means "we've sent a close_notify and we're waiting
                * for one back". But we already know we got one from the peer
                * because of the SSL_ERROR_ZERO_RETURN above.
                */
               printf("Error shutting down\n");
               goto end;
           }

       The SSL_shutdown(3) function will either return 1, 0, or less than 0. A
       return value of 1 is a success, and a return value less than 0 is an
       error. More precisely a return value of 1 means that we have sent a
       "close_notify" alert to the server, and that we have also received one
       back. A return value of 0 means that we have sent a "close_notify" alert
       to the server, but we have not yet received one back. Usually in this
       scenario you would call SSL_shutdown(3) again which (with a blocking
       socket) would block until the "close_notify" is received. However in
       this case we already know that the server has sent us a "close_notify"
       because of the SSL_ERROR_ZERO_RETURN that we received from the call to
       SSL_read_ex(3). So this scenario should never happen in practice. We
       just treat it as an error in this example.

   Final clean up
       Before the application exits we have to clean up some memory that we
       allocated.  If we are exiting due to an error we might also want to
       display further information about that error if it is available to the
       user:

           /* Success! */
           res = EXIT_SUCCESS;
        end:
           /*
            * If something bad happened then we will dump the contents of the
            * OpenSSL error stack to stderr. There might be some useful diagnostic
            * information there.
            */
           if (res == EXIT_FAILURE)
               ERR_print_errors_fp(stderr);

           /*
            * Free the resources we allocated. We do not free the BIO object here
            * because ownership of it was immediately transferred to the SSL object
            * via SSL_set_bio(). The BIO will be freed when we free the SSL object.
            */
           SSL_free(ssl);
           SSL_CTX_free(ctx);
           return res;

       To display errors we make use of the ERR_print_errors_fp(3) function
       which simply dumps out the contents of any errors on the OpenSSL error
       stack to the specified location (in this case stderr).

       We need to free up the SSL object that we created for the connection via
       the SSL_free(3) function. Also, since we are not going to be creating
       any more TLS connections we must also free up the SSL_CTX via a call to
       SSL_CTX_free(3).

TROUBLESHOOTING
       There are a number of things that might go wrong when running the demo
       application. This section describes some common things you might
       encounter.

   Failure to connect the underlying socket
       This could occur for numerous reasons. For example if there is a problem
       in the network route between the client and the server; or a firewall is
       blocking the communication; or the server is not in DNS. Check the
       network configuration.

   Verification failure of the server certificate
       A verification failure of the server certificate would result in a
       failure when running the SSL_connect(3) function. ERR_print_errors_fp(3)
       would display an error which would look something like this:

        Verify error: unable to get local issuer certificate
        40E74AF1F47F0000:error:0A000086:SSL routines:tls_post_process_server_certificate:certificate verify failed:ssl/statem/statem_clnt.c:2069:

       A server certificate verification failure could be caused for a number
       of reasons. For example

       Failure to correctly setup the trusted certificate store
           See  the  page  ossl-guide-tls-introduction(7)  and  check that your
           trusted certificate store is correctly configured

       Unrecognised CA
           If the CA used by the server's certificate is  not  in  the  trusted
           certificate store for the client then this will cause a verification
           failure  during  connection.  Often  this can occur if the server is
           using a self-signed certificate (i.e. a test  certificate  that  has
           not been signed by a CA at all).

       Missing intermediate CAs
           This  is a server misconfiguration where the client has the relevant
           root CA in its trust store, but the server has not supplied  all  of
           the  intermediate  CA  certificates  between  that  root  CA and the
           server's  own  certificate.  Therefore  a  trust  chain  cannot   be
           established.

       Mismatched hostname
           If  for  some  reason  the hostname of the server that the client is
           expecting does not match the hostname in the certificate  then  this
           will cause verification to fail.

       Expired certificate
           The date that the server's certificate is valid to has passed.

       The "unable to get local issuer certificate" we saw in the example above
       means  that  we  have  been  unable  to  find the issuer of the server's
       certificate (or one 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).

FURTHER READING
       See  ossl-guide-tls-client-non-block(7)  to  read  a  tutorial on how to
       modify the client developed  on  this  page  to  support  a  nonblocking
       socket.

       See  ossl-guide-tls-server-block(7) for a tutorial on how to implement a
       simple TLS server handling one client at a time over a blocking socket.

       See ossl-guide-quic-client-block(7) to read a tutorial on how to  modify
       the client developed on this page to support QUIC instead of TLS.

SEE ALSO
       ossl-guide-introduction(7),        ossl-guide-libraries-introduction(7),
       ossl-guide-libssl-introduction(7),       ossl-guide-tls-introduction(7),
       ossl-guide-tls-client-non-block(7), ossl-guide-quic-client-block(7)

COPYRIGHT
       Copyright 2023-2024 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
       <https://www.openssl.org/source/license.html>.

3.5.4                              2025-09-30 OSSL-GUIDE-TLS-CLIENT-BLOCK(7SSL)

Generated by dwww version 1.16 on Tue Dec 16 05:08:10 CET 2025.