Arduino – ESP32 で SSLサーバーを構築し、セキュアな Wi-Fi アクセスポイントセレクターを作ってみました

ESP32 ( ESP-WROOM-32 )

こんばんは。

年度初めでいろいろバタついていて、記事の更新が久々です。

今回は、なんと、Arduino – ESP32 で SSL サーバーを構築して、それを使ったセキュアなスマホブラウザ Wi-Fi アクセスポイントセレクターを作ってみました。

以前、こちらの記事でもスマホブラウザアクセスポイントセレクターは作ったことがあります。

ただ、これではパスワードを送信する時に、悪意ある者に傍受されてしまうと、パスワードが丸見え状態でした。

そこで、今回は ESP32 の soft AP モードサーバーを 「信頼ある」SSL サーバーにして、ブラウザ側からのパスワード送信を暗号化してみました。

スポンサーリンク


今回の最大のキモ(肝)は、前回の記事から更に進化して、Arduino core for the ESP32 で SSL サーバーを構築できたことです。
Arduino – ESP32 で OpenSSL サーバーを作った例はネット上に殆どありません。
自分自身でいろいろ試行錯誤してみた結果、ESP-IDF のサンプルスケッチをちょっと変えるだけで Arduino IDE で流用できるようになったのです。
元々、Arduino core for the ESP32 は ESP-IDF で作られているので、当たり前と言えば当たり前ですが・・・。

その SSL サーバーが使えれば、スマホやPCなどのブラウザとの Wi-Fi 通信で、パスワードなどを暗号化通信が可能になります。

私みたいなアマチュアでこんなことができるようになると、我ながら「やったぜ!」っていう感じでした。
これで、公共(公衆)Wi-Fi を利用する時もかなり安心できると思います。
(もちろん、100%安全とは言えませんが・・・)

そして、今回のもう一つのポイントとしては、セレクトしたアクセスポイントを SPIFFS フラッシュに保存し、次回 ESP32 を起動したときには自動でそのアクセスポイントに接続するようにしたことです。
残念ながら今回のバージョンでは1つのアクセスポイントしか保存しませんが、今後バージョンアップで複数のアクセスポイントに対応していこうとは考えています。

そもそも、これを作ろうと思ったのは、コメント投稿でそういう要望をいただいたことが切っ掛けですが、自分自身も外出先のWi-Fiアクセスポイントをセキュアな環境で使いたいという欲求も湧いてきたからです。

ということで、早速以下の動画をご覧ください。

今回から以下のHOZAN の 導電性手袋を使用してみました。
自分の手をズームアップすると見苦しかったので・・・。
(※以下のリンクはSサイズです。この他にMサイズとLサイズがあります)


この導電性手袋は静電気対策にもなり、電子工作にはもってこいです。
薄手のために小さいパーツも掴めてとてもイイ感じです。

スマホは Android 8.0 で、ブラウザは最新版 Google Chrome です。

いかがでしょうか?
ブラウザの URL 欄に、ちゃんと soft AP モードの IP アドレスで緑色の鍵マークが出ていますね。
「信頼ある」SSLサーバーと認識されています。
これが出ていればパスワード入力も安心です。

これのポイントとしては、最初に暗号化されていない普通の HTTP 通信で soft AP サーバーに接続して、自動的にSSL サーバーへリダイレクトしているところです。
この方法は、Twitter で けり さんから教えていただきました。
けり さん、ありがとうございます。
m(_ _)m

こうすることによって、「https:// 」という面倒な文字を入力する必要も無くなりますし、mDNSのスケッチが使えたりしますしね。
要するにパスワードをやり取りするところだけ SSL 通信であれば良いのです。

ただ、これで難しかったのは、soft AP モードと STA モードで HTTP サーバーポート番号や、clientハンドルの引き継ぎに苦労しました。
結局のところ、soft AP モードとSTAモードを切り替えても、サーバーポート番号80 や、clientハンドルはそのままグローバル変数として定義しておくしかないという結論になりました。
今の私の頭ではこれしか思いつきませんでした。

