Node-RED MCUで自己署名の証明書を使ってクラウドサーバ上のmosquittoにセキュアな通信を行う。

　Node-RED MCUで「MQTT in」や「MQTT out」ノードを使用する際にもSSL/TLSを使用したセキュアな通信を行う事が出来るようになりました。MQTT通信は「MQTT Broker」と呼ぶ仲介役を経由してPublish/Subscribeすることで通信をおこないますので、デバイスとMQTT Broker間の通信にSSL/TLSを使用します。MQTTでは通常TCP/IPの1883ポートで通信を行いますが、一般的にSSL/TLSを使用する場合は8883ポートを使用します。
 
事例にもよりますが、これまでマイコンデバイスから一旦エッジサーバを介してセ キュアな通信を行っていたケースでもマイコンデバイスから直接セキュアな通信を行う といったように、システムをシンプルにできる可能性があります。

<img width="780" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3012948/d3864427-d435-c3f1-4998-3180b9d54906.png">

 ・この記事は、実際の事例としてマイコンデバイスからSSL/TLSを使用してクラウド上で自身で運用しているmosquitto MQTTブローカーを経由した通信を行う事例を紹介するものです。以下の取り組みの概要の記事からの実例の紹介です。

https://qiita.com/mshioji/private/00dd35a14e20df84c0d4

これに使用した、[Node-RED MCU Editionについてはこちら](https://github.com/phoddie/node-red-mcu/blob/main/readme.md)をご覧ください。

https://github.com/phoddie/node-red-mcu/

また、この内容は@kitazakiさんの記事で紹介されている書籍、補足書とも連動しています。

https://qiita.com/kitazaki/items/0768eb7484ee3c27f6e1

:::note info
ここではコンピュータ上で操作するコマンド等はUbuntuを事例として紹介します。お試しの場合は、お使いのシステムに応じて読み替えてください。
:::

## 自己署名の証明書を使ってクラウドサーバ上のmosquittoにセキュアな通信をおこなう

　自身で運用しているクラウドサーバ(Ubuntu)にインストールしたmosquitto MQTT Brokerに対して、自己署名の証明書を使用してSSL/TLSでのセキュアな通信を試してみます。

　注意点として、自己署名証明書は一般に提供するサービスでの使用には適していません。サービスを一般に提供するようなプロダクション環境では、信頼できる証明書発行機関（CA）から証明書を取得することが推奨されます。自己署名証明書は自身で運用しているクローズドな環境、開発やテスト環境で使用する際には便利な場合があります。

#### 各種暗号鍵と証明書の関係
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3012948/76458823-c53c-2f8b-01bb-f02832424d8e.png)
クラウド上のmosquittoに自己署名の証明書を使った通信をする場合の設定

#### 必要な暗号鍵と証明書の作成
・Ubuntuの場合、ターミナルからOpenSSLを使用して上記の手順を進めていきます。
OpenSSLがインストールされていない場合は次のコマンドでインストールします。
```
$ sudo apt install openssl
```

・上記の図にある各種ファイルを保存するディレクトリを適当な名前で作成します。
　作成したディレクトリに移動して、この中に各種ファイルを作成していきます。
 ```
$ mkdir ~/certificates
$ cd ~/certificates
```

##### １）自己認証のCAを作成
　本来は公式な認証局に証明書をリクエストしますが、今回は自己認証なので自分だけの非公式な認証局を作成するイメージです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3012948/b1389a42-62a3-38b8-f490-b017560482ca.png)

まず、CA証明書に必要な暗号鍵「ca.key」を作成します。
これにはパスフレーズを設定しておきます。
```
$ openssl genrsa -des3 -out ca.key 2048
Enter PEM pass phrase:<my password>
Verifying - Enter PEM pass phrase:<my password>
```

