Server Name Indication (SNI) with Qt and OpenSSL (Not QSslSocket)

If you are reading this post, you are probably looking for a way to implement Server Name Indication (SNI) with Qt. I would even go further and assume that you have been using the QTcpSocket and QSslSocket classes for unencrypted and encrypted socket communications respectively in your program. You have also probably looked around and cannot find a way to implement SNI in a server program with Qt while using the QSslSocket class.

Let’s stop for a minute and look closely at exactly what we are talking about here. What is SNI, and why is it important? Simply put, Server Name Indication is a TLS protocol extension that allows a client to indicate which host name it is requesting. Without SNI, an HTTPS server set up to serve content for multiple host names (virtual hosts) over a unique IP address would not be able to differentiate which client requests are meant for each host name respectively.

During an HTTP communication (request and response), which is not encrypted, the host name can easily be read by a receiving server simply by parsing the request headers. The server then knows whether it needs to respond to, let’s say, a request for thatPicture.jpg on the website with the host name nachega.com instead of thatPicture.jpg on the website with the host name nicholasnachega.com. The server here is obviously set up to serve content for both of those websites. Otherwise, it would simply return a response saying that the content is not available.

But what happens if the request is encrypted and sent over an HTTPS communication? The client and the server then have to do what is called a TLS/SSL handshake first, in which they will exchange a certificate and key and agree on how their communication is going to be encrypted. As during the HTTP communication, the server still needs to know which host name is being requested by the client during the TLS/SSL handshake in order to send the certificate that corresponds to that host name.

But, unlike during an HTTP communication, the server cannot read the HTTPS request headers before the handshake is complete in order to find out which host name is being requested by the client. Otherwise, reading the request headers before the handshake, if it was at all possible, would be done insecurely since the client and the server would not yet have agreed on how to secure their communication. But, even during an HTTPS communication, the server can still find out which host name is being requested thanks to Server Name Indication.

The SNI extension to the TLS protocol adds a way for a client to send the host name to the server during the handshake. This way, the server can know which exact certificate it needs to present to the client in order to complete a successful TLS/SSL handshake. By the time the HTTPS request headers are received by the server, the TLS/SSL handshake will have been successfully completed, and the server will then be able to read the encrypted HTTPS headers and respond to the request.

The server is able to find out which host name is being requested because the ClientHello message sent by the client to the server at the start of the TLS handshake contains the host name being requested. Adding the host name to the ClientHello message is a requirement of the TLS protocol since the SNI extension was standardized in 2003. The ClientHello message is sent unencrypted before the server and the client agree on how to secure their communication, which means that even if the handshake is not successfully completed, the server might still be able to know which host name was requested.

It is important to note that knowing the right host name during the TLS /SSL handshake is only important when a server is serving content for multiple host names such as nachega.com and nicholasnachega.com (virtual hosts). If a server is only set up to serve content for one host name, let’s say nachega.com, then it would not necessarily have to worry about identifying which host name a client is requesting. It could just assume that all requests are meant for nachega.com.

So, now let’s go back to Qt and SNI. As I wrote earlier, you are probably looking for a way to implement SNI using the QSslSocket class. I am afraid to inform you that, as of the current available version of Qt (5.14.0), you cannot do it using the QSslSocket class.

“Why not?”, you might ask. Well, it is simply not implemented yet. QSslSocket does currently allow you to set one certificate that will be used during the TLS/SSL handshake, but for either a predefined host name or multiple host names in case the certificate is a Unified Communications certificate (UCC) or a Subject Alternative Names (SAN) certificate. That’s it. As of the day of publication of this post, you do not have access, at least through QSslSocket, to a way that will allow you to read the SNI data sent by the client at the beginning of the TLS/SSL handshake. And without access to the SNI data at the beginning of the TLS/SSL handshake, you simply cannot securely complete a handshake using SNI and QSslSocket.

Obviously, you can successfully complete a TLS/SSL handshake by using QSslSocket alone without relying on SNI. That’s not the issue. You will be limited to setting your server to use the one certificate you provided using QSslSocket. The issue arises when you want to be able to set up your server to respond to requests for multiple host names while using different certificates depending on which host name is requested. You cannot yet set up different certificates to respond to requests for multiple host names using QSslSocket. That’s the issue.

