Qt文档阅读笔记-DTLS server解析
此篇博文展示了如何創建一個簡單的DTLS服務端:
注意:這個DTLS服務端需要和DTLS客戶端一起跑,才能看出效果。
服務端實現了DtlsServer類,這個類使用了QUdpSocket,QDtlsClientVerifier,QDtls使用這些類用于和客戶端的連接,完成握手及數據加密傳輸與讀取。
class DtlsServer : public QObject{Q_OBJECTpublic:DtlsServer();~DtlsServer();bool listen(const QHostAddress &address, quint16 port);bool isListening() const;void close();signals:void errorMessage(const QString &message);void warningMessage(const QString &message);void infoMessage(const QString &message);void datagramReceived(const QString &peerInfo, const QByteArray &cipherText,const QByteArray &plainText);private slots:void readyRead();void pskRequired(QSslPreSharedKeyAuthenticator *auth);private:void handleNewConnection(const QHostAddress &peerAddress, quint16 peerPort,const QByteArray &clientHello);void doHandshake(QDtls *newConnection, const QByteArray &clientHello);void decryptDatagram(QDtls *connection, const QByteArray &clientMessage);void shutdown();bool listening = false;QUdpSocket serverSocket;QSslConfiguration serverConfiguration;QDtlsClientVerifier cookieSender;std::vector<std::unique_ptr<QDtls>> knownClients;Q_DISABLE_COPY(DtlsServer)};構造函數中QUdpSocket::readyRead()信號連接了readyRead()槽,用于獲取客戶端的數據報,最簡單的設置了DTLS的配置:
DtlsServer::DtlsServer(){connect(&serverSocket, &QAbstractSocket::readyRead, this, &DtlsServer::readyRead);serverConfiguration = QSslConfiguration::defaultDtlsConfiguration();serverConfiguration.setPreSharedKeyIdentityHint("Qt DTLS example server");serverConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone);}注意:服務端未使用證書,僅僅是依賴共享密鑰(PSK)握手。
listen()與QUdpSocket進行了捆綁:
bool DtlsServer::listen(const QHostAddress &address, quint16 port){if (address != serverSocket.localAddress() || port != serverSocket.localPort()) {shutdown();listening = serverSocket.bind(address, port);if (!listening)emit errorMessage(serverSocket.errorString());} else {listening = true;}return listening;}readyRead()槽函數獲取客戶端數據報:
...const qint64 bytesToRead = serverSocket.pendingDatagramSize();if (bytesToRead <= 0) {emit warningMessage(tr("A spurious read notification"));return;}QByteArray dgram(bytesToRead, Qt::Uninitialized);QHostAddress peerAddress;quint16 peerPort = 0;const qint64 bytesRead = serverSocket.readDatagram(dgram.data(), dgram.size(),&peerAddress, &peerPort);if (bytesRead <= 0) {emit warningMessage(tr("Failed to read a datagram: ") + serverSocket.errorString());return;}dgram.resize(bytesRead);...隨后獲取客戶端的IP和端口,服務端發送數據報給客戶端:
...if (peerAddress.isNull() || !peerPort) {emit warningMessage(tr("Failed to extract peer info (address, port)"));return;}const auto client = std::find_if(knownClients.begin(), knownClients.end(),[&](const std::unique_ptr<QDtls> &connection){return connection->peerAddress() == peerAddress&& connection->peerPort() == peerPort;});...如果是新的客戶端,未知地址和端口,客戶端會發送ClientHello消息,隨后服務端回HelloVerifyRequest這個要先處理下:
...if (client == knownClients.end())return handleNewConnection(peerAddress, peerPort, dgram);...如果是連上的已知的客戶端,服務端會進行解密:
...if ((*client)->isConnectionEncrypted()) {decryptDatagram(client->get(), dgram);if ((*client)->dtlsError() == QDtlsError::RemoteClosedConnectionError)knownClients.erase(client);return;}...以及進行握手:
...doHandshake(client->get(), dgram);...handleNewConnect()對客戶端進行認證,以及發送HelloVerifyRequest:
void DtlsServer::handleNewConnection(const QHostAddress &peerAddress,quint16 peerPort, const QByteArray &clientHello){if (!listening)return;const QString peerInfo = peer_info(peerAddress, peerPort);if (cookieSender.verifyClient(&serverSocket, clientHello, peerAddress, peerPort)) {emit infoMessage(peerInfo + tr(": verified, starting a handshake"));...如果服務端認為新來的客戶端可達,那個服務端會創建一個Qtls的配置,然后進行握手:
...std::unique_ptr<QDtls> newConnection{new QDtls{QSslSocket::SslServerMode}};newConnection->setDtlsConfiguration(serverConfiguration);newConnection->setPeer(peerAddress, peerPort);newConnection->connect(newConnection.get(), &QDtls::pskRequired,this, &DtlsServer::pskRequired);knownClients.push_back(std::move(newConnection));doHandshake(knownClients.back().get(), clientHello);...關于握手的代碼在doHandshake()中
void DtlsServer::doHandshake(QDtls *newConnection, const QByteArray &clientHello){const bool result = newConnection->doHandshake(&serverSocket, clientHello);if (!result) {emit errorMessage(newConnection->dtlsErrorString());return;}const QString peerInfo = peer_info(newConnection->peerAddress(),newConnection->peerPort());switch (newConnection->handshakeState()) {case QDtls::HandshakeInProgress:emit infoMessage(peerInfo + tr(": handshake is in progress ..."));break;case QDtls::HandshakeComplete:emit infoMessage(tr("Connection with %1 encrypted. %2").arg(peerInfo, connection_info(newConnection)));break;default:Q_UNREACHABLE();}}在握手中,QDtls::pskRequired()信號關聯到pskRequired()槽函數中,并且設置了PSK:
void DtlsServer::pskRequired(QSslPreSharedKeyAuthenticator *auth){Q_ASSERT(auth);emit infoMessage(tr("PSK callback, received a client's identity: '%1'").arg(QString::fromLatin1(auth->identity())));auth->setPreSharedKey(QByteArrayLiteral("\x1a\x2b\x3c\x4d\x5e\x6f"));}在握手完成后,就可以進行數據的加密發送和響應了:
void DtlsServer::decryptDatagram(QDtls *connection, const QByteArray &clientMessage){Q_ASSERT(connection->isConnectionEncrypted());const QString peerInfo = peer_info(connection->peerAddress(), connection->peerPort());const QByteArray dgram = connection->decryptDatagram(&serverSocket, clientMessage);if (dgram.size()) {emit datagramReceived(peerInfo, clientMessage, dgram);connection->writeDatagramEncrypted(&serverSocket, tr("to %1: ACK").arg(peerInfo).toLatin1());} else if (connection->dtlsError() == QDtlsError::NoError) {emit warningMessage(peerInfo + ": " + tr("0 byte dgram, could be a re-connect attempt?"));} else {emit errorMessage(peerInfo + ": " + connection->dtlsErrorString());}}服務端關閉DTLS服務調用QDtls::shutdown()
void DtlsServer::shutdown(){for (const auto &connection : qExchange(knownClients, {}))connection->shutdown(&serverSocket);serverSocket.close();}下面是出現問題或警告時的代碼:
const QString colorizer(QStringLiteral("<font color=\"%1\">%2</font><br>"));void MainWindow::addErrorMessage(const QString &message){ui->serverInfo->insertHtml(colorizer.arg(QStringLiteral("Crimson"), message));}void MainWindow::addWarningMessage(const QString &message){ui->serverInfo->insertHtml(colorizer.arg(QStringLiteral("DarkOrange"), message));}void MainWindow::addInfoMessage(const QString &message){ui->serverInfo->insertHtml(colorizer.arg(QStringLiteral("DarkBlue"), message));}void MainWindow::addClientMessage(const QString &peerInfo, const QByteArray &datagram,const QByteArray &plainText){static const QString messageColor = QStringLiteral("DarkMagenta");static const QString formatter = QStringLiteral("<br>---------------""<br>A message from %1""<br>DTLS datagram:<br> %2""<br>As plain text:<br> %3");const QString html = formatter.arg(peerInfo, QString::fromUtf8(datagram.toHex(' ')),QString::fromUtf8(plainText));ui->messages->insertHtml(colorizer.arg(messageColor, html));}?
總結
以上是生活随笔為你收集整理的Qt文档阅读笔记-DTLS server解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 信息安全工程师笔记-数字证书
- 下一篇: 信息安全工程师笔记-云计算安全需求分析与