ESP-WROOM-02で、IFTTTをhttps(SSL/TLS)で呼び出す例

  • 28
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

IFTTTのMakerChannelを使用して、ESP-WROOM-02からレシピを呼び出します。

MakerChannelのREST-APIは非常にシンプルで扱いやすいのですが、httpではシークレットキーがバレバレです。https(SSL/TLS)を使用して通信を暗号化し、シークレットキーを隠しましょう。

■ 注意 ■
 紹介している手順で通信は暗号化されますが、なりすまし等の攻撃は防ぐ事はできません。
 SSL証明書の検証がデフォルトで無効化されているからです。
 そのため、安全性は「平文でキーをネットワークに流すよりマシ」なレベルである事にご注意ください。

事前準備

  • Arduino IDE にてESP-WROOM-02(ESP8266)の開発ができる準備ができていること。
    事前に"Lチカ"する事をお勧めします。

  • IFTTTでMakerChannelをトリガーとするレシピを用意してあること。
    通知系のレシピがおススメです。(iOS Notification、Android Notification)

SSLライブラリの有効化

2015年8月7日時点では、SSLライブラリがビルド時にリンクされない設定になっていますので、以下の手順でリンクするように設定を変更します。また、公式SDKのフォーラムにてSSLライブラリの修正パッチが出ていますので、ファイルの差し替えを行う必要があります。

手順1:設定フォルダを開き、ボードパッケージのリビジョンを確認します。

筆者が検証したリビジョンは 1.6.5-947-g39819f0 です。同じであれば良いのですが、新しいバージョンでは仕様が変わっている可能性がありますので注意してください。

  • Arduino IDE の設定画面から、設定フォルダを開きます。

  • \packages\esp8266\hardware\esp8266 と辿り、 1.6.5-947-g39819f0 というフォルダが存在すれば同じリビジョンです。

手順2:SSLライブラリのパッチをダウンロードしてファイルを差し替えます。

この手順はおそらく次のSDKに含まれる事になると思います。新しいリビジョンのボードパッケージを使っている場合は、一旦この手順を飛ばしてスケッチの動作を確認してみてください。

公式サイトから「SDK v1.2.0 Patch for SSL」をダウンロードします。

http://bbs.espressif.com/viewtopic.php?f=5&t=708

よく読むと、修正パッチを再リリースしているようです。2つ目のファイル( libssl.zip )をダウンロードします。zipファイルを解凍すると、libssl.a というファイルが1つだけ入っていますので、次のフォルダに入っている同名ファイルに上書きして差し替えます。

\packages\esp8266\hardware\esp8266\1.6.5-947-g39819f0\tools\sdk\lib

手順3:ビルド時にSSLライブラリがリンクされるように設定します。

次のフォルダを辿り、platform.txt を開きます。

\packages\esp8266\hardware\esp8266\1.6.5-947-g39819f0

下記のような記述を探し、リンクするライブラリに -lssl を追加します。(27行目付近です。)

platform.txt 27行目
変更前
compiler.c.elf.libs=-lm -lgcc -lhal -lphy -lnet80211 -llwip -lwpa -lmain -lpp -lsmartconfig -lwps -lcrypto

変更後
compiler.c.elf.libs=-lm -lgcc -lhal -lphy -lnet80211 -llwip -lwpa -lmain -lpp -lsmartconfig -lwps -lcrypto -lssl

サンプルスケッチと回路図

  • ESP-WROOM-02の17番ピンにタクトスイッチを繋いで、10KΩ程度でプルアップしてください。
  • スケッチ内のWiFiアクセスポイントの設定や、MakerChannelのシークレットキーの変更を忘れずに行ってください。

スケッチが正しく書き込まれると、WiFiアクセスポイントに接続したのち、1度だけMakerChannelを呼び出します。その後、タクトスイッチが押されるまで待機し、押されたらMakerChannelを呼び出す動作を繰り返します。

IFTTT_with_SSL.ino
/**
 *  ESP-WROOM-02
 *  SSL/TLSで、IFTTT MakerChannelを呼び出すスケッチ
 *  
 *  注意
 *  ・DNSからIPアドレスを取得せず、直接IPアドレスをスケッチに書いています。
 *  ・SSL/TLSスタックにホスト名を渡して、証明書を検証する方法が分かっていません。
 *  ・そのため、かなり適当なオレオレ証明書でも難なく通過してしまうでしょう。(^_^;)
 */

#ifdef ESP8266
extern "C" {
#include "ip_addr.h"
#include "espconn.h"
#include "user_interface.h"
}
#endif

#include <ESP8266WiFi.h>

