知的好奇心 for IoT

IoT関連の知的好奇心を探求するブログです

SSLClientは証明書情報を自動で作るサイトが用意されていて、ESP32で標準のWiFiClintSecureよりサイズも小さくEthernetでも使えるのでとっても便利!

電波状況の良くない場所からデータを送信する必要があったため、M5Stack BasicにLANモジュールを付けて有線でデータを送信することにしたんです。

f:id:IntellectualCuriosity:20210710175833j:plain

データの送信先サイトがhttpsアクセスだったので、セキュアクライアントを使うプログラミングをしようと思ったところで気が付きました。

いままでESP32で使っていたWiFiClientSecureライブラリは名前の通りWiFi用だということに!

そのため、LANでセキュアクライアントが使えるライブラリを探すことになり、最終的に使うことにしたのがOPEnSLab-OSU/SSLClientです。

SSLClientはESP8266で馴染みのあるBearSSLベースのセキュアクライアントライブラリで、WiFiClientSecureでは面倒くさくてやる気が起きなかった証明書情報の取得作業を簡略化するサイトが用意されていたのが決め手になりました。

 

SSLClientで使う証明書情報の取得方法

SSLClientで使う証明書情報は次のサイトから取得することができます。

「Domains To Include」に、SSLClientでアクセスしたいサイトのドメイン名(ホスト名)を複数入力して、「Submit」ボタンを押すと...

f:id:IntellectualCuriosity:20210710203317p:plain

このように、そのドメイン名の証明書を発行した認証局のルート認証局の証明書情報を含んだヘッダーファイルが表示されます。

f:id:IntellectualCuriosity:20210710214627p:plain

この例では、ドメインにLINE Notifyの通知用APIサーバー(notify-api.line.me)とMachinistのデータ受信用サーバー(gw.machinist.iij.jp)を指定していて、両方のルート認証局がGlobalSignだったので1つの証明書情報が含まれたヘッダーファイルが表示されています。 

ヘッダーファイルと言っているのは、表示された情報をプログラムから使うときにつぎのようにヘッダーファイルとして読み込むからです。

#include "trust_anchors.h"    // アクセスするサイトのルート証明書情報

SSLClientのサンプルプログラムが 「trust_anchors.h」というファイル名を使用していたので、同じ名前を使っています。

 

WiFiClientとのサイズ比較

今回用意したテストプログラムのスケッチサイズとグローバル変数のサイズを比較した結果が次の表です。

f:id:IntellectualCuriosity:20210711002948p:plain

WiFiClientSecureと比較してスケッチのサイズは132,504バイト(10%)も小さくなっていますが、グローバル変数が7,724バイト(3%)増えています。

スケッチサイズが約132Kバイトも違うっていうのはどういうことなんでしょうね。

グローバル変数のサイズが増えているのは、メディア用とセキュア用のクライアントオブジェクトを2つ作っていることが 影響しているように思います。

オブジェクトを作るプログラムは次のようになっています。

#ifdef USE_WIFICLIENTSECURE
  WiFiClientSecure client;
#endif
#ifdef USE_SSLClient
  WiFiClient wifi_client;
  SSLClient client(wifi_client, TAs, (size_t)TAs_NUM, 2);
#endif
#ifdef USE_ETHERNET
  EthernetClient ethernet_client;
  SSLClient client(ethernet_client, TAs, (size_t)TAs_NUM, 2);
#endif

SSLClientの最後の引数「2」はGPIOの番号です。この番号をアナログ入力にして乱数発生用に使っているそうです。

この番号は何も繋いでいなくても大丈夫です。逆に使用した番号はデジタル出力など他の用途では使用できないので注意が必要です。

 

テストプログラム

テストプログラムは、LINEとMachinistに起動してからの経過秒数60秒ごとに送信するシンプルなものとなっています。

M5Stack Basicでは3種全て、ATOM LiteでWiFiClientSecureとWiFiでのSSLClientの動作確認をしました。M5Stackのライブラリは使用していないので、他のESP32搭載ボードでも動作すると思います。

