ESP8266(Arduino環境)で AWS IoT(MQTT over TLS)にアクセスする

投稿者: | 2019/07/08

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 のユーザー名とパスワードも与える。

こんな感じです。今日はここまで。