Mosquittoでクライアント認証をやろうとして詰まったところ

# 要約
* Mosquittoライブラリを使ってローカルにあるMQTTブローカに接続したい
* 以下の記事を参考にルートCA証明書とサーバ証明書・鍵を作った

https://qiita.com/Nenshu_agetai/items/b000cd33027ac17dea9f#tls%E3%81%AE%E5%AF%BE%E5%BF%9C-openssl-%E3%82%92%E7%94%A8%E3%81%84%E3%81%9F%E8%A8%BC%E6%98%8E%E6%9B%B8%E4%BD%9C%E6%88%90

* また以下の記事を参考にクライアント認証をしようとしたが、うまくいかなかった

https://qiita.com/n-yamanaka/items/73e4d3022186732b4e88

* 最終的に```use_identity_as_username true```をmosquitto.confに追加する必要があることがわかった

# 証明書の作成
## CA証明書
テスト目的なので自己署名
生成するときにいろいろ聞かれるが、CNはクライアント証明書と違う値にする必要がある
* 今回は空白にした
```
$ openssl req -new -x509 -days 365 -extensions v3_ca -keyout ca.key -out ca.crt
```

## サーバ証明書
```
$ openssl genrsa -out server.key 2048
$ openssl req -out server.csr -key server.key -new
$ openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365
```

## クライアント証明書
前述のとおりCNはCA証明書と違う値にする必要がある
* 今回はocalhostにした
```
$ openssl genrsa -out client.key 2048
$ openssl req -out client.csr -key client.key -new
$ openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 365
```

## 証明書の配置
CA証明書を/etc/mosquitto/ca_certificatesにコピー
```
$ ls -l
total 12
-rw-r--r-- 1 root root 1245 Apr  6 08:41 ca.crt
-rw------- 1 root root 1854 Apr  6 08:41 ca.key
-rw-r--r-- 1 root root   73 Oct  1  2023 README
```

サーバ証明書と鍵を/etc/mosquitto/certsにコピー
その際オーナーをchownでmosquittoに変更
```
$ ls -l
total 12
-rw-r--r-- 1 root      root       130 Oct  1  2023 README
-rw-r--r-- 1 mosquitto mosquitto 1151 Apr  6 08:42 server.crt
-rw------- 1 mosquitto mosquitto 1704 Apr  6 08:42 server.key
```

クライアント証明書はクライアントアプリケーションと同じディレクトリに配置

# mosquitto.confの設定
TLSを使う場合ポートは8883に設定
cafile, certfile, keyfileにそれぞれCA証明書、サーバ証明書、サーバ鍵のパスを設定
require_certificateをtrueにすることでクラインと認証を有効にする

```
listener 8883
cafile   /etc/mosquitto/ca_certificates/ca.crt
certfile /etc/mosquitto/certs/server.crt
keyfile  /etc/mosquitto/certs/server.key
require_certificate true
```


# クライアントアプリケーションの実装
mosquitto_connect()の前にmosquitto_tls_set()を呼ぶ
* CA証明書のパスを指定していればcapathはNULLでもいい
* certfileとkeyfileにクライアント証明書を指定
* 今回クライアント鍵にパスワードを設定していないのでpw_callbackはNULLにした

mosquitto_tls_insecure_setをtrueにするとサーバ証明書のホストを確認する
* 自己署名CAなのでtrueにしておかないとcertificate verify failedになる


```cpp
//省略しているが、各関数呼び出しのあとにエラーチェックしている
mosquitto_lib_init();
m_mosq = mosquitto_new("iot_device_hub", true, NULL);
mosquitto_tls_set(
    m_mosq, 
    "/etc/mosquitto/ca_certificates/ca.crt", 
    NULL, 
    "client.crt",  
    "client.key",
    NULL);

mosquitto_tls_insecure_set(m_mosq,true);
mosquitto_connect(m_mosq, "127.0.0.1", 8883, 60);
mosquitto_loop_start(m_mosq);
//この後にpublishなどを実行している
```

# テスト結果
各関数を呼び出したときにエラーは返ってきていなかったが、すぐにDisconnectされてしまう
/var/log/mosquitto/mosquitto.logを見ると以下のような出力が出ていた
```
1743898855: New connection from 127.0.0.1:46826 on port 8883.
1743898855: Sending CONNACK to 127.0.0.1 (0, 5)
1743898855: Client <unknown> disconnected, not authorised.
```
ログを見るとunknownユーザが接続しようとして認証に失敗しているように見える

# 解決方法
mosquitto.confに```use_identity_as_username```を設定したら接続後Publishまですることができた

```
1743900176: New connection from 127.0.0.1:55378 on port 8883.
1743900176: New client connected from 127.0.0.1:55378 as iot_device_hub (p2, c1, k60, u'localhost').
1743900176: No will message specified.
1743900176: Sending CONNACK to iot_device_hub (0, 0)
1743900177: Received PUBLISH from iot_device_hub (d0, q0, r0, m0, 'iot_device_hub/sensor_data/bed_room/temperature', ... (18 bytes))
```

* copilotにこのログを見せたらuse_identity_as_usernameを設定するといいよと教えてくれて、確かにmanページにもそんなことが書いてあった

> If require_certificate is true, the client must provide a valid certificate in order to connect successfully. In this case, the second and third options, use_identity_as_username and use_subject_as_username, become relevant. If set to true, use_identity_as_username causes the Common Name (CN) from the client certificate to be used instead of the MQTT username for access control purposes. 

https://mosquitto.org/man/mosquitto-conf-5.html

* ブログ記事は便利だがちゃんとマニュアルもしっかり読まないといけないと反省
* ただAPIドキュメント(https://mosquitto.org/api/files/mosquitto-h.html#mosquitto_tls_set) には書いていて欲しい気も

最終的なmosquitto.confの設定は以下の通り

```
listener 8883
allow_anonymous false
use_identity_as_username true
cafile   /etc/mosquitto/ca_certificates/ca.crt
certfile /etc/mosquitto/certs/server.crt
keyfile  /etc/mosquitto/certs/server.key
require_certificate true
```