// 証明書ストア
unsigned char default_certificate[] = {};
unsigned int  default_certificate_len = 0;
unsigned char default_private_key[] = {};
unsigned int  default_private_key_len = 0;

// WiFiアクセスポイントの設定
const char* ssid     = "mbed_ap";
const char* password = "password1234";

// IFTTT MakerChannelの設定
const char* host        = "maker.ifttt.com";
const char* secret_key  = "dummyaXaYaRalatafa8ava";

// 投稿ボタンの設定 = IO16
//  ESP-WROOM-02のピンでは 17番 なので注意。
const int buttonPin = 16;

// データ受信バッファとポインタ
char  recvbuffer[2048];
char* recvbuffer_pos;

LOCAL struct espconn esp;
LOCAL struct _esp_tcp tcp;

void setup() {
  Serial.begin(115200);
  delay(10);

  // 投稿ボタンの設定
  pinMode(buttonPin, INPUT);

  Serial.print("Connecting to ");
  Serial.println(ssid);

  // アクセスポイントに接続
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("connected");  
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
}

void connect_cb(void *arg) {
  Serial.println("connect_cb");
}
void reconnect_cb(void *arg, sint8 err) {
  Serial.print("reconnect_cb: ");
  Serial.println(err);
}
void disconnect_cb(void *arg) {
  Serial.println("disconnect_cb");
}

void recv_cb(void *arg, char *pdata, unsigned short len) {
  Serial.print("recv_cb:");
  Serial.println(len);
  memcpy(recvbuffer_pos, pdata, len);
  recvbuffer_pos += len;
}
void sent_cb(void *arg) {
  Serial.println("sent_cb");
}

void loop() {
  Serial.print("connecting to ");
  Serial.println(host);

  tcp.local_ip[0] = 0;
  tcp.local_ip[1] = 0;
  tcp.local_ip[2] = 0;
  tcp.local_ip[3] = 0;
  tcp.local_port = espconn_port();

  tcp.remote_ip[0] = 54;   //maker.ifttt.com = 54.243.120.63
  tcp.remote_ip[1] = 243;
  tcp.remote_ip[2] = 120;
  tcp.remote_ip[3] = 63;
  tcp.remote_port = 443;

  esp.type  = ESPCONN_TCP;
  esp.state = ESPCONN_NONE;
  esp.proto.tcp = &tcp;

  // 接続・切断のコールバックを設定
  espconn_regist_connectcb(&esp, connect_cb);
  espconn_regist_reconcb(&esp, reconnect_cb);
  espconn_regist_disconcb(&esp, disconnect_cb);

  // データ送受信のコールバックを設定
  espconn_regist_recvcb(&esp, recv_cb);
  espconn_regist_sentcb(&esp, sent_cb);

  // SSL/TLSで使用するバッファサイズがデフォルトでは不足するためサイズを変更
  //  http://bbs.espressif.com/viewtopic.php?t=548
  espconn_secure_set_size(ESPCONN_CLIENT, 5120);

  // サーバに接続
  Serial.print( "espconn_secure_connect = ");
  Serial.println(espconn_secure_connect(&esp));

  // 接続待ち
  while(esp.state != ESPCONN_CONNECT) {
    delay(100);
  }

  String url = String("/trigger/test/with/key/") + secret_key + "?value1=0&value2=0&value3=0";

  Serial.print("Requesting URL: ");
  Serial.println(url);

  String data = String("GET ") + url + " HTTP/1.1\r\n" +
               "Host: " + host + "\r\n" + 
               "Connection: close\r\n\r\n";

  char senddata[512];
  data.toCharArray(senddata, sizeof(senddata));
  Serial.println(senddata);

  // 受信バッファを初期化
  recvbuffer_pos = recvbuffer;

  // データ送信
  Serial.print( "espconn_secure_sent = ");
  Serial.println(espconn_secure_sent(&esp, (uint8*)senddata, strlen(senddata)));
  delay(10);

  // 応答の受信完了待ち
  while(esp.state != ESPCONN_CLOSE) {
    delay(100);
  }

  // 受信したデータを出力
  recvbuffer_pos = '\0';
  Serial.println(recvbuffer);

  // (過度な連続投稿を避けるため、少しウェイトを入れる)
  Serial.println("delay wait ...");
  delay(5000);

  // 実行ボタンが押されるまで待機
  Serial.println("button wait ...");
  while(digitalRead(buttonPin) == HIGH) {
    delay(10);
  }
}

最後に・・・

WDTが厳しいので、ちょっと長めの処理を行うとスタックをダンプして落ちます。処理を短めに工夫したり、delay(1);を挿入するなどで対処してください。(特にforやwhileを使うとWDTに噛まれやすいです。)