ESP32 および M5Stack で DNS および mDNS の SSL server を作ってみた

SSL ( TLS )

mDNS ネームのサーバーおよびクライアントのスケッチ(プログラム)の入力

mDNS の場合は、DNS と同じというわけにはいかず、Arduino – ESP32 の別のライブラリを使います。
ただ、私の自作ライブラリはそのまま使えます。
これはちょっと自慢です。(^^)
因みに、先にも述べたように、Android は mDNS がサポートされていません
クライアントのブラウザは、iOS または最新版の Windows 10 の Google Chromeで使用してください( Bonjour がインストールしてあるもの )。
Windows 10 の場合は、ファイアウォールで UDP ポート 5353 を許可しておく必要があります。
私の場合は、Windows 10 の場合は Google Chrome はアクセスできますが、Edge や InternetExplorer ではアクセスできませんでした。

mDNS サーバー側スケッチ(プログラム)

では、mDNS のサーバー側のスケッチ(プログラム)は以下となります。
これは、STAモードで、外部ルーターに接続するコネクションです。
iOS のスマホならば、soft AP にしても良いと思いますが、ここでは STA モードで説明します。

【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)

/* WiFi STA mode OK!
 * iOS Safari and Windows 10 Google Chrome OK!
 * Can not use Android.
 * Use SPIFFS.
 */
#include <ESPmDNS.h> //Arduino-ESP32 default library
#include <ESP32_SSLserver.h> //mgo-tec library

const char* ssid = "xxxxxxxx"; //ご自分のルーターのSSIDに書き換えてください
const char* password = "xxxxxxxx"; //ご自分のルーターのパスワードに書き換えてください

//※ファイル名は半角8文字以内にすること
const char* server_cert_file_path = "/esp32lsv.pem"; //必ずスラッシュ必要
const char* server_prvt_file_path = "/esp32lsv.key"; //必ずスラッシュ必要

Esp32SSLServer server(443);
const char* mdns_name = "esp32"; //※esp32.local としないことに注意

//*****************************************
void setup(void)
{  
  Serial.begin(115200);
  delay(1000);
  if ( !SPIFFS.begin() ) {
    Serial.println("SPIFFS failed, or not present");
    return;
  }

  WiFi.begin( ssid, password );
  Serial.println("");

  while ( WiFi.status() != WL_CONNECTED ) {
      delay(500);
      Serial.print(".");
  }
  Serial.println("");
  Serial.print( "Connected to " );
  Serial.println(ssid);
  Serial.print( "IP address: " );

  IPAddress myIP = WiFi.localIP();
  Serial.println( myIP );

  if ( !MDNS.begin( mdns_name ) ) {
    Serial.println( "Error setting up MDNS responder!" );
    while(1) {
        delay(1000);
    }
  }
  Serial.println( "mDNS responder started" );

  server.beginSPIFFS( server_cert_file_path, server_prvt_file_path );
  delay(3000); 
  Serial.println( "TCP server started" );
  // Add service to MDNS-SD
  MDNS.addService( "https", "tcp", 443 );
}
//*****************************************
void loop(void)
{
  if( server.available() ){
    String receive_str = server.readStrClient();
    uint16_t len = receive_str.length();
    if( len > 0 ){
      Serial.printf( "receive_str.length = %d\r\n", len );
      Serial.println( receive_str );
      String send_str1, send_str2;
      if( receive_str.indexOf( "GET / HTTP/1.1" ) >= 0 ){
        send_str1 = "HTTP/1.1 200 OK\r\n";
        send_str1 += "Content-type:text/html\r\n";
        send_str1 += "Connection:close\r\n\r\n";

        send_str2 = "<!DOCTYPE html>\r\n<html>\r\n";
        send_str2 += "<head>\r\n";
        send_str2 += "<meta name='viewport' content='initial-scale=1.3'>\r\n";
        send_str2 += "</head>\r\n";
        send_str2 += "<body style='background:#000; color:#fff; font-size:1em;'>\r\n";
        send_str2 += "ESP32 and M5Stack<br>\r\n";
        send_str2 += "SSL server by mDNS<br>\r\n";
        send_str2 += "Hello World!\r\n";
        send_str2 += "</body>\r\n</html>\r\n\r\n";

        server.writeStrClient( send_str1 );
        server.writeStrClient( send_str2 );
        delay(10);
        server.stopClient();
      }else{
        send_str1 = "HTTP/1.1 404 Not Found\r\n";
        send_str1 += "Connection:close\r\n\r\n";
        server.writeStrClient( send_str1 );
        delay(10);
        server.stopClient();
      }
    }
  }
}

