この記事は最終更新日から1年以上が経過しています。

@minwinmin

ESP32からWiFiClientSecureをつかってHTTPS GETをする方法をちょっと理解した

はじめに

ESP32からAPIをGETするとき、HTTPなら簡単にできました。
しかしHTTPSの場合、なかなか上手くいかず手間取ってしまったので、手順を残したいと思います。

手間取った部分

HTTPSの場合、CA証明書をプログラム上に書く必要があるのですが、証明書の取得の仕方がわからず時間がかかりました。

やりかた

プログラムはこちらを参考にしました。
- Arduino – ESP32 WiFiClientSecure ライブラリで、安定して https ( SSL )記事をGETする方法
- ESP32 Arduino: HTTPS GET Request

CA証明書の確認方法

例えば、下記の参考サイトではYahoo! Japan RSSサイトの証明書を取得して、それをプログラムに書きHTTP GETをしています。
Arduino – ESP32 WiFiClientSecure ライブラリで、安定して https ( SSL )記事をGETする方法
しかし、Chromeから証明書を確認する方法が説明されていますが、Chromeの仕様が変わったのか同じように証明書を取得することができませんでした。

ですが、下記の参考サイトではFirefoxから証明書を確認する方法がかかれていました。実際にやってみると、Firefoxで証明書を確認する方がやりやすかった印象です。
Arduino – ESP32 WiFiClientSecure ライブラリで、安定して https ( SSL )記事をGETする方法

Yahoo! Japan RSSサイトの場合

Firefoxでの証明書の確認を行う手順を下記に示します。

  1. URL横にある鍵マーク → 「>」 → 「詳細を表示」の順にクリックしていきます。 1.png

2.その後、ダイアログが出てくるので「セキュリュティー」 → 「証明書を表示」の順にクリックします。
すると、「証明書」とかかれたページが現れます。そのページにはタブが3つ存在します。ここで、どの証明書を取得するかで迷い、1番の右の「*.yahoo.co.jp」を選び証明書取得してプログラムでつかったところHTTPS GETに失敗しました。。
結論から述べると、一番左のタブを選びましょう。
HTTPS GETしたいページと同じ表記のタブにある証明書はどうやら別物のようです。
あまり説明にはなっていませんが...。

2.png

  1. 選択したタブを下にスクロールすると、「PEM(証明書)」という表記があります。これをクリックしダウンロードします。そして、適当なエディタで開くと、下記のよう表示されていることが確認できます。これをESP32に書き込むプログラムに使用します。こちらは、それぞれ異なるデータとなりますので各自でダウンロードして確認してください。

3.png

サンプルコード

参考にしたプログラムは下記に記載されているものです。
コンパイル時にエラーが出たので、少しだけ関数の順番に変更を加えました。
Arduino – ESP32 WiFiClientSecure ライブラリで、安定して https ( SSL )記事をGETする方法

#include <WiFi.h>
#include <WiFiClientSecure.h>
//wifiのSSIDとパスワードを代入
const char* ssid       = "ssid";
const char* password   = "PW";

//取得したYahoo! Japan RSSニュース、ルート証明書を下記のフォーマットで記載する
const char* yahoo_root_ca= \
"-----BEGIN CERTIFICATE-----\n" \
"MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ\n" \
"RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD\n" \
"VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX\n" \
"DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y\n" \
"ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy\n" \
"VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr\n" \
"mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr\n" \
"IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK\n" \
"mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu\n" \
"XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy\n" \
"dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye\n" \
"jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1\n" \
"BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3\n" \
"DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92\n" \
"9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx\n" \
"jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0\n" \
"Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz\n" \
"R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp\n" \
"-----END CERTIFICATE----- \n";

uint32_t WebGet_LastTime = 0;