今回は Wi-Fi セレクター以外は通常の HTTP 通信ですが、これを https の SSL 通信に統一してもサーバーのポート番号はグローバル定義にしないとダメでした。

また、別件ですが、Wi-Fi Scan ボタンや、Disconnect、Reconnect ボタンを追加してみました。
これ、外出先ではかなり使えるのではないかと思われます。

アクセスポイントに接続できた後は、STAモードの IP アドレスで ESP32 に接続し、通常の HTTP 通信でWEB ページを表示させています。

こういうことが自分自身で作ることができる時代になってきたんですね。
ESP-IDF や Arduino core for the ESP32 、および OpenSSL の開発チームの皆さんのおかげですね。
感謝せずにはいられません。

ということで、前置きが長くなりましたが、これの使い方を説明したいと思います。
(※以下、パソコンはWindows 10 、スマホは Android 8.0 で説明します)

なお、何度も言っておりますが、私はアマチュアの素人です。
プログラムも素人コードですので、これを使ったことによる不具合やトラブルには一切責任を負えません。無保証です。

ただ、誤りやご意見等がありましたらコメント等でご連絡いただけると助かります。

最近では、DNS および mDNS ネームアドレスを使った ESP32 および M5Stack の SSL サーバーもできちゃいました。。
以下の記事も合わせて参照してください。
ESP32 および M5Stack で DNS および mDNS の SSL server を作ってみた
(2018/11/15)

 

Wi-Fiルーターについて

最近、自分のスマホのセキュリティアプリで、Wi-Fiアクセスポイントの脆弱性を警告するメッセージが出るようになりました。
アプリがちゃんと検証してくれているのか疑問だったのですが、Amazon のポイントも溜まっていたので、Wi-Fiルーターを新たに購入してみることにしました。
ただ、2017年後半に発覚した、WPA2 脆弱性 ( KRACKs )対策済みのものに絞って探してみました。

私の場合はルーター自体を持ち運びしたいということも有り、とてもコンパクトな以下のホテルルーターを購入してみました。

BUFFALO WMR—433W (現在流通しているものは WMR-433W2 です。)

あまり細かなセキュリティ設定は出来ませんが、最新版ファームウェアをアップデートすると、KRACKs 対策済みになるとのことで購入を決めました。
下図をみると分かる通り、ESP32-DevKitC と比べても、超小さいですね。

有線LAN でインターネットと接続すれば、簡単にインターネットWi-Fi アクセスポイントになります。
また、既存のルーターにブリッジモード接続すれば、有線ルーターがWi-Fiルーターに早変わりします。
5GHz帯にも対応していて、とても手軽で便利です。

ファイアウォールなどの高級なセキュリティ設定はできませんが、電子工作用途ならばこれで十分です。

私の場合、これに変えたらスマホのセキュリティアプリの警告が無くなりました。

なお、その他の本格的なルーターを使う場合、事前に ESP32 が弾かれないようにファイアウォール設定やMACアドレスフィルタリング等の設定を済ませておいてください。
ESP-WROOM-32 ( ESP32 )の Wi-Fi MACアドレスを調べる方法は、以下の記事を参照してください。

ESP-WROOM-32 ( ESP32 ) チップ・メモリ・MACアドレス情報取得方法

使用するもの

ESP-WROOM-32 ( ESP32 )開発ボード

Espressif Systems社製マイコンチップ ESP32 を使った、2.4GHz 帯 技適取得済みの Wi-Fi マイコンボード、ESP-WROOM-32 を使いやすくした開発ボードが必要です。
お薦めはUSB機器の電源保護機能が充実したスイッチサイエンス製ESPr Developer 32 です。

ESPr Developer 32
スイッチサイエンス(Switch Science)

その他、Espressif Systems 社純正のマイコンボード、ESP32-DevKitC でも良いです。