SSLTest.ino
#define WIFI_SSID       "2.4GHzのSSID"
#define WIFI_PASS       "2.4GHzのSSIDのパスワード"
#define LINE_TOKEN      "LINE Notifyのアクセストークン"
#define MACHINIST_KEY   "MachinistのAPIキー"
#define MACHINIST_ID    "MachinistのエージェントID"

// コメントを外した行のライブラリが有効になります
//#define USE_WIFICLIENTSECURE  // ESP32の標準ライブラリを使う場合
#define USE_SSLClient           // WiFiでSSLClientライブラリを使う場合
//#define USE_ETHERNET          // M5Stack用LANモジュールを使う場合

#ifdef USE_WIFICLIENTSECURE
  #include <WiFiClientSecure.h>
#endif
#ifdef USE_SSLClient
  #include <WiFi.h>
  #include <SSLClient.h>
  #include "trust_anchors.h"    // アクセスするサイトのルート証明書情報
#endif
#ifdef USE_ETHERNET
  #include <Ethernet.h>
  #include <SSLClient.h>
  #include "trust_anchors.h"    // アクセスするサイトのルート証明書情報
#endif

// グローバル変数
time_t now;
#ifdef USE_WIFICLIENTSECURE
  WiFiClientSecure client;
#endif
#ifdef USE_SSLClient
  WiFiClient wifi_client;
  SSLClient client(wifi_client, TAs, (size_t)TAs_NUM, 2);
#endif
#ifdef USE_ETHERNET
  EthernetClient ethernet_client;
  SSLClient client(ethernet_client, TAs, (size_t)TAs_NUM, 2);
#endif

// LINEへ送信
void send2Line() {
  String message = "message=";
  message += String(now);
  message += "秒";

#ifdef USE_WIFICLIENTSECURE
  client.setInsecure();         // 証明書の検証をスキップする
#endif
  client.connect("notify-api.line.me", 443);
  client.println("POST /api/notify HTTP/1.0");
  client.println("Authorization: Bearer " LINE_TOKEN);
  client.println("Content-Type: application/x-www-form-urlencoded");
  client.print("Content-Length: ");
  client.print(strlen(message.c_str()));
  client.print("\r\n\r\n");
  client.print(message.c_str());

  while (!client.available()) {
    delay(10);
  }
  Serial.println(String("レスポンスコード(LINE)     :") + client.readStringUntil('\n'));
  client.stop();
}

// Machinistへ送信
void send2Machinist() {
  String message = "{\"agent_id\": \"" MACHINIST_ID "\",";
  message += "\"metrics\": [";
  message += "{\"name\": \"起動時間\",";
  message += "\"data_point\": {";
  message += "\"value\": " + String(now);
  message += "}}]}";
 
#ifdef USE_WIFICLIENTSECURE
  client.setInsecure();          // 証明書の検証をスキップする
#endif
  client.connect("gw.machinist.iij.jp", 443);
  client.println("POST /endpoint HTTP/1.1");
  client.println("Host: gw.machinist.iij.jp");
  client.println("Authorization: Bearer " MACHINIST_KEY);
  client.println("Content-Type: application/json");
  client.print("Content-Length: ");
  client.print(strlen(message.c_str()));
  client.print("\r\n\r\n");
  client.print(message.c_str());
 
  while (!client.available()) {
    delay(10);
  }
  Serial.println(String("レスポンスコード(Machinist):") + client.readStringUntil('\n'));
  client.stop();
}
 
void setup() {
  Serial.begin(115200);
#ifdef USE_ETHERNET
  Ethernet.init(26);                // GPIO26はW5500のCS(SPI)
  byte mac[6];
  esp_read_mac(mac, ESP_MAC_ETH);   // ESP32のイーサネットMACアドレスを取得
  Ethernet.begin(mac);              // 取得したMACアドレスで初期化
  Serial.println(Ethernet.localIP());
#else
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  Serial.print("Connecting");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(1000);
  }
  Serial.println("Connected");
  Serial.println(WiFi.localIP());
