QSslSocket::startServerEncryption() does not lead to an encypted SSL / TLS session



  • I am trying to implement a threaded SSL server using QTcpServer::incomingConnection(qintptr handle) approach.

    The QSslSocket documentation has the following wisdom:

    void QSslSocket::startServerEncryption()
    Starts a delayed SSL handshake for a server connection. This function can be called when the socket is in the ConnectedState but still in UnencryptedMode. If it is not connected or it is already encrypted, the function has no effect.
    For server sockets, calling this function is the only way to initiate the SSL handshake. Most servers will call this function immediately upon receiving a connection, or as a result of having received a protocol-specific command to enter SSL mode (e.g, the server may respond to receiving the string "STARTTLS\r\n" by calling this function).
    The most common way to implement an SSL server is to create a subclass of QTcpServer and reimplement QTcpServer::incomingConnection(). The returned socket descriptor is then passed to QSslSocket::setSocketDescriptor().

    The most interesting bit is the last paragraph, which I tried to implement below.

    SSLServer.h:

    #ifndef SSLSERVER_H
    #define SSLSERVER_H
    
    #include <QTcpServer>
    
    class SSLServer : public QTcpServer
    {
        Q_OBJECT
    
    public:
        static SSLServer* instance(qint16 port);
    
    protected:
        void incomingConnection(qintptr handle) override;
    
    private:
        SSLServer(qint16 port);
        ~SSLServer();
    };
    
    #endif // SSLSERVER_H
    
    

    SSLServer.cpp:

    #include "SSLServer.h"
    #include "SSLServerThread.h"
    #include <QFile>
    #include <QSslConfiguration>
    #include <QStandardPaths>
    #include <QSslCipher>
    #include <QSslKey>
    
    #ifdef DEBUG
    #include <QDebug>
    #endif
    
    SSLServer* s_instance = NULL;
    
    SSLServer* SSLServer::instance(qint16 port ){
        if(!s_instance)
        {
            s_instance = new SSLServer(port);
        }
        return s_instance;
    }
    
    SSLServer::SSLServer(const qint16 port)
        : QTcpServer()
    {
    
        QString qrc_template(":/%1/%2"), local_cert_name, key_name, ca_cert_name;
        local_cert_name = qrc_template
                .arg(CERT_PATH)
                .arg(SSL_CERTIFICATE);
        key_name = qrc_template
                .arg(CERT_PATH)
                .arg(SSL_KEY);
        ca_cert_name = qrc_template
                .arg(CERT_PATH)
                .arg(CA_CERTIFICATE);
    #ifdef DEBUG
        qDebug() << "Local cert name: " << local_cert_name;
        qDebug() << "key name: " << key_name;
        qDebug() << "ca cert name: " << ca_cert_name;
    #endif
    
        QSslCertificate local_cert;
        QSslKey key;
        QList<QSslCertificate> ca_certs;
    
        ca_certs = QSslCertificate::fromPath(local_cert_name, QSsl::Pem);
        local_cert = ca_certs.first();
        QFile tmp(key_name);
        if(tmp.open(QFile::ReadOnly)) {
            key = QSslKey(&tmp, QSsl::Rsa, QSsl::Pem);
            tmp.close();
        } else {
    #ifdef DEBUG
            qDebug() << "SSLServer::SSLServer() - unable to read key from qrc file";
    #endif
            exit(3);
        }
        ca_certs = QSslCertificate::fromPath(ca_cert_name, QSsl::Pem);
    
    #ifdef DEBUG
        qDebug() << "local cert: " ;
        qDebug() << local_cert.toPem() ;
        qDebug() << "ca certs: " ;
        for (QSslCertificate cert : ca_certs) {
            qDebug() << cert.toPem() ;
        }
        qDebug() << "key: " ;
        qDebug() << key.toPem();
    #endif
    
        QSslConfiguration ssl_default;
        ssl_default.setLocalCertificate(local_cert);
        ssl_default.setPrivateKey(key);
        ssl_default.setLocalCertificateChain(ca_certs);
        ssl_default.setCaCertificates(ca_certs);
        ssl_default.setProtocol(QSsl::TlsV1SslV3);
        ssl_default.setPeerVerifyMode(QSslSocket::VerifyNone);
    
        QSslConfiguration::setDefaultConfiguration(ssl_default);
    
    #ifdef DEBUG
        QList<QSslCipher> lstciphers = QSslConfiguration::supportedCiphers();
        for (QSslCipher cipher : lstciphers) {
            qDebug() << cipher.name();
        }
    #endif
    
        if (!listen(QHostAddress::Any, port)) {
    #ifdef DEBUG
            qDebug() << "error when listening on port " << port << "\n";
    #endif
            exit(2);
        }
    
    }
    
    SSLServer::~SSLServer () {
    }
    
    void SSLServer::incomingConnection(qintptr handle)
    {
        SSLServerThread *thread = new SSLServerThread(handle, this);
        connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
        thread->start();
    
    }
    

    SSLServerThread.h

    #ifndef SSLSERVERTHREAD_H
    #define SSLSERVERTHREAD_H
    
    #include <QThread>
    #include <QTcpSocket>
    #include <QSslSocket>
    #include <QSslError>
    #include "defines.h"
    class SSLServerThread : public QThread
    {
        Q_OBJECT
    
    public:
        /*! \brief constructor */
        SSLServerThread(qintptr socketDescriptor, QObject* parent = 0);
    
        /*! \brief destructor */
        ~SSLServerThread();
    
        void run() override;
    
    public slots:
        void startComms();
        void onSslError(QList<QSslError> errors);
    
    signals:
        void error(QTcpSocket::SocketError socketError);
    
    private:
        qintptr m_socketDescriptor;
        QSslSocket *m_sslSocket;
    
    };
    #endif // SSLSERVERTHREAD_H
    

    defines.h

    #ifndef DEFINES_H
    #define DEFINES_H
    
    #define CERT_PATH "certificates"
    #define CA_CERTIFICATE "cacert.pem"
    #define SSL_CERTIFICATE "SSL_certificate.pem"
    #define SSL_KEY "SSL_key.pem"
    
    #endif // DEFINES_H
    
    

    SSLServerThread.cpp

    #include "SSLServerThread.h"
    #include "TcpSocketHelper.h"
    
    #include <QFile>
    #include <QSslKey>
    #include <QSslCipher>
    #include <QSslConfiguration>
    
    #ifdef DEBUG
    #include <QDebug>
    #include <QHostAddress>
    #endif
    
    SSLServerThread::SSLServerThread(qintptr socketDescriptor, QObject *parent)
        : QThread(parent), m_socketDescriptor(socketDescriptor)
    {
    }
    
    SSLServerThread::~SSLServerThread()
    {
    }
    
    void SSLServerThread::run()
    {
        // Doco: http://doc.qt.io/qt-5/qsslsocket.html
        m_sslSocket = new QSslSocket(this);
        if(! m_sslSocket->setSocketDescriptor(m_socketDescriptor)) {
    #ifdef DEBUG
            qDebug() << "failed to set socket descriptor";
    #endif
            delete m_sslSocket;
            return;
        }
    #ifdef DEBUG
        qDebug() << QString("SSLServerthread::run() - connected to %1:%2")
                    .arg(m_sslSocket->peerAddress().toString())
                    .arg(m_sslSocket->peerPort()) ;
    #endif
    
        connect(m_sslSocket, SIGNAL(encrypted()), this, SLOT(startComms()));
        connect(m_sslSocket, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(onSslError(QList<QSslError>)));
    
    #ifdef DEBUG
        qDebug() << "SSLServerThread::run() - local certificate chain is";
        QList<QSslCertificate> chain =  m_sslSocket->localCertificateChain();
        for (QSslCertificate cert : chain) {
            qDebug() << cert.toText();
        }
    
        qDebug() << "SSLServerThread::run() - local certificate is";
        qDebug() << m_sslSocket->localCertificate().toPem();
    
        qDebug() << "SSLServerThread::run() - private key is";
        qDebug() << m_sslSocket->privateKey().toPem();
    #endif
    
    
        m_sslSocket->startServerEncryption();
        while(! m_sslSocket->waitForEncrypted(2000)) {
    #ifdef DEBUG
            qDebug() << "wait for encrypted failed";
    #endif
        }
    #ifdef DEBUG
        qDebug() << "Socket is now encrypted";
    #endif
    
    }
    
    void SSLServerThread::startComms()
    {
    // implement protocol here
    }
    
    void SSLServerThread::onSslError(QList<QSslError> errors)
    {
    #ifdef DEBUG
        for (QSslError error : errors) {
            qDebug() <<  error.errorString();
        }
    #endif
        //this->m_sslSocket->ignoreSslErrors();
    }
    

    main.cpp

    #include <QCoreApplication>
    #include <SSLServer.h>
    
    #ifdef DEBUG
    #include <QDebug>
    #endif
    
    int main(int argc, char *argv[])
    {
        QCoreApplication instance(argc, argv);
        if(argc != 2)
        {
    #ifdef DEBUG
            qDebug() << "Please start with SSLServer <port>\n";
    #endif
            return 1;
        }
    
        SSLServer* server = SSLServer::instance(QString(argv[1]).toInt());
    
    #ifdef DEBUG
        qDebug() << "Server started at port " << server->serverPort() << "\n";
    #endif
    
        return instance.exec();
    
    }
    

    However, the run() method of my SSLServerThread never gets an encrypted connection. It sits forever there and waits for an encrypted connection. This can easily be tested by connecting with openssl as follows.

    openssl s_client -connect host:port
    

    where host is the host that this runs on (e.g. localhost) and the port is the port that was specified as an argument to the main method (e.g. 1234).

    Interestingly, the SSL Websocket example code that uses an event loop works well with the openssl command above. I'm wondering what I may be doing wrong here?

    I don't want to use an event loop, because I may need some plaintext communication before establishing the SSL context on the socket, and I also think that a threaded implementation has better performance under high load as it can use multiple CPU cores more efficiently.

    Thanks for reviewing!

    I found a similar problem described in QSslSocket Server Side SNI Support but the post has not been active since March, and doesn't have a resolution, so I decided to start a new topic.



  • To answer my own problem: always make sure the certificate chain that you set for the SSL connection contains the leaf certificate. Otherwise startServerEncryption() does not return.

    It would be good if QSslSocket::startServerEncryption() would throw an exception or at least give some debug output when it can't parse the local certificate chain correctly.


  • Lifetime Qt Champion

    Hi and welcome to devnet,

    Did you check whether the bug report system contains something related to that ? If not, you could consider opening a feature request for that.



  • @SGaist: Sorry, no I didn't check the bug report system for that.

    I also found out that a QSslSocket requires an event loop to function - it simply doesn't work in the run() method of the thread. You have to create it from the socket descriptor / handle in the exec() method, and write a custom run() method that calls exec(). Once exec() is active, you've got the event loop.

    Another pitfall you may encounter with using threads is that the "this" pointer points to the original class instance that lives in the parent thread. So if you create new objects in the thread, don't use QObject(this) but use QObject((classname*)QObject::currentThread()) instead.

    Overall the problem is resolved now for me. Not sure how I mark it resolved?


  • Qt Champions 2016

    @Dr.G said in QSslSocket::startServerEncryption() does not lead to an encypted SSL / TLS session:

    Not sure how I mark it resolved?

    On the first post, in Topic tools, should be way to mark as solved :)



  • @mrjj Cheers, that worked for me :)


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.