【解説】

●6行目:
Arduino – ESP32 標準の mDNS ライブラリのインクルードです。

●7行目:
私の自作ライブラリのインクルードです。

●17行目:
ここに、mDNS サーバーネームを入力します。
ここで注意!!
mDNS サーバーネームは、***.local でしたね。
でも、Arduino – ESP32 の mDNSライブラリでは、” .local “は不要です
44行目の begin 関数で自動的に ” .local “ が付加されているようです。
ここはハマるポイントですね。

●52行:
ここで、サーバー証明書と秘密鍵を SPIFFS から読み込んで、SSL 通信を待ち受けます。

●56行:
mDNS ライブラリのサンプルスケッチをそのまま使っています。
おそらく、SSL通信ポートをセットしているものと思われます。
これ以降、UDP 5353 ポートで mDNS ネームからのアクセスを待ち受ける状態になります。
UDPポートで mDNS ネームでクライアントからアクセスがあり次第、このデバイスの IP アドレスに変換して、443ポートに TCP/IP プロトコルでデータが渡されるのだと思われます。
そうすると、61行目の available 関数で検知できるという流れだと思います。
以降は、先に述べた DNS のサーバー側ソースコードと同じです。

残念ながら、mDNS の SSL クライアントは作成できなかった…

今回は、残念ながら、Arduino – ESP32 のライブラリで mDNS の SSL クライアント用のスケッチ(プログラム)は作成できませんでした。

いつか、ものすごい暇になったらチャレンジしたいと思います。
(しないかも知れません…)

ESP32 同士、および M5Stack 同士でローカルエリアネットワーク ( LAN )で SSL 通信したい場合は、soft AP モードでDNS を使って下さい。

mDNS では、クライアント側は iOS safari 、および Windows 10 Google Chrome で通信できると思います。

コンパイル書き込み実行

では、コンパイルする前に、先ほどと同様、手持ちのデバイスをどの DNS ネームにするか割り振って決めておきます。
先ほどと同じ図ですが、例として、下図のような感じです。

それぞれ先ほど説明したように、SPIFFS に証明書のアップロードは事前に済ませておきます。
ここでは、m5stack という DNS サーバーを作っているので、それ用のサーバー証明書も発行して、アップロードしてあります。

そして、 Arduino IDE でスケッチをコンパイル書き込み実行させます。
Arduino IDE の「ツール」→「Core Debug Level」は
Verbose
を選択してください。
そうすると、アクセス中にどこでエラーが起きたか分かりやすくなります。
余分なログを見たくない場合は、「なし」にしてください。

シリアルモニタを 115200 bps で起動し、実行させてみてください。
DNS サーバーの場合は、soft AP モードなので、外部ルーターを介しません。
mDNS サーバーの場合は、外部ルーターを使いますので、電源を入れておいてください。
DNS および mDNS サーバーとも下図のように表示されてクライアントからのデータ待ち状態になれば OK です。

次に、ESP32 および M5Stack のクライアント側のスケッチをコンパイル書き込みします。
ターゲットの SSID や パスワードを間違えないようにしてください。
そして、 DNS ネームも間違えていないか、ルート CA とターゲットデバイスのサーバー証明書はペアになっているものを使っているかを確認してからコンパイルします。

特に問題が無ければ、コネクション成功して、シリアルモニタには下図のように HTML が表示されれば、OKです。