#endif
}
 
void loop() {
  now = time(nullptr);              // 起動してからの時間を取得
  Serial.println("起動時間:" + String(now));
  send2Line();                      // LINEへ送信
  send2Machinist();                 // Machinistへ送信
  delay(60000);
}
trust_anchors.h
#ifndef _CERTIFICATES_H_
#define _CERTIFICATES_H_

#ifdef __cplusplus
extern "C"
{
#endif

/* This file is auto-generated by the pycert_bearssl tool.  Do not change it manually.
 * Certificates are BearSSL br_x509_trust_anchor format.  Included certs:
 *
 * Index:    0
 * Label:    GlobalSign
 * Subject:  CN=GlobalSign,O=GlobalSign,OU=GlobalSign Root CA - R3
 * Domain(s): notify-api.line.me, gw.machinist.iij.jp
 */

#define TAs_NUM 1

static const unsigned char TA_DN0[] = {
    0x30, 0x4c, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
    0x17, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20,
    0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, 0x52, 0x33,
    0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0a, 0x47,
    0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x31, 0x13, 0x30,
    0x11, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x0a, 0x47, 0x6c, 0x6f, 0x62,
    0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e,
};

static const unsigned char TA_RSA_N0[] = {
    0xcc, 0x25, 0x76, 0x90, 0x79, 0x06, 0x78, 0x22, 0x16, 0xf5, 0xc0, 0x83,
    0xb6, 0x84, 0xca, 0x28, 0x9e, 0xfd, 0x05, 0x76, 0x11, 0xc5, 0xad, 0x88,
    0x72, 0xfc, 0x46, 0x02, 0x43, 0xc7, 0xb2, 0x8a, 0x9d, 0x04, 0x5f, 0x24,
    0xcb, 0x2e, 0x4b, 0xe1, 0x60, 0x82, 0x46, 0xe1, 0x52, 0xab, 0x0c, 0x81,
    0x47, 0x70, 0x6c, 0xdd, 0x64, 0xd1, 0xeb, 0xf5, 0x2c, 0xa3, 0x0f, 0x82,
    0x3d, 0x0c, 0x2b, 0xae, 0x97, 0xd7, 0xb6, 0x14, 0x86, 0x10, 0x79, 0xbb,
    0x3b, 0x13, 0x80, 0x77, 0x8c, 0x08, 0xe1, 0x49, 0xd2, 0x6a, 0x62, 0x2f,
    0x1f, 0x5e, 0xfa, 0x96, 0x68, 0xdf, 0x89, 0x27, 0x95, 0x38, 0x9f, 0x06,
    0xd7, 0x3e, 0xc9, 0xcb, 0x26, 0x59, 0x0d, 0x73, 0xde, 0xb0, 0xc8, 0xe9,
    0x26, 0x0e, 0x83, 0x15, 0xc6, 0xef, 0x5b, 0x8b, 0xd2, 0x04, 0x60, 0xca,
    0x49, 0xa6, 0x28, 0xf6, 0x69, 0x3b, 0xf6, 0xcb, 0xc8, 0x28, 0x91, 0xe5,
    0x9d, 0x8a, 0x61, 0x57, 0x37, 0xac, 0x74, 0x14, 0xdc, 0x74, 0xe0, 0x3a,
    0xee, 0x72, 0x2f, 0x2e, 0x9c, 0xfb, 0xd0, 0xbb, 0xbf, 0xf5, 0x3d, 0x00,
    0xe1, 0x06, 0x33, 0xe8, 0x82, 0x2b, 0xae, 0x53, 0xa6, 0x3a, 0x16, 0x73,
    0x8c, 0xdd, 0x41, 0x0e, 0x20, 0x3a, 0xc0, 0xb4, 0xa7, 0xa1, 0xe9, 0xb2,
    0x4f, 0x90, 0x2e, 0x32, 0x60, 0xe9, 0x57, 0xcb, 0xb9, 0x04, 0x92, 0x68,
    0x68, 0xe5, 0x38, 0x26, 0x60, 0x75, 0xb2, 0x9f, 0x77, 0xff, 0x91, 0x14,
    0xef, 0xae, 0x20, 0x49, 0xfc, 0xad, 0x40, 0x15, 0x48, 0xd1, 0x02, 0x31,
    0x61, 0x19, 0x5e, 0xb8, 0x97, 0xef, 0xad, 0x77, 0xb7, 0x64, 0x9a, 0x7a,
    0xbf, 0x5f, 0xc1, 0x13, 0xef, 0x9b, 0x62, 0xfb, 0x0d, 0x6c, 0xe0, 0x54,
    0x69, 0x16, 0xa9, 0x03, 0xda, 0x6e, 0xe9, 0x83, 0x93, 0x71, 0x76, 0xc6,
    0x69, 0x85, 0x82, 0x17,
};

static const unsigned char TA_RSA_E0[] = {
    0x01, 0x00, 0x01,
};

static const br_x509_trust_anchor TAs[] = {
    {
        { (unsigned char *)TA_DN0, sizeof TA_DN0 },
        BR_X509_TA_CA,
        {
            BR_KEYTYPE_RSA,
            { .rsa = {
                (unsigned char *)TA_RSA_N0, sizeof TA_RSA_N0,
                (unsigned char *)TA_RSA_E0, sizeof TA_RSA_E0,
            } }
        }
    },
};

#ifdef __cplusplus
} /* extern "C" */
#endif