続いて、先ほどの「ca.key」を使ってCA証明書「ca.crt」を作成します。
下記の例では有効期間が365日ですので、必要に応じて-daysの値を変更してください。
先ほど設定した「ca.key」のパスフレーズを聞かれるので入力します。
```
$ openssl req -new -x509 -days 365 -key ca.key -out ca.crt
Enter pass phrase for ca.key:<my password>
```

ターミナルでコマンドを実行するとCA証明書の項目の入力を求められますので順に入力していきます。下記の内容を参考に、ご自身で使う情報を入力してください。
```
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]:Osaka
Locality Name (eg, city) []:Sakai
Organization Name (eg, company) [Internet Widgits Pty Ltd]:OpenCreationLab
Organizational Unit Name (eg, section) []:admin
Common Name (e.g. server FQDN or YOUR name) []:mossgcp
Email Address []:<my email address>
```

これでディレクトリに「ca.key」と「ca.crt」ファイルが生成されます。

##### ２）サーバ用の暗号鍵「server.key」と、証明書署名要求「server.csr」を作成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3012948/16812a80-c14d-53e9-425e-d811b9330272.png)

サーバ証明書署名要求「server.csr」に必要な暗号鍵「server.key」を作成します。
こちらにはパスフレーズを設定しません。
```
$ openssl genrsa -out server.key 2048
```
作成した「server.key」を使ってサーバ証明書署名要求「server.csr」を作成します。
```
$ openssl req -new -out server.csr -key server.key
```
サーバ証明書の項目を順に入力していきます。下記の内容を参考に、ご自身のサーバの情報を入力してください。
・このとき、Common Nameが重要です。FQDN(Fully Qualified Domain Name)としてサーバを特定できるドメイン名があればそれを設定します。ドメインを取得していないサーバではこれまではIPアドレスを設定しても動作していましたが推奨されておらず、今後は無視されるのでこの後に対応を追加します。
```
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]:Osaka
Locality Name (eg, city) []:Sakai
Organization Name (eg, company) [Internet Widgits Pty Ltd]:OpenCreationLab
Organizational Unit Name (eg, section) []:mosscreation
Common Name (e.g. server FQDN or YOUR name) []:<my server FQDN or IP Address>
Email Address []:dekkirukana@gmail.com

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:<my password>
An optional company name []:ocl
```

IPアドレスでサーバにアクセスするために追加でファイルを作成します。これは下記のWarningのように「TLS ServerNameにIPアドレスを設定することはRFC 6066で認められていない。これは将来のバージョンでは無視される。」とあるためです。
「DeprecationWarning: Setting the TLS ServerName to an IP address is not permitted by RFC 6066. This will be ignored in a future version.」

解決策として、IPアドレスを証明書のサブジェクト代替名(subjectAltName; SAN) にセットすることでmosquittoではサーバの証明書の検証が可能になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3012948/92caa878-2c04-d7b9-b61c-d24771d823c0.png)
同じディレクトリに、エディタで「san.conf」を作成します。
```
$ nano san.conf
```
「san.conf」ファイルの内容は次のとおりです。サーバのIPアドレスを設定します。
```
subjectAltName = IP:<my IP Address>
```
設定ができたら「^o」で保存して「^x」でエディタを終了します。
これで、ディレクトリに「server.key」と「server.csr」ファイル、そして
「san.conf」ファイルが追加されました。

それでは、これらのファイルを使って自己認証CAでサーバ証明書を生成しましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3012948/dc8c4916-f727-785b-94a4-c59b11553087.png)

下記の例では有効期間が90日ですので、必要に応じて-daysの値を変更してください。
```
$ openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 90 -extfile san.conf
```
最初の手順で作成した「ca.key」のパスフレーズを聞かれますので、入力します。
```
Certificate request self-signature ok
subject=C = JP, ST = Osaka, L = Sakai, O = OpenCreationLab, OU = mosscreation, CN = <XXX.XXX.XXX.XXX>, emailAddress = <my email address>
Enter pass phrase for ca.key:
```

これでサーバー証明書「server.crt」ファイルが生成されました。

