Schneller sicherer CoAP-Client

Sicherung des CoAP-Clients und dessen Verwendung mit einer Qt Quick Benutzeroberfläche.

Quick Secure CoAP Client demonstriert, wie man einen sicheren CoAP-Client erstellt und ihn in einer Qt Quick -Anwendung verwendet.

Hinweis: Qt CoAP bietet in seiner aktuellen Version keine QML-API. Sie können jedoch die C++-Klassen des Moduls für QML verfügbar machen, wie es im Beispiel gezeigt wird.

Ausführen des Beispiels

Zum Ausführen des Beispiels von Qt Creatorzu starten, öffnen Sie den Modus Welcome und wählen Sie das Beispiel unter Examples aus. Weitere Informationen finden Sie unter Erstellen und Ausführen eines Beispiels.

Um die Beispielanwendung auszuführen, müssen Sie zunächst einen sicheren CoAP-Server einrichten. Sie können das Beispiel mit jedem sicheren CoAP-Server ausführen, der einen der Authentifizierungsmodi Pre-Shared Key (PSK) oder Zertifikat unterstützt. Weitere Informationen zum Einrichten eines sicheren CoAP-Servers finden Sie unter Einrichten eines sicheren CoAP-Servers.

Freigabe von C++-Klassen für QML

In diesem Beispiel müssen Sie die Klasse QCoapClient und den Namespace QtCoap für QML freigeben. Um dies zu erreichen, erstellen Sie eine benutzerdefinierte Wrapper-Klasse und verwenden Sie die speziellen Registrierungsmakros.

Erstellen Sie die Klasse QmlCoapSecureClient als Wrapper um QCoapClient. Diese Klasse enthält auch den ausgewählten Sicherheitsmodus und die Sicherheitskonfigurationsparameter. Verwenden Sie das Q_INVOKABLE Makro, um mehrere Methoden für QML freizugeben. Verwenden Sie auch das QML_NAMED_ELEMENT Makro, um die Klasse in QML als CoapSecureClient zu registrieren.

class QmlCoapSecureClient : public QObject
{
    Q_OBJECT
    QML_NAMED_ELEMENT(CoapSecureClient)

public:
    QmlCoapSecureClient(QObject *parent = nullptr);
    ~QmlCoapSecureClient() override;

    Q_INVOKABLE void setSecurityMode(QtCoap::SecurityMode mode);
    Q_INVOKABLE void sendGetRequest(const QString &host, const QString &path, int port);
    Q_INVOKABLE void setSecurityConfiguration(const QString &preSharedKey, const QString &identity);
    Q_INVOKABLE void setSecurityConfiguration(const QString &localCertificatePath,
                                              const QString &caCertificatePath,
                                              const QString &privateKeyPath);
    Q_INVOKABLE void disconnect();

Q_SIGNALS:
    void finished(const QString &result);

private:
    QCoapClient *m_coapClient;
    QCoapSecurityConfiguration m_configuration;
    QtCoap::SecurityMode m_securityMode;
};

Danach registrieren Sie den Namespace QtCoap, so dass Sie die dort bereitgestellten Enums verwenden können:

namespace QCoapForeignNamespace
{
    Q_NAMESPACE
    QML_FOREIGN_NAMESPACE(QtCoap)
    QML_NAMED_ELEMENT(QtCoap)
}

Anpassen der Build-Dateien

Um die benutzerdefinierten Typen in QML verfügbar zu machen, aktualisieren Sie die Buildsystemdateien entsprechend.

CMake

Für einen CMake-basierten Build fügen Sie Folgendes zur CMakeLists.txt hinzu:

qt_add_qml_module(quicksecureclient
    URI CoapSecureClientModule
    SOURCES
        qmlcoapsecureclient.cpp qmlcoapsecureclient.h
    QML_FILES
        FilePicker.qml
        Main.qml
)
qmake

Für einen qmake-Build ändern Sie die Datei quicksecureclient.pro auf folgende Weise:

CONFIG += qmltypes
QML_IMPORT_NAME = CoapSecureClientModule
QML_IMPORT_MAJOR_VERSION = 1
    ...
qml_resources.files = \
    qmldir \
    FilePicker.qml \
    Main.qml

qml_resources.prefix = /qt/qml/CoapSecureClientModule