#endif /* ifndef _CERTIFICATES_H_ */

LINEのアクセストークンやMachinistのキー・IDの取得方法

LINEのアクセストークンは次のサイトで発行できます。

LINE Notifyのアクセストークン発行画面

f:id:IntellectualCuriosity:20210711023050p:plain

 

MachinistのAPIキーとエージェントIDは次のサイトでアカウント登録すると発行できます。

 APIキー参照画面

f:id:IntellectualCuriosity:20210711024726p:plain

エージェントID参照画面

f:id:IntellectualCuriosity:20210711024831p:plain

 

LINEとMachinistのデータ表示画面

LINE

f:id:IntellectualCuriosity:20210711025622j:plain

Machinist

f:id:IntellectualCuriosity:20210711030138p:plain

 

※2021年8月15日追記

WiFiClientSecure用の簡単な証明書の取得方法

WiFiClientSecure用の簡単なルートCAの証明書の取得方法がありましたので紹介します。

OpenSSLを使いますので、Windowsの人は次の記事を参考にしてインストールしてください。

Macの人はXcodeコマンドラインツールがインストールされている必要があります。

Xcodeコマンドラインツールは次のコマンドでインストールできます。

xcode-select --install

OpenSSLがインストールできたら、次ようにのコマンドを実行します。

(LINE Notify API サーバー(notify-api.line.me)の場合の例です。)

Windowsコマンドプロンプト

openssl s_client -showcerts -verify 5 -connect notify-api.line.me:443 < nul

Macはターミナルで

openssl s_client -showcerts -verify 5 -connect notify-api.line.me:443 < /dev/null

コマンドを実行すると次のように表示されます。 (Windowsの場合)

C:\Users\hiro>openssl s_client -showcerts -verify 5 -connect notify-api.line.me:443 < nul
verify depth is 5
CONNECTED(000001A8)
depth=1 C = BE, O = GlobalSign nv-sa, CN = GlobalSign RSA OV SSL CA 2018
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 C = JP, ST = Tokyo-to, L = Shinjuku-ku, O = LINE Corporation, CN = *.line.me
verify return:1
---
Certificate chain
 0 s:C = JP, ST = Tokyo-to, L = Shinjuku-ku, O = LINE Corporation, CN = *.line.me
   i:C = BE, O = GlobalSign nv-sa, CN = GlobalSign RSA OV SSL CA 2018
