クイックセキュア 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サーバーが必要です。以下のオプションがあります:
- libcoap、Californium、FreeCoAP、または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.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。