RESOURCES += qml_resources

Neue QML-Typen verwenden

Nun, da die C++-Klassen ordnungsgemäß in QML exponiert sind, können Sie die neuen Typen verwenden.

Erstellen des Clients

CoapSecureClient wird aus der Datei Main.qml instanziiert. Er verarbeitet das Signal QmlCoapSecureClient::finished() und aktualisiert die Benutzeroberfläche entsprechend:

CoapSecureClient {
    id: client
    onFinished: (result) => {
        outputView.text = result;
        statusLabel.text = "";
        disconnectButton.enabled = true;
    }
}

Die Instanz von QCoapClient wird erstellt, wenn der Benutzer den Sicherheitsmodus in der Benutzeroberfläche auswählt oder ändert. Die Methode QmlCoapSecureClient::setSecurityMode() wird vom QML-Code aufgerufen, wenn einer der Sicherheitsmodi ausgewählt wird:

ButtonGroup {
    id: securityModeGroup
    onClicked: {
        if ((securityModeGroup.checkedButton as RadioButton) === preSharedMode)
            client.setSecurityMode(QtCoap.SecurityMode.PreSharedKey);
        else
            client.setSecurityMode(QtCoap.SecurityMode.Certificate);
    }
}

Auf der C++-Seite erstellt diese Methode ein QCoapClient und stellt eine Verbindung zu seinen Signalen finished() und error() her. Die Klasse verarbeitet beide Signale intern und leitet sie an das neue Signal finished() weiter.

void QmlCoapSecureClient::setSecurityMode(QtCoap::SecurityMode mode)
{
    // Create a new client, if the security mode has changed
    if (m_coapClient && mode != m_securityMode) {
        delete m_coapClient;
        m_coapClient = nullptr;
    }

    if (!m_coapClient) {
        m_coapClient = new QCoapClient(mode);
        m_securityMode = mode;

        connect(m_coapClient, &QCoapClient::finished, this,
                [this](QCoapReply *reply) {
                    if (!reply)
                        emit finished(tr("Something went wrong, received a null reply"));
                    else if (reply->errorReceived() != QtCoap::Error::Ok)
                        emit finished(errorMessage(reply->errorReceived()));
                    else
                        emit finished(reply->message().payload());
                });

        connect(m_coapClient, &QCoapClient::error, this,
                [this](QCoapReply *, QtCoap::Error errorCode) {
                    emit finished(errorMessage(errorCode));
                });
    }
}
Senden einer Anfrage

Klicken Sie auf die Schaltfläche Send Request, um die Sicherheitskonfiguration auf der Grundlage des ausgewählten Sicherheitsmodus einzustellen und eine Anfrage an GET zu senden:

Button {
    id: requestButton
    text: qsTr("Send Request")
    enabled: securityModeGroup.checkState !== Qt.Unchecked

    onClicked: {
        outputView.text = "";
        if ((securityModeGroup.checkedButton as RadioButton) === preSharedMode)
            client.setSecurityConfiguration(pskField.text, identityField.text);
        else
            client.setSecurityConfiguration(localCertificatePicker.selectedFile,
                                            caCertificatePicker.selectedFile,
                                            privateKeyPicker.selectedFile);

        client.sendGetRequest(hostComboBox.editText, resourceField.text,
                              parseInt(portField.text));

        statusLabel.text = qsTr("Sending request to %1%2...").arg(hostComboBox.editText)
                                                             .arg(resourceField.text);
    }
}

Es gibt zwei Überladungen für die Methode setSecurityConfiguration.

Die Überladung für den PSK-Modus legt einfach die Client-Identität und den Pre-Shared Key fest:

void
QmlCoapSecureClient::setSecurityConfiguration(const QString &preSharedKey, const QString &identity)
{
    QCoapSecurityConfiguration configuration;
    configuration.setPreSharedKey(preSharedKey.toUtf8());
    configuration.setPreSharedKeyIdentity(identity.toUtf8());
    m_configuration = configuration;
}

Und die Überladung für X.509-Zertifikate liest die Zertifikatsdateien und den privaten Schlüssel und setzt die Sicherheitskonfiguration:

void QmlCoapSecureClient::setSecurityConfiguration(const QString &localCertificatePath,
                                                   const QString &caCertificatePath,
                                                   const QString &privateKeyPath)
{
    QCoapSecurityConfiguration configuration;

    const auto localCerts =
            QSslCertificate::fromPath(QUrl(localCertificatePath).toLocalFile(), QSsl::Pem,
                                      QSslCertificate::PatternSyntax::FixedString);
    if (localCerts.isEmpty())
        qCWarning(lcCoapClient, "The specified local certificate file is not valid.");
    else
        configuration.setLocalCertificateChain(localCerts.toVector());

    const auto caCerts = QSslCertificate::fromPath(QUrl(caCertificatePath).toLocalFile(), QSsl::Pem,
                                                   QSslCertificate::PatternSyntax::FixedString);
    if (caCerts.isEmpty())
        qCWarning(lcCoapClient, "The specified CA certificate file is not valid.");
    else
        configuration.setCaCertificates(caCerts.toVector());

    QFile privateKey(QUrl(privateKeyPath).toLocalFile());
    if (privateKey.open(QIODevice::ReadOnly)) {
        QCoapPrivateKey key(privateKey.readAll(), QSsl::Ec);
        configuration.setPrivateKey(key);
    } else {
        qCWarning(lcCoapClient) << "Unable to read the specified private key file"
                                << privateKeyPath;
    }
    m_configuration = configuration;
}

Nach dem Einstellen der Sicherheitskonfiguration legt die Methode sendGetRequest die Anfrage-URL fest und sendet eine GET Anfrage:

void QmlCoapSecureClient::sendGetRequest(const QString &host, const QString &path, int port)
{
    if (!m_coapClient)
        return;

    m_coapClient->setSecurityConfiguration(m_configuration);

    QUrl url;
    url.setHost(host);
    url.setPath(path);
    url.setPort(port);
    m_coapClient->get(url);
}

Beim Senden der ersten Anfrage wird ein Handshake mit dem CoAP-Server durchgeführt. Nach erfolgreichem Handshake werden alle nachfolgenden Nachrichten verschlüsselt, und eine Änderung der Sicherheitskonfiguration nach einem erfolgreichen Handshake hat keine Auswirkungen. Wenn Sie die Konfiguration ändern oder den Host wechseln wollen, müssen Sie die Verbindung zuerst unterbrechen.

void QmlCoapSecureClient::disconnect()
{
    if (m_coapClient)
        m_coapClient->disconnect();
}

Dadurch wird der Handshake abgebrochen und die offenen Sockets werden geschlossen.

Für die Authentifizierung mit X.509-Zertifikaten müssen die Zertifikatsdateien angegeben werden. Zu diesem Zweck wird die Komponente FilePicker verwendet. Sie kombiniert ein Textfeld und eine Schaltfläche, um bei Betätigung der Schaltfläche einen Dateidialog zu öffnen:

Item {
    id: filePicker

    property string dialogText
    property alias selectedFile: filePathField.text

    height: addFileButton.height

    FileDialog {
        id: fileDialog
        title: qsTr("Please Choose %1").arg(filePicker.dialogText)
        currentFolder: StandardPaths.writableLocation(StandardPaths.HomeLocation)
        fileMode: FileDialog.OpenFile
        onAccepted: filePathField.text = fileDialog.selectedFile
    }

    RowLayout {
        anchors.fill: parent
        TextField {
            id: filePathField
            placeholderText: qsTr("<%1>").arg(filePicker.dialogText)
            inputMethodHints: Qt.ImhUrlCharactersOnly
            selectByMouse: true
            Layout.fillWidth: true
        }

        Button {
            id: addFileButton
            text: qsTr("Add %1").arg(filePicker.dialogText)
            onClicked: fileDialog.open()
        }
    }
}

FilePicker Die Komponente Main.qml wird in der Datei mehrfach instanziiert, um Eingabefelder für Zertifikate und den privaten Schlüssel zu erzeugen:

FilePicker {
    id: localCertificatePicker
    dialogText: qsTr("Local Certificate")
    enabled: (securityModeGroup.checkedButton as RadioButton) === certificateMode
    Layout.columnSpan: 2
    Layout.fillWidth: true
}

