From 5b8d5c7493259544f853eb2732cca2829c0f67ca Mon Sep 17 00:00:00 2001 From: Timur Pocheptsov Date: Mon, 6 Aug 2018 12:05:26 +0200 Subject: Document DTLS examples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task-number: QTBUG-68070 Change-Id: I2b08322049005b02f1ed680bee21992ade16813a Reviewed-by: MÃ¥rten Nordheim Reviewed-by: Paul Wicking Reviewed-by: Edward Welbourne --- .../network/doc/images/secureudpclient-example.png | Bin 0 -> 23211 bytes .../network/doc/images/secureudpserver-example.png | Bin 0 -> 38412 bytes examples/network/doc/src/secureudpclient.qdoc | 93 ++++++++++++++++++- examples/network/doc/src/secureudpserver.qdoc | 101 ++++++++++++++++++++- examples/network/secureudpclient/addressdialog.h | 2 - examples/network/secureudpclient/association.cpp | 35 ++++++- examples/network/secureudpclient/association.h | 6 +- examples/network/secureudpclient/mainwindow.cpp | 4 + examples/network/secureudpclient/mainwindow.h | 2 - examples/network/secureudpserver/mainwindow.cpp | 2 + examples/network/secureudpserver/mainwindow.h | 3 - examples/network/secureudpserver/server.cpp | 28 +++++- examples/network/secureudpserver/server.h | 6 +- 13 files changed, 255 insertions(+), 27 deletions(-) create mode 100644 examples/network/doc/images/secureudpclient-example.png create mode 100644 examples/network/doc/images/secureudpserver-example.png (limited to 'examples/network') diff --git a/examples/network/doc/images/secureudpclient-example.png b/examples/network/doc/images/secureudpclient-example.png new file mode 100644 index 0000000000..a566aa4ce5 Binary files /dev/null and b/examples/network/doc/images/secureudpclient-example.png differ diff --git a/examples/network/doc/images/secureudpserver-example.png b/examples/network/doc/images/secureudpserver-example.png new file mode 100644 index 0000000000..a117b02834 Binary files /dev/null and b/examples/network/doc/images/secureudpserver-example.png differ diff --git a/examples/network/doc/src/secureudpclient.qdoc b/examples/network/doc/src/secureudpclient.qdoc index 587689ac47..dc8538cf85 100644 --- a/examples/network/doc/src/secureudpclient.qdoc +++ b/examples/network/doc/src/secureudpclient.qdoc @@ -29,9 +29,96 @@ \example secureudpclient \title DTLS client \ingroup examples-network - \brief Demonstrates how to implement a simple DTLS client + \brief This example demonstrates how to implement client-side DTLS connections. - This example uses QUdpSocket, QDtlsClientVerifier, and QDtls to securely - communicate over the User Datagram Protocol with DTLS servers. + \image secureudpclient-example.png Screenshot of the DTLS client example. + + \note The DTLS client example is intended to be run alongside the \l{secureudpserver}{DTLS server} example. + + The example DTLS client can establish several DTLS connections to one + or many DTLS servers. A client-side DTLS connection is implemented by the + DtlsAssociation class. This class uses QUdpSocket to read and write datagrams + and QDtls for encryption: + + \snippet secureudpclient/association.h 0 + + The constructor sets the minimal TLS configuration for the new DTLS connection, + and sets the address and the port of the server: + + \dots + \snippet secureudpclient/association.cpp 1 + \dots + + The QDtls::handshakeTimeout() signal is connected to the handleTimeout() slot + to deal with packet loss and retransmission during the handshake phase: + + \dots + \snippet secureudpclient/association.cpp 2 + \dots + + To ensure we receive only the datagrams from the server, we connect our UDP socket to the server: + + \dots + \snippet secureudpclient/association.cpp 3 + \dots + + The QUdpSocket::readyRead() signal is connected to the readyRead() slot: + + \dots + \snippet secureudpclient/association.cpp 13 + \dots + + When a secure connection to a server is established, a DtlsAssociation object + will be sending short ping messages to the server, using a timer: + + \snippet secureudpclient/association.cpp 4 + + startHandshake() starts a handshake with the server: + + \snippet secureudpclient/association.cpp 5 + + The readyRead() slot reads a datagram sent by the server: + + \snippet secureudpclient/association.cpp 6 + + If the handshake was already completed, this datagram is decrypted: + + \snippet secureudpclient/association.cpp 7 + + otherwise, we try to continue the handshake: + + \snippet secureudpclient/association.cpp 8 + + When the handshake has completed, we send our first ping message: + + \snippet secureudpclient/association.cpp 9 + + The pskRequired() slot provides the Pre-Shared Key (PSK) needed during the handshake + phase: + + \snippet secureudpclient/association.cpp 14 + + \note For the sake of brevity, the definition of pskRequired() is oversimplified. + The documentation for the QSslPreSharedKeyAuthenticator class explains in detail + how this slot can be properly implemented. + + pingTimeout() sends an encrypted message to the server: + + \snippet secureudpclient/association.cpp 10 + + During the handshake phase the client must handle possible timeouts, which + can happen due to packet loss. The handshakeTimeout() slot retransmits + the handshake messages: + + \snippet secureudpclient/association.cpp 11 + + Before a client connection is destroyed, its DTLS connection must be shut down: + + \snippet secureudpclient/association.cpp 12 + + Error messages, informational messages, and decrypted responses from servers + are displayed by the UI: + + \snippet secureudpclient/mainwindow.cpp 0 */ diff --git a/examples/network/doc/src/secureudpserver.qdoc b/examples/network/doc/src/secureudpserver.qdoc index a00d65773b..0857f7065f 100644 --- a/examples/network/doc/src/secureudpserver.qdoc +++ b/examples/network/doc/src/secureudpserver.qdoc @@ -29,8 +29,103 @@ \example secureudpserver \title DTLS server \ingroup examples-network - \brief Demonstrates how to implement a simple DTLS server + \brief This examples demonstrates how to implement a simple DTLS server. - This example uses QUdpSocket, QDtlsClientVerifier, and QDtls to securely respond - to DTLS client requests over the User Datagram Protocol. + \image secureudpserver-example.png Screenshot of the DTLS server example. + + \note The DTLS server example is intended to be run alongside the \l{secureudpclient}{DTLS client} example. + + The server is implemented by the DtlsServer class. It uses QUdpSocket, + QDtlsClientVerifier, and QDtls to test each client's reachability, complete a handshake, + and read and write encrypted messages. + + \snippet secureudpserver/server.h 0 + + The constructor connects the QUdpSocket::readyRead() signal to its + readyRead() slot and sets the minimal needed TLS configuration: + + \snippet secureudpserver/server.cpp 1 + + \note The server is not using a certificate and is relying on Pre-Shared + Key (PSK) handshake. + + listen() binds QUdpSocket: + + \snippet secureudpserver/server.cpp 2 + + The readyRead() slot processes incoming datagrams: + + \dots + \snippet secureudpserver/server.cpp 3 + \dots + + After extracting an address and a port number, the server first tests + if it's a datagram from an already known peer: + + \dots + \snippet secureudpserver/server.cpp 4 + \dots + + If it is a new, unknown address and port, the datagram is processed as a + potential ClientHello message, sent by a DTLS client: + + \dots + \snippet secureudpserver/server.cpp 5 + \dots + + If it's a known DTLS client, the server either decrypts the datagram: + + \dots + \snippet secureudpserver/server.cpp 6 + \dots + + or continues a handshake with this peer: + + \dots + \snippet secureudpserver/server.cpp 7 + \dots + + handleNewConnection() verifies it's a reachable DTLS client, or sends a + HelloVerifyRequest: + + \snippet secureudpserver/server.cpp 8 + \dots + + If the new client was verified to be a reachable DTLS client, the server creates + and configures a new QDtls object, and starts a server-side handshake: + + \dots + \snippet secureudpserver/server.cpp 9 + \dots + + doHandshake() progresses through the handshake phase: + + \snippet secureudpserver/server.cpp 11 + + During the handshake phase, the QDtls::pskRequired() signal is emitted and + the pskRequired() slot provides the preshared key: + + \snippet secureudpserver/server.cpp 13 + + \note For the sake of brevity, the definition of pskRequired() is oversimplified. + The documentation for the QSslPreSharedKeyAuthenticator class explains in detail + how this slot can be properly implemented. + + After the handshake is completed for the network peer, an encrypted DTLS + connection is considered to be established and the server decrypts subsequent + datagrams, sent by the peer, by calling decryptDatagram(). The server also + sends an encrypted response to the peer: + + \snippet secureudpserver/server.cpp 12 + + The server closes its DTLS connections by calling QDtls::shutdown(): + + \snippet secureudpserver/server.cpp 14 + + During its operation, the server reports errors, informational messages, and + decrypted datagrams, by emitting signals errorMessage(), warningMessage(), + infoMessage(), and datagramReceived(). These messages are logged by the server's + UI: + + \snippet secureudpserver/mainwindow.cpp 0 */ diff --git a/examples/network/secureudpclient/addressdialog.h b/examples/network/secureudpclient/addressdialog.h index 7c5e2e03e8..43792faa4b 100644 --- a/examples/network/secureudpclient/addressdialog.h +++ b/examples/network/secureudpclient/addressdialog.h @@ -69,7 +69,6 @@ class AddressDialog : public QDialog Q_OBJECT public: - explicit AddressDialog(QWidget *parent = nullptr); ~AddressDialog(); @@ -77,7 +76,6 @@ public: quint16 remotePort() const; private: - void setupHostSelector(); void setupPortSelector(); diff --git a/examples/network/secureudpclient/association.cpp b/examples/network/secureudpclient/association.cpp index 6b510909f1..c950260078 100644 --- a/examples/network/secureudpclient/association.cpp +++ b/examples/network/secureudpclient/association.cpp @@ -57,27 +57,38 @@ DtlsAssociation::DtlsAssociation(const QHostAddress &address, quint16 port, : name(connectionName), crypto(QSslSocket::SslClientMode) { + //! [1] auto configuration = QSslConfiguration::defaultDtlsConfiguration(); configuration.setPeerVerifyMode(QSslSocket::VerifyNone); crypto.setPeer(address, port); crypto.setDtlsConfiguration(configuration); + //! [1] + //! [2] connect(&crypto, &QDtls::handshakeTimeout, this, &DtlsAssociation::handshakeTimeout); + //! [2] connect(&crypto, &QDtls::pskRequired, this, &DtlsAssociation::pskRequired); - + //! [3] socket.connectToHost(address.toString(), port); + //! [3] + //! [13] connect(&socket, &QUdpSocket::readyRead, this, &DtlsAssociation::readyRead); - + //! [13] + //! [4] pingTimer.setInterval(5000); connect(&pingTimer, &QTimer::timeout, this, &DtlsAssociation::pingTimeout); + //! [4] } +//! [12] DtlsAssociation::~DtlsAssociation() { if (crypto.isConnectionEncrypted()) crypto.shutdown(&socket); } +//! [12] +//! [5] void DtlsAssociation::startHandshake() { if (socket.state() != QAbstractSocket::ConnectedState) { @@ -86,11 +97,12 @@ void DtlsAssociation::startHandshake() return; } - if (!crypto.doHandshake(&socket, {})) + if (!crypto.doHandshake(&socket)) emit errorMessage(tr("%1: failed to start a handshake - %2").arg(name, crypto.dtlsErrorString())); else emit infoMessage(tr("%1: starting a handshake").arg(name)); } +//! [5] void DtlsAssociation::udpSocketConnected() { @@ -100,7 +112,8 @@ void DtlsAssociation::udpSocketConnected() void DtlsAssociation::readyRead() { - QByteArray dgram(socket.pendingDatagramSize(), '\0'); + //! [6] + QByteArray dgram(socket.pendingDatagramSize(), Qt::Uninitialized); const qint64 bytesRead = socket.readDatagram(dgram.data(), dgram.size()); if (bytesRead <= 0) { emit warningMessage(tr("%1: spurious read notification?").arg(name)); @@ -108,6 +121,8 @@ void DtlsAssociation::readyRead() } dgram.resize(bytesRead); + //! [6] + //! [7] if (crypto.isConnectionEncrypted()) { const QByteArray plainText = crypto.decryptDatagram(&socket, dgram); if (plainText.size()) { @@ -124,27 +139,36 @@ void DtlsAssociation::readyRead() emit warningMessage(tr("%1: zero-length datagram received?").arg(name)); } else { + //! [7] + //! [8] if (!crypto.doHandshake(&socket, dgram)) { emit errorMessage(tr("%1: handshake error - %2").arg(name, crypto.dtlsErrorString())); return; } + //! [8] + + //! [9] if (crypto.isConnectionEncrypted()) { emit infoMessage(tr("%1: encrypted connection established!").arg(name)); pingTimer.start(); pingTimeout(); } else { + //! [9] emit infoMessage(tr("%1: continuing with handshake ...").arg(name)); } } } +//! [11] void DtlsAssociation::handshakeTimeout() { emit warningMessage(tr("%1: handshake timeout, trying to re-transmit").arg(name)); if (!crypto.handleTimeout(&socket)) emit errorMessage(tr("%1: failed to re-transmit - %2").arg(name, crypto.dtlsErrorString())); } +//! [11] +//! [14] void DtlsAssociation::pskRequired(QSslPreSharedKeyAuthenticator *auth) { Q_ASSERT(auth); @@ -153,7 +177,9 @@ void DtlsAssociation::pskRequired(QSslPreSharedKeyAuthenticator *auth) auth->setIdentity(name.toLatin1()); auth->setPreSharedKey(QByteArrayLiteral("\x1a\x2b\x3c\x4d\x5e\x6f")); } +//! [14] +//! [10] void DtlsAssociation::pingTimeout() { static const QString message = QStringLiteral("I am %1, please, accept our ping %2"); @@ -166,5 +192,6 @@ void DtlsAssociation::pingTimeout() ++ping; } +//! [10] QT_END_NAMESPACE diff --git a/examples/network/secureudpclient/association.h b/examples/network/secureudpclient/association.h index 157882f23d..be89ce695e 100644 --- a/examples/network/secureudpclient/association.h +++ b/examples/network/secureudpclient/association.h @@ -55,19 +55,18 @@ QT_BEGIN_NAMESPACE +//! [0] class DtlsAssociation : public QObject { Q_OBJECT public: - DtlsAssociation(const QHostAddress &address, quint16 port, const QString &connectionName); ~DtlsAssociation(); void startHandshake(); signals: - void errorMessage(const QString &message); void warningMessage(const QString &message); void infoMessage(const QString &message); @@ -75,7 +74,6 @@ signals: const QByteArray &plainText); private slots: - void udpSocketConnected(); void readyRead(); void handshakeTimeout(); @@ -83,7 +81,6 @@ private slots: void pingTimeout(); private: - QString name; QUdpSocket socket; QDtls crypto; @@ -93,6 +90,7 @@ private: Q_DISABLE_COPY(DtlsAssociation) }; +//! [0] QT_END_NAMESPACE diff --git a/examples/network/secureudpclient/mainwindow.cpp b/examples/network/secureudpclient/mainwindow.cpp index 07c614cf3a..2fbf757c81 100644 --- a/examples/network/secureudpclient/mainwindow.cpp +++ b/examples/network/secureudpclient/mainwindow.cpp @@ -72,6 +72,8 @@ MainWindow::~MainWindow() delete ui; } +//! [0] + const QString colorizer(QStringLiteral("%2
")); void MainWindow::addErrorMessage(const QString &message) @@ -102,6 +104,8 @@ void MainWindow::addServerResponse(const QString &clientInfo, const QByteArray & ui->serverMessages->insertHtml(colorizer.arg(messageColor, html)); } +//! [0] + void MainWindow::on_connectButton_clicked() { if (lookupId != -1) { diff --git a/examples/network/secureudpclient/mainwindow.h b/examples/network/secureudpclient/mainwindow.h index b231b44627..0d443fd376 100644 --- a/examples/network/secureudpclient/mainwindow.h +++ b/examples/network/secureudpclient/mainwindow.h @@ -76,7 +76,6 @@ class MainWindow : public QMainWindow Q_OBJECT public: - explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); @@ -94,7 +93,6 @@ private slots: void lookupFinished(const QHostInfo &hostInfo); private: - void updateUi(); void startNewConnection(const QHostAddress &address); diff --git a/examples/network/secureudpserver/mainwindow.cpp b/examples/network/secureudpserver/mainwindow.cpp index d751ec931d..ef1974c311 100644 --- a/examples/network/secureudpserver/mainwindow.cpp +++ b/examples/network/secureudpserver/mainwindow.cpp @@ -104,6 +104,7 @@ void MainWindow::updateUi() : ui->startButton->setText(tr("Start listening")); } +//! [0] const QString colorizer(QStringLiteral("%2
")); void MainWindow::addErrorMessage(const QString &message) @@ -134,3 +135,4 @@ void MainWindow::addClientMessage(const QString &peerInfo, const QByteArray &dat QString::fromUtf8(plainText)); ui->messages->insertHtml(colorizer.arg(messageColor, html)); } +//! [0] diff --git a/examples/network/secureudpserver/mainwindow.h b/examples/network/secureudpserver/mainwindow.h index 0c914f5021..b39d984d50 100644 --- a/examples/network/secureudpserver/mainwindow.h +++ b/examples/network/secureudpserver/mainwindow.h @@ -69,12 +69,10 @@ class MainWindow : public QMainWindow Q_OBJECT public: - MainWindow(); ~MainWindow(); private slots: - void addErrorMessage(const QString &message); void addWarningMessage(const QString &message); void addInfoMessage(const QString &message); @@ -85,7 +83,6 @@ private slots: void on_quitButton_clicked(); private: - void updateUi(); Ui::MainWindow *ui = nullptr; diff --git a/examples/network/secureudpserver/server.cpp b/examples/network/secureudpserver/server.cpp index 763024e4f4..6870123163 100644 --- a/examples/network/secureudpserver/server.cpp +++ b/examples/network/secureudpserver/server.cpp @@ -87,6 +87,7 @@ QString connection_info(QSharedPointer connection) } // unnamed namespace +//! [1] DtlsServer::DtlsServer() { connect(&serverSocket, &QAbstractSocket::readyRead, this, &DtlsServer::readyRead); @@ -94,12 +95,14 @@ DtlsServer::DtlsServer() serverConfiguration.setPreSharedKeyIdentityHint("Qt DTLS example server"); serverConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone); } +//! [1] DtlsServer::~DtlsServer() { shutdown(); } +//! [2] bool DtlsServer::listen(const QHostAddress &address, quint16 port) { if (address != serverSocket.localAddress() || port != serverSocket.localPort()) { @@ -113,6 +116,7 @@ bool DtlsServer::listen(const QHostAddress &address, quint16 port) return listening; } +//! [2] bool DtlsServer::isListening() const { @@ -126,6 +130,7 @@ void DtlsServer::close() void DtlsServer::readyRead() { + //! [3] const qint64 bytesToRead = serverSocket.pendingDatagramSize(); if (bytesToRead <= 0) { emit warningMessage(tr("A spurious read notification")); @@ -143,7 +148,8 @@ void DtlsServer::readyRead() } dgram.resize(bytesRead); - + //! [3] + //! [4] if (peerAddress.isNull() || !peerPort) { emit warningMessage(tr("Failed to extract peer info (address, port)")); return; @@ -154,20 +160,28 @@ void DtlsServer::readyRead() return connection->peerAddress() == peerAddress && connection->peerPort() == peerPort; }); + //! [4] + //! [5] if (client == knownClients.end()) return handleNewConnection(peerAddress, peerPort, dgram); + //! [5] + //! [6] if ((*client)->isConnectionEncrypted()) { decryptDatagram(*client, dgram); if ((*client)->dtlsError() == QDtlsError::RemoteClosedConnectionError) knownClients.erase(client); return; } + //! [6] + //! [7] doHandshake(*client, dgram); + //! [7] } +//! [13] void DtlsServer::pskRequired(QSslPreSharedKeyAuthenticator *auth) { Q_ASSERT(auth); @@ -176,7 +190,9 @@ void DtlsServer::pskRequired(QSslPreSharedKeyAuthenticator *auth) .arg(QString::fromLatin1(auth->identity()))); auth->setPreSharedKey(QByteArrayLiteral("\x1a\x2b\x3c\x4d\x5e\x6f")); } +//! [13] +//! [8] void DtlsServer::handleNewConnection(const QHostAddress &peerAddress, quint16 peerPort, const QByteArray &clientHello) { @@ -186,7 +202,8 @@ void DtlsServer::handleNewConnection(const QHostAddress &peerAddress, const QString peerInfo = peer_info(peerAddress, peerPort); if (cookieSender.verifyClient(&serverSocket, clientHello, peerAddress, peerPort)) { emit infoMessage(peerInfo + tr(": verified, starting a handshake")); - + //! [8] + //! [9] DtlsConnection newConnection(new QDtls(QSslSocket::SslServerMode)); newConnection->setDtlsConfiguration(serverConfiguration); newConnection->setPeer(peerAddress, peerPort); @@ -194,6 +211,7 @@ void DtlsServer::handleNewConnection(const QHostAddress &peerAddress, this, &DtlsServer::pskRequired); knownClients.push_back(newConnection); doHandshake(newConnection, clientHello); + //! [9] } else if (cookieSender.dtlsError() != QDtlsError::NoError) { emit errorMessage(tr("DTLS error: ") + cookieSender.dtlsErrorString()); } else { @@ -201,6 +219,7 @@ void DtlsServer::handleNewConnection(const QHostAddress &peerAddress, } } +//! [11] void DtlsServer::doHandshake(DtlsConnection newConnection, const QByteArray &clientHello) { const bool result = newConnection->doHandshake(&serverSocket, clientHello); @@ -223,7 +242,9 @@ void DtlsServer::doHandshake(DtlsConnection newConnection, const QByteArray &cli Q_UNREACHABLE(); } } +//! [11] +//! [12] void DtlsServer::decryptDatagram(DtlsConnection connection, const QByteArray &clientMessage) { Q_ASSERT(connection->isConnectionEncrypted()); @@ -239,7 +260,9 @@ void DtlsServer::decryptDatagram(DtlsConnection connection, const QByteArray &cl emit errorMessage(peerInfo + ": " + connection->dtlsErrorString()); } } +//! [12] +//! [14] void DtlsServer::shutdown() { for (DtlsConnection &connection : knownClients) @@ -248,5 +271,6 @@ void DtlsServer::shutdown() knownClients.clear(); serverSocket.close(); } +//! [14] QT_END_NAMESPACE diff --git a/examples/network/secureudpserver/server.h b/examples/network/secureudpserver/server.h index 33444f7407..b720368e7b 100644 --- a/examples/network/secureudpserver/server.h +++ b/examples/network/secureudpserver/server.h @@ -57,12 +57,12 @@ QT_BEGIN_NAMESPACE +//! [0] class DtlsServer : public QObject { Q_OBJECT public: - DtlsServer(); ~DtlsServer(); @@ -71,7 +71,6 @@ public: void close(); signals: - void errorMessage(const QString &message); void warningMessage(const QString &message); void infoMessage(const QString &message); @@ -80,12 +79,10 @@ signals: const QByteArray &plainText); private slots: - void readyRead(); void pskRequired(QSslPreSharedKeyAuthenticator *auth); private: - void handleNewConnection(const QHostAddress &peerAddress, quint16 peerPort, const QByteArray &clientHello); @@ -103,6 +100,7 @@ private: Q_DISABLE_COPY(DtlsServer) }; +//! [0] QT_END_NAMESPACE -- cgit v1.2.3