##### ３）クライアント用の暗号鍵「client.key」と、証明書署名要求「client.csr」を作成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3012948/6836dcc0-2c80-5d26-0a6d-54423620ffb9.png)

クライアント証明書署名要求「client.csr」に必要な暗号鍵「client.key」を作成します。こちらもパスフレーズは設定しません。
```
$ openssl genrsa -out client.key 2048
```

作成した「client.key」を使ってクライアント証明書署名要求「client.csr」を作成します。
```
$ openssl req -new -out client.csr -key client.key
```

クライアント証明書の項目を順に入力していきます。
```
Country Name (2 letter code) [AU]:JP
State or Province Name (full name) [Some-State]:Osaka
Locality Name (eg, city) []:Sakai
Organization Name (eg, company) [Internet Widgits Pty Ltd]:OpenCreationLab
Organizational Unit Name (eg, section) []:development
Common Name (e.g. server FQDN or YOUR name) []:OpenCreationLab
Email Address []:<my email address>

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:<my password>
An optional company name []:ocl
```

これで、ディレクトリに「client.key」と「client.csr」ファイルが追加されました。
これらのファイルを使ってクライアント用に自己認証CAで証明書を生成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3012948/c5d1cc6b-3e85-4825-94b1-1c19220a354e.png)

下記の例でも有効期間が90日ですので、必要に応じて-daysの値を変更してください。
```
$ openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 90
```

こちらも最初に作成した「ca.key」のパスフレーズを聞かれますので、入力します。
```
Certificate request self-signature ok
subject=C = JP, ST = Osaka, L = Sakai, O = OpenCreationLab, OU = development, CN = OpenCreationLab, emailAddress = <my email address>
Enter pass phrase for ca.key:
```

これでクライアント証明書「client.crt」ファイルが生成されました。

##### ４）ファイルの確認
　ここまでの作業で必要なファイルができていますので、サーバとクライアントに必要なものを設定して行きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3012948/de8bfb95-a777-7efb-34b0-e29a9568c754.png)

クラウドサーバ上のmosquittoの設定
クラウドサーバ側のmosquittoの設定に必要な３つのファイルをクラウドサーバに転送し、各フォルダに配置します。
「ca.crt」	→　/etc/mosquitto/ca_certificates/ca.crt
「server.crt」	→　/etc/mosquitto/certs/server.crt
「server.key」	→　/etc/mosquitto/certs/server.key

mosquittoの設定ファイルを編集します。編集前にバックアップを取ると安心です。
```
$ sudo nano /etc/mosquitto/conf.d/mosquitto.conf
```

設定ファイルを開いたら、「Default listener」の項目を探してポート設定を8883に変更します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3012948/f274ab02-f8a3-5a98-da39-d6ecb0cf8113.png)

次に、「Certificate based SSL/TLS support」の項目を探して先ほど配置した証明書等のファイルのパスを設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3012948/a151845b-6c3c-dc00-9e99-016786377368.png)

設定ができたら「^o」で保存して「^x」でエディタを終了します。

変更した設定を反映させるため、mosquittoを再起動します。
```
$ sudo systemctl restart mosquitto.service
```

*この時エラーが表示されたら設定ファイルの修正が正しくできていない可能性があります。設定ファイルを見直してください。

mosquittoが正常に稼働しているか確認します。
```
$ sudo systemctl status mosquitto.service
```
「Active: active(running)」が表示されていたら正常に動作しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3012948/b7fbdcb6-d126-09b8-677b-c876c65bae9b.png)

##### クライアント側のノードの設定
パブリッシャ、サブスクライバではそれぞれ「MQTT out」/「MQTT in」ノードを使用します。

##### デバイス側フロー
・送信側：デバイス側は「MQTT out」ノードを使ってNode-RED MCUでM5StackにMQTT パブリッシュのフローを用意します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3012948/0242fef3-ed37-78b0-c48f-14bdec538766.png)

 デバイスから送信するフローです。先の事例と同様に、シンプルにM5Stackの中央ボタン(pin:38)を押した時にmsg.payloadのメッセージを「MQTT out」ノードでPublishして動作を確認します。MQTTブローカーを置いているクラウド上のサーバに接続します。