“Why not just use a SAN certificate then?”, you might ask. Using a UCC or SAN certificate might allow you to circumvent the fact that QSslSocket doesn’t yet support SNI, but using one certificate for multiple host names is not always a practical solution. Not everyone wants their certificate to include multiple host names, which for some does lower the authentication trust level. Also, if you need to add a host name to a SAN certificate, then you need to get a new certificate including all the other host names. That might be a costly approach unless you are using a free certificate issuer such as Let’s Encrypt, since SAN certificates cost much more than single host name certificates.

Now, hold on! You are not completely out of luck. You can still use Qt and SNI in a server program. If you want to continue using QSslSocket, you will have to rewrite the underlying code of how Qt handles the start of TLS/SSL connections. At the moment, Qt uses the OpenSSL library to handle TLS/SSL connections. If you have enough time and know-how, you can rewrite the QSslSocket class, or extend it, or do whatever you feel like, to support SNI. Not exactly a small task, especially if all you expected was to browse the Internet for an easy solution.

Another way to support SNI in your Qt server program would be to simply not use QSslSocket when you write the part of your program that receives and responds to HTTPS requests. You can use OpenSSL directly to receive requests, decide what happens at the beginning of the TLS/SSL handshake, and then respond. Here is the code for a simple TLS server found on the OpenSSL wiki.

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
int create_socket(int port)
{
    int s;
    struct sockaddr_in addr;
    
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    s = socket(AF_INET, SOCK_STREAM, 0);
    if (s < 0) {
        perror("Unable to create socket");
        exit(EXIT_FAILURE);
    }
    
    if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        perror("Unable to bind");
        exit(EXIT_FAILURE);
    }
    
    if (listen(s, 1) < 0) {
        perror("Unable to listen");
        exit(EXIT_FAILURE);
    }
    
    return s;
}
void init_openssl()
{ 
    SSL_load_error_strings();   
    OpenSSL_add_ssl_algorithms();
}
void cleanup_openssl()
{
    EVP_cleanup();
}
SSL_CTX *create_context()
{
    const SSL_METHOD *method;
    SSL_CTX *ctx;
    
    method = SSLv23_server_method();
    
    ctx = SSL_CTX_new(method);
    if (!ctx) {
        perror("Unable to create SSL context");
        ERR_print_errors_fp(stderr);
        exit(EXIT_FAILURE);
    }
    
    return ctx;
}
void configure_context(SSL_CTX *ctx)
{
    SSL_CTX_set_ecdh_auto(ctx, 1);
    
    /* Set the key and cert */
    if (SSL_CTX_use_certificate_file(ctx, "cert.pem", SSL_FILETYPE_PEM) <= 0) {
        ERR_print_errors_fp(stderr);
        exit(EXIT_FAILURE);
    }
    
    if (SSL_CTX_use_PrivateKey_file(ctx, "key.pem", SSL_FILETYPE_PEM) <= 0 ) {
        ERR_print_errors_fp(stderr);
        exit(EXIT_FAILURE);
    }
}
int main(int argc, char **argv)
{
    int sock;
    SSL_CTX *ctx;
    
    init_openssl();
    ctx = create_context();
    
    configure_context(ctx);
    
    sock = create_socket(4433);
    
    /* Handle connections */
    while(1) {
        struct sockaddr_in addr;
        uint len = sizeof(addr);
        SSL *ssl;
        const char reply[] = "test\n";
        
        int client = accept(sock, (struct sockaddr*)&addr, &len);
        if (client < 0) {
            perror("Unable to accept");
            exit(EXIT_FAILURE);
        }
        
        ssl = SSL_new(ctx);
        SSL_set_fd(ssl, client);
        
        if (SSL_accept(ssl) <= 0) {
            ERR_print_errors_fp(stderr);
        }
        else {
            SSL_write(ssl, reply, strlen(reply));
        }
        
        SSL_shutdown(ssl);
        SSL_free(ssl);
        close(client);
    }
    
    close(sock);
    SSL_CTX_free(ctx);
    cleanup_openssl();
}


You can integrate and adapt this code into your Qt server program. “But, where is the SNI part?”, you ask. Well, hold on! We’re getting there. OpenSSL does support SNI. As you can read on this thread on StackOverflow, after integrating some of the code above into your program, you’d also need to:

  • Set up an additional SSL_CTX() for each different certificate;
  • Add a servername callback to each SSL_CTX() using SSL_CTX_set_tlsext_servername_callback();
  • In the callback, retrieve the client-supplied servername with SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name). Figure out the right SSL_CTX to go with that host name, then switch the SSL object to that SSL_CTX with SSL_set_SSL_CTX().

