So far I thought ESP8266 can’t connect to AWS IoT (MQTT over TLS with client certificate), but it is NOT true any longer.
先入観なのか事実なのか分かりませんが、いままで Espressif ESP8266 はクライアント証明書を使った TLS (SSL) はできない、つまり AWS IoT(MQTT ブローカ)にアクセスできないと思い込んでいたのですが、事実と違ったのか、あるいは最近に開発されたのか、ちゃんと AWS IoT に繋がることが分かりましたので、メモしておきます。ネットを探すと既に試みられた方がたくさんあるのですが、なかなかまとまった情報、公式情報的なものが見つからないので、皆さん苦労されているようです。(私も苦労しました。)
最近は ESP32 のほうが主流だとは思うのですが、諸般の事情で ESP8266 でないと困ることもあるのですね(ごにょごにょ)。ESP32 Arduino のほうは Espressif 社の公式サポートがあるようですが、ESP8266 のほうは有志による設計ぽいので、贅沢は言えません。有志の方に感謝して利用させて頂きましょう。
先達の皆様の情報
まず最初に、参考 URL をまとめておきます。私の「備忘録(メモ)」よりもしっかりした情報です。
- MQTT Client for Arduino (PubSubClient) 作者の方の公式コメント
つまり、「PubSubClient で SSL を使うには、WiFiClientSecure が必要だよ」だそうです。簡にして要を得た、と言えばそれまでですが、優秀な技術者の方にありがちな回答で、はっきり言ってこれだけ読んでもコードは書けませんorz - BearSSL WiFi Classes
knolleary さんが引用していたのは実はちょっと古い情報なのか、現在は ESP8266 の ESP8266WiFi では axTLS は deprecated になっていて、BearSSL というのを使ったほうが良いようです。しかし、これを見ても分からん。 - debsahu/ESP-MQTT-AWS-IoT-Core
一番参考になったのは、この Debashish Sahu さんのコードです。ESP32 を使う場合、ESP8266 を使う場合で、それぞれのやり方が分かりやすくコーディングされています。 - SSL certificate verification: [Cert not yet valid] despite using RTC clock
実は、証明書を使った SSL で問題になるのは、証明書には有効期限がある、という問題です。つまり、証明書が有効かどうか確認するのに、現在の時刻が分からないとイカンのです。ここの記事はそれを議論していて、とりあえず、settimeofday() で時刻を設定したらうまくいったよ! という重要なお話です。 - Unable to Connect
実は上述の「有効期限の問題」に密接に絡んでいるのですが、TLS (SSL) のような暗号化通信は、デバッグが面倒なことで知られています。どうして繋がらないのか分からないヨ! という質問に対して、「ちゃんとデバッグオプションを有効にしてる?」というアドバイスでした。ESP8266 の Arduino では、デバッグオプションを有効にすることができるようになっていて、TLS や MQTT の通信で困って質問するときは、ちゃんとデバッグオプションを有効にしてね、というお話でした。実は私もこれを有効にしてから、TLS がうまく接続できない理由が「証明書の有効期限」による問題だと分かったのです。
ま、ネットで関連情報を探していると、皆が「時刻」の話をしているので、なんか臭いなあ、とは思っていたのですが。
まとめ
実際のコードは、また後日投稿したいと思いますが、PubSubClient のオリジナルサンプルコード mqtt_esp8266 に対する修正のポイントをまとめておきます。
- TLS (SSL) を使うときは、#include <WiFiClientSecure.h> を追加する。
- コンストラクタ WiFiClient でなく、WiFiClientSecure を使ってインスタンス(以下説明では espClient オブジェクトとする)を作る。
- PEM 形式の root 証明書, クライアント証明書, クライアント秘密鍵を用意し、3つのオブジェクトを生成しておく。
BearSSL::X509List cert(root_ca); // root 証明書
BearSSL::X509List client_crt(client_cert); // クライアント証明書
BearSSL::PrivateKey key(client_key); // クライアントの秘密鍵 - PubSubClient client(espClient) で、PubSubClient オブジェクト client を生成する。
- そんでもって、
espClient.setTrustAnchors(&cert);
espClient.setClientRSACert(&client_crt, &key);
client.setServer(サーバーアドレス, サーバーポート);
する。 - TLS で接続する前に、SNTP 等で時計を合わせておく。
- 最後に client.connect(client_id) する。必要ならば MQTT のユーザー名とパスワードも与える。
こんな感じです。今日はここまで。