その他、パソコン、スマホ等

事前に Arduino core for the ESP32 をインストールしておく

Arduino IDE は 1.8.5 で動作確認しております。

以下の記事を参照して、事前に Arduino core for the ESP32 をインストールしておいてください。

Arduino core for the ESP32 のインストール方法

Arduino IDE に SPIFFS アップローダープラグインを予めインストールしておく

ESP-WROOM-32 モジュールに内蔵してある、SPIFFSフラッシュにファイルをアップロードするためのプラグインを、Arduino IDE にインストールしておきます。
その方法は以下の記事を参照してインストールしておいてください。

ESP-WROOM-32 ( ESP32 ) SPIFFS アップローダープラグインの使い方

OpenSSL を使って、IPアドレス=192.168.4.1 用のサーバー証明書と秘密鍵を作成しておく

OpenSSL を使って、ESP-WROOM-32 ( ESP32 )の soft AP モードのデフォルトIP アドレス、
192.168.4.1
でサーバー証明書と秘密鍵を作成しておきます。

因みに、soft AP モードとは、ESP-WROOM-32 ( ESP32 )自身が Wi-Fi ルーターになるモードのことです。
この場合、デフォルト設定で IP アドレスが 192.168.4.1 となります。
そのアドレスでサーバーを構築するので、その証明書を発行しておくわけです。

ファイル名は以下のようにしておいてください。

【サーバー証明書】
※crt 形式ではなく、必ずpem形式に変換しておいてください

esp32softap_server.pem

【サーバー秘密鍵】

esp32softap_server.key

また、ブラウザのURL欄に鍵マークを表示させ、「信頼ある」サーバーにする為に、 ルート認証局 ( CA )証明書の環境設定用cfg ファイルの[ alt_names ] 項目に、192.168.4.1 という IPアドレスを登録しておきます。
そのルートCA証明書を事前にスマホにインストールしておいてください。

OpenSSL によるサーバー証明書等の作成方法や、スマホへのルートCA証明書のインストール方法は以下の記事を参照してください。
初めて OpenSSL を使う方にとってはかなり敷居が高いと思いますが、一回作成できると後は難なくポンポンと作成できるようになります。

●SSL サーバーを自作するための自分的予備知識

●SSL Server 自作するための OpenSSL 使用方法

●ESP32 ( ESP-WROOM-32 ) で信頼ある SSL サーバーを自作する ( ESP-IDF編 )

Arduino – ESP32 用の自作ライブラリをインストール

ESP-IDF のサンプルコード、openssl_server_example_main.c をArduino 用に私が改変し、Arduino – ESP32 用ライブラリにしてみました。
GitHub の以下のサイトに置いておきますで、ZIPファイルをダウンロードして、Arduino IDE にインストールしてください。

https://github.com/mgo-tec/ESP32_WiFi_AP_SSL_Selector

ZIPファイルのままArduino IDE にインストールする方法は以下の記事を参照してください。

GitHubにある ZIP形式ライブラリ のインストール方法 ( Arduino IDE )

サンプルスケッチをご自分の環境に合わせて修正し、別名で保存しておく

自作ライブラリをインストールしたら、Arduino IDE の「ファイル」メニューの「スケッチ例」で、以下のスケッチを開きます。

ESP32_WiFi_AP_SSL_Selector_sample

以下のようなスケッチになります。

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

#include <WiFi.h>
#include "ESP32_WiFi_AP_SSL_Selector.h"

const char *ap_ssid = "xxxxxxxx"; //ESP32 softAP SSID
const char *ap_pass = "xxxxxxxx"; //ESP32 softAP password

IPAddress local_IP(192, 168, 13, 10); //your ESP32 STA mode Static Local IP
IPAddress gateway(192, 168, 13, 1); //your Wi-Fi router default gateway
IPAddress subnet(255, 255, 255, 0);
IPAddress primaryDNS(8, 8, 8, 8); //optional
IPAddress secondaryDNS(8, 8, 4, 4); //optional