//***************セットアップ関数**************************
void setup() {
  Serial.begin(115200);
  delay(100);

  Serial.println();
  Serial.print("Attempting to connect to SSID: ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }

  Serial.println();
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.println(WiFi.localIP());
  delay(2000);

  WebGet_LastTime = 200000; //起動時に記事をGETするために、多めの数値で初期化しておく
}

//*****************Yahoo RSSニュースを取得する関数*************
String https_Web_Get(const char* host1, String target_page, char char_tag, String Final_tag, String Begin_tag, String End_tag, String Paragraph){

  String ret_str;

  WiFiClientSecure https_client;
  https_client.setCACert(yahoo_root_ca); //Yahooサイトのルート証明書をセットする

  if (https_client.connect(host1, 443)){
    Serial.print(host1); Serial.print(F("-------------"));
    Serial.println(F("connected"));
    Serial.println(F("-------WEB HTTPS GET Request Send"));

    String str1 = String("GET https://") + String( host1 ) + target_page + " HTTP/1.1\r\n";
           str1 += "Host: " + String( host1 ) + "\r\n";
           str1 += "User-Agent: BuildFailureDetectorESP32\r\n";
           str1 += "Connection: close\r\n\r\n"; //closeを使うと、サーバーの応答後に切断される。最後に空行必要
           str1 += "\0";

    https_client.print(str1); //client.println にしないこと。最後に改行コードをプラスして送ってしまう為
    https_client.flush(); //client出力が終わるまで待つ
    Serial.print(str1);
    Serial.flush(); //シリアル出力が終わるまで待つ

  }else{
    Serial.println(F("------connection failed"));
  }

  if(https_client){
    String dummy_str;
    uint16_t from, to;
    Serial.println(F("-------WEB HTTPS Response Receive"));

    while(https_client.connected()){
      while(https_client.available()) {
        if(dummy_str.indexOf(Final_tag) == -1){          
          dummy_str = https_client.readStringUntil(char_tag);

          if(dummy_str.indexOf(Begin_tag) >= 0){
            from = dummy_str.indexOf(Begin_tag) + Begin_tag.length();
            to = dummy_str.indexOf(End_tag);
            ret_str += Paragraph;
            ret_str += dummy_str.substring(from,to);
            ret_str += "  ";
          }
        }else{
          while(https_client.available()){
            https_client.read(); //サーバーから送られてきた文字を1文字も余さず受信し切ることが大事
            //delay(1);
          }
          delay(10);
          https_client.stop(); //特に重要。コネクションが終わったら必ず stop() しておかないとヒープメモリを食い尽くしてしまう。
          delay(10);
          Serial.println(F("-------Client Stop"));

          break;
        }
        //delay(1);
      }
      //delay(1);
    }
  }

  ret_str += "\0";
  ret_str.replace("&amp;","&"); //XMLソースの場合、半角&が正しく表示されないので、全角に置き換える
  ret_str.replace("&#039;","\'"); //XMLソースの場合、半角アポストロフィーが正しく表示されないので置き換える
  ret_str.replace("&#39;","\'"); //XMLソースの場合、半角アポストロフィーが正しく表示されないので置き換える
  ret_str.replace("&apos;","\'"); //XMLソースの場合、半角アポストロフィーが正しく表示されないので置き換える
  ret_str.replace("&quot;","\""); //XMLソースの場合、ダブルクォーテーションが正しく表示されないので置き換える

  if(ret_str.length() < 20) ret_str = "※ニュース記事を取得できませんでした";

  if(https_client){
    delay(10);
    https_client.stop(); //特に重要。コネクションが終わったら必ず stop() しておかないとヒープメモリを食い尽くしてしまう。
    delay(10);
    Serial.println(F("-------Client Stop"));
  }
  Serial.flush(); //シリアル出力が終わるまで待つ

  return ret_str;
}

//*********************メインループ***************************
void loop() {
  if((millis() - WebGet_LastTime) > 180000){ //180秒(3分)毎に記事取得
    String str = https_Web_Get("news.yahoo.co.jp", "/pickup/rss.xml", '\n', "</rss>", "<title>", "</title>", "◆ ");
    Serial.println(str);
    Serial.flush();
    WebGet_LastTime = millis();
  }
}

これを動作させると、下記のように動作確認がとれました。
詳しいプログラムの解説は参考サイトを確認してください。

image.png

証明書をArduinoに書き込める体裁にする

サンプルコード上の証明書の書き方は改行コードを用いて表記しています。
そのため、ダウンロードした証明書の文字列を少し書き直す必要あります。分量が多いので、コピー&ペースト繰り返すのは手間なので、Excelを使いました。全体の文字列をセルに貼り付け、改行コードなどコピー必要なものを下記の画像のようにつけくわました。
その後、これをArduino IDE上に貼り付けました。

その後、体裁を確認し余計な空白などを削除して整えました。

これはもっと良い方法がありそうですね。

image.png

まとめ

ESP32からHTTPS GETする方法がわかりました。
証明書の取得方法が理解できておらず手間取りましたが、上記のようにすれば他のHTTPSでかかれたAPIも無事たたけそうです。

ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
minwinmin
高専卒で大学生をやっていましたが、今は社会人です。 航空宇宙分野で電気推進の研究をしていました。 現在はあるベンチャー企業で働いています。
dotstudio
全ての人がモノづくりを楽しむ世界を目指して活動しています。 ( https://dotstud.io ) プロトタイピングスクールの運営をしています。

コメント

証明書の取得参考になりました。
結果的に一番右側を参考にして取得、それで接続できました。
情報ありがとう御座います。

0
あなたもコメントしてみませんか :)
ユーザー登録
すでにアカウントを持っている方はログイン
記事投稿イベント開催中
Qiita 10周年記念イベント - 10年後のために今勉強しておきたい技術
~
Qiita 10周年記念イベント - 10年前の自分に伝えたい、勉強しておきたかった技術
~