-----BEGIN CERTIFICATE-----
MIIGvTCCBaWgAwIBAgIMA5+Eduqn8JC9rUTPMA0GCSqGSIb3DQEBCwUAMFAxCzAJ
BgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSYwJAYDVQQDEx1H
bG9iYWxTaWduIFJTQSBPViBTU0wgQ0EgMjAxODAeFw0yMDA2MTcwNjAxNThaFw0y
MjA5MDUxMjAwMDBaMGUxCzAJBgNVBAYTAkpQMREwDwYDVQQIEwhUb2t5by10bzEU
MBIGA1UEBxMLU2hpbmp1a3Uta3UxGTAXBgNVBAoTEExJTkUgQ29ycG9yYXRpb24x
EjAQBgNVBAMMCSoubGluZS5tZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAMLkCXzXU8mAQajoVb8J80eYBE/qejymyJW2jqEunJIKakFt/fX4mqILUHsi
1auGnoZxXu5tJCfYTbfchibZZ/PiLqSRldJURgIXxub3njrpIcplAc26P1n4y62q
A38XeKiIF80fTols1LNBNXhV2cKj1lRv2SwUTXZcke3noWye1oZ47QFtEuAzEFkp
F9k9r2YdEqjWwuLBicrQEXVbrsRQer8YVJFvt3tpfeWSgQIOB35WReZi1M876+Yq
GHBAW0/ty1Bjg+yR9hNSdrcbzOnzhnsBWantZishoJp/P5o+WhsYn8z1Oxgs7KwW
Htuf59nbQgMkdAwjxugem5b2h48CAwEAAaOCA4AwggN8MA4GA1UdDwEB/wQEAwIF
oDCBjgYIKwYBBQUHAQEEgYEwfzBEBggrBgEFBQcwAoY4aHR0cDovL3NlY3VyZS5n
bG9iYWxzaWduLmNvbS9jYWNlcnQvZ3Nyc2FvdnNzbGNhMjAxOC5jcnQwNwYIKwYB
BQUHMAGGK2h0dHA6Ly9vY3NwLmdsb2JhbHNpZ24uY29tL2dzcnNhb3Zzc2xjYTIw
MTgwVgYDVR0gBE8wTTBBBgkrBgEEAaAyARQwNDAyBggrBgEFBQcCARYmaHR0cHM6
Ly93d3cuZ2xvYmFsc2lnbi5jb20vcmVwb3NpdG9yeS8wCAYGZ4EMAQICMAkGA1Ud
EwQCMAAwHQYDVR0RBBYwFIIJKi5saW5lLm1lggdsaW5lLm1lMB0GA1UdJQQWMBQG
CCsGAQUFBwMBBggrBgEFBQcDAjAfBgNVHSMEGDAWgBT473/yzXhnqN5vjySNiPGH
AwKz6zAdBgNVHQ4EFgQUlMcjP5HQIbB0fruuvxoWlNUpg6QwggH2BgorBgEEAdZ5
AgQCBIIB5gSCAeIB4AB2ACJFRQdZVSRWlj+hL/H3bYbgIyZjrcBLf13Gg1xu4g8C
AAABcsDevRsAAAQDAEcwRQIgKO4iNHwKctJ6i23CcusiS/eRybD7OgmyK6DtVJkZ
nskCIQDMzwX1NU3ySt2E7a3dq6tCbgBzSGpHd56300ntq06/6QB2ACl5vvCeOTkh
8FZzn2Old+W+V32cYAr4+U1dJlwlXceEAAABcsDevcQAAAQDAEcwRQIhAONwL/C8
/sz+UxaLfwfyt+RuQNpK5TO/opHyPK2WcPIkAiAVTLmQL0LT0vIyUX0sJjPZjJ22
l3aC6FG6x803fHxQ/wB3AFWB1MIWkDYBSuoLm1c8U/DA5Dh4cCUIFy+jqh0HE9MM
AAABcsDevckAAAQDAEgwRgIhANwqyszHCK1h0Y19oKD8coAROyUkVm/fcKuDqQqq
5dGJAiEA6MlqAolv+RNuiRhVBA0LNrtHE5HAdHwkRqwecNxirFgAdQBRo7D1/QF5
nFZtuDd4jwykeswbJ8v3nohCmg3+1IsF5QAAAXLA3r3dAAAEAwBGMEQCIGaE5uuM
9iv0U+i7S5+V+Hum1bMquroZ3T427DAJsi5MAiBEXZxZjFab994Ca4xhyqYKEvKP
cHnXBjNqpBdCgpQB8zANBgkqhkiG9w0BAQsFAAOCAQEAfyFH/m+uOMS6zOMp8iag
JTMq6d4UtFMasAEHFbbtyUS8u59gcrsDEPDRfcJbVNDPe+knIc7H8FjTrVVdWz17
Ii+ZaA5R5QWdABfejl82n8qGYuTrJk5UuZN6FTSTsoDbMxBZzcBED+ghD2vB2G6G
w32Ltwail16LsDWNnwLAvd5idS2svU8BlA+O0rhBvIsBX7kFuP4TJbZ3yGjPAYXk
ztj5SGLUenuTj0Yy5CWa6U67Er53x98ezOKkzaO9C0sAiv1M9ux8yK/zA1ggUpSl
7WmRjBf1LoGWFHB66olsSxYd0YwZcBgRTyzmG2S+6zURZPIipFy5OqyOaiAEtWYm
wQ==
-----END CERTIFICATE-----
 1 s:C = BE, O = GlobalSign nv-sa, CN = GlobalSign RSA OV SSL CA 2018
   i:OU = GlobalSign Root CA - R3, O = GlobalSign, CN = GlobalSign