「MQTT out」ノードの設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3012948/cff5a827-327c-8390-9605-19b2308868f3.png)

　トピックを設定し、“サーバ”のプルダウンメニューで「新規にmqtt-brokerを追加」を選択し、隣の鉛筆アイコンでmqtt-brokerの設定を開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3012948/dee0be0b-bcb1-7430-f6bb-8ca285a58dcc.png)

 ”サーバ”のフィールドに、クラウドサーバのIPアドレスを設定します。
”ポート”には”8883”を設定します。
”TLSを使用”のチェックボックスをマークして、”新規にtls-configを追加”をプルダウンメニューで選択し、隣の鉛筆アイコンを押してtls-configを開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3012948/bb5e1d43-656a-55f3-db5d-0367b5a89ec8.png)

証明書、秘密鍵、CA証明書の各項目にある「↑ファイル」の矢印アイコンを押し、先程の手順で作成したファイル、client.crt, client.key,ca.crtをそれぞれ選択します。
サーバ名のフィールドにもクラウドサーバのIPアドレスを設定します。

設定が完了したら順次更新ボタン/完了ボタンを押して戻り、デプロイした上でNode-RED MCUでデバイスに書き込みます。

##### PC側フロー
　デバイスからPublishしたデータをPC側でクラウドサーバのMQTT Brokerからサブスクライブして表示するフローです。こちらもシンプルに、「MQTT in」ノードでSubsccribeしたデータを「debug」ノードで表示します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3012948/c06ee808-adee-0807-2dc4-95d1baaa3a8c.png)

「MQTT in」ノードの設定
　”サーバ”のプルダウンメニューで、デバイス用のフローで作ったクラウドサーバ用のMQTT Broker設定を選択します。
”トピック”にはデバイスからPublishする「MQTT out」ノードに設定したトピックと同じものを設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3012948/5b60b7b1-3220-16a5-cb5b-93934810dfaa.png)

これでデプロイして動作状態にします。
デバイス側のセンターボタン（39ピン）を押すと、セットされたメッセージがクラウド上のMQTT Broker、mosquittoを経由してPC側に送り返されてデバッグウインドウに表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3012948/e68af724-d1df-af21-99d1-3f0032f20940.png)

これで自己署名の証明書でクラウドサーバ上のmosquittoにセキュアな通信を行う事ができました。

### 追記
:::note info
opensslのgenrsaコマンドでうまく動作しない場合は、下記のgenpkeyコマンドを使った例を試してみてください。
:::
・ca.keyの作成（パスフレーズ付き）
```
$ openssl genpkey -algorithm RSA -aes256 -out ca.key -pkeyopt rsa_keygen_bits:2048
```
・ca.crtの作成
```
$ openssl req -new -key ca.key -out ca.crt -x509 -days 365
```
・server.keyの作成
```
$ openssl genpkey -algorithm RSA -out server.key -pkeyopt rsa_keygen_bits:2048
```
・server.csrの作成
```
$ openssl req -new -key server.key -out server.csr
```
・san.confの作成
```
$ nano san.conf
(内容：subjectAltName = IP:34.125.153.12)
```
・server.crtの作成
```
$ openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 90 -extfile san.conf
```
・client.keyの作成
```
$ openssl genpkey -algorithm RSA -out client.key -pkeyopt rsa_keygen_bits:2048
```
・client.csrの作成
```
$ openssl req -new -key client.key -out client.csr
```
・client.crtの作成
```
$ openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 90
```



## 関連記事
お待たせしました、Node-RED アドベントカレンダー2023の進行に沿って、各記事へのリンクを更新しました。

https://qiita.com/mshioji/items/3b98aaaab492ff942961

https://qiita.com/mshioji/private/bbdd3be9deca253e8267