ESP32_WiFi_AP_SSL_Selector EWASS;

const uint16_t https_port = 443;
uint16_t ssl_recv_buf_len = 1024;

const char* softAP_cert_filePath = "/cert/esp32softap_server.pem";
const char* softAP_prvt_filePath = "/cert/esp32softap_server.key";

const char* AP_config_file = "/APconfig/APconfig.txt"; //ssid, password save file

char spiffs_read_ssid[64] = {};
char spiffs_read_pass[64] = {};

WiFiClient __client1;
WiFiServer __server1(80);

boolean softAP_server_status = true;

//**************************************
void setup() {
  Serial.begin(115200);
  delay(500);

  if (!SPIFFS.begin()) {
    Serial.println("SPIFFS failed, or not present");
    return;
  }

  EWASS.SPIFFS_readFile(AP_config_file, spiffs_read_ssid, spiffs_read_pass);
  Serial.print("SPIFFS read ssid = "); Serial.println(spiffs_read_ssid);
  Serial.print("SPIFFS read pass = "); Serial.println(spiffs_read_pass);

  WiFi.mode(WIFI_AP_STA);
  WiFi.softAP(ap_ssid, ap_pass);
  delay(100);

  Serial.println("Setup done");

  IPAddress myIP = WiFi.softAPIP();
  Serial.println(myIP);
  delay(1000);

  EWASS.Init_StaticIP(https_port, ssl_recv_buf_len, local_IP, gateway, subnet, primaryDNS, secondaryDNS, spiffs_read_ssid, spiffs_read_pass);
  //EWASS.Init(https_port, ssl_recv_buf_len, spiffs_read_ssid, spiffs_read_pass); //Not Static IP

  EWASS.WiFi_STA_Connect(spiffs_read_ssid, spiffs_read_pass, 20000);
  delay(1000);


  Serial.println("--------------- SSL init");
  EWASS.openssl_init(softAP_cert_filePath, softAP_prvt_filePath);
}
//**********************************************
void loop() {
  EWASS.wifi_scan(20000); //20秒以上にすること
  if( softAP_server_status ){
    EWASS.softAP_http_server();
  }else{
    http_sta_server_test();
  }
  if( EWASS.SSL_softAP_WiFi_Selector(AP_config_file, spiffs_read_ssid, spiffs_read_pass) == 2){
    softAP_server_status = false;
    Serial.println("------------ softAP--->STA change!");
  }
}
//*********************************************
void http_sta_server_test(){
  __client1 = __server1.available();

  if (__client1) {
    String html_str = "<!DOCTYPE html>\r\n<html>\r\n";
          html_str += "<head>\r\n";
          html_str += "<meta name='viewport' content='initial-scale=1.3'>\r\n";
          html_str += "</head>\r\n";
          html_str += "<body style='background:#DDF; color:#00F; font-size:1em;'>\r\n";
          html_str += "My ESP32 STA mode HTTP server<br>\r\n";
          html_str += "<p style='font-size:2em'>Hello! World!</p>\r\n";
          html_str += EWASS.HTML_Submit_Button("exit_sta", "#000", "#DDD", "", "EXIT STA");
          html_str += "</body></html>\r\n\r\n";
    Serial.println(F("my_sta_HTTP New Client."));
    String currentLine = "";

    uint32_t LastTime = millis();
    while (__client1.connected()) {
      if( (millis()-LastTime) > 30000 ){ //timeout setting
        Serial.println("-------------__client1 timeout");
        break;
      }
      if (__client1.available()) {
        currentLine = __client1.readStringUntil('\n');
        if(currentLine.indexOf("\r") == 0) break;
        Serial.println(currentLine);
        if(currentLine.indexOf("GET / HTTP/1.1") >= 0){
          Serial.println(F("------------ my_sta_HTTP request received ---------------"));    
          __client1.println( EWASS.HTML_Res_Head() );
          __client1.println( html_str );
          __client1.println();
          currentLine = "";
          break;
        }else if(currentLine.indexOf("GET /?exit_sta=") >= 0){
          Serial.println(F("------------ request received [GET /?exit_sta=]---------------"));    
          softAP_server_status = true;
          __client1.println( EWASS.HTML_Http_Body("", "EXIT STA mode") );
          break;
        }else if(currentLine.indexOf("GET /favicon") >= 0){
          __client1.print("HTTP/1.1 404 Not Found\r\n");
          __client1.println("Connection:close\r\n\r\n");
          break;
        }
      }
    }
    char c = 0x00;
    while(__client1.available()){
      c = __client1.read();
      Serial.print(c);
    }
    delay(10);
    __client1.stop();
    delay(10);
  }
}

