Qt使用QSslSocket无法正确连接

5

我有一个客户端-服务器应用程序,之前是使用 QTcpSocket 进行通信的。现在我想要改为加密的 SSL 连接,因此尝试切换到 QSslSocket。但是连接不上服务器。 这里是客户端的代码:

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();
} 

以下是针对服务器的内容:

 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";

}

我收到的客户端输出为:
  QAbstractSocket::HostLookupState
  QAbstractSocket::ConnectingState
  QAbstractSocket::ConnectedState
  "The remote host closed the connection"
  QAbstractSocket::ClosingState
  QAbstractSocket::UnconnectedState

对于服务器而言:

  QAbstractSocket::ConnectedState
  "Error during SSL handshake: error:1408A0C1:SSL routines:SSL3_GET_CLIENT_HELLO:no shared cipher"
  QAbstractSocket::UnconnectedState

有人知道如何修复这个问题吗?

1个回答

16

我假设使用未加密的套接字一切正常。因此,让我们只关注处理 QSslSocket 的特别之处。如果需要,我可以分享更多代码或工作代码。这里还有一个关于 SSL 证书的大故事,我会简要谈一下。

首先,让我们在一些外部 HTTP SSL 服务器上检查您的客户端,例如:

socket->connectToHostEncrypted("gmail.com", 443);

使用默认的SSL协议(不需要任何setProtocol()设置)即可立即运行。在信号encrypted()上,您可以编写HTTP GET标头,而在readyRead()上,将会接收响应。

现在尝试为"gmail.com"设置socket->setProtocol(QSsl::SslV3);。 预期结果:

sslErrorOccured: ("The host name did not match any of the valid hosts for this certificate")
请注意,通知客户端可以忽略的是sslErrors()信号而非error()信号,关于证书问题。
因此,为了简单起见,让我们使用默认的SSL协议,不使用setProtocol()
由于客户端处于工作状态,我们可以转向服务器。您在SSL认证挑战的第一级被打断。要通过服务器开始初始化SSL连接,您必须至少提供私有加密密钥和公共证书。这就是您的错误:
"Error during SSL handshake: error:1408A0C1:SSL routines:SSL3_GET_CLIENT_HELLO:no shared cipher"

在完美的情况下,您的证书应该由认证机构公司签名。在这种情况下,任何具有此类根证书列表的客户端都能够检查您的证书是否有效。但是,这项服务需要付费且可能需要几周时间。我们可以先使用自签名的证书。

您可以找到各种生成证书的方法,例如:“如何创建一个用于测试或内部使用的自签名SSL证书”

简短摘录:

#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

现在有 server.keyserver.crt 两个文件。在调用startServerEncryption()之前,服务器应该将这些文件传递给QSslSocket:

    socket->setPrivateKey("c:/test/ssl/cert/server.key", QSsl::Rsa);
    socket->setLocalCertificate("c:/test/ssl/cert/server.crt");

现在服务器可以启动SSL连接。您可以使用浏览器进行测试。通常,浏览器会发出警报,表示连接不受信任。这是因为证书是自签名的,无法防止“中间人”攻击。但是,您可以要求浏览器继续该连接。因此,在服务器端,您将在信号readyRead()上具有HTTP GET头。

尝试使用Qt客户端连接到该服务器。现在,客户端会引发错误:

sslErrorOccured: ("The certificate is self-signed, and untrusted")

服务器在error()中说:"远程主机关闭了连接"。客户端引发了SSL证书错误,但就像浏览器一样,我们可以继续使用该连接。将socket->ignoreSslErrors()放入 sslErrors() 信号处理程序中:

void Client::sslErrorOccured(const QList<QSslError> & error) {
    qDebug() << "sslErrorOccured:" << error;
    socket->ignoreSslErrors(error);
}

这就是全部内容。 当然,你的客户端不应该接受所有服务器发来的SSL证书错误,因为这些错误意味着连接并不真正安全且易被黑客攻击。这只是为了测试。对象QSslError包含证书数据,因此在客户端上可能仅接受来自您的服务器的一个特定的自签名证书,并忽略所有其他类似的错误。

还可以创建自己的“授权根证书”,并将其手动编写到系统中。然后,该证书可用于签署您的服务器证书。您的客户端将认为它是受信任的连接,因为它将能够通过系统根证书进行验证。

请注意,您也可能会遇到OpenSSL库的某些问题。在Linux或OS X上,默认设置中有OpenSSL,但对于Windows,需要手动安装。Windows系统中可能已经存在一些OpenSSL库的编译版本,例如CMake具有一些有限的OpenSSL库。但是,一般来说,对于Windows,您应该与应用程序一起部署OpenSSL。


1
非常好的解释。干得好。简单、简洁而且重点突出。 - developer01

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接