Document DTLS examples

Task-number: QTBUG-68070
Change-Id: I2b08322049005b02f1ed680bee21992ade16813a
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
Reviewed-by: Paul Wicking <paul.wicking@qt.io>
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
This commit is contained in:
Timur Pocheptsov 2018-08-06 12:05:26 +02:00
parent 4c089601d7
commit 5b8d5c7493
13 changed files with 255 additions and 27 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -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
*/

View File

@ -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
*/

View File

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

View File

@ -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

View File

@ -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

View File

@ -72,6 +72,8 @@ MainWindow::~MainWindow()
delete ui;
}
//! [0]
const QString colorizer(QStringLiteral("<font color=\"%1\">%2</font><br>"));
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) {

View File

@ -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);

View File

@ -104,6 +104,7 @@ void MainWindow::updateUi()
: ui->startButton->setText(tr("Start listening"));
}
//! [0]
const QString colorizer(QStringLiteral("<font color=\"%1\">%2</font><br>"));
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]

View File

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

View File

@ -87,6 +87,7 @@ QString connection_info(QSharedPointer<QDtls> 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

View File

@ -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