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.
-
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?
-
@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 :)