Введение
Любой сетевой обмен, который пересекает недоверенную сеть, должен быть зашифрован — это требование сегодня является базовым, а не опциональным. В Qt шифрование TCP-соединений реализуется через QSslSocket, наследника QTcpSocket, который добавляет TLS-хэндшейк, проверку сертификатов и контроль версий протокола. В статье разберём базовую настройку клиентского TLS-соединения и типичные проблемы с сертификатами.
Концепция
QSslSocket выполняет TLS-хэндшейк после вызова connectToHostEncrypted(). Об успешном завершении хэндшейка сигнализирует encrypted(), а о проблемах с сертификатом сервера — sslErrors(const QList<QSslError> &). По умолчанию, если есть ошибки сертификата (например, самоподписанный сертификат или истёкший срок действия) и приложение не обработало sslErrors, соединение будет прервано — это защита от атак «человек посередине» (MITM).
Для тестовых сред с самоподписанными сертификатами разработчики иногда игнорируют все ошибки SSL через socket->ignoreSslErrors(), но это крайне небезопасно для продакшена — правильнее добавить конкретный сертификат в список доверенных через QSslConfiguration::setCaCertificates().
Пример кода: SSL/TLS в Qt
#include <QCoreApplication>
#include <QSslSocket>
#include <QSslConfiguration>
#include <QDebug>
class SecureClient : public QObject
{
Q_OBJECT
public:
explicit SecureClient(QObject *parent = nullptr) : QObject(parent)
{
connect(&m_socket, &QSslSocket::encrypted, this, &SecureClient::onEncrypted);
connect(&m_socket, &QSslSocket::sslErrors, this, &SecureClient::onSslErrors);
connect(&m_socket, &QSslSocket::readyRead, this, &SecureClient::onReadyRead);
}
void connectToServer(const QString &host, quint16 port)
{
qDebug() << "Поддерживаемые версии TLS:" << QSslSocket::supportedProtocols();
m_socket.connectToHostEncrypted(host, port);
}
private slots:
void onEncrypted()
{
qDebug() << "TLS-соединение установлено, протокол:"
<< m_socket.sessionProtocol();
QSslCertificate cert = m_socket.peerCertificate();
qDebug() << "Сертификат сервера выдан для:" << cert.subjectInfo(QSslCertificate::CommonName);
m_socket.write("GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n");
}
void onSslErrors(const QList<QSslError> &errors)
{
for (const QSslError &error : errors) {
qWarning() << "Ошибка SSL:" << error.errorString();
}
// В продакшене НЕ игнорировать ошибки бездумно. Здесь — только для отладки
// с самоподписанным сертификатом конкретного известного сервера.
// m_socket.ignoreSslErrors();
}
void onReadyRead()
{
qDebug() << m_socket.readAll();
}
private:
QSslSocket m_socket;
};
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
SecureClient client;
client.connectToServer("example.com", 443);
return app.exec();
}
#include "main.moc"
Пояснения к коду
После вызова connectToHostEncrypted() сокет выполняет и TCP-подключение, и TLS-хэндшейк. В обработчике onEncrypted() можно получить информацию о согласованном протоколе через sessionProtocol() и о сертификате сервера через peerCertificate(). Метод onSslErrors() показывает, как перехватывать ошибки сертификата — в данном примере они только логируются, а закомментированный вызов ignoreSslErrors() показывает, где было бы место для обхода ошибок в контролируемой тестовой среде.
Подводные камни
ignoreSslErrors()без аргументов игнорирует ВСЕ ошибки SSL, включая критичные (например, отозванный сертификат или несовпадение домена) — используйте версию с конкретным списком ожидаемых ошибокignoreSslErrors(const QList<QSslError>&), если нужно сделать исключение только для известной ситуации.- Отсутствие системных корневых сертификатов на embedded-платформах. На некоторых встраиваемых Linux-сборках нет стандартного хранилища CA-сертификатов — в этом случае
QSslSocketне сможет проверить даже валидный сертификат, и нужно явно подключать бандл сертификатов (например, изca-certificates). - Устаревшие версии TLS. По умолчанию Qt может разрешать старые протоколы (TLS 1.0/1.1) для совместимости — для безопасных систем стоит явно ограничить
QSslConfiguration::setProtocol(QSsl::TlsV1_2OrLater). - Несовпадение библиотеки OpenSSL во время сборки и выполнения. На Windows и Linux при динамической линковке OpenSSL версия библиотеки в системе должна быть совместима с той, под которую собран Qt — иначе
QSslSocket::supportedProtocols()может вернуть пустой список.