1.記事一覧
記事は複数回に分けて投稿する予定です。AMD/XilinxのARM SoC(ZynqMP Kria K26 SOM)で動作する、AMD/Xilinx公式認定Ubuntuやベアメタル開発を勉強していく覚書きです。
ZynqMP(APU:Cortex-A53,Arm64) 組込みLinux入門編
- 【ZynqMP】1. 認定UbuntuでPLのGPIOを使う
- 【ZynqMP】2. 認定Ubuntuのカーネルビルド
- 【ZynqMP】3. 認定UbuntuのDeviceTree変更
- 【ZynqMP】4. 認定UbuntuでOpenAMPを試す【CR5Lockstep】
- 【ZynqMP】5. UARTでAMP【コア間通信】
- 【Linux】6. crash toolでカーネルメモリを見る
ZynqMP(RPU:Cortex-R5,Arm32) RPU入門編
2.暗号化通信:Internetに出かける為のドレスコード
今回でPL Ethernet編4回目になります。KR260ボードのPL側Ethernet+Cortex-R5(CR5)+LwIPスタックを使用してTCP/IP通信が出来るようになりました。
ネットワーク通信が出来るようになったので、いよいよインターネットと会話したいところですが、このままではすべての通信データが平文なので、中身が丸見えです。セキュリティ上良くないので、TLSによる暗号化通信をやってみようと思います。
今回はオープンソース&商用ライセンスの組込み用TLSライブラリである、wolfSSLを使います。
2-1. 今回やること:MQTT+TLSで相互認証サンプルを試す
私自身TLS通信の知識が「HTTPS通信で使われるアレ」程度の知識でしたので、いきなりwolfSSLライブラリを前回の環境にポーティングして動かすのはさすがに無謀過ぎました。
そこで、wolfSSLのチュートリアルとして、「ホストPC上(Ubuntu)でビルドしてMQTTSクライアントサンプルを動かす」をやってみます。
今回やること:
- TLS通信の準備で必要な認証/証明書まわりの説明
- MQTTS通信のPub/SubサンプルをホストPC(Ubuntu)で動かす
詳細:
- wolfSSLのTLSライブラリとMQTTクライアントライブラリを使用
- サーバ認証とクライアント認証のどちらも行う(相互認証,mTLS)
- MQTTブローカはEclipse Mosquittoのテストサーバを使用
- Eclipse MosquittoのCA(認証局)でクライアント証明書を署名する
※MQTTSとはMQTT over TLSを指します。
2-2. SSL/TLS通信とは
TLS(Transport Layer Security)とはインターネット通信におけるセキュリティプロトコルです。初代はSSLだったのですが、脆弱性が見つかり、再設計されたTLSを現在では使用することが必須となっています。「SSL通信」という用語を名残で使っているところもありますが、実際はTLSを指しています。
TLSはTCP/IPモデルのうち、TCP層の上のレイヤに位置するプロトコルです。UDP層で作動するTLSはDTLS(Datagram Transport Layer Security)と呼ばれます。現在最新規格は2018年に登場したTLSv1.3でRFC8446で規格化されています。
情報セキュリティは以下の6要素が重要になります。
- 1.機密性(Confidentiality)
- 2.完全性(Integrity)
- 3.可用性(Availability)
- 4.責任追従性(Acountability)
- 5.真正性(Authenticity)
- 6.信頼性(Reliability)
この内、TLS通信は以下を実現できます。
- 1.機密性→暗号化による盗聴防止
- 2.完全性→第三者による改ざん検知
- 5.真正性→サーバ&クライアント認証によるなりすまし防止
2-3. wolfSSLとは
wolfSSLは米wolfSSL Inc.が開発する、組込み向けのセキュリティプロトコルライブラリです。OpenSSLよりも軽量で、ベアメタル、RTOS環境などへの移植を考えられたC言語実装のライブラリです。ライセンスはオープンソース(GPL)と商用ライセンスのデュアルライセンスです。
今回はAPS様の記事「はじめてのセキュアMQTT」を参考にMQTTS通信のサンプルを動かします。
ソースコードはwolfSSLのホームページまたは以下のGithubで入手できます。
- wolfSSL-TLS:https://github.com/wolfSSL/wolfssl
- wolfSSL-MQTTClient:https://github.com/wolfSSL/wolfMQTT
また、TLSに関する参考書として、wolfSSLの方が執筆している以下の本が参考になりました。(アフィリエイトではございません)
こちらの本は最新規格TLSv1.3に関する内容になっており、徹底解剖の名に恥じないとても詳しい本です。正直まだすべて読み切れていません。wolfSSLライブラリの構成説明も後半にあります。
他のオープンソースのTLS実装は多くなく、私が認知しているのはサーバ&PC向けではOpenSSL、GnuTLSがあり、組込み向けではMbedTLSがあります。
2-4. MQTT通信とは
MQTTS通信とはIoT機器の通信でよく使用される、MQTT通信のTLS対応版になります。
MQTT通信は以下のようなPublisher/Subscriberアーキテクチャになっています。
BrokerはTopicと呼ばれる配信チャネルを持っていてメッセージキューの構造になっています。Brokerを経由することで非同期の通信が可能になり、通信状態が悪い環境でも対応できます。
Brokerの実装として、Eclipseが開発している”Mosquitto”が有名です。Dockerコンテナイメージも配布されているので、ローカルで簡単に動かすことも出来ます。また、テスト用のBrokerサーバも公開されています。
アーキテクチャの例として、例えばIoT機器からクラウドへデータ送信する場合は
- Publisher:IoT機器
- Brocker:エッジゲートウェイ機器
- Subscriber:クラウドのサーバアプリ
クラウドからIoT機器の場合は
- Publisher:クラウドのサーバアプリ
- Brocker:エッジゲートウェイ機器
- Subscriber:IoT機器
IoT機器から別のIoT機器
- Publisher:IoT機器
- Brocker:エッジゲートウェイ機器
- Subscriber:別のIoT機器
以上はあくまで一例です。
IoT機器とクラウドの間に例えばセンサゲートウェイを設置するような多階層になる場合はBrokerをどこで動かすかは設計次第になると思います。通信環境によってはBrokerをクラウド側やエッジ側に持ってくることもあります。
2-5. MQTTSサンプルの実験環境
実験環境について説明します。以下の構成になっています。
BrokerはMosquitto本家が公開しているテスト公開サーバを利用します。本来MQTTのTCP Port番号は1883、MQTTSはPort 8883ですが、Port番号別に様々な試験条件を試すことが出来ます。
今回は「Port 8884:MQTTS + サーバ&クライアント認証有効(mTLS)」を使用します。
クライアント認証はTLSではオプションの扱いで、例えばHTTPS通信では使われず、サーバ認証のみとなります。HTTPSでは基本的にクライアントデバイスは不特定のものになるので認証を使うのは出来ません。しかし、IoTデバイスがクライアントの場合、IoTデバイスのなりすまし防止のため、基本的にクライアント認証が必須になります。
続いて、wolfSSLのMQTTサンプルについてですが、Pub/Subサンプルコードが用意されています。ソースコードをビルドする必要があるため、Dockerコンテナ内で作業することにします。
開発PC | Ubuntu20.04LTS |
Docker | Docker version 25.0.3 Ubuntu22.04 Image |
必要パッケージ | ビルド関連:git, build-essential wolfSSL関連:autoconf, libtool 公開鍵証明書関連:openssl |
wolfSSL | Github wolfSSL-TLS:https://github.com/wolfSSL/wolfssl wolfSSL-MQTTClient:https://github.com/wolfSSL/wolfMQTT Pub/SubMQTTクライアントサンプルApp:https://github.com/wolfSSL/wolfMQTT/blob/master/examples/mqttclient/mqttclient.c |
MQTT Broker | Eclipse Mosquitto テスト公開サーバ https://test.mosquitto.org/ TCP Port 1883 : MQTT (TLS無し)+ 認証なし TCP Port 8884 : MQTTS + サーバ&クライアント認証有効(mTLS) ※本サーバは実験的かつ非力なため、大量のパケットを送ったり、本格的な試験に使用したりすることはできません。 |
CA (認証局) | Eclipse Mosquitto テスト公開サーバの独自CAを利用 サーバ証明書: →テストサーバが既に独自CAにて署名付きサーバ証明書を持っているため発行手続き不要。そのため、クライアントには独自CAのRootCA証明書を予めダウンロードしておくのみで良い。MosquittoのRootCA証明書(PEM形式) クライアント証明書: 独自CAのクライアント証明書発行ページにて作成する https://test.mosquitto.org/ssl/ |
パケットキャプチャ | WireShark (Linux版) Version 3.2.3 |
3.wolfSSLサンプルAppを動かす
サンプルを動かすための手順を説明します。作業途中で出てくる用語は次章で解説します。作業手順はAPS様の記事「はじめてのセキュアMQTT」を参考にしています。
3-1. Docker ImageビルドとwolfSSLソースビルド
開発PC側の準備を行います。DockerでUbuntu22.04イメージをベースにビルドします。
$ docker image build ./ -t ub2204/wolfmqtt
FROM ubuntu:22.04
#あらかじめ入れておくパッケージ
RUN apt update && apt upgrade -y && apt install -y sudo git build-essential autoconf libtool
#一般ユーザを作成
## ユーザ名
ARG USERNAME=user
ARG GROUPNAME=user
## ユーザIDはホストマシンの自身のユーザIDと一致させる。$ id コマンドで調べる
ARG UID=1000
ARG GID=1000
ARG PASSWORD=user
## ユーザを作成する。またsudo実行権限を付与する
RUN groupadd -g $GID $GROUPNAME && \
useradd -m -s /bin/bash -u $UID -g $GID -G sudo $USERNAME && \
echo $USERNAME:$PASSWORD | chpasswd && \
echo "$USERNAME ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
USER $USERNAME
WORKDIR /home/$USERNAME/
CMD ["/bin/bash"]
wolfSSLをビルドするには、build-essential、autoconf、libtoolが必要です。
3-2. コンテナ立ち上げ
開発ホストPCと共有フォルダを設定して起動します。あとでホスト側の共有フォルダから証明書を配置します。
コンテナ立ち上げ。ホストPCと共有フォルダを設定する
$ docker run -v "/ホストPCの共有フォルダ:/コンテナ側の共有フォルダ" --name "wolfmqtt" -it "ub2204/wolfmqtt" /bin/bash
例
$ docker run -v "/home/sh-goto/work/tls_wk/share:/home/user" --name "wolfmqtt" -it "ub2204/wolfmqtt" /bin/bash
3-3. wolfSSL(TLSライブラリ)のソースビルド
dockerコンテナ内で共有フォルダに入り、作業をする
(docker)$ git clone --depth 1 https://github.com/wolfssl/wolfssl.git
(docker)$ cd wolfssl
(docker)$ ./autogen.sh
(docker)$ ./configure
(docker)$ make
(docker)$ sudo make install
3-4. wolfMQTTライブラリのソースビルド
(docker)$ git clone --depth 1 https://github.com/wolfssl/wolfMQTT.git
(docker)$ cd wolfMQTT
(docker)$ ./autogen.sh
(docker)$ ./configure
(docker)$ make
3-5. TLSのサーバ認証関係の準備
今回はMQTTBrokerサーバはEclipse Mosquittoの公開テストサーバを用いるので、署名付きサーバ証明書はEclipse MosquittoのプライベートCAにて既に用意されています。残りの準備作業はそのサーバ証明書を検証するためのCA証明書をクライアント側の認知出来る場所に置くことです。ここで言うクライアントとはコンテナ内で動作させるサンプルに該当します。
まず、公開テストサーバのwebページより、RootCA証明書(PEM形式)をダウンロードします。
場所は以下の橙色の枠のとこです。
ダウンロードしたRootCA証明書ファイル(mosquitto.org.crt)をコンテナの共有フォルダの適当な位置に配置します。
例
$ cd ~work/tls_wk/share
$ mkdir certs
$ mv ~/Downloads/mosquitto.org.crt ./certs/
これでサーバ認証は出来るようになります。サーバ証明書やCA証明書がそもそも何なのかについては最後の4.【補足】で補足します。
3-6. TLSのクライアント認証関係の準備
続いてクライアント認証の準備です。サーバ認証はサーバがなりすましでないかをクライアント側が確認するための仕組みですが、クライアント認証はクライアントがなりすましでないかをサーバが確かめるための仕組みです。
必要なものはクライアントがサーバに送るクライアント証明書とその証明書に署名を行ったCA(認証局)のCA証明書が必要になります。ただし、CA証明書はサーバ証明書と同じMosquittoのプライベートCAを利用するので、CA証明書は既にサーバ側が持っています。つまり、クライアント証明書の作成のみをすれば完了です。
クライアント証明書作成の流れ
- クライアント用秘密鍵の生成
- 秘密鍵からクライアント用公開鍵の作成
- 公開鍵から証明書署名要求を作成(CSR)
- CAに署名手続きを行い署名する
①〜③は開発PCの端末でOpenSSLコマンドを使用して作成します。
1.クライアント秘密鍵の作成
OpenSSLコマンドを使用してパスワードなし秘密鍵を生成します。秘密鍵名は任意で拡張子は.keyが一般的です。とりあえず”device_prv.key”にします。
$ openssl genrsa -out device_prv.key 2048
Generating RSA private key, 2048 bit long modulus (2 primes)
.....+++++
........+++++
e is 65537 (0x010001)
$ ls
device.key
【注意】-des3オプションをつけるとパスワード付き秘密鍵を生成できますが、今回使用するMQTTのサンプルはパスワード付き秘密鍵に対応していないので使えません。
2〜3.証明書署名要求(CSR)を作成
先程生成した秘密鍵を用いて証明書署名要求を作成します。これはいわば署名前クライアント証明書のようなものです。証明書の中には公開鍵が含まれていますので、手動で公開鍵作成はしなくて大丈夫です。
CSRを作成するには以下のコマンドを使用します。
$ openssl req -new -key device_prv.key -out device.csr
作成時にはいくつかの設定項目を入力します。
- Country Name []: <-2文字の国名 例:JP
- State or Province Name (full name) []:地域名 例:Tokyo
- Locality Name (eg, city) []:町名 例:Chiyoda-ku
- Organization Name (eg, company) []:組織名 例:blog-between-hw-sw.com
- Organizational Unit Name (eg, section) []:組織の部署 例:IoT
- Common Name (eg, YOUR name) []:CN名 例:device-001-001
- Email Address []: 例:未入力
どの様な内容を入れるかは組織ごとに異なり、開発時に設計します。例えばCN名(Common Name)はサーバ証明書の場合、ドメインのFQDNが一般的ですが、これはパブリックCAなどがその様なルールを定めていたりします。クライアント証明書の場合、プライベートCAを使うことが出来ますので、独自ルールで決めることも出来ます。例えばIoTデバイスのユニークIDをCNに設定するなどが可能です。
MosquittoのプライベートCAでは「最低でも国名、組織名、CN名でデフォルト値を使わないこと」とある以外、特にルールはないです。上の例の内容でとりあえず作成してみましょう。
$ openssl req -new -key device_prv.key -out device.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:JP
State or Province Name (full name) [Some-State]:Tokyo
Locality Name (eg, city) []:Chiyoda-ku
Organization Name (eg, company) [Internet Widgits Pty Ltd]:blog-between-hw-sw.com
Organizational Unit Name (eg, section) []:IoT
Common Name (e.g. server FQDN or YOUR name) []:device-001-001
Email Address []: ←入力せずにEnter
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []: ←入力せずにEnter
An optional company name []: ←入力せずにEnter
$ ls
device.csr device.key
生成されたCSRの内容を見てみます。
CSRの内容をデコードして確認する
$ openssl req -noout -text -in device.csr
Certificate Request:
Data:
Version: 1 (0x0)
Subject: C = JP, ST = Tokyo, L = Chiyoda-ku, O = blog-between-hw-sw.com, OU = IoT, CN = device-001-001
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
Modulus:
00:de:40:a6:02:4f:0b:4c:02:8c:eb:46:5c:46:55:
d0:b1:d6:e9:ca:00:cb:8d:ce:5e:02:80:9d:95:94:
1e:f0:7b:27:ad:36:2a:78:d0:99:94:65:14:08:d1:
bd:1e:6d:26:a4:84:97:d4:a5:c0:16:aa:a2:3b:ec:
18:ee:37:e8:5a:61:e8:d5:38:9f:1e:eb:f9:e9:24:
df:ed:6a:83:90:25:dc:63:34:51:e8:5b:03:79:a9:
e9:56:50:67:52:91:26:31:8e:38:1a:6d:e5:0f:27:
23:63:13:e2:e9:37:4e:dc:84:65:a0:ae:d6:9e:71:
60:5d:2c:89:eb:ce:02:eb:5c:69:97:aa:02:3a:8d:
06:e4:56:e6:d0:99:00:2a:c1:86:64:65:69:48:87:
22:18:6f:bb:b8:a9:f0:4e:1b:9a:37:04:b5:df:75:
6c:29:68:04:32:64:ec:53:2d:54:fe:7f:bb:58:4f:
15:6d:6a:52:d2:79:0a:82:a0:99:68:f4:0a:94:c3:
2a:6d:76:7e:32:d3:2e:3e:a1:3d:88:5e:c7:67:ee:
03:4b:5a:0f:e4:c4:1e:74:69:46:b6:5a:13:7a:be:
48:e8:d7:b5:51:33:b3:d0:46:06:13:97:81:7e:90:
ff:57:c9:84:98:ad:77:e6:67:b9:e8:6a:2b:86:3a:
4b:61
Exponent: 65537 (0x10001)
Attributes:
a0:00
Signature Algorithm: sha256WithRSAEncryption
a7:79:82:b1:3b:72:c6:e6:e4:4b:43:f0:ca:69:ff:29:c9:b3:
d2:ef:2f:07:95:24:15:14:47:83:28:2c:3c:2e:f8:a0:4c:15:
05:64:7c:a9:26:b3:df:15:37:36:82:53:39:1c:48:b8:bc:04:
53:ca:f9:64:f7:d2:ea:ef:30:76:1b:2b:db:28:6f:5a:1e:8e:
5d:2e:77:96:4f:34:48:2e:57:50:39:8d:22:68:e0:69:ac:3b:
26:13:88:6d:c6:bf:68:be:3c:42:bc:3f:de:00:07:4a:3c:bb:
da:09:86:2e:c8:7b:cc:75:72:72:7d:81:2a:59:1e:7c:7a:aa:
a3:b6:9e:70:15:9d:43:29:df:48:c2:64:f7:89:91:a7:1a:40:
49:40:de:ac:0c:c3:33:4a:f9:d0:f5:ff:c9:57:99:1d:59:51:
45:3e:b5:b9:c7:b3:ef:68:e4:08:80:1c:e1:3c:43:42:38:d1:
d1:da:d4:28:15:fb:9f:44:9b:0d:ed:af:31:6a:e5:5b:51:74:
0d:44:d9:bc:30:f2:ea:63:31:f1:2e:b3:3e:8b:28:d4:db:78:
0c:d1:16:14:da:ae:4f:13:5b:d1:74:ac:87:75:f1:52:0a:4e:
a4:b2:9d:af:e9:f7:08:56:2b:93:bf:ff:c1:f0:0b:45:49:eb:
a5:0c:91:e5
CSRのファイルを直接開いてみます。CAへの署名申請にはこちらの"-----BEGIN..."から"...END CERTIFICATE REQUEST-----"までの文字列を含む形でコピペします。
CSRファイルの中身
$ cat device.csr
-----BEGIN CERTIFICATE REQUEST-----
MIICvzCCAacCAQAwejELMAkGA1UEBhMCSlAxDjAMBgNVBAgMBVRva3lvMRMwEQYD
VQQHDApDaGl5b2RhLWt1MR8wHQYDVQQKDBZibG9nLWJldHdlZW4taHctc3cuY29t
MQwwCgYDVQQLDANJb1QxFzAVBgNVBAMMDmRldmljZS0wMDEtMDAxMIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3kCmAk8LTAKM60ZcRlXQsdbpygDLjc5e
AoCdlZQe8HsnrTYqeNCZlGUUCNG9Hm0mpISX1KXAFqqiO+wY7jfoWmHo1TifHuv5
6STf7WqDkCXcYzRR6FsDeanpVlBnUpEmMY44Gm3lDycjYxPi6TdO3IRloK7WnnFg
XSyJ684C61xpl6oCOo0G5Fbm0JkAKsGGZGVpSIciGG+7uKnwThuaNwS133VsKWgE
MmTsUy1U/n+7WE8VbWpS0nkKgqCZaPQKlMMqbXZ+MtMuPqE9iF7HZ+4DS1oP5MQe
dGlGtloTer5I6Ne1UTOz0EYGE5eBfpD/V8mEmK135me56GorhjpLYQIDAQABoAAw
DQYJKoZIhvcNAQELBQADggEBAKd5grE7csbm5EtD8Mpp/ynJs9LvLweVJBUUR4Mo
LDwu+KBMFQVkfKkms98VNzaCUzkcSLi8BFPK+WT30urvMHYbK9sob1oejl0ud5ZP
NEguV1A5jSJo4GmsOyYTiG3Gv2i+PEK8P94AB0o8u9oJhi7Ie8x1cnJ9gSpZHnx6
qqO2nnAVnUMp30jCZPeJkacaQElA3qwMwzNK+dD1/8lXmR1ZUUU+tbnHs+9o5AiA
HOE8Q0I40dHa1CgV+59Emw3trzFq5VtRdA1E2bww8upjMfEusz6LKNTbeAzRFhTa
rk8TW9F0rId18VIKTqSyna/p9whWK5O//8HwC0VJ66UMkeU=
-----END CERTIFICATE REQUEST-----
$
4.CAに署名手続きを行い署名する
MosquittoのプライベートCA申請ページに行きます。下記のリンクからでもいけます。
CSRファイルの中身をそのまま右側の欄にコピペしてSubmitを押します。
クライアント証明書”client.crt”がダウンロードされます。有効期間は90日間となります。
コンテナの共有フォルダに持ってきます。これで準備完了です。
$ ls
client.crt device_prv.key mosquitto.org.crt
3-7. サンプルの実行
コンテナのshellに入り、ホストPC側で作成した以下3つのファイルにアクセス出来ることを確認します。
- client.crt ←クライアント証明書
- device_prv.key ←クライアント秘密鍵
- mosquitto.org.crt ←RootCA証明書
gitクローンしたwolfMQTTのサンプルコードフォルダに入ります。
(docker)$ cd ~/
(docker)$ cd wolfMQTT/examples/mqttclient
(docker)$ ls
mqttclient mqttclient-mqttclient.o mqttclient.c mqttclient.h mqttclient.vcxproj
証明書関係の相対パスを以下とする
(docker)$ ls ../../../certs/
client.crt device_prv.key mosquitto.org.crt
以下の引数でサンプルを実行すると相互認証のPub/Subが実行されます。終了するときは”Ctrl+C”です。
(docker)$ ./mqttclient -t -htest.mosquitto.org -p8884 -A../../../certs/mosquitto.org.crt -K../../../certs/device_prv.key -c../../../certs/client.crt -nsh-goto/Topic001 -mHello_MQTT_byWolfSSL
MQTT Client: QoS 0, Use TLS 1 ←"TLS 1"はTLS有効
MQTT Net Init: Success (0)
MQTT Init: Success (0)
NetConnect: Host test.mosquitto.org, Port 8884, 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) ←先にSubして待機する。Topic=sh-goto/Topic001
Topic sh-goto/Topic001, Qos 0, Return Code 0
MQTT Publish: Topic sh-goto/Topic001, ID 2, Success (0) ←Pubする。Topic=sh-goto/Topic001
MQTT Waiting for message...
MQTT Message: Topic sh-goto/Topic001, Qos 0, Len 20 Topic=sh-goto/Topic001から20Byte受信
Payload (0 - 20) printing 20 bytes:
Hello_MQTT_byWolfSSL ←Subで受信した文字列
MQTT Message: Done
^CReceived SIGINT ←ここでCtrl+C
Network Error Callback: Error (Network) (error -8)
MQTT Exiting...
MQTT Unsubscribe: Success (0)
MQTT Disconnect: Success (0)
MQTT Socket Disconnect: Success (0)
(docker)$
引数は以下の通りです。注意点としてオプションとオプション引数の間にスペースをいれてはいけません。
- [-t] : TLS有効
- [-h] “-htest.mosquitto.org” : MQTTブローカURL or IP address
- [-p] “-p8884” : TCP Port8884←サーバ側のポート番号
- [-A] “-A../../../certs/mosquitto.org.crt” : RootCA証明書Path
- [-K] “-K../../../certs/device_prv.key” : クライアント秘密鍵Path
- [-c] “-c../../../certs/client.crt” : クライアント証明書Path
- [-n] “-nsh-goto/Topic001” : Pub/SubするTopic名
- [-m] “-mHello_MQTT_byWolfSSL” : Pubする送信文字列
helpは “-?”で見れます。
(docker)$ ./mqttclient -?
mqttclient:
-? Help, print this usage
-h <host> Host to connect to, default: test.mosquitto.org
-p <num> Port to connect on, default: Normal 1883, TLS 8883
-t Enable TLS
-A <file> Load CA (validate peer)
-K <key> Use private key (for TLS mutual auth)
-c <cert> Use certificate (for TLS mutual auth)
-S <str> Use Host Name Indication, blank defaults to host
-p <num> Port to connect on, default: 1883
-q <num> Qos Level 0-2, default: 0
-s Disable clean session connect flag
-k <num> Keep alive seconds, default: 60
-i <id> Client Id, default: WolfMQTTClient
-l Enable LWT (Last Will and Testament)
-u <str> Username
-w <str> Password
-m <str> Message, default: test
-n <str> Topic name, default: wolfMQTT/example/testTopic
-r Set Retain flag on publish message
-C <num> Command Timeout, default: 30000ms
-T Test mode
-f <file> Use file contents for publish
3-8. 偽のCA署名によるクライアント証明書でサンプル実行
おまけとして、クライアント証明書をホストPCのOpenSSLコマンドで作成した通称”オレオレCA”を使用して署名してみました。つまり偽物のCAということです。実行するとクライアント認証を通過しないのでエラーになるはずです。
(docker)$ ./mqttclient -t -htest.mosquitto.org -p8884 -A../../../certs/mosquitto.org.crt -K../../../certs/device_prv.key -c../../../certs/oreoreca/oreca_client.crt -nsh-goto/Topic001 -mHello_MQTT_byWolfSSL
MQTT Client: QoS 0, Use TLS 1
MQTT Net Init: Success (0)
MQTT Init: Success (0)
NetConnect: Host test.mosquitto.org, Port 8884, Timeout 5000 ms, Use TLS 1
MQTT TLS Setup (1)
MQTT Socket Connect: Success (0)
Network Error Callback: Error (Network) (error -8) ←接続エラー発生
MQTT Connect: Proto (v3.1.1), Error (Network) (-8)
MQTT Disconnect: Success (0)
MQTT Socket Disconnect: Success (0)
上記だとはっきりわからないですが、TCPハンドシェイク後のTLSハンドシェイクでエラーになっていると思われます。しっかり認証が機能しています。
3-9. サンプル実行時のパケットを見てみる(Wireshark)
せっかくインターネット越しに暗号化MQTT通信が出来る環境が揃ったので、パケットをキャプチャしてみます。
ホストPC 上でWiresharkを立ち上げ、キャプチャします。フィルタはMQTTブローカの公開テストサーバのIPアドレスで掛けることにします。
テストサーバのIPを調べます。DNS名前解決はnslookupコマンドで出来ます。
$ nslookup test.mosquitto.org
Server: 127.0.0.53
Address: 127.0.0.53#53
Non-authoritative answer:
Name: test.mosquitto.org
Address: 91.121.93.94
Name: test.mosquitto.org
Address: 2001:41d0:1:925e::1
フィルタは”ip.addr==91.121.93.94″でやってみます。
キャプチャ1:TLS暗号化なしMQTT(Port 1883)
$ ./mqttclient -htest.mosquitto.org -nsh-goto/Topic001 -mHello_MQTT_byWolfSSL
- Topic:”sh-goto/Topic001″
- message:”Hello_MQTT_byWolfSSL”
まずはTLS無しで通信しました。当然平文なのでキャプチャしたら全部丸見えです。橙色の枠で囲っているところで、PublishしたTopic名と送信したメッセージがすべて丸見えです。
キャプチャ2:MQTTS + 相互認証
$ ./mqttclient -t -htest.mosquitto.org -p8884 -A../../../certs/mosquitto.org.crt -K../../../certs/device_prv.key -c../../../certs/client.crt -nsh-goto/Topic001 -mHello_MQTT_byWolfSSL
TLSなしの時と同じTopicとMessegeですが、内容は暗号化されています。また、最新のTLSv1.3規格の通信になっています。
キャプチャ3:オレオレCAによる偽クライアント証明書の通信
$ ./mqttclient -t -htest.mosquitto.org -p8884 -A../../../certs/mosquitto.org.crt -K../../../certs/device_prv.key -c../../../certs/oreoreca/oreca_client.crt -nsh-goto/Topic001 -mHello_MQTT_byWolfSSL
oreca_client.crtはオレオレCAによる署名を行ったクライアント証明書です。
TLSハンドシェイクのServer Helloの後、1往復の暗号通信を行い、クライアントから2回サーバへ暗号送信をした後、サーバからTCPコネクションを切断されていました。
TLSプロトコルの詳細をまだ理解できていないので、ここでどんなやり取りを行ったのかわかりませんが、今後の課題としたいと思います。
4.【補足】TLSにおける認証・証明書とは
TLS通信では公開鍵暗号技術よってサーバ証明書・クライアント証明書をそれぞれ送り合い、CA証明書によって検証し、お互いなりすましでないことを認証します。暗号通信に詳しくない私はこのあたりの専門用語で四苦八苦しました。
今回各証明書は、Webで調べてなんとなく見よう見まねで用意してもできるのですが、扱いを間違えると不具合やセキュリティ事故につながるので、しっかり役割を理解しておきたいところです。
私が参考になったYoutube動画と補足説明の2つ述べて行きます。
4-1. TLSの認証・証明書に関する参考Youtube
TLS通信の認証を1から説明するとさすがに膨大になるので、理解の助けになったYoutube動画を紹介します。動画を見るだけでほとんど理解できると思います。
A.TLSの通信の全体流れとCA(認証局)の役割
補足:ルート証明書とはRootCA証明書のことです。(routeでは無いです)
B.民間CA機関にサーバ証明書の署名申請をする
補足:この動画はパブリックCAの一種である、民間のCAに有償で署名をしてもらう内容です。HTTPSサーバだと一般的に必要ですが、IoTの場合、クライアント証明書はIoT機器の数だけ必要になりますし、CA証明書をサーバに予め配置可能なので、必ずしもパブリックCAを使うとは限りません。例えば、AWS IoTはそのサービス内にAmazonRootCAを持っていますのでこの場合はプライベートCAを使うケースになります。
C.プライベートCAでサーバ証明書の署名をする
補足:テスト環境でローカルネット内のTLS通信する際に仮の証明書をサクッと用意したい、あるいは自組織内で運用する場合など、CAの信用は不要でとりあえずTLS通信を動作させたいケースはよくあります。この様なときはCAを自分で用意することが出来ます。Linuxなどが動くマシンでOpenSSLコマンドが打てればCAとすることが出来ます。
4-2. サーバ&クライアント証明書の検証とは
動画と内容が重なりますが別途補足説明します。
サーバ証明書とは
X.509仕様のフォーマットのファイルで、公開鍵と付加情報が入っています。TLS通信を始める最初にサーバからクライアントに証明書を渡し、クライアントがその証明書が本物であるかを確認します。基本的にサーバ認証はTLS通信で必須です。
クライアント証明書とは
サーバ証明書と同様にX.509仕様のフォーマットのファイルで公開鍵と付加情報が入っています。TLS通信を始める最初にクライアントからサーバに証明書を渡し、サーバがその証明書が本物であるかを確認します。HTTPSなどでは使われませんが、MQTTSを用いるIoT機器ではなりすましのリスクがあるので、基本的には必須です。正確には確認できなかったのですが、クライアント証明書をデバイス証明書と呼ぶところもあるようです。
証明書の検証の仕組み
基本的にOpenSSlコマンドで証明書自体を作成できるのですが、誰でも作れるorコピーできるので偽物でないことを証明できません。そこで第3者のCA(認証局)に”署名”と呼ばれる作業を証明書に実施します。
署名とは証明書の内容をハッシュ関数でハッシュ値化し、CAが保有する秘密鍵でハッシュ値を暗号化したデータを追記する作業です。これを理解するためには公開鍵とハッシュ関数の特徴を理解する必要があります。
公開鍵の特徴
- 公開鍵は秘密鍵と公開鍵のペアの文字列と暗号アルゴリズム
- 秘密鍵は決して外部に漏らしてはならない
- 暗号アルゴリズムはRSA-2048方式が主流
- データを公開鍵で暗号化するとペアの秘密鍵でのみ復号できる
- データを秘密鍵で暗号化するとペアの公開鍵でのみ復号できる
ハッシュ関数の特徴
- あるデータから別のデータを出力するアルゴリズム
- 出力データをハッシュ値と呼ぶ
- 入力データが少しでも異なると全く異なる出力をする
- 同一の出力値になる入力データを求めるのが非常に困難
- 出力値から入力値の逆算が非常に困難
- アルゴリズムはMD5やSHA-2(SHA256など)やSHA-3がある
SHA256で文字列のハッシュ値を計算
$ echo -n 'blog-between-hw-sw.com'|shasum -a 256
8ecdc7aa60b9bee6834ad4214be77f31e8d08f33003d3c282059bd92cf23f054
文字列の頭文字を'b'->'B'に変更すると全く異なるハッシュ値になる
$ echo -n 'Blog-between-hw-sw.com'|shasum -a 256
00782ff0cb54a6579a319db64c76ec44caa5b72b4d331273630b8166ffd17b7d
サーバ証明書の検証例
証明書にはCAが保有する秘密鍵で暗号化した証明書内容のハッシュ値が入っています。つまり、
- クライアント側で証明書内容をハッシュ関数で計算したハッシュ値
- クライアント側でCA証明書の公開鍵で署名を復号したハッシュ値
2つのハッシュ値が一致すると改ざんされていないことがわかります。これを”検証”といいます。CA証明書で復号した真のハッシュ値になるような偽のサーバ証明書を逆算して作成するのはハッシュ関数の特徴として事実上不可能と言えます。
しかし、悪意のある第3者は本物のサーバ証明書も本物のCA証明書も入手可能です。TLS通信できるデバイスを用意できれば偽クライアントとしてサーバ証明書を入手できますし、CA証明書はそもそも公開されています。
しかし、問題ありません。あくまでサーバ証明書が本物かをCA証明書で検証しただけなので、次の工程としてクライアントはサーバ証明書の中の公開鍵を用いて、通信データを暗号化してサーバに送信します。ここで偽のサーバは暗号データを復号出来ません。サーバ秘密鍵を入手出来ないからです。この時点で通信できなくなります。
クライアント証明書の検証はサーバとクライアントの逆の立場になるだけです。
4-3. パブリックCA とプライベートCAとは
Web上やYoutubeなどの情報では、TLS通信はHTTPS通信の例なので、サーバ認証まわりの話のみでクライアント認証の話は出てきません。それを補足する形で説明します。
CA(認証局)とは
CA:Certification Authorityとはサーバ&クライアント証明書に署名を行い、本物であることを認証する信用機関です。運転免許証を発行する警察庁交通課や印鑑証明を発行する行政機関のようなものです。
CAは自身の秘密鍵とX.509仕様のCA証明書(公開鍵)を保有していて、それを用いて証明書の署名手続きを行います。また、署名済証明書の失効管理も行っています。
CAはより信用度の高い別のCAの秘密鍵によって自分のCA証明書に署名する階層構造を取ることが出来ます。別のCAに署名してもらったCA証明書を中間CA証明書と言います。そして階層のトップにある最も信用度が高いCAをRootCAと呼び、自身の秘密鍵で署名したRootCA証明書(またはルート証明書)を保有しています。
サーバ証明書などを検証する際、中間CA証明書だった場合はまずそれを検証します。上位CA証明書によって検証し、最終的にRootCA証明書までたどり着いたらそのサーバ証明書の検証が完了できます。
パブリックCA とは
パブリックCA の場合は政府機関や民間企業が運営しています。信用度の度合い別に、無償から年間数十万程度の有償で署名を依頼できます。本ブログサーバのドメインもレンタルサーバサービスで無償のSSL/TLS化を依頼しています。
HTTPSではクライアントPCのブラウザからTLS通信でサーバ証明書の検証が出来るように、ブラウザに各パブリックCA証明書が予めインストールされています。そのため、Webサーバなどでは基本的にパブリックCA にサーバ証明書の署名を依頼します。
プライベートCAとは
プライベートCAは独自で運用するCAです。テスト用にTLSの疎通確認をローカルネット内で行う場合や、自組織内でIoT機器を運用する場合などが相当します。MQTTSなどを扱うIoTの通信では必ずしもパブリックCA を使う必要はありません。CA証明書をあらかじめサーバとクライアントの両方に直接配置することが可能だからです。
実際Linuxが動作するPCが1台あればOpenSSLコマンドでCAの秘密鍵・CA証明書の生成、サーバ&クライアント証明書の署名も行えるので、そのPCをプライベートCAにすることが出来ます。ただし、CAの秘密鍵は決して漏らしてはならないので厳重な管理が必要になります。
自社でプライベートCAを運用する場合、IoT機器の秘密鍵やクライアント証明書を製造時のどの段階で仕込むかの検討やクライアント証明書の発行・失効手続きの運用、特にIoT機器の破損、盗難などのセキュリティインシデント対応や数千台規模の多数の機器の証明書管理をどうするかなどIoTならではの難しさがあります。(ちなみに私はIoTの専門家ではないです)
このような証明書やCAの管理、製造時の証明書の扱いなどをサポートする外部サービスを利用するという手段もあります。例えば、AWS IoTは個人でもできそうなのでやってみたいですね。