I have a Client-Server application that was working with QTcpSocket
. Now I would like to use an encrypted SSL connection instead, therefore I tried to switch to QSslSocket
. But I can't establish a connection to server.
Here is the code for the client:
ConnectionHandler::ConnectionHandler(QString ip, int port, QObject *parent) : QObject(parent) {
// connect(this->socket, SIGNAL(connected()), this, SLOT(connected()));
connect(this->socket, SIGNAL(disconnected()), this, SLOT(disconnected()));
connect(this->socket, SIGNAL(readyRead()), this, SLOT(readyRead()));
connect(this->socket, SIGNAL(encrypted()), this, SLOT(encryptedReady()));
connect(this->socket, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(SSLerrorOccured(const QList<QSslError> &)));
connect(this->socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError)));
connect(this->socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(socketStateChanged(QAbstractSocket::SocketState)));
this->ip = ip;
this->port = port;
}
void ConnectionHandler::socketStateChanged(QAbstractSocket::SocketState state) {
qDebug() << state;
}
void ConnectionHandler::socketError(QAbstractSocket::SocketError) {
qDebug() << this->socket->errorString();
}
void ConnectionHandler::encryptedReady() {
qDebug() << "READY";
}
void ConnectionHandler::SSLerrorOccured(const QList<QSslError> &) {
qDebug() << "EEROR";
}
void ConnectionHandler::connectToServer() {
// this->socket->connectToHost(this->ip, this->port);
this->socket->connectToHostEncrypted(this->ip, this->port);
if (!this->socket->waitForConnected(5000)) {
this->socket->close();
this->errorMsg = this->socket->errorString();
}
}
void ConnectionHandler::connected() {
qDebug() << "connected";
this->connectedHostAddress = this->socket->peerAddress().toString();
this->connectionEstablished = true;
this->localIP = this->socket->localAddress().toString();
this->localPort = this->socket->localPort();
}
Here the one for the server:
ClientHandler::ClientHandler() {
this->socket->setProtocol(QSsl::SslV3);
this->socket->setSocketOption(QAbstractSocket::KeepAliveOption, true);
}
void ClientHandler::run() {
if (!this->fd)
return;
connect(this->socket, SIGNAL(readyRead()), this, SLOT(readyRead()));
connect(this->socket, SIGNAL(disconnected()), this, SLOT(disconnected()), Qt::DirectConnection);
connect(this->socket, SIGNAL(encrypted()), this, SLOT(encryptedReady()));
connect(this->socket, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(sslErrorOccured(const QList<QSslError> &)));
connect(this->socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError)));
connect(this->socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(socketStateChanged(QAbstractSocket::SocketState)));
if (!this->socket->setSocketDescriptor(this->fd)) {
emit error(socket->error());
return;
} else {
connect(this->socket, SIGNAL(encrypted()), this, SLOT(ready()));
this->socket->startServerEncryption();
}
this->peerIP = socket->peerAddress().toString();
QString tmp;
tmp.append(QString("%1").arg(socket->peerPort()));
this->peerPort = tmp;
QHostInfo::lookupHost(this->peerIP, this, SLOT(lookedUp(QHostInfo)));
}
void ClientHandler::socketStateChanged(QAbstractSocket::SocketState state) {
qDebug() << state;
}
void ClientHandler::socketError(QAbstractSocket::SocketError) {
qDebug() << this->socket->errorString();
}
void ClientHandler::setFileDescriptor(int fd) {
this->fd = fd;
}
void ClientHandler::ready() {
qDebug() << "READY";
}
void ClientHandler::sslErrorOccured(const QList<QSslError> &) {
qDebug() << "EEROR";
}
void ClientHandler::encryptedReady() {
qDebug() << "READY";
}
The output for the client I receive is:
QAbstractSocket::HostLookupState
QAbstractSocket::ConnectingState
QAbstractSocket::ConnectedState
"The remote host closed the connection"
QAbstractSocket::ClosingState
QAbstractSocket::UnconnectedState
and for the server:
QAbstractSocket::ConnectedState
"Error during SSL handshake: error:1408A0C1:SSL routines:SSL3_GET_CLIENT_HELLO:no shared cipher"
QAbstractSocket::UnconnectedState
Does anyone know how to fix this?
I assume that with non encrypted sockets everything is fine. So let's focus only on peculiarities of detailing with QSslSocket
. I can share larger pieces or working code if needed. There is also a large story regarding SSL certificates that I will touch briefly here.
To start let's check you client on some external HTTP SSL servers, for example:
socket->connectToHostEncrypted("gmail.com", 443);
It should work immediately with default SSL protocol (without any setProtocol()
). On the signal encrypted()
you can write HTTP GET header and on the readyRead()
the reply will come.
Now try to set socket->setProtocol(QSsl::SslV3);
for "gmail.com"
. Expected result:
sslErrorOccured: ("The host name did not match any of the valid hosts for this certificate")
Note that it is not the error()
signal but the sslErrors()
signal notifying about certificate issues that can be ignored by client.
So, for simplicity let's work with default SSL protocol without using setProtocol()
.
Since the client is in working state we can move to the server. You were interrupted on the first level of SSL certification challenge. To start initializing SSL connection by the server you have to provide at least private encryption key and public certificate. That is your error:
"Error during SSL handshake: error:1408A0C1:SSL routines:SSL3_GET_CLIENT_HELLO:no shared cipher"
In the perfect case your certificate should be signed by an authentication authority company. In that case any client that has a list of such root certificates is able to check that your certificate is valid. However that service is paid and it may take few weeks. We can start with self signed certificate.
You can find various recipes for generating certificates, for example: "How to create a self-signed SSL Certificate which can be used for testing purposes or internal usage"
Short extract:
#Step 1: Generate a Private Key
openssl genrsa -des3 -out server.key 1024
#Step 2: Generate a CSR (Certificate Signing Request)
#Common Name (eg, your name or your server's hostname) []:example.com
openssl req -new -key server.key -out server.csr
#Step 3: Remove Passphrase from Key
cp server.key server.key.org
openssl rsa -in server.key.org -out server.key
#Step 4: Generating a Self-Signed Certificate
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
Now there are server.key
and server.crt
files. Those files should be passed by the server to QSslSocket
before startServerEncryption()
:
socket->setPrivateKey("c:/test/ssl/cert/server.key", QSsl::Rsa);
socket->setLocalCertificate("c:/test/ssl/cert/server.crt");
Now the server can start SSL connection. You can test it with a browser. Typically browser will raise alarm that the connection is not trusted. That is because the certificate is self signed and it cannot protect from "man in the middle" attack. However, you can ask your browser to continue with that connection. So, on the server side you will have the HTTP GET header on the signalreadyRead()
.
Try to connect with the Qt client to that server. Now the error is raised by the client:
sslErrorOccured: ("The certificate is self-signed, and untrusted")
The server says in error()
: "The remote host closed the connection". The client raised the SSL certificate error, but as with browsers we can continue with that connection. Put socket->ignoreSslErrors()
in the sslErrors()
signal handler:
void Client::sslErrorOccured(const QList<QSslError> & error) {
qDebug() << "sslErrorOccured:" << error;
socket->ignoreSslErrors(error);
}
That's it. Of course, your client should not accept all SSL certificate errors from all servers, since such errors mean that the connection is not really secure and can be hacked. It is just for testing. The object QSslError
contains the certificate data, so it possible on your client side to accept only one specific self-signed certificate from your server and ignore all other such errors.
It is also possible to create your own 'authority root certificate' that you can manually write to your system. Then that certificate can be used for signing your server certificate. Your client will think that it is trusted connection, since it will be able to validate it by system root certificates.
Note that you can also have some issue with OpenSSL library. On Linux or OS X OpenSSL is in default setup, however for Windows it should be installed manually. Some compilation of OpenSSL may be already present in Windows system PATH
, for example CMake has some limited OpenSSL library. However in general for Windows you should deploy OpenSSL together with your application.