-----BEGIN CERTIFICATE-----
MIIETjCCAzagAwIBAgINAe5fIh38YjvUMzqFVzANBgkqhkiG9w0BAQsFADBMMSAw
HgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFs
U2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjAeFw0xODExMjEwMDAwMDBaFw0yODEx
MjEwMDAwMDBaMFAxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52
LXNhMSYwJAYDVQQDEx1HbG9iYWxTaWduIFJTQSBPViBTU0wgQ0EgMjAxODCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKdaydUMGCEAI9WXD+uu3Vxoa2uP
UGATeoHLl+6OimGUSyZ59gSnKvuk2la77qCk8HuKf1UfR5NhDW5xUTolJAgvjOH3
idaSz6+zpz8w7bXfIa7+9UQX/dhj2S/TgVprX9NHsKzyqzskeU8fxy7quRU6fBhM
abO1IFkJXinDY+YuRluqlJBJDrnw9UqhCS98NE3QvADFBlV5Bs6i0BDxSEPouVq1
lVW9MdIbPYa+oewNEtssmSStR8JvA+Z6cLVwzM0nLKWMjsIYPJLJLnNvBhBWk0Cq
o8VS++XFBdZpaFwGue5RieGKDkFNm5KQConpFmvv73W+eka440eKHRwup08CAwEA
AaOCASkwggElMA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/AgEAMB0G
A1UdDgQWBBT473/yzXhnqN5vjySNiPGHAwKz6zAfBgNVHSMEGDAWgBSP8Et/qC5F
JK5NUPpjmove4t0bvDA+BggrBgEFBQcBAQQyMDAwLgYIKwYBBQUHMAGGImh0dHA6
Ly9vY3NwMi5nbG9iYWxzaWduLmNvbS9yb290cjMwNgYDVR0fBC8wLTAroCmgJ4Yl
aHR0cDovL2NybC5nbG9iYWxzaWduLmNvbS9yb290LXIzLmNybDBHBgNVHSAEQDA+
MDwGBFUdIAAwNDAyBggrBgEFBQcCARYmaHR0cHM6Ly93d3cuZ2xvYmFsc2lnbi5j
b20vcmVwb3NpdG9yeS8wDQYJKoZIhvcNAQELBQADggEBAJmQyC1fQorUC2bbmANz
EdSIhlIoU4r7rd/9c446ZwTbw1MUcBQJfMPg+NccmBqixD7b6QDjynCy8SIwIVbb
0615XoFYC20UgDX1b10d65pHBf9ZjQCxQNqQmJYaumxtf4z1s4DfjGRzNpZ5eWl0
6r/4ngGPoJVpjemEuunl1Ig423g7mNA2eymw0lIYkN5SQwCuaifIFJ6GlazhgDEw
fpolu4usBCOmmQDo8dIm7A9+O4orkjgTHY+GzYZSR+Y0fFukAj6KYXwidlNalFMz
hriSqHKvoflShx8xpfywgVcvzfTO3PYkz6fiNJBonf6q8amaEsybwMbDqKWwIX7e
SPY=
-----END CERTIFICATE-----
---
Server certificate
subject=C = JP, ST = Tokyo-to, L = Shinjuku-ku, O = LINE Corporation, CN = *.line.me