では、ご自分の環境に合わせて、以下の項目を修正して別名で保存してください。

●4-5行目:
ここは、ESP-WROOM-32 ( ESP32 )の soft AP モードの SSID とパスワードを設定します。
パスワードは必ず8文字以上にしてください。

●7行目;
ここでは、ESP-WROOM-32 ( ESP32 ) のローカルIPアドレスを指定します。
固定IP ( Static IP )にする場合の設定です。
例えば、先に紹介したBUFFALO のホテルルーター WMR-433W の場合、インターネットに接続しないローカルモードにする場合、デフォルトGateway は
192.168.13.1
となりますので、それ以降のアドレスにします。
例えば、
192.168.13.10
など。

●8行目:
ルーターのデフォルトGateway を設定します。
通常のメインルーターならば、
192.168.0.1
となると思います。
その他、先で紹介した BUFFALO のホテルルーター WMR-433W をメインルーターにブリッジモードで接続した場合も同じように
192.168.0.1
となります。
ホテルルーターをインターネットに接続しないローカルモードの場合、例えば
192.168.13.1
となります。

●9行目:
サブネットマスクはとりあえずこのサンプルスケッチ通りで良いかと思います。
後はご自分の好きなように設定してください。

●18-19行目:
先ほど作った、ESP32 soft AP モードのサーバー証明書と秘密鍵を保存しておく、SPIFFS フラッシュのパスを指定します。

●21行目:
スマホのブラウザでWi-Fiアクセスポイントをセレクトした設定ファイルを保存しておく、SPIFFSフラッシュのパスを指定します。
事前にこのフォルダを作成していなくても問題ありません。

●55-56行目:
固定IP ( Static IP )にする場合は、55行目の関数を使います。
もし、流動的なIPアドレスにしたければ、56行目のコメントを外し、55行目をコメントアウトしてください。

●67行目:
自動 Wi-Fi Scan の間隔を指定します。
ここでは、20秒(20000ms)で設定していますが、これより小さい値にしないでください。
小さすぎると、ちゃんとスキャンできません。

●79-133行:
Wi-Fiアクセスポイントをスマホブラウザでセレクトした後、ESP32 の STA モードで接続した場合にブラウザに表示させるためのHTMLタグを出力します。
83-91行をご自分の好きな Webページ用に書き換えてください。
因みに、90行のexit_sta ボタンが無いと、再びアクセスポイントを変えるための soft AP サーバーに接続できなくなるので要注意です。

soft AP モード用サーバー証明書と秘密鍵ファイルを ESP-WROOM-32 SPIFFS フラッシュにアップロードしておく

上記で保存したサンプルスケッチフォルダを開き、その中に「data」フォルダを作成します。
そして、更にその中に「cert」フォルダを作成します。

例えば、先ほど保存したスケッチフォルダが、ESP32_WiFi_AP_SSL_Selector_sample というフォルダならば、Windows10 の場合、以下のパスになります。
USER-NAME はご自分のPCのユーザー名です。

C:\Users\USER-NAME\Documents\Arduino\ESP32_WiFi_AP_SSL_Selector_sample\data\cert