FilePicker {
    id: caCertificatePicker
    dialogText: qsTr("CA Certificate")
    enabled: (securityModeGroup.checkedButton as RadioButton) === certificateMode
    Layout.columnSpan: 2
    Layout.fillWidth: true
}

FilePicker {
    id: privateKeyPicker
    dialogText: qsTr("Private Key")
    enabled: (securityModeGroup.checkedButton as RadioButton) === certificateMode
    Layout.columnSpan: 2
    Layout.fillWidth: true
}

Einrichten eines sicheren CoAP-Servers

Um dieses Beispiel auszuführen, benötigen Sie einen sicheren CoAP-Server, der entweder den PSK- oder den Zertifikatsmodus (oder beide) unterstützt. Sie haben die folgenden Möglichkeiten:

  • Manuelles Erstellen und Ausführen eines sicheren CoAP-Servers, z. B. mit libcoap, Californium, FreeCoAP oder einer anderen CoAP-Bibliothek, die DTLS unterstützt.
  • Verwenden Sie die fertigen Docker-Images, die auf Docker Hub verfügbar sind und mit denen die für unser Beispiel geeigneten sicheren CoAP-Server erstellt und ausgeführt werden können. Die für die Verwendung der Docker-basierten CoAP-Server erforderlichen Schritte werden im Folgenden beschrieben.
Einrichten eines Servers für den PSK-Modus

Mit dem folgenden Befehl wird der Docker-Container für einen sicheren CoAP-Server auf der Basis von Californium Plugtest (der standardmäßig nicht sicher ist) aus dem Docker Hub gezogen und gestartet:

docker run --name coap-test-server -d --rm -p 5683:5683/udp -p 5684:5684/udp tqtc/coap-californium-test-server:3.8.0

Der CoAP-Testserver ist über die Ports 5683 (nicht sicher) und 5684 (sicher) erreichbar. Anweisungen zum Abrufen der IP-Adresse finden Sie unter Abrufen der IP-Adresse.

Um das Beispiel mit diesem Server auszuführen, müssen Sie den Pre-Shared Key auf secretPSK und die Identität auf Client_identity setzen.

Einrichten eines Servers für den Zertifikatsmodus

Das Docker-Image des sicheren Servers, der die Authentifizierung mit X.509-Zertifikaten verwendet, basiert auf dem Zeitserver-Beispiel aus der FreeCoAP-Bibliothek. Der folgende Befehl zieht den Container aus Docker Hub und startet ihn:

docker run --name coap-time-server -d --rm -p 5684:5684/udp tqtc/coap-secure-time-server:freecoap

Eine Anleitung zum Abrufen der IP-Adresse finden Sie unter Abrufen der IP-Adresse. Der CoAP-Testserver ist über die ermittelte IP-Adresse auf Port 5684 und den Ressourcenpfad /time erreichbar.

Um das Beispiel mit diesem Server auszuführen, müssen Sie die vom Server benötigten Zertifikatsdateien angeben. Sie befinden sich im Docker-Container unter dem Verzeichnis /root/certs. Um sie in ein lokales Verzeichnis zu kopieren, verwenden Sie den folgenden Befehl:

docker cp <container_id>:/root/certs <local_directory_path>

Zum Beispiel:

$ docker cp 5e46502df88f:/root/certs ~/

Die Anweisungen zum Abrufen der Container-ID werden im Folgenden beschrieben.

Ermittlung der IP-Adresse

Um die IP-Adresse eines Docker-Containers herauszufinden, rufen Sie zunächst die Container-ID ab, indem Sie den Befehl docker ps ausführen, der in etwa Folgendes ausgibt

$ docker ps
CONTAINER ID        IMAGE
5e46502df88f        tqtc/coap-californium-test-server:3.8.0

Anschließend können Sie die IP-Adresse mit dem folgenden Befehl abrufen:

docker inspect <container_id> | grep IPAddress

Zum Beispiel:

$ docker inspect 5e46502df88f | grep IPAddress
...
"IPAddress": "172.17.0.2",
...
Beenden eines Docker-Containers

Um einen Docker-Container nach der Verwendung zu beenden, verwenden Sie den folgenden Befehl:

docker stop <container_id>

Die <container_id> ist hier die gleiche, die mit dem Befehl docker ps abgerufen wird.

Dateien:

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.