issuer=C = BE, O = GlobalSign nv-sa, CN = GlobalSign RSA OV SSL CA 2018

---
No client certificate CA names sent
Peer signing digest: SHA512
Peer signature type: RSA
Server Temp Key: ECDH, P-256, 256 bits
---
SSL handshake has read 3333 bytes and written 446 bytes
Verification error: unable to get local issuer certificate
---
New, TLSv1.2, Cipher is ECDHE-RSA-AES256-GCM-SHA384
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-RSA-AES256-GCM-SHA384
    Session-ID: A895A59CAA3A3FC4039ACE2C76EC64B771C845A6531812B27027327B85719FC1
    Session-ID-ctx:
    Master-Key: EA0D759B060AD0D567AB0BA115AE5230F9B02E13478044070FD32D101DEC981AC11E37547BE9E00E8A343A6CA96EA780
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    Start Time: 1628990980
    Timeout   : 7200 (sec)
    Verify return code: 20 (unable to get local issuer certificate)
    Extended master secret: no
---
DONE

後に表示された「-----BEGIN CERTIFICATE-----」から「-----END CERTIFICATE-----」がnotify-api.line.meのルートCAの証明書(GlobalSign Root CA)です。

プログラム例

WiFiClientSecureと証明書の扱い方が同じで、WiFiClientSecureよりも簡単にメッセージを送ることができるHTTPClinetを使った例です。

WiFiClinetSecureを使う場合は

  client.setInsecure();         // 証明書の検証をスキップする

を次のようにします。

  client.setCACert(line_root_ca); // ルートCAの証明書をセット

プログラム例

#include <httpclient.h>       // ESP32用HTTPクライアントライブラリ

#define WIFI_SSID   "2.4GHzのSSID"
#define WIFI_PASS   "2.4GHzのSSIDのパスワード"
#define LINE_TOKEN  "LINE Notifyのアクセストークン"

