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

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

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

# やりかた
プログラムはこちらを参考にしました。
- [Arduino – ESP32 WiFiClientSecure ライブラリで、安定して https ( SSL )記事をGETする方法](https://www.mgo-tec.com/blog-entry-arduino-esp32-ssl-stable-root-ca.html/2)
- [ESP32 Arduino: HTTPS GET Request](https://techtutorialsx.com/2017/11/18/esp32-arduino-https-get-request/)

# CA証明書の確認方法
例えば、下記の参考サイトではYahoo! Japan RSSサイトの証明書を取得して、それをプログラムに書きHTTP GETをしています。
[Arduino – ESP32 WiFiClientSecure ライブラリで、安定して https ( SSL )記事をGETする方法](https://www.mgo-tec.com/blog-entry-arduino-esp32-ssl-stable-root-ca.html/2)
しかし、Chromeから証明書を確認する方法が説明されていますが、Chromeの仕様が変わったのか同じように証明書を取得することができませんでした。

ですが、下記の参考サイトではFirefoxから証明書を確認する方法がかかれていました。実際にやってみると、Firefoxで証明書を確認する方がやりやすかった印象です。
[Arduino – ESP32 WiFiClientSecure ライブラリで、安定して https ( SSL )記事をGETする方法](https://www.mgo-tec.com/blog-entry-arduino-esp32-ssl-stable-root-ca.html/2)

## Yahoo! Japan RSSサイトの場合
Firefoxでの証明書の確認を行う手順を下記に示します。

1. URL横にある鍵マーク → 「＞」 → 「詳細を表示」の順にクリックしていきます。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212276/fd9f4bae-30ed-cec2-8589-6731a43e6b83.png)

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

![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212276/1abe1b28-2f59-6027-a0bf-38e131ec2ed4.png)

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

![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212276/e7866a59-e882-4a4f-0fec-7aa62784cf6f.png)

# サンプルコード
参考にしたプログラムは下記に記載されているものです。
コンパイル時にエラーが出たので、少しだけ関数の順番に変更を加えました。
[Arduino – ESP32 WiFiClientSecure ライブラリで、安定して https ( SSL )記事をGETする方法](https://www.mgo-tec.com/blog-entry-arduino-esp32-ssl-stable-root-ca.html/2)

``` c++
#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(); //サーバーから送られてきた文字を１文字も余さず受信し切ることが大事
            //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();
  }
}
```

これを動作させると、下記のように動作確認がとれました。
詳しいプログラムの解説は[参考サイト](https://www.mgo-tec.com/blog-entry-arduino-esp32-ssl-stable-root-ca.html/2)を確認してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212276/3b52584d-0bc7-2a0f-fee5-5ab0630021be.png)


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

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

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212276/082e6276-2b8b-32fa-1c01-df666489ad05.png)

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