Here is an example of a callback found on this StackOverflow thread that retrieves the SNI host name data and sets the appropriate certificate.

static int ServerNameCallback(SSL *ssl, int *ad, void *arg) { UNUSED(ad); UNUSED(arg); ASSERT(ssl); if (ssl == NULL) return SSL_TLSEXT_ERR_NOACK; const char* servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); ASSERT(servername && servername[0]); if (!servername || servername[0] == '\0') return SSL_TLSEXT_ERR_NOACK; /* Does the default cert already handle this domain? */ if (IsDomainInDefCert(servername)) return SSL_TLSEXT_ERR_OK; /* Need a new certificate for this domain */ SSL_CTX* ctx = GetServerContext(servername); ASSERT(ctx != NULL); if (ctx == NULL) return SSL_TLSEXT_ERR_NOACK; /* Useless return value */ SSL_CTX* v = SSL_set_SSL_CTX(ssl, ctx); ASSERT(v == ctx); if (v != ctx) return SSL_TLSEXT_ERR_NOACK; return SSL_TLSEXT_ERR_OK; }

And that’s it! By integrating these two examples into your Qt server program, you should be able to get the SNI host name data sent during the TLS/SSL handshake, set the certificate that corresponds to the host name, complete a successful handshake, and securely read and respond to the encrypted request.

How to solve no service found for – org.qt-project.qt.mediaplayer

If you deploy a Qt Multimedia project to a Linux distro, you might come accross this error message:

defaultServiceProvider::requestService(): no service found for – “org.qt-project.qt.mediaplayer”

The error occurs when there are Qt plugins that are either missing from the files you deployed, or their dependencies on the distro are not installed.

Qt Multimedia platform uses the Gstreamer multimedia framework. So, the first place to look is to make sure that you install all the Gstreamer files that your Qt Multimedia program might need. There are several libraries available for GStreamer. The Qt documentation says that you need to install even the “ugly”, “good”, “bad”, and ffmpeg and libav libraries.

To build Qt Multimedia, you need the GStreamer library, base plugins, and development files for your system. To run applications that use Qt Multimedia, you might also need to install the following GStreamer plugins: ‘good’, ‘ugly’, ‘bad’, ffmpeg (0.10), and libav (1.x). These additional plugins contain various codecs for audio and video decoding, as well as the necessary components for using the camera APIs. The package names for GStreamer vary between Linux distributions; try searching for gstreamer or libgstreamerin your distribution’s package repository to find suitable packages.

Here is a command to install many of these libraries at once with yum:

yum install gstreamer1-devel gstreamer1-plugins-base-tools gstreamer1-devel-docs gstreamer1-plugins-base-devel gstreamer1-plugins-base-devel-docs gstreamer1-plugins-good gstreamer1-plugins-good-extras gstreamer1-plugins-ugly gstreamer1-plugins-ugly-devel-docs gstreamer1-plugins-bad-free gstreamer1-plugins-bad-free-devel gstreamer1-plugins-bad-free-extras

If you are still getting the error after installing the GStreamer libraries, then you should look at the Qt Multimedia plugins you deployed with your program.

The easiest way to find out if one of your libraries is not being loaded properly by the program is to set the QT_DEBUG_PLUGINS to a non-zero value. This will make the program print debug information about the libraries it is loading during runtime.

You can set the QT_DEBUG_PLUGINS environment variable with the export command:

export QT_DEBUG_PLUGINS=1

You can check to see if the variable has been set with the command:

echo $QT_DEBUG_PLUGINS

Which should print: 1

If you run your program from the terminal, you will now see debug information about the plugins being loaded. If a plugin that is needed was not loaded, the program should print its name and where it looked for the plugin.

In my case, the debug information showed that the “mediaservice” folder was not deployed. After adding it to the deployed files, the debug information showed that the “libqgsttools_p” libraries were not loading properly.

I had to run the command: ldd libgstmediaplayer.so from the mediaservice directory to find out which dependencies were still missing. The libQt5MultimediaWidgets.(*) files and the libqgsttools_p.(*) files were missing from the /lib directory. The mediaservice folder was also missing from the plugins directory.

It turns out that the linuxdeployqt tool I used to package the libraries and create the AppImage file did not include the missing files. You can find the “mediaservice” folder and other missing libraries under your Qt installation. To avoid this error, you need to make sure they are deployed with your program.

After installing the missing Qt Multimedia files, your program should be able to run.

Contact: nick@nachega.com
© Nachega.com