あとは、先に紹介した動画のように動作すれば OK です。
スマホのブラウザでもアクセスしてみて下さい。
ブラウザの URL 欄に鍵マークが表示されていれば成功です。

もし、鍵マークが表示されず、警告が出る場合は、大抵は証明書がペアでないか、サーバーネームと CN 名が間違えている場合が多いです。

その他、SPIFFS のファイル名を8文字以上にしてしまっていることも考えられます。
私は何度もやらかしました。

また、Google Chrome でアクセスする場合は、特別な挙動があります
次で説明します。

Google Chrome でアクセスする場合の注意点

ブラウザの Google Chrome から DNS SSL サーバーに接続する場合には注意点があります。
サーバーのシリアルモニタの振る舞いを見ていれば何となく分かると思うのですが、Google Chrome の場合は、初回コネクションの場合、アクセスして HTML データを受け取ったら、すぐに再度アクセスしてきて、Favicon をリクエストしてきます。
サーバー側が 404 エラーを返しても、またそのすぐ後に更にアクセスしてきます。
そして、サーバー側のシリアルモニタでは、下図のようなところで Wait 状態になり、動かない場合があります。

Android の場合は、数十秒でクライアント側がタイムアウトしてきますが、Windows 10 の Google Chrome からアクセスすると、延々と Wait 状態になります。
この状態で、同じデバイスのブラウザから再接続すればすぐにつながります。
おそらく、Google Chrome はブラウザからのリクエストに素早く対応できるようにするためにそうなっていると思われます。
ですが、この状態のまま別のデバイスからアクセスすると、フリーズします。

また、この状態で、スマホの WiFi を切ったり、アクセスポイントを変えたりすると、フリーズしたままになってしまいます。
これの原因はまだよく分かりません。
フリーズすることを避けるためには、用が済んだらブラウザを閉じると、ちゃんと SSL_free になってくれます。

iOS の Safari からアクセスするとそんなことはありません。
初回接続しても、勝手に再接続しません。
電子工作的には Safari の挙動はシンプルで有り難いですね。
ブラウザによって挙動が様々なので、シリアルモニタをみていると、コネクションの良い勉強になるかも知れません。

編集後記

いかがでしょうか。

この記事は私の備忘録的な要素も入っていて、途中であまりにも収拾がつかなくなってしまって嫌になりました。
手順が多すぎて、ダラダラと小難しいことばかりです。
逆にプロの方々からみれば、当たり前のことばかりかも知れません。
いずれにしても、SSL サーバーを作るということは、とっても地味で目立たない作業で、達成した喜びも直ぐ冷めてしまう、空しい作業だなぁと、つくづく思いました。

「もう絶対こんなこと2度とやらん!!!」

と思いました。

ただ、今回の事で、SSL 通信と証明書のやり取り手順や、SSL 通信の弱点も知ることが出来、IoT 機器を自作した場合のセキュリティについて、多くを学ぶことができました。
それだけでもヨシとしよう!!!

ということで、今回はここまでです。
疲労激激困ぱいです。
ではまた・・・。

 

コメント

  1. 匿名 より:

    わたしもAndroidでmDNSしたかったのですが、Android9になってもmDNSに対応してくれなかったので、自分でアプリを作ってみました。
    https://qiita.com/maccadoo/items/48ace84f8aca030a12f1
    https://play.google.com/store/apps/details?id=com.dokoden.dotlocalfinder&hl=ja
    デザインはデフォルトのまま、なんもいじっていないのはご容赦を

    • mgo-tec mgo-tec より:

      匿名さん

      コメント投稿ありがとうございます。

      これは良いかも知れませんね。
      私はまだ Android8 なのですが、Android9になってもmDNS対応していないんですね。
      それは残念です。
      でも、このアプリがあればすべてmDNSで統一できそうで、すばらしいですね。
      今は忙しくて、なかなか試せませんが、時間が空いた時に試してみようと思います。
      ありがとうございました。
      m(_ _)m

タイトルとURLをコピーしました