ESP8266 モジュールの AT コマンドに SSL クライアント機能を追加する
ただ、この AT コマンドセットで HTTPS リクエストを処理する方法が見当たらないことを不思議に思っていました。あらためてドキュメントを確認するとどうやら AT コマンドで SSL 通信を扱うことはできないようです。プログラマブルであることがこの製品の魅力のひとつであるとは言え 出荷時の状態で SSL を利用できないのはそのこと自体が残念に思われます。AT コマンドのインターフェイスそのものは何かと便利なのでこれを拡張する形で SSL への対応を試みました。
最終的にはサーバ証明書の検証も含め期待通りに動作しましたが、その過程で意外な曲折もあり、手元で経験したことを備忘をかねてほぼ順番どおりに記録しておくことにします。興味のある方は追ってみて下さい。
AT コマンド拡張について
メーカーは次のページで ESP8266 用の SDK を公開しています。
- http://espressif.com/new-sdk-release/ - espressif.com
2015-08-21 時点の最新版は以下の通りです。
- esp_iot_sdk_v1.3.0_15_08_08 - bbs.espressif.com
- esp_iot_sdk_v1.3.0 patch for SSL issue - bbs.espressif.com
SDK アーカイブを覗いてみると examples/at/ ディレクトリ下に AT コマンドセットを拡張するためのサンプルコードが収録されており「AT+CIUPDATE」コマンドは他のコマンド群とは別にここで定義されていることが見てとれます。
examples/at/user/user_main.c
コードの末尾付近の at_custom_cmd[] テーブルには「AT+CIUPDATE」に加え「AT+TEST」という拡張コマンドが記述されています。拡張コマンドの定義の方法は次の記事に説明があります。
In the example of \esp_iot_sdk\examples\at\user\user_main.c, ways are delivered on how to implement a self-defined AT Command, AT+TEST”. The structure, at_funcationType, is used to define four types of a command, e.g., “AT+TEST”. "at_testCmd" is a testing command and it’s formatted as AT+TEST=?. In the example of AT, the registered callback is “at_testCmdTest”; the testing demand could be designed as the value range of the return parameter. If registered as NULL, there will be no testing command. "at_queryCmd" is a query command and it’s formatted as AT+TEST?. In the example of AT, the registered callback is “at_queryCmdTest” ; the query command could be designed as returning the current value. If registered as NULL, there will be no query command. "at_setupCmd" is a setup command and it’s formatted as AT+TEST=parameter1,parameter2,........ In the example of AT, the registered callback is "at_setupCmdTest"; the setup command could be designed as the value of the parameter; if registered as NULL, there will be no setup command. "at_exeCmd" is an execution command and it’s formatted as AT+TEST. In the example of AT, the registered callback is “at_exeCmdTest”; if registered as NULL, there will be no execution command.一連の書式は他のコマンドでも見かけるものです。一読したところでは既存のコマンドセットにオリジナルの AT コマンドを追加することはあまり難しくないようです。
SDK に同梱のビルドずみファームを書き込んでみる
SDK の bin/ ディレクトリにはメーカー側がビルドしたファームウェアバイナリ群が格納されています。
ESP-WROOM-02 出荷時の標準ファーム(AT コマンドセット)は以下のバイナリから構成されます。
- bin/boot_v1.2.bin ---- ブートローダ
- bin/at/user1.1024.new.2.bin ---- AT コマンドプログラム本体
- bin/blank.bin ---- 領域初期化用 中身は 0xFF * 4096バイト
- bin/esp_init_data_default.bin --- "Stores default RF parameter values"
自作のファームをビルドして書き込む前に、まずこれらを練習台として内蔵フラッシュメモリに書き込んでみることにしました。 正しく書き込めるようになっておけばいざと言うときの切り戻しも楽でしょう。
「bin/at/readme.txt」冒頭に次の記述があります。
download: boot_v1.2+.bin 0x00000 user1.1024.new.2.bin 0x01000 blank.bin 0x7e000 & 0xfe000
※「user1.1024.new.2.bin」の「1024」は 1024KB(8Mbit)容量のフラッシュメモリ使用を想定してビルドされたバイナリであることを意味する。ESP-WROOM-02 は 32Mbit のフラッシュメモリを内蔵しているが、以下の記事においてはこのバイナリの仕様に準じ 8Mbit 空間範囲の使用を前提とする。
※「esp_init_data_default.bin」には RF(Radio Frequency)パラメータのデフォルト値が保持されている旨の説明が SDK の「ESP8266 IOT SDK User Manual」に記載されている。次の記事にも説明あり。
- PURPOSE OF ESP_INIT_DATA_DEFAULT.BIN - www.esp8266.com
- Flash Download Tool - bbs.espressif.com
(図は Windows 版)
※1024KB フラッシュ使用時のメモリマップ - SDK の「Espressif IOT SDK User Manual」より Note • System param (system parameter area) is the last 16KB offlash. • User param is the user parameter area used by Espressif demo code ( IOT_Demo or AT ). If users develop their own application, user data can be saved in any available flash area. • User Data area ( green area in pictures below ) means the flash area that may be available, if program area doesn’t reach the maximum size, remaining area can be used to save user data.動作確認
AATTEE00 OK AT+GMR AT version:0.40.0.0(Aug 8 2015 14:45:58) SDK version:1.3.0 compile time:Aug 8 2015 17:19:38 OK AT+CWMODE=1 OK AT+CWLAP +CWLAP:(1,"GPMAP",-93,"10:6f:3f:XX:XX:XX",1,-47) +CWLAP:(0,"000D0BF6C164_G",-91,"00:16:01:XX:XX:XX",5,-32) +CWLAP:(3,"hogehogeSSID",-72,"00:22:cf:XX:XX:XX",10,-91) +CWLAP:(2,"STWEBKAIGI",-88,"4c:e6:76:XX:XX:XX",10,-27) +CWLAP:(4,"elecom2g-DE8564",-90,"00:90:fe:XX:XX:XX",10,-26) +CWLAP:(3,"e-timer-DE8564",-90,"02:90:fe:XX:XX:XX",10,-26) +CWLAP:(3,"auhome_acyCYh",-92,"8c:4c:dc:XX:XX:XX",1,-31) OK
開発環境の導入
ESP8266 モジュールのファームウェア開発には現在 Arduino 向けの IDE・言語を用いることも可能ですが SDK に含まれるコードはいずれもネイティブの開発環境を前提としているため今回はそれを使います。
メーカーは必要な一式をインストールずみの開発環境(Linux)の VirtualBox 仮想マシンイメージを配布しており、導入手順・方法は以下の記事で説明されています。
- Step 1. Setup Linux Compile Environment - bbs.espressif.com
- SDK の一式を配置した PC 上のディレクトリを "share" の名前で VirtualBox 仮想マシンの共有フォルダとして設定しておく
- 仮想マシンのデスクトップ上の "LXTerminal" を起動するとアカウント esp8266 でターミナルが開く
- アカウント esp8266 ホームディレクトリの ./mount.sh を実行すると PC 上の共有フォルダが ~/Share にマウントされる
- アカウント esp8266 のパスワードは "espressif"
※仮想マシンのタイムゾーンは CST 北京時間に設定されているため変更が必要です
ファームウェアをビルドしてみる
SDK の examples/ 下には複数のサンプルプロジェクトが配置されており、利用するプロジェクトのファイル群をそこから app/ 下へ丸ごとコピーして使います。今回は前述のとおり examples/at/* を使用します。
仮想マシン側で app/ へ移動し gen_misc.sh スクリプトを実行するとバイナリがビルドされます。 以下のビルド開始時のオプション指定はメーカー側ビルドでの内容に準じています。
$ pwd /home/esp8266/Share/esp_iot_sdk_v1.3.0_15_08_08_build/esp_iot_sdk_v1.3.0/app $ make clean make -C user clean; make[1]: Entering directory `/mnt/Share/esp_iot_sdk_v1.3.0_15_08_08_build/esp_iot_sdk_v1.3.0/app/user' rm -f -r .output/eagle/debug make[1]: Leaving directory `/mnt/Share/esp_iot_sdk_v1.3.0_15_08_08_build/esp_iot_sdk_v1.3.0/app/user' rm -f -r .output/eagle/debug $./gen_misc.sh gen_misc.sh version 20150511 Please follow below steps(1-5) to generate specific bin(s): STEP 1: choose boot version(0=boot_v1.1, 1=boot_v1.2+, 2=none) enter(0/1/2, default 2): 1 boot mode: new STEP 2: choose bin generate(0=eagle.flash.bin+eagle.irom0text.bin, 1=user1.bin, 2=user2.bin) enter (0/1/2, default 0): 1 generate bin: user1.bin STEP 3: choose spi speed(0=20MHz, 1=26.7MHz, 2=40MHz, 3=80MHz) enter (0/1/2/3, default 2): spi speed: 40 MHz STEP 4: choose spi mode(0=QIO, 1=QOUT, 2=DIO, 3=DOUT) enter (0/1/2/3, default 0): spi mode: QIO STEP 5: choose spi size and map 0= 512KB( 256KB+ 256KB) 2=1024KB( 512KB+ 512KB) 3=2048KB( 512KB+ 512KB) 4=4096KB( 512KB+ 512KB) 5=2048KB(1024KB+1024KB) 6=4096KB(1024KB+1024KB) enter (0/2/3/4/5/6, default 0): 2 spi size: 1024KB spi ota map: 512KB + 512KB start... make[1]: Entering directory `/mnt/Share/esp_iot_sdk_v1.3.0_15_08_08_build/esp_iot_sdk_v1.3.0/app/user' DEPEND: xtensa-lx106-elf-gcc -M -Os -g -Wpointer-arith -Wundef -Werror -Wl,-EL -fno-inline-functions -nostdlib -mlongcalls -mtext-section-literals -ffunction-sections -fdata- (中略) !!! Support boot_v1.2 and + Generate user1.1024.new.2.bin successully in folder bin/upgrade. boot.bin------------>0x00000 user1.1024.new.2.bin--->0x01000 !!!
以上の手順で bin/upgrade/ 下に生成される user1.1024.new.2.bin を前出の bin/at/user1.1024.new.2.bin の代わりにフラッシュメモリの 0x01000 番地を起点に書き込めば完了です。
なお、boot*.bin, blank.bin はユーザによるビルドの対象ではありません。これらは SDK に含まれているものをそのまま使います。
HTTPS クライアント処理はどのように書けばいいの?
SDK にはドキュメントとして「ESP8266 SSL User Manual」が同梱されています。しかし、現時点では ESP8266 モジュールでの SSL 利用に関する情報は少ないのが実状です。
ちなみに、IBM 所属の Neil Kolban 氏が 2015年8月1日に公開した「Kolban's book on the ESP8266」は技術情報が詳細に記述された貴重な資料ですが、同著においても SSL 処理については API の紹介のみに留まっています。
- ANNOUNCE: FREE BOOK ON THE ESP8266 - www.esp8266.com
- Kolban’s book on the ESP8266 - Neil Kolban Tech - neilkolban.com
各国の利用者も手探りの途中といった印象で次のような応酬が随所で見受けられます。
- DOES ESP8266 SUPPORT SSL TO RETRIEVE DATA FROM HTTPS SITES? - www.esp8266.com
By helpme - Tue Jun 30, 2015 5:14 am Can the ESP8266 support HTTPS websites? Does the Espressif SDK support SSL function?
By Angelo Santagata - Tue Jun 30, 2015 4:51 pm Alas I no, but I happened to see this ESP8266 SSL Example on their website! http://bbs.espressif.com/viewtopic.php?f=21&t=389 hopefully someone can port it to the SDKというわけで、メーカー側は下記ページに HTTPS クライアント処理のサンプルコードを掲載しています。
- ESP8266 as TCP SSL client - bbs.espressif.com
Here is a demo code of ESP8266 as TCP SSL client. It is based on ESP8266 SDK without OS. It just like ESP8266 to be normal TCP client except change espconn_xxx to be espconn_secure_xxx If your SSL packet is larger than 2048 bytes, please try to call espconn_secure_set_size to enlarge SSL buffer size (max:8192 )
このコードを下敷きに AT コマンドを拡張すればよいと考えました。
AT コマンド拡張では SSL 処理が正常動作しない?? (SDK v1.2.0)
さっそく自作の +GETTEST コマンドを書いてみました。
ちなみにこの実験の時点で使用していた SDK は 2015年8月初旬当時の最新版だった v1.2.0 です。
- esp_iot_sdk_v1.2.0_15_07_03 - bbs.espressif.com
- SDK v1.2.0 Patch for SSL - bbs.espressif.com
試作した +GETTEST コマンドは次の内容です。
AT+GETTEST="hostname" で当該ホストへ "GET /" リクエストを送る AT+GETTEST --- 引数がなければ www.example.com:80 へ HTTP リクエスト AT+GETTEST="www.example.com",0 --- 2nd arg が 0 なら 80番ポートへ HTTP リクエスト AT+GETTEST="www.example.com",1 --- 2nd arg が 1 なら 443番ポートへ HTTPS リクエスト AT+GETTEST="www.example.com",1,0 --- 3rd arg が 0 ならサーバ証明書検証なしで 443番ポートへ HTTPS リクエスト AT+GETTEST="www.example.com",1,1 --- 3rd arg が 1 ならサーバ証明書検証ありで 443番ポートへ HTTPS リクエストソースコードを以下に示します。
(注:これらはその後の調査結果も反映したものです。最初の実験の段階ではずっとラフな内容でした) ところがこの AT+GETTEST コマンドは期待通りには動作しませんでした。HTTP リクエストは正しく処理されるものの HTTPS リクエストではエラーが発生するのです。
AATTEE00 OK AT+GMR AT version:0.30.0.0(Jul 3 2015 19:35:49) SDK version:1.2.0 compile time:Aug 11 2015 14:09:10 OK AT+GETTEST="www.example.com",1 mode : sta(18:fe:34:a4:0f:a0) + softAP(1a:fe:34:a4:0f:a0) add if0 11 f 0, 221 scandone add 0 aid 4 connected with hogehogeSSID, channel 10 WIFI CONNECTED dhcp client start... WIFI GOT IP ip:10.10.0.19,mask:255.255.255.0,gw:10.10.0.1 got ip !!! start connect to server client handshake start. client handshake failed reconnect callback, error code -28 !!!エラーコード -28 は SDK の include/espconn.h に定義されています
/* Definitions for error constants. */ #define ESPCONN_OK 0 /* No error, everything OK. */ #define ESPCONN_MEM -1 /* Out of memory error. */ #define ESPCONN_TIMEOUT -3 /* Timeout. */ #define ESPCONN_RTE -4 /* Routing problem. */ #define ESPCONN_INPROGRESS -5 /* Operation in progress */ #define ESPCONN_ABRT -8 /* Connection aborted. */ #define ESPCONN_RST -9 /* Connection reset. */ #define ESPCONN_CLSD -10 /* Connection closed. */ #define ESPCONN_CONN -11 /* Not connected. */ #define ESPCONN_ARG -12 /* Illegal argument. */ #define ESPCONN_ISCONN -15 /* Already connected. */ #define ESPCONN_HANDSHAKE -28 /* ssl handshake failed */ #define ESPCONN_SSL_INVALID_DATA -61 /* ssl application invalid */ハンドシェイクに失敗しているようですがこれでは何もわかりません。さて?
SDK v1.2.0 での AT コマンド拡張内で SSL が機能しない理由
不審に思い情報を探したところ同じ問題に遭遇したユーザとスタッフの応酬に気づきました。
- SSL connection via AT commands - bbs.espressif.com
Re: SSL connection via AT commands Postby doughboy ≫ Tue Apr 21, 2015 11:59 pm I am writing AT command to support SSL, but it seems none of the espconn_secure_* api works. At least when I connect to real ssl servers using real cerfificates. (and yes, I have set buffer size to 8196 and use the patched ssl library) esp module will reset (I think due to wdt timeout). I already reported this via email.
Re: SSL connection via AT commands Postby Espressif_Faye ≫ Mon Apr 27, 2015 10:22 am Hi, doughboy, We debugged on your problem, it's the RAM limitation cause that. AT commands has only 17KBytes available heap now, SSL need 12KBytes free heap size, if you espconn_secure_set_size to set 5KBytes,it is not enough.. So SSL can not be used in AT commands because RAM is limited. Sorry for the inconvenience.
AT コマンドプログラム実行時には残ヒープが 17KB 程度だが、SSL 処理には 12KB のヒープが必要で、そこに espconn_secure_set_size() で数 KB の SSL バッファ確保を指定すると完全に枯渇してしまう。そのため、AT コマンドで SSL を扱うことはできない、という話です。
ホストへの接続要求の前に system_print_meminfo() および system_get_free_heap_size() を呼び出してみると次の結果でした。
data : 0x3ffe8000 ~ 0x3ffe89d8, len: 2520 rodata: 0x3ffe89e0 ~ 0x3ffeabd4, len: 8692 bss : 0x3ffeabd8 ~ 0x3fff5010, len: 42040 heap : 0x3fff5010 ~ 0x3fffc000, len: 28656 system_get_free_heap_size=16592
なるほど。なお、AT コマンドセットは SDK においてはライブラリ形式で供給されており、自作コマンドを追加することはできても既存のものを変更することはできません。
さて?
冒頭に書いたように、ESP-WROOM-02 を通信モジュールとして扱う状況においてはシリアル経由でのコマンド渡しで簡単に通信処理を利用できることには便宜があります。
しかし、そこで SSL を利用できないことは大きなマイナスであり実用を想定すると使途が限定されることになるでしょう。そのため既存の AT コマンドセットに代わるインターフェイス込みでの SSL 処理の実装が必要であり、その方法はいろいろ考えられるもののどうも遠まわりな印象があります。
それはそれとして、一方では ESP-WROOM-02 は自立したマイコンとしての側面も持ち合わせているため、要件によっては単体で処理をまかなうことが可能であるわけで、とどのつまり製品出荷時の AT コマンドセットは「おまけ」程度のものと割り切るべきなのかも?
そんなことを考えながら、SSL 処理そのものが正常に動作することを確認するために前掲のプログラムを AT コマンドセットと切り離し単体で起動する形に変更して動作を試してみました。
ホストへの接続要求前のヒープ状況は以下の通りで、HTTPS リクエストは問題なく処理されました。やはりメモリ不足の問題のようです。
data : 0x3ffe8000 ~ 0x3ffe8560, len: 1376 rodata: 0x3ffe8560 ~ 0x3ffe9764, len: 4612 bss : 0x3ffe9768 ~ 0x3fff2b30, len: 37832 heap : 0x3fff2b30 ~ 0x3fffc000, len: 38096 system_get_free_heap_size=34856
サーバ証明書の検証について
立ち止まっていても仕方がないので、いずれにしても必要となる SSL サーバ証明書の検証に手をつけてみることにしました。
SDK に同梱の「ESP8266 SSL User Manual」には HTTPS クライアント処理においてサーバ証明書を検証する方法が次のように記述されています。
上記ドキュメントからの抜粋
CA verify function default to be disabled, user can enable it by espconn_secure_ca_enable. 3.1. Generate CA Certificate (1) Put script “make_cert.py”and CA certificate into the same folder. (2) Run script “make_cert.py” to generate esp_ca_cert.bin which contains all CA certificates (2 CA certificates at most) in the same folder. Download address of esp_ca_cert.bin depends on espconn_secure_ca_enable. 3.2. CA Verify STEP 1: ESP8266 connects to server, read esp_ca_cert.bin from flash, get the corresponding SSL ctx. Only 2 CA certificates is allowed at most. STEP 2: ESP8266 starts TLS handshake, get certificate from SSL server, check with the CA in step 1: • if CA check fail, connection break; • if succeed, CA verify pass.
4.2. espconn_secure_ca_enable Function: Enable SSL CA (certificate authenticate) function Note: • CA function is disabled by default • If user want to call this API, please call it before espconn_secure_accept (ESP8266 as TCP SSL server) or espconn_secure_connect (ESP8266 as TCP SSL client) Prototype: bool espconn_secure_ca_enable (uint8 level, uint16 flash_sector) Parameter: uint8 level : set configuration forESP8266 SSL server/client: 0x01 SSL client; 0x02 SSL server; 0x03 both SSL client and SSL server uint16 flash_sector : flash sector in which CA (esp_ca_cert.bin) is downloaded. For example, flash_sector is 0x3B, then esp_ca_cert.bin need to download into flash 0x3B000 Return: true : succeed false : fail
「make_cert.py」は SDK には含まれません。下記ページにダウンロードリンクが掲載されています。
- where can I get make_cert.py - bbs.espressif.com
以下の手順で「https://www.example.com/」のサーバ証明書の検証を試みました。
- DigiCert のルート証明書をブラウザから X509 形式(DER エンコード)でエクスポートし拡張子を .cer とする
- *.cer を置いたディレクトリで上記の make_cert.py を実行し「esp_ca_cert.bin」を生成する
- espconn_secure_connect() の前に下記行を記述しuser1.bin をフラッシュメモリサイズ 1024KB の指定でビルド
espconn_secure_ca_enable(0x01, 0x65)); // 0x01 = client - 他の *.bin ファイルと一緒に、esp_ca_cert.bin をフラッシュメモリの 0x65000 番地(User Data area)を起点に書き込む
※ビルドした user1.bin のサイズは 300KB ほどであり余裕をみて 400KB として
ブート領域の 4KB と合算すると 404KB につき 404 * 1024 = 413696 = 0x65000
- 下のメモリマップ(再掲)を参照のこと※1024KB フラッシュ使用時のメモリマップ - SDK の「Espressif IOT SDK User Manual」より Note • System param (system parameter area) is the last 16KB offlash. • User param is the user parameter area used by Espressif demo code ( IOT_Demo or AT ). If users develop their own application, user data can be saved in any available flash area. • User Data area ( green area in pictures below ) means the flash area that may be available, if program area doesn’t reach the maximum size, remaining area can be used to save user data.
- 期待通りにサーバ証明書の検証が行われ www.example.com への HTTPS リクエストは正常に処理され、ルート CA が DigiCert ではないサイトへの HTTPS リクエストにおいてはハンドシェイクの時点でエラーとなることを確認
解決:SDK v1.3.0 なら AT コマンド拡張内で SSL 処理が動く!
そうこうしている内に、2015-08-08 に公開された esp_iot_sdk_v1.3.0_15_08_08 のリリースノートの次の記述が目に飛び込んできました。
Optimization: 1.Memory optimization to save 12KBytes. :
前述のとおり、SDK v1.2.0 を使ってビルドした AT コマンド拡張版で SSL が機能しなかったのはメモリ不足が原因だったため、v1.3.0 で 12KB 余裕が出来たのであればあるいは動作可能ではないかと考えました。 実際に v1.3.0 でビルドした user1.bin で AT+GETTEST コマンドを試したところ HTTPS リクエストが正常に処理されることを確認、ホストへの接続要求前のヒープ状況は以下の通りでした。まずはめでたし、です。
data : 0x3ffe8000 ~ 0x3ffe83ac, len: 940 rodata: 0x3ffe83b0 ~ 0x3ffe9d40, len: 6544 bss : 0x3ffe9d40 ~ 0x3fff18b8, len: 31608 heap : 0x3fff18b8 ~ 0x3fffc000, len: 42824 system_get_free_heap_size=31376
動作の様子 (動画:2分19秒)
今回試作した GETTEST コマンドは所定のホストに対し "GET /" を行うのみの内容ですが、以上のように大枠での動作を確認できたため今後は任意の URI の指定や POST メソッドへの対応など実用上必要な肉付けを手元で行うことになるでしょう。楽しみです。
上の動画で使用している現時点のファーム一式のうち user1.bin 以外のバイナリを以下のアーカイブに収めています。 自分の環境の AP の SSID とパスワードをソースに記述して user1.bin をビルドし、図の要領でこれらのバイナリ一式を ESP-WROOM-02 のフラッシュメモリに書き込めば AT+GETTEST コマンドが動作するはずです。興味のある方は自己責任でお試し下さい。
(tanabe)