遠隔測定用機器が収集した少量のデータを頻繁にサーバーに送り出す用途に、MQTT (Message Queueing Telemetry Transport )プロトコルが使われています。この記事ではMQTTプロトコルにセキュリティ機能を取り入れたセキュアMQTT(MQTTS)をご紹介します。また、機器向けのセキュアMQTTの実装についてwolfMQTTを例に紹介します。
MQTT
MQTTプロトコルは標準化団体OASISによって標準化が進められ、現在の最新バージョンはv5です。ちなみに前バージョンのMQTTv3.11はISO/IEC 20922:2016としても標準ドキュメントが公開されています。
クラウドサービスとして知られているAWS IoTやAzure IoT Hub ではMQTTv3.11のMQTTブローカーがサービスとして提供されています。このほかにも、MQTT-SN(MQTT for Sensor Networks)と呼ばれるUDPベースのMQTTも利用されています。この記事では基本となるMQTTとMQTTSを中心に説明します。
メッセージングモデル
MQTTはTCPを使ったネットワーク上の通信プロトコルの一つです。メッセージングモデルとしてはPublish/Subscribeモデルを使用します。このモデルでは両者を仲介する役割としてBrokerが登場します。Brokerの存在により、PublisherとSubscriberが直接接続する必要がなく、また同時に接続する必要もなくなるという利点が得られます。
Brokerはメッセージを蓄積、管理する目的でメッセージキューを複数保持します。メッセージキューは下図の様なモデルです。各キューは“トピック”というキューを識別する情報を備えており、メッセージの発行(パブリッシュ)先です。
Subscriberはメッセージの配信元である”トピック“を指定して、そこに届くメッセージを配信してもらうようBrokerに配信依頼(Subscribe)します。一方、Publisherは”トピック“宛にメッセージを発行(Publish)します。Brokerは”トピック“に対して配信依頼を行っていたクライアントに対してメッセージをトピックとともに配信します。
PublisherもSubscriberも複数存在し、各々が複数のトピックに対してメッセージを依頼・発行できるので下図のような構成となることも可能です。
ユースケース
MQTTを使用している簡単なユースケースを見てみましょう。
ケース1:複数のIoT機器が計測したデータをクラウドサーバーに送り、集計システムがそれらのデータを処理するケース
工場内で多くのIoT機器が何らかの動作(処理)を行いつつ例えば温度などを測定し逐次サーバーに送り出し、遠隔管理システムではサーバーに集まった温度データを監視して異常を検出したり、適切な温度調節を行うなどのシステムが考えられます。
この場合には、複数のIoT機器はPublisherであり、クラウド上で稼働しているサーバーがBrokerになります。遠隔管理システムはSubscriberに相当します。IoT機器は温度データを送出するだけでなく、別のメッセージ(制御メッセージ)を遠隔管理システムから待ち受けている場合もあります。温度異常が検出された場合には、IoT機器に対して、動作停止などの制御の為のメッセージが配信されることが考えられます。
つまり一つのIoT機器がPublisherとSubscriberの両方として機能することになります。また、同様に遠隔管理システムは温度データの受信に関してはSubscriberですが、制御メッセージの配信に関してはPublisherとして動作します。このような場合、それぞれの方向のメッセージのために複数のトピック(メッセージキュー)を利用します。
ケース2:IoT機器のファームウェアを、プロビジョニングサーバーが保持する最新ファームウェアで更新するケース
このケースでは、IoT機器は例えば“ファームウェア”というトピックに対してサブスクライブしており、プロビジョニングサーバーは“最新ファームウェアイメージ”をメッセージとしてこのトピックに対して配信します。
MQTT接続モデル
最初にMQTTの通信チャネルについて説明します。Publisher、SubscriberとBrokerの間はTCP接続で実現されています。
PublisherとBroker間、SubscriberとBroker間は共にBrokerがTCPのポート1883番で待ち受けて、そこに対してPublisherとSubscriberが接続を行うモデルになっています。Subscriberはメッセージ配信を受ける側ですがBrokerからSubscriber側に接続を行ってメッセージ配信を行うわけではありません。
PublisherあるいはSubscriberが各IoT機器に相当しますが、一つのIoT機器がPublisherとSubscriberを兼ねている場合もあります。この場合にはPublisherとSubscriberが2つで一つのTCP接続を共有することになります。下図ではIoT機器はMQTTブローカーへ接続したTCP接続内で、あるトピックに宛ててPublishしたり、別のトピックからメッセージを受け取ったりします。
セキュアMQTT
MQTTではTCP接続を使うので、その中を流れるデータは暗号化されていません。MQTTではユーザー認証の仕組みとしてIDとパスワードを使用しますが、このIDとパスワードも平文で送信されます。第三者にメッセージを傍受されるとIoT機器に成りすますことができてしまいます。このような危険性を排除する目的のためにセキュアMQTTが用いられます。セキュアMQTTの目的はセキュアなTLS接続を確立し、その上でメッセージの安全を保証することにあります。
セキュアMQTTではMQTTブローカーが待ち受けるポート番号が8883番である点が異なります。またこのポート番号への接続はTLS接続を実行することがチャネルの両端で理解されていますから、TCPで接続した直後からTLSハンドシェークを実行してTLS接続を確立します。TLS接続の確立に関してはwolfMQTTではTLS通信ライブラリwolfSSLが担当します。
TLS接続内を流れるデータはすべて暗号化され、もし改ざんされても検出できる仕組みとなっています。しかし、TLS接続の両端で送受信するMQTTメッセージは通常のMQTTメッセージと全く同じです。このように、TLS接続はMQTTパケットには影響しません。ですからMQTTが提供する機能(QoS,パスワード認証、Last Will等)には変更や制限が加わったりはしません。
MQTTパケットをみてみよう
ではMQTTプロトコルパケットを観測してみることにします。MQTTブローカーはオープンソースのEclipseプロジェクトがテスト用に用意しているネット上のMQTTブローカーを使います。MQTTクライアントはオープンソースのMQTTクライアントライブラリであるwolfMQTTのサンプルクライアントを使います。LinuxあるいはWindowsのWSL上であればコードの取得とビルドは比較的簡単です。
mqttclientサンプルプログラム
mqttclientはwolfMQTTに含まれているMQTTクライアントとして動作するサンプルアプリケーションです。このアプリケーションを使うためにはwolfSSLとwolfMQTTをビルドします。
wolfSSLのビルドとインストール
wolfMQTTはwolfSSLに依存しています。そこで最初にwolfSSLをインストールします。ターミナルを開いて以下でwolfSSLの最新ソースコードをGitHubレポジトリからクローンし、ビルド&インストールします。
$ git clone --depth 1 https://github.com/wolfssl/wolfssl.git $ cd wolfssl $ ./autogen.sh $ ./configure $ make $ sudo make install
wolfMQTTのビルド
引き続き、最新のwolfMQTTのソースコードを入手しビルドします。
$ git clone --depth 1 https://github.com/wolfssl/wolfMQTT.git $ cd wolfmqtt $ ./autogen.sh $ ./configure $ make
wolfMQTTをビルドするといくつかサンプルプログラムも同時に作成されます。そのなかのmqttclientプログラムを実行します。このプログラムはwolfSSLを使用してセキュアMQTTも実行できるようになっているMQTTクライアントプログラムで、パブリッシャーとサブスクライバーの両方の役割をこなします。
mqttclientの実行
wolfmqttのベースフォルダから、以下の様に起動してみます。接続するMQTTブローカーはデフォルトで”test.mosquitto.org”のポート1883番(TCP接続)となっているので引数の指定は不要です。
$ ./examples/mqttclient/mqttclient
コマンドを実行すると次のようなログメッセージが出力されます。
$ ./examples/mqttclient/mqttclient MQTT Client: QoS 0, Use TLS 0 MQTT Net Init: Success (0) MQTT Init: Success (0) NetConnect: Host test.mosquitto.org, Port 1883, Timeout 5000 ms, Use TLS 0 MQTT Socket Connect: Success (0) MQTT Connect: Proto (v3.1.1), Success (0) MQTT Connect Ack: Return Code 0, Session Present 0 MQTT Subscribe: Success (0) Topic wolfMQTT/example/testTopic, Qos 0, Return Code 0 MQTT Publish: Topic wolfMQTT/example/testTopic, Success (0) MQTT Waiting for message... MQTT Message: Topic wolfMQTT/example/testTopic, Qos 0, Len 4 Payload (0 - 4) printing 4 bytes: test MQTT Message: Done ^CReceived SIGINT Network Error Callback: Error (Network) (error -8) MQTT Exiting... MQTT Unsubscribe: Success (0) MQTT Disconnect: Success (0) MQTT Socket Disconnect: Success (0)
mqttclientが出力したログから、TLSは使わない(TLS 0)でtest.mosquitto.orgの1883番ポートにMQTTv3.1.1で接続していることがわかります。さらに”wolfMQTT/example/testTopic”をサブスクライブしていて、”test”メッセージをパブリッシュしたことがわかります。
パケット
上記のターミナル出力に対応するパケットをWiresharkでキャプチャしたのが下図です。
Publishメッセージパケットからトピックと送られたメッセージの内容が次のようであることが読み取れます:
トピック:wolfMQTT/example/testTopic
メッセージ:test
このキャプチャから#163パケットと#164パケットは同一パケットですが、宛先が異なっています。これは#163 パケットの方がmqttclientアプリケーションからのPublishパケットであり、#164パケットの方はmqttclientアプリケーションのサブスクライブに対応してブローカーから配信されたPublishパケットであることが、送信元、送信先のIPアドレスまたはポート番号からわかります。
MQTTにおける脅威
キャプチャしたパケットからわかるように、通常のMQTTパケットは以下に示すような脅威に対抗する手段がとられていません:
- データ盗聴
- データ改ざん
- なりすまし
データそのものが平文で流れますから第三者にもその情報が筒抜けです。悪意を持った第三者はデータの内容を書き換えてしまうかもしれません。ユーザー認証のためのIDとパスワードも平文で流れます。さらに、IoT機器とMQTTブローカーは互いに相手が正規の通信相手だと確信する手段がありません。次の章でこのセキュアMQTTを使ってMQTTブローカーにアクセスしてみます。
セキュアMQTTを使ってみよう
今度はセキュアMQTT を使用する例を紹介します。改めて次の準備を行います。
セキュア化に必要なもの
先に説明したMQTTの脅威のうちの「なりすまし」を見破るための仕組みとして「認証」があります。クライアントがアクセスしているサーバー(Broker)の正当性を確認する行為が「サーバー認証」で同様にクライアント(IoT機器)を認証する「クライアント認証」もあります。IoT機器からの重要なデータを誤った接続先ブローカーに送信することと、関係のないIoT機器から大量のデータが送信されてくることの両方を防ぐ必要があります。そのためには本来は両方の認証を行うことが望ましいといえますが、まずはサーバー認証のみを実行させてみます。サーバー認証にはCA証明書ファイルが必要となりますが、https://test.mosquitto.org/ において、CA証明書(mosquitto.org.crt)ファイルが入手できますからダウンロードしておきます。
先ほどのmqttclientアプリケーションを8883番ポートとTLS指定および、入手したCA証明書を指定して起動してみます。TLS接続した後のクライアントアプリケーションの動作は同じなので、このように先ほどと同じメッセージが出力されます。
$ ./examples/mqttclient/mqttclient -t -p8883 -A../certs/mosquitto.org.crt MQTT Client: QoS 0, Use TLS 1 MQTT Net Init: Success (0) MQTT Init: Success (0) NetConnect: Host test.mosquitto.org, Port 8883, Timeout 5000 ms, Use TLS 1 MQTT TLS Setup (1) MQTT Socket Connect: Success (0) MQTT Connect: Proto (v3.1.1), Success (0) MQTT Connect Ack: Return Code 0, Session Present 0 MQTT Subscribe: Success (0) Topic wolfMQTT/example/testTopic, Qos 0, Return Code 0 MQTT Publish: Topic wolfMQTT/example/testTopic, Success (0) MQTT Waiting for message... MQTT Message: Topic wolfMQTT/example/testTopic, Qos 0, Len 4 Payload (0 - 4) printing 4 bytes: test MQTT Message: Done
パケットキャプチャーを“tls”フィルターで見てみると、ClientHello、ServerHelloのあと、ハンドシェークの後半とアプリケーションメッセージは暗号化され内容が平文では読み取れなくなっているのがわかります。
mqttclientプログラムを見てみよう
MQTTクライアントアプリケーションとして使ったmqttclientのコードをみてみることにします。mqttclient はMQTTもセキュアMQTTも両方を利用できますがその内部はどのようになっているのか、また下層のMQTTライブラリやTLSライブラリをどのように使っているのかを紹介します。
mqttclientのコード(mqttclient.c)
下にmqttclientのコードを抜粋しています。コードはwolfMQTT/examples/mqttclientフォルダのmqttclient.cファイルに記述されているコードです。以下のコードでセットアップからBroker接続を行い、PublishとSubscribeを行い、接続を維持しながらメッセージ待ちを行うIoT機器の動作が実現できます。
int mqttclient_test(MQTTCtx* mqttCtx) { /* 初期化と接続 */ rc = MqttClientNet_Init(&mqttCtx->net, mqttCtx); /*ネットワーク初期化*/ rc = MqttClient_Init(&mqttCtx->client, mqttCtx->net, …); /*クライアント初期化*/ rc = MqttClient_NetConnect(&mqttCtx->client, mqttCtx->host, …); /* ネットワーク接続*/ rc = MqttClient_Connect(&mqttCtx->client, &mqttCtx->connect); /* MQTT Connect */ /* サブスクライブ */ rc = MqttClient_Subscribe(&mqttCtx->client, &mqttCtx->subscribe); /* パブリッシュ */ rc = MqttClient_Publish(&mqttCtx->client, &mqttCtx->publish); do { /* メッセージ待ち */ rc = MqttClient_WaitMessage(&mqttCtx->client, mqttCtx->cmd_timeout_ms); rc = MqttClient_Ping_ex(); /* Pingで接続維持 */ while(!mStopRead); ...
MQTTへの理解がある方であれば、コードからMQTTのプロトコルに従った処理の流れであると理解できると思います。しかし、このコードには明示的に表れていない以下の3点あります:
- トランスポート層でのメッセージ送受信
- TLSのサーバー認証、クライアント認証
- サブスクライブメッセージの非同期処理
トランスポート層でのメッセージ送受信
wolfMQTTではMQTTの場合にはTCP接続、MQTTSの場合はTLS接続を使うというようにトランスポートを切り替えて使うことになります。しかし、この使用するトランスポートの違いをMQTTクライアントアプリケーションは意識しません。データの送受信はwolfMQTTが適宜、TCPソケットないしwolfSSLを呼び出して実現します。
wolfMQTTではさらに標準のBSDソケットがサポートされていない組み込みプラットフォームで利用できるように、このメッセージ通信部分はカスタマイズしたコールバック関数を登録できるようになっています。環境に応じて、サンプルのコールバック関数を利用するか、さらにカスタマイズして使用します。
先ほどのサンプルではTLSによるサーバー認証にはデフォルトで用意されているコールバック関数をそのまま使用しました。本格的利用にはこの部分を環境に合わせて準備する必要がある場合もありますが、サンプルクライアントでは以下のようにコマンドアーギュメントのオプションで認証に使用する証明書や鍵ファイルを指定することもできます。
-A
-c
-k
MQTTのサブスクライブでは、メッセージはパブリッシュされたタイミングで非同期に到着することになります。そのために、MQTTクライアントアプリケーションはサブスクライブ時に (MqttClient_Subscribe)でメッセージ受信コールバック関数を登録しておきます。これによりPublishメッセージが到着すると登録してあるコールバック関数が呼び出されクライアントプログラムがメッセージを受け取ることができます。
まとめ
この記事ではMQTTについての知識がある方を対象として、セキュアMQTTの、MQTTとの違いを説明しました。また、サンプルクライアントアプリケーションの構造とwolfMQTTを使ったコードについても解説しました。
現在クラウド上動作する商用Brokerサービスが提供されており、実際に使用する機会が増えていくことでしょう。その際にはセキュリティとその設定に関する知識が必要になります。wolfSSLとwolfMQTTはそのような場合にそなえた豊富なサンプルプログラムを提供しています。特に、wolfSSLはポスト量子暗号アルゴリズムも搭載しているなど、先進的な取り組みを続けているTLS実装です。技術的な調査用として評価する、あるいはコードを参考にしてご自身の製品開発を加速するなどに活用していただけると考えています。