そのフォルダに、先ほど作成したESP32 のsoft AP サーバー証明書と秘密鍵を保存しておきます。
すると、こんな感じになります。

そうしたら、Arduino – ESP32 の SPIFFS ファイルアップローダープラグインを使って、ESP-WROOM-32 の SPIFFS フラッシュへアップロードしておいてください。
アップロード方法は以下の記事を参照してください。
※既にアップロードしてあるファイルは消えてしまいますのでご注意ください。
消したくない場合は、dataフォルダにアップロードしたいファイルをすべて入れてからアップロードするようにしてください

ESP-WROOM-32 ( ESP32 ) SPIFFS アップローダープラグインの使い方

コンパイル書き込み実行

以上、すべてができたら、Arduino IDE でコンパイル書き込み実行してみてください。

同時にシリアルモニターにもログが出力されます。

Arduino IDE の「ツール」メニューの「Core Debug Level」を「Verbose」にすると、SPIFFS から読み込んだ証明書ファイルやその他の細かいログが出力されるようにライブラリを作りました。
あとは最初の方で紹介した動画のように操作してもらえればよいです。

ちょっと間違えやすいのが、soft AP モードで Wi-Fi に接続する時と、STA モードで接続する時を切り替えるのを忘れてしまうことです。
HTTPサーバーポート番号と client ハンドルが両方とも同じで使いまわしているので、気付かないことがあります。
ご注意ください。

因みに、私の環境ではiOS の iPad でも動作確認できました。
iOS ではルートCA証明書のインストールがちょっと面倒ですが、これは後日記事にしたいと思っていますが、いつになることやら・・・。

ところで、この実行結果を見てみると、ブラウザとのSSL通信で、証明書や秘密鍵のやり取りは最初のアクセスだけだということが分かってきましたね。
自分でライブラリを作っていて、だんだん SSL 通信というものの理解が深まってきました。

まとめ

どうでしょうか?
上手く動きましたでしょうか。

今回、このライブラリを作るにあたって、自分的にネットワークや SSL通信というものの理解がかなり深まってきました。
この数か月で蓄積した知識はとても貴重なものです。
電子工作でセキュアな IoT を作ることが自由にできそうな気がしてきました。

でもプログラミングにしてはまだまだド素人の領域なので、ライブラリを作っている最中に何度も挫折しそうになりました。
結果、かなり妥協したものになっているので、無駄が多いと思います。
でも、できる限りSRAM の消費を抑えられたのではないかとは思っています。

今後、Websocket通信もセキュアなものにして見ようと思っています。

今回はここまで。

ではまた・・・。

Amazon.co.jp 当ブログのおすすめ

スイッチサイエンス ESPr Developer 32 Type-C SSCI-063647
スイッチサイエンス
¥2,420(2024/04/18 23:57時点)
ZEROPLUS ロジックアナライザ LAP-C(16032)
ZEROPLUS
¥22,503(2024/04/19 09:57時点)
Excelでわかるディープラーニング超入門
技術評論社
¥2,068(2024/04/19 19:50時点)

コメント

  1. 名無し より:

    最近になってESP32の存在を知り、使い方を勉強しております。
    これまではAVRをメインで使用しており、ESP32のコストパフォーマンスの良さに感動しました。
    SSL通信を用いたサーバー構築方法の解説はとても解りやすく、容易に自分のプログラムに取り入れる事が出来そうです。
    他のページも拝見させて頂きましたが、どれも丁寧な作りで私の様な初心者には大変有難いです。
    今後のご活躍に益々期待しております。

    • mgo-tec mgo-tec より:

      匿名さん

      記事をご覧いただき、ありがとうございます。

      そう言っていただけると、とても嬉しいですね。
      ただ、この記事は随分前に書いたもので、今ちゃんと動くかちょっと心配です。
      もし、動かないとかありましたらご連絡いただけると助かります。

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