クイックセキュア CoAP クライアント

CoAP クライアントをセキュアにし、Qt Quick ユーザーインターフェースで使用します。

Quick Secure CoAP Clientは、安全な CoAP クライアントを作成し、Qt Quick アプリケーションで使用する方法を説明します。

注意: Qt CoAP は現在のバージョンでは QML API を提供していません。しかし、この例で示されているように、モジュールの C++ クラスを QML で利用できるようにすることができます。

例の実行

Qt Creator からサンプルを実行するには、Welcome モードを開き、Examples からサンプルを選択します。詳しくは、Building and Running an Example を参照してください。

サンプル・アプリケーションを実行するには、まず安全な CoAP サーバーをセットアップする必要があります。事前共有鍵(PSK)または証明書認証モードのいずれかをサポートするセキュアな CoAP サーバーであれば、サンプルを実行できます。セキュアな CoAP サーバーのセットアップの詳細については、「セキュアな CoAP サーバーのセットアップ」を参照してください。

C++ クラスの QML への公開

この例では、QCoapClient クラスとQtCoap 名前空間を QML に公開する必要があります。そのためには、カスタムラッパークラスを作成し、特別な登録マクロを使用します。

QCoapClient のラッパーとしてQmlCoapSecureClient クラスを作成します。このクラスは選択されたセキュリティモードとセキュリティ設定パラメータも保持します。Q_INVOKABLE マクロを使って、いくつかのメソッドを QML に公開します。また、QML_NAMED_ELEMENT マクロを使用して、このクラスを QML にCoapSecureClient として登録します。

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

その後、QtCoap 名前空間を登録し、そこで提供される列挙型を使用できるようにします:

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

ビルドファイルの調整

QMLからカスタム型を利用できるようにするために、ビルドシステムファイルを適宜更新してください。

CMake

CMakeベースのビルドでは、CMakeLists.txt

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

qmakeでビルドする場合は、quicksecureclient.pro ファイルを以下のように変更してください:

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

新しいQML型の使用

これで、C++のクラスがQMLに正しく公開され、新しい型を使うことができます。

クライアントの作成

CoapSecureClient は ファイルからインスタンス化されます。 シグナルを処理し、それに応じて UI を更新します:Main.qml QmlCoapSecureClient::finished()

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

QCoapClient のインスタンスは、ユーザーがUIでセキュリティモードを選択または変更したときに生成されます。セキュリティモードが選択されると、QmlCoapSecureClient::setSecurityMode() メソッドが QML コードから呼び出されます:

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

C++ 側では、このメソッドはQCoapClient を生成し、そのfinished() とerror() シグナルに接続します。このクラスはこの二つのシグナルを内部的に処理し、新しいfinished() シグナルに転送します。

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));
                });
    }
}
リクエストの送信

選択したセキュリティ・モードに基づいてセキュリティ設定を行い、GET リクエストを送信するには、Send Request ボタンをクリックします:

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

setSecurityConfiguration メソッドには2つのオーバーロードがあります。

PSKモード用のオーバーロードは、クライアントのIDと事前共有鍵 を設定するだけである:

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

X.509証明書のオーバーロードは、証明書ファイルと秘密鍵を読み込んで、セキュ リティ・コンフィギュレーションを設定する:

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

セキュリティ設定を行った後、sendGetRequest メソッドはリクエストURLを設定し、GET リクエストを送信する:

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

最初のリクエストを送信するとき、CoAPサーバーとのハンドシェイクが実行される。最初のリクエストを送信するとき、CoAPサーバーとのハンドシェイクが行われる。ハンドシェイクが成功すると、それ以降のメッセージはすべて暗号化され、ハンドシェイク成功後にセキュリティ・コンフィギュレーションを変更しても影響はない。変更したい場合やホストを変更したい場合は、まず切断する必要がある。

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

これによりハンドシェイクが中止され、開いているソケットが閉じられる。

X.509証明書を使用した認証では、証明書ファイルを指定する必要がある。このためにFilePicker コンポーネントを使用します。これはテキスト・フィールドとボタンを組み合わせたもので、ボタンを押すとファイル・ダイアログが開きます:

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 証明書と秘密鍵の入力フィールドを作成するために、 ファイル内で数回インスタンス化されます:Main.qml

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
}

セキュアなCoAPサーバーのセットアップ

このサンプルを実行するには、PSKまたは証明書モードのいずれか(あるいは両方)をサポートするセキュアなCoAPサーバーが必要です。以下のオプションがあります:

  • libcoapCaliforniumFreeCoAP、またはDTLSをサポートするその他のCoAPライブラリを使用して、セキュアなCoAPサーバーを手動で構築し、実行する。
  • Docker Hubで入手できる準備の整ったDockerイメージを使用し、この例に適したセキュアなCoAPサーバーを構築して実行する。DockerベースのCoAPサーバーを使用するために必要な手順を以下に説明します。
PSKモード用サーバーのセットアップ

以下のコマンドは、Californium plugtest(デフォルトではセキュアではない)をベースにしたセキュアなCoAPサーバー用のdockerコンテナをDocker Hubから取り出し、起動する:

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

CoAPテストサーバーは、ポート5683(非セキュア)とポート5684(セキュア)で到達可能になる。IPアドレスの取得方法については、IPアドレスの取得を参照してください。

このサーバーでサンプルを実行するには、事前共有鍵をsecretPSK に、ID をClient_identity に設定する必要がある。

証明書モード用サーバーのセットアップ

X.509 証明書による認証を使用するセキュアサーバーの docker イメージは、FreeCoAP ライブラリのタイムサーバーの例に基づいています。以下のコマンドで Docker Hub からコンテナを取得し、起動する:

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

IPアドレスの取得方法については、IPアドレスの取得を参照してください。CoAPテストサーバーは、ポート5684とリソースパス/time で、取得したIPアドレスで到達可能になります。

このサーバーでサンプルを実行するには、サーバーが必要とする証明書ファイルを指定する必要があります。これらは docker コンテナの/root/certs ディレクトリにあります。これらをローカル・ディレクトリにコピーするには、以下のコマンドを使用する:

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

例えば

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

コンテナIDの取得手順を以下に示す。

IP アドレスの取得

docker コンテナの IP アドレスを調べるには、まずdocker ps コマンドを実行してコンテナ ID を取得する:

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

次に、以下のコマンドで IP アドレスを取得できる:

docker inspect <container_id> | grep IPAddress

例えば、以下のようになります:

$ docker inspect 5e46502df88f | grep IPAddress
...
"IPAddress": "172.17.0.2",
...
Dockerコンテナの終了

使用後にドッカー・コンテナを終了するには、以下のコマンドを使用する:

docker stop <container_id>

ここでの<container_id> は、docker ps コマンドで取得したものと同じである。

ファイル

©2024 The Qt Company Ltd. 本文書に含まれる文書の著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。