// LINE Notify API サーバーのルートCA(GlobalSign Root CA)の証明書
const char* line_root_ca = \
"-----BEGIN CERTIFICATE-----\n"
"MIIETjCCAzagAwIBAgINAe5fIh38YjvUMzqFVzANBgkqhkiG9w0BAQsFADBMMSAw"
"HgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFs"
"U2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjAeFw0xODExMjEwMDAwMDBaFw0yODEx"
"MjEwMDAwMDBaMFAxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52"
"LXNhMSYwJAYDVQQDEx1HbG9iYWxTaWduIFJTQSBPViBTU0wgQ0EgMjAxODCCASIw"
"DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKdaydUMGCEAI9WXD+uu3Vxoa2uP"
"UGATeoHLl+6OimGUSyZ59gSnKvuk2la77qCk8HuKf1UfR5NhDW5xUTolJAgvjOH3"
"idaSz6+zpz8w7bXfIa7+9UQX/dhj2S/TgVprX9NHsKzyqzskeU8fxy7quRU6fBhM"
"abO1IFkJXinDY+YuRluqlJBJDrnw9UqhCS98NE3QvADFBlV5Bs6i0BDxSEPouVq1"
"lVW9MdIbPYa+oewNEtssmSStR8JvA+Z6cLVwzM0nLKWMjsIYPJLJLnNvBhBWk0Cq"
"o8VS++XFBdZpaFwGue5RieGKDkFNm5KQConpFmvv73W+eka440eKHRwup08CAwEA"
"AaOCASkwggElMA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/AgEAMB0G"
"A1UdDgQWBBT473/yzXhnqN5vjySNiPGHAwKz6zAfBgNVHSMEGDAWgBSP8Et/qC5F"
"JK5NUPpjmove4t0bvDA+BggrBgEFBQcBAQQyMDAwLgYIKwYBBQUHMAGGImh0dHA6"
"Ly9vY3NwMi5nbG9iYWxzaWduLmNvbS9yb290cjMwNgYDVR0fBC8wLTAroCmgJ4Yl"
"aHR0cDovL2NybC5nbG9iYWxzaWduLmNvbS9yb290LXIzLmNybDBHBgNVHSAEQDA+"
"MDwGBFUdIAAwNDAyBggrBgEFBQcCARYmaHR0cHM6Ly93d3cuZ2xvYmFsc2lnbi5j"
"b20vcmVwb3NpdG9yeS8wDQYJKoZIhvcNAQELBQADggEBAJmQyC1fQorUC2bbmANz"
"EdSIhlIoU4r7rd/9c446ZwTbw1MUcBQJfMPg+NccmBqixD7b6QDjynCy8SIwIVbb"
"0615XoFYC20UgDX1b10d65pHBf9ZjQCxQNqQmJYaumxtf4z1s4DfjGRzNpZ5eWl0"
"6r/4ngGPoJVpjemEuunl1Ig423g7mNA2eymw0lIYkN5SQwCuaifIFJ6GlazhgDEw"
"fpolu4usBCOmmQDo8dIm7A9+O4orkjgTHY+GzYZSR+Y0fFukAj6KYXwidlNalFMz"
"hriSqHKvoflShx8xpfywgVcvzfTO3PYkz6fiNJBonf6q8amaEsybwMbDqKWwIX7e"
"SPY=\n"
"-----END CERTIFICATE-----\n";

// LINEへ送信
void send2Line(time_t now) {
  // 送信するメッセージ
  String message = "message=";
  message += String(now);
  message += "秒";

  // LINE Notify APIサーバーにメッセージを送信
  HTTPClient http;
  http.begin("notify-api.line.me", 443, "/api/notify", line_root_ca);
  http.addHeader("Authorization", "Bearer " LINE_TOKEN);
  http.addHeader("Content-Type", "application/x-www-form-urlencoded");
  int sc = http.POST(message.c_str());
  http.end();
  Serial.print("ステータスコード:");
  Serial.println(sc);
}

// ネットワーク接続
void connectNetwork() {
  Serial.print("Connecting " WIFI_SSID);
  WiFi.mode(WIFI_STA);
  WiFi.disconnect(true);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) { // WiFiが繋がるまでループ
    Serial.print(".");
    delay(1000);
  }
  Serial.println(" done.");
  Serial.print("IP Address: ");
  Serial.println(WiFi.localIP()); // IPアドレスを表示
}

void setup() {
  Serial.begin(115200);
  while (!Serial);
  Serial.println();
  connectNetwork();           // ネットワーク接続
}

void loop() {
  time_t now = time(nullptr); // 起動してからの時間を取得
  Serial.println("起動時間:" + String((int)now));
  send2Line(now);             // LINEへ送信
  delay(60*1000);
}

HTTPClientはWiFiClientSecureよりコードが短く簡単ですが、バイナリデータを送れません。