mirror of
https://github.com/Links2004/arduinoWebSockets.git
synced 2025-07-12 15:06:30 +02:00
Add support for CA bundles (#885)
Why: - Allow CA cert bundles to be used This change addresses the need by: - Adding a constructor that takes a pointer to the bundle - Setting the WiFiClientSecure to use the bundle - Adding an example
This commit is contained in:
10
README.md
10
README.md
@ -48,6 +48,16 @@ a WebSocket Server and Client for Arduino based on RFC6455.
|
|||||||
by running the device behind an SSL proxy. See [Nginx](examples/Nginx/esp8266.ssl.reverse.proxy.conf) for a
|
by running the device behind an SSL proxy. See [Nginx](examples/Nginx/esp8266.ssl.reverse.proxy.conf) for a
|
||||||
sample Nginx server configuration file to enable this.
|
sample Nginx server configuration file to enable this.
|
||||||
|
|
||||||
|
### Root CA Cert Bundles for SSL/TLS connections ###
|
||||||
|
|
||||||
|
Secure connections require the certificate of the server to be verified. One option is to provide a single certificate in the chain of trust. However, for flexibility and robustness, a certificate bundle is recommended. If a server changes the root CA from which it derives its certificates, this will not be a problem. With a single CA cert it will not connect.
|
||||||
|
|
||||||
|
- For [technical details](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/protocols/esp_crt_bundle.html)
|
||||||
|
- For a [PlatformIO setup](https://github.com/Duckle29/esp32-certBundle/)
|
||||||
|
- For an [example](examples/esp32/WebSocketClientSSLBundle/)
|
||||||
|
|
||||||
|
Including a bundle with all CA certs will use 77.2 kB but this list can be reduced to 16.5 kB for the 41 most common. This results in 90% absolute usage coverage and 99% market share coverage according to [W3Techs](https://w3techs.com/technologies/overview/ssl_certificate). The bundle is inserted into the compiled firmware. The bundle is not loaded into RAM, only its index.
|
||||||
|
|
||||||
### ESP Async TCP ###
|
### ESP Async TCP ###
|
||||||
|
|
||||||
This libary can run in Async TCP mode on the ESP.
|
This libary can run in Async TCP mode on the ESP.
|
||||||
|
@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* main.cpp
|
||||||
|
*
|
||||||
|
* Created on: 15.06.2024
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <WiFiMulti.h>
|
||||||
|
|
||||||
|
#include <WebSocketsClient.h>
|
||||||
|
|
||||||
|
// Use the incbin library to embedd the cert binary
|
||||||
|
// extern const uint8_t rootca_crt_bundle_start[] asm(
|
||||||
|
// "_binary_data_cert_x509_crt_bundle_bin_start");
|
||||||
|
|
||||||
|
WiFiMulti wifiMulti;
|
||||||
|
WebSocketsClient webSocket;
|
||||||
|
|
||||||
|
#define USE_SERIAL Serial
|
||||||
|
|
||||||
|
void setClock() {
|
||||||
|
configTime(0, 0, "pool.ntp.org", "time.nist.gov");
|
||||||
|
|
||||||
|
USE_SERIAL.print(F("Waiting for NTP time sync: "));
|
||||||
|
time_t nowSecs = time(nullptr);
|
||||||
|
while(nowSecs < 8 * 3600 * 2) {
|
||||||
|
delay(500);
|
||||||
|
USE_SERIAL.print(F("."));
|
||||||
|
yield();
|
||||||
|
nowSecs = time(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
USE_SERIAL.println();
|
||||||
|
struct tm timeinfo;
|
||||||
|
gmtime_r(&nowSecs, &timeinfo);
|
||||||
|
USE_SERIAL.print(F("Current time: "));
|
||||||
|
USE_SERIAL.print(asctime(&timeinfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
void hexdump(const void * mem, uint32_t len, uint8_t cols = 16) {
|
||||||
|
const uint8_t * src = (const uint8_t *)mem;
|
||||||
|
USE_SERIAL.printf("\n[HEXDUMP] Address: 0x%08X len: 0x%X (%d)", (ptrdiff_t)src, len, len);
|
||||||
|
for(uint32_t i = 0; i < len; i++) {
|
||||||
|
if(i % cols == 0) {
|
||||||
|
USE_SERIAL.printf("\n[0x%08X] 0x%08X: ", (ptrdiff_t)src, i);
|
||||||
|
}
|
||||||
|
USE_SERIAL.printf("%02X ", *src);
|
||||||
|
src++;
|
||||||
|
}
|
||||||
|
USE_SERIAL.printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {
|
||||||
|
switch(type) {
|
||||||
|
case WStype_DISCONNECTED:
|
||||||
|
USE_SERIAL.printf("[WSc] Disconnected!\n");
|
||||||
|
break;
|
||||||
|
case WStype_CONNECTED:
|
||||||
|
USE_SERIAL.printf("[WSc] Connected to url: %s\n", payload);
|
||||||
|
|
||||||
|
// send message to server when Connected
|
||||||
|
webSocket.sendTXT("Connected");
|
||||||
|
break;
|
||||||
|
case WStype_TEXT:
|
||||||
|
USE_SERIAL.printf("[WSc] get text: %s\n", payload);
|
||||||
|
|
||||||
|
// send message to server
|
||||||
|
// webSocket.sendTXT("message here");
|
||||||
|
break;
|
||||||
|
case WStype_BIN:
|
||||||
|
USE_SERIAL.printf("[WSc] get binary length: %u\n", length);
|
||||||
|
hexdump(payload, length);
|
||||||
|
|
||||||
|
// send data to server
|
||||||
|
// webSocket.sendBIN(payload, length);
|
||||||
|
break;
|
||||||
|
case WStype_ERROR:
|
||||||
|
case WStype_FRAGMENT_TEXT_START:
|
||||||
|
case WStype_FRAGMENT_BIN_START:
|
||||||
|
case WStype_FRAGMENT:
|
||||||
|
case WStype_FRAGMENT_FIN:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
USE_SERIAL.begin(115200);
|
||||||
|
|
||||||
|
USE_SERIAL.setDebugOutput(true);
|
||||||
|
|
||||||
|
USE_SERIAL.println();
|
||||||
|
USE_SERIAL.println();
|
||||||
|
USE_SERIAL.println();
|
||||||
|
|
||||||
|
for(uint8_t t = 4; t > 0; t--) {
|
||||||
|
USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t);
|
||||||
|
USE_SERIAL.flush();
|
||||||
|
delay(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
wifiMulti.addAP("SSID", "WIFI_PASSPHRASE");
|
||||||
|
|
||||||
|
// WiFi.disconnect();
|
||||||
|
while(wifiMulti.run() != WL_CONNECTED) {
|
||||||
|
delay(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
setClock();
|
||||||
|
|
||||||
|
// server address, port and URL. This server can be flakey.
|
||||||
|
// Expected response: Request served by 0123456789abcdef
|
||||||
|
// webSocket.beginSslWithBundle("echo.websocket.org", 443, "/", rootca_crt_bundle_start, "");
|
||||||
|
webSocket.beginSslWithBundle("echo.websocket.org", 443, "/", NULL, "");
|
||||||
|
|
||||||
|
// event handler
|
||||||
|
webSocket.onEvent(webSocketEvent);
|
||||||
|
|
||||||
|
// use HTTP Basic Authorization this is optional enable if needed
|
||||||
|
// webSocket.setAuthorization("user", "Password");
|
||||||
|
|
||||||
|
// try ever 5000 again if connection has failed
|
||||||
|
webSocket.setReconnectInterval(5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
webSocket.loop();
|
||||||
|
}
|
8
examples/esp32_pio/WebSocketClientSSLBundle/.gitignore
vendored
Normal file
8
examples/esp32_pio/WebSocketClientSSLBundle/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
.pio
|
||||||
|
.vscode/.browse.c_cpp.db*
|
||||||
|
.vscode/c_cpp_properties.json
|
||||||
|
.vscode/launch.json
|
||||||
|
.vscode/ipch
|
||||||
|
*secret*
|
||||||
|
!*secrets.hpp.template
|
||||||
|
*x509_crt_bundle.bin
|
3581
examples/esp32_pio/WebSocketClientSSLBundle/cacrt_all.pem
Normal file
3581
examples/esp32_pio/WebSocketClientSSLBundle/cacrt_all.pem
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,39 @@
|
|||||||
|
Owner,Common Name or Certificate Name
|
||||||
|
Amazon Trust Services,Amazon Root CA 1
|
||||||
|
Amazon Trust Services,Amazon Root CA 2
|
||||||
|
Amazon Trust Services,Amazon Root CA 3
|
||||||
|
Amazon Trust Services,Amazon Root CA 4
|
||||||
|
Amazon Trust Services,Starfield Services Root Certificate Authority - G2
|
||||||
|
DigiCert,Baltimore CyberTrust Root
|
||||||
|
DigiCert,Cybertrust Global Root
|
||||||
|
DigiCert,DigiCert Assured ID Root CA
|
||||||
|
DigiCert,DigiCert Assured ID Root G2
|
||||||
|
DigiCert,DigiCert Assured ID Root G3
|
||||||
|
DigiCert,DigiCert Global Root CA
|
||||||
|
DigiCert,DigiCert Global Root G2
|
||||||
|
DigiCert,DigiCert Global Root G3
|
||||||
|
DigiCert,DigiCert High Assurance EV Root CA
|
||||||
|
DigiCert,DigiCert Trusted Root G4
|
||||||
|
GlobalSign,GlobalSign ECC Root CA - R5
|
||||||
|
GlobalSign,GlobalSign Root CA - R3
|
||||||
|
GlobalSign,GlobalSign Root CA - R6
|
||||||
|
GlobalSign,GlobalSign Root CA
|
||||||
|
GoDaddy,Go Daddy Class 2 CA
|
||||||
|
GoDaddy,Go Daddy Root Certificate Authority - G2
|
||||||
|
GoDaddy,Starfield Class 2 CA
|
||||||
|
GoDaddy,Starfield Root Certificate Authority - G2
|
||||||
|
Google Trust Services LLC (GTS),GlobalSign ECC Root CA - R4
|
||||||
|
Google Trust Services LLC (GTS),GlobalSign Root CA - R2
|
||||||
|
Google Trust Services LLC (GTS),GTS Root R1
|
||||||
|
Google Trust Services LLC (GTS),GTS Root R2
|
||||||
|
Google Trust Services LLC (GTS),GTS Root R3
|
||||||
|
Google Trust Services LLC (GTS),GTS Root R4
|
||||||
|
"IdenTrust Services, LLC",DST Root CA X3
|
||||||
|
"IdenTrust Services, LLC",IdenTrust Commercial Root CA 1
|
||||||
|
"IdenTrust Services, LLC",IdenTrust Public Sector Root CA 1
|
||||||
|
Sectigo,Comodo AAA Services root
|
||||||
|
Sectigo,COMODO Certification Authority
|
||||||
|
Sectigo,COMODO ECC Certification Authority
|
||||||
|
Sectigo,COMODO RSA Certification Authority
|
||||||
|
Sectigo,USERTrust ECC Certification Authority
|
||||||
|
Sectigo,USERTrust RSA Certification Authority
|
|
227
examples/esp32_pio/WebSocketClientSSLBundle/gen_crt_bundle.py
Normal file
227
examples/esp32_pio/WebSocketClientSSLBundle/gen_crt_bundle.py
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# ESP32 x509 certificate bundle generation utility
|
||||||
|
#
|
||||||
|
# Converts PEM and DER certificates to a custom bundle format which stores just the
|
||||||
|
# subject name and public key to reduce space
|
||||||
|
#
|
||||||
|
# The bundle will have the format: number of certificates; crt 1 subject name length; crt 1 public key length;
|
||||||
|
# crt 1 subject name; crt 1 public key; crt 2...
|
||||||
|
#
|
||||||
|
# Copyright 2018-2019 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http:#www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
from io import open
|
||||||
|
|
||||||
|
try:
|
||||||
|
from cryptography import x509
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
from cryptography.hazmat.primitives import serialization
|
||||||
|
except ImportError:
|
||||||
|
print('The cryptography package is not installed.'
|
||||||
|
'Please refer to the Get Started section of the ESP-IDF Programming Guide for '
|
||||||
|
'setting up the required packages.')
|
||||||
|
raise
|
||||||
|
|
||||||
|
ca_bundle_bin_file = 'x509_crt_bundle'
|
||||||
|
|
||||||
|
quiet = False
|
||||||
|
|
||||||
|
|
||||||
|
def status(msg):
|
||||||
|
""" Print status message to stderr """
|
||||||
|
if not quiet:
|
||||||
|
critical(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def critical(msg):
|
||||||
|
""" Print critical message to stderr """
|
||||||
|
sys.stderr.write('gen_crt_bundle.py: ')
|
||||||
|
sys.stderr.write(msg)
|
||||||
|
sys.stderr.write('\n')
|
||||||
|
|
||||||
|
|
||||||
|
class CertificateBundle:
|
||||||
|
def __init__(self):
|
||||||
|
self.certificates = []
|
||||||
|
self.compressed_crts = []
|
||||||
|
|
||||||
|
if os.path.isfile(ca_bundle_bin_file):
|
||||||
|
os.remove(ca_bundle_bin_file)
|
||||||
|
|
||||||
|
def add_from_path(self, crts_path):
|
||||||
|
|
||||||
|
found = False
|
||||||
|
for file_path in os.listdir(crts_path):
|
||||||
|
found |= self.add_from_file(os.path.join(crts_path, file_path))
|
||||||
|
|
||||||
|
if found is False:
|
||||||
|
raise InputError('No valid x509 certificates found in %s' % crts_path)
|
||||||
|
|
||||||
|
def add_from_file(self, file_path):
|
||||||
|
try:
|
||||||
|
if file_path.endswith('.pem'):
|
||||||
|
status('Parsing certificates from %s' % file_path)
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
crt_str = f.read()
|
||||||
|
self.add_from_pem(crt_str)
|
||||||
|
return True
|
||||||
|
|
||||||
|
elif file_path.endswith('.der'):
|
||||||
|
status('Parsing certificates from %s' % file_path)
|
||||||
|
with open(file_path, 'rb') as f:
|
||||||
|
crt_str = f.read()
|
||||||
|
self.add_from_der(crt_str)
|
||||||
|
return True
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
critical('Invalid certificate in %s' % file_path)
|
||||||
|
raise InputError('Invalid certificate')
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def add_from_pem(self, crt_str):
|
||||||
|
""" A single PEM file may have multiple certificates """
|
||||||
|
|
||||||
|
crt = ''
|
||||||
|
count = 0
|
||||||
|
start = False
|
||||||
|
|
||||||
|
for strg in crt_str.splitlines(True):
|
||||||
|
if strg == '-----BEGIN CERTIFICATE-----\n' and start is False:
|
||||||
|
crt = ''
|
||||||
|
start = True
|
||||||
|
elif strg == '-----END CERTIFICATE-----\n' and start is True:
|
||||||
|
crt += strg + '\n'
|
||||||
|
start = False
|
||||||
|
self.certificates.append(x509.load_pem_x509_certificate(crt.encode(), default_backend()))
|
||||||
|
count += 1
|
||||||
|
if start is True:
|
||||||
|
crt += strg
|
||||||
|
|
||||||
|
if(count == 0):
|
||||||
|
raise InputError('No certificate found')
|
||||||
|
|
||||||
|
status('Successfully added %d certificates' % count)
|
||||||
|
|
||||||
|
def add_from_der(self, crt_str):
|
||||||
|
self.certificates.append(x509.load_der_x509_certificate(crt_str, default_backend()))
|
||||||
|
status('Successfully added 1 certificate')
|
||||||
|
|
||||||
|
def create_bundle(self):
|
||||||
|
# Sort certificates in order to do binary search when looking up certificates
|
||||||
|
self.certificates = sorted(self.certificates, key=lambda cert: cert.subject.public_bytes(default_backend()))
|
||||||
|
|
||||||
|
bundle = struct.pack('>H', len(self.certificates))
|
||||||
|
|
||||||
|
for crt in self.certificates:
|
||||||
|
""" Read the public key as DER format """
|
||||||
|
pub_key = crt.public_key()
|
||||||
|
pub_key_der = pub_key.public_bytes(serialization.Encoding.DER, serialization.PublicFormat.SubjectPublicKeyInfo)
|
||||||
|
|
||||||
|
""" Read the subject name as DER format """
|
||||||
|
sub_name_der = crt.subject.public_bytes(default_backend())
|
||||||
|
|
||||||
|
name_len = len(sub_name_der)
|
||||||
|
key_len = len(pub_key_der)
|
||||||
|
len_data = struct.pack('>HH', name_len, key_len)
|
||||||
|
|
||||||
|
bundle += len_data
|
||||||
|
bundle += sub_name_der
|
||||||
|
bundle += pub_key_der
|
||||||
|
|
||||||
|
return bundle
|
||||||
|
|
||||||
|
def add_with_filter(self, crts_path, filter_path):
|
||||||
|
|
||||||
|
filter_set = set()
|
||||||
|
with open(filter_path, 'r', encoding='utf-8') as f:
|
||||||
|
csv_reader = csv.reader(f, delimiter=',')
|
||||||
|
|
||||||
|
# Skip header
|
||||||
|
next(csv_reader)
|
||||||
|
for row in csv_reader:
|
||||||
|
filter_set.add(row[1])
|
||||||
|
|
||||||
|
status('Parsing certificates from %s' % crts_path)
|
||||||
|
crt_str = []
|
||||||
|
with open(crts_path, 'r', encoding='utf-8') as f:
|
||||||
|
crt_str = f.read()
|
||||||
|
|
||||||
|
# Split all certs into a list of (name, certificate string) tuples
|
||||||
|
pem_crts = re.findall(r'(^.+?)\n(=+\n[\s\S]+?END CERTIFICATE-----\n)', crt_str, re.MULTILINE)
|
||||||
|
|
||||||
|
filtered_crts = ''
|
||||||
|
for name, crt in pem_crts:
|
||||||
|
if name in filter_set:
|
||||||
|
filtered_crts += crt
|
||||||
|
|
||||||
|
self.add_from_pem(filtered_crts)
|
||||||
|
|
||||||
|
|
||||||
|
class InputError(RuntimeError):
|
||||||
|
def __init__(self, e):
|
||||||
|
super(InputError, self).__init__(e)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
global quiet
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description='ESP-IDF x509 certificate bundle utility')
|
||||||
|
|
||||||
|
parser.add_argument('--quiet', '-q', help="Don't print non-critical status messages to stderr", action='store_true')
|
||||||
|
parser.add_argument('--input', '-i', nargs='+', required=True,
|
||||||
|
help='Paths to the custom certificate folders or files to parse, parses all .pem or .der files')
|
||||||
|
parser.add_argument('--filter', '-f', help='Path to CSV-file where the second columns contains the name of the certificates \
|
||||||
|
that should be included from cacrt_all.pem')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
quiet = args.quiet
|
||||||
|
|
||||||
|
bundle = CertificateBundle()
|
||||||
|
|
||||||
|
for path in args.input:
|
||||||
|
if os.path.isfile(path):
|
||||||
|
if os.path.basename(path) == 'cacrt_all.pem' and args.filter:
|
||||||
|
bundle.add_with_filter(path, args.filter)
|
||||||
|
else:
|
||||||
|
bundle.add_from_file(path)
|
||||||
|
elif os.path.isdir(path):
|
||||||
|
bundle.add_from_path(path)
|
||||||
|
else:
|
||||||
|
raise InputError('Invalid --input=%s, is neither file nor folder' % args.input)
|
||||||
|
|
||||||
|
status('Successfully added %d certificates in total' % len(bundle.certificates))
|
||||||
|
|
||||||
|
crt_bundle = bundle.create_bundle()
|
||||||
|
|
||||||
|
with open(ca_bundle_bin_file, 'wb') as f:
|
||||||
|
f.write(crt_bundle)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except InputError as e:
|
||||||
|
print(e)
|
||||||
|
sys.exit(2)
|
46
examples/esp32_pio/WebSocketClientSSLBundle/lib/README
Normal file
46
examples/esp32_pio/WebSocketClientSSLBundle/lib/README
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
|
||||||
|
This directory is intended for project specific (private) libraries.
|
||||||
|
PlatformIO will compile them to static libraries and link into executable file.
|
||||||
|
|
||||||
|
The source code of each library should be placed in a an own separate directory
|
||||||
|
("lib/your_library_name/[here are source files]").
|
||||||
|
|
||||||
|
For example, see a structure of the following two libraries `Foo` and `Bar`:
|
||||||
|
|
||||||
|
|--lib
|
||||||
|
| |
|
||||||
|
| |--Bar
|
||||||
|
| | |--docs
|
||||||
|
| | |--examples
|
||||||
|
| | |--src
|
||||||
|
| | |- Bar.c
|
||||||
|
| | |- Bar.h
|
||||||
|
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
||||||
|
| |
|
||||||
|
| |--Foo
|
||||||
|
| | |- Foo.c
|
||||||
|
| | |- Foo.h
|
||||||
|
| |
|
||||||
|
| |- README --> THIS FILE
|
||||||
|
|
|
||||||
|
|- platformio.ini
|
||||||
|
|--src
|
||||||
|
|- main.c
|
||||||
|
|
||||||
|
and a contents of `src/main.c`:
|
||||||
|
```
|
||||||
|
#include <Foo.h>
|
||||||
|
#include <Bar.h>
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
PlatformIO Library Dependency Finder will find automatically dependent
|
||||||
|
libraries scanning project source files.
|
||||||
|
|
||||||
|
More information about PlatformIO Library Dependency Finder
|
||||||
|
- https://docs.platformio.org/page/librarymanager/ldf.html
|
25
examples/esp32_pio/WebSocketClientSSLBundle/platformio.ini
Normal file
25
examples/esp32_pio/WebSocketClientSSLBundle/platformio.ini
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
; PlatformIO Project Configuration File
|
||||||
|
;
|
||||||
|
; Build options: build flags, source filter
|
||||||
|
; Upload options: custom upload port, speed and extra flags
|
||||||
|
; Library options: dependencies, extra library storages
|
||||||
|
; Advanced options: extra scripting
|
||||||
|
;
|
||||||
|
; Please visit documentation for the other options and examples
|
||||||
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
|
[env:esp32dev]
|
||||||
|
platform = espressif32
|
||||||
|
board = esp32dev
|
||||||
|
framework = arduino
|
||||||
|
monitor_speed = 115200
|
||||||
|
upload_speed = 921600
|
||||||
|
build_flags =
|
||||||
|
-DCORE_DEBUG_LEVEL=5
|
||||||
|
lib_deps = ../../../src
|
||||||
|
|
||||||
|
extra_scripts =
|
||||||
|
pre:run_gen_script.py
|
||||||
|
|
||||||
|
board_build.embed_txtfiles =
|
||||||
|
data/cert/x509_crt_bundle.bin
|
12
examples/esp32_pio/WebSocketClientSSLBundle/readme.md
Normal file
12
examples/esp32_pio/WebSocketClientSSLBundle/readme.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
This is a PlatformIO project that uses a modified WiFiClientSecure library (in `lib`) to
|
||||||
|
implement proper SSL support using root certificates as discussed
|
||||||
|
[here](https://github.com/espressif/arduino-esp32/issues/3646#issuecomment-648292677)
|
||||||
|
|
||||||
|
It is based on the work by [meltdonw03](https://github.com/meltdown03) in that thread, and the
|
||||||
|
[BasicHttpsClient example](https://github.com/espressif/arduino-esp32/blob/1.0.4/libraries/HTTPClient/examples/BasicHttpsClient/BasicHttpsClient.ino) from the arduino-esp32 project.
|
||||||
|
|
||||||
|
Just copy `include/secrets.hpp.template` to `include/secrets.hpp` and fill in your WiFi details.
|
||||||
|
Then it should be pretty much ready to go. The local WiFiClientSecure library should take priority.
|
||||||
|
Debug is set to verbose, so you'll see a lot of noise, but there should also be this readme on success :)
|
||||||
|
|
||||||
|
To get a current CA cert bundle download it from [curl's website](https://curl.se/docs/caextract.html).
|
@ -0,0 +1,6 @@
|
|||||||
|
Import("env")
|
||||||
|
|
||||||
|
env.Execute("$PYTHONEXE -m pip install cryptography")
|
||||||
|
env.Execute("$PYTHONEXE gen_crt_bundle.py --input cacrt_all.pem")
|
||||||
|
env.Execute("mkdir -p data/cert")
|
||||||
|
env.Execute("mv -f x509_crt_bundle data/cert/x509_crt_bundle.bin")
|
127
examples/esp32_pio/WebSocketClientSSLBundle/src/main.cpp
Normal file
127
examples/esp32_pio/WebSocketClientSSLBundle/src/main.cpp
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* main.cpp
|
||||||
|
*
|
||||||
|
* Created on: 15.06.2024
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <WiFiMulti.h>
|
||||||
|
|
||||||
|
#include <WebSocketsClient.h>
|
||||||
|
|
||||||
|
extern const uint8_t rootca_crt_bundle_start[] asm(
|
||||||
|
"_binary_data_cert_x509_crt_bundle_bin_start");
|
||||||
|
|
||||||
|
WiFiMulti wifiMulti;
|
||||||
|
WebSocketsClient webSocket;
|
||||||
|
|
||||||
|
#define USE_SERIAL Serial
|
||||||
|
|
||||||
|
void setClock() {
|
||||||
|
configTime(0, 0, "pool.ntp.org", "time.nist.gov");
|
||||||
|
|
||||||
|
USE_SERIAL.print(F("Waiting for NTP time sync: "));
|
||||||
|
time_t nowSecs = time(nullptr);
|
||||||
|
while(nowSecs < 8 * 3600 * 2) {
|
||||||
|
delay(500);
|
||||||
|
USE_SERIAL.print(F("."));
|
||||||
|
yield();
|
||||||
|
nowSecs = time(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
USE_SERIAL.println();
|
||||||
|
struct tm timeinfo;
|
||||||
|
gmtime_r(&nowSecs, &timeinfo);
|
||||||
|
USE_SERIAL.print(F("Current time: "));
|
||||||
|
USE_SERIAL.print(asctime(&timeinfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
void hexdump(const void * mem, uint32_t len, uint8_t cols = 16) {
|
||||||
|
const uint8_t * src = (const uint8_t *)mem;
|
||||||
|
USE_SERIAL.printf("\n[HEXDUMP] Address: 0x%08X len: 0x%X (%d)", (ptrdiff_t)src, len, len);
|
||||||
|
for(uint32_t i = 0; i < len; i++) {
|
||||||
|
if(i % cols == 0) {
|
||||||
|
USE_SERIAL.printf("\n[0x%08X] 0x%08X: ", (ptrdiff_t)src, i);
|
||||||
|
}
|
||||||
|
USE_SERIAL.printf("%02X ", *src);
|
||||||
|
src++;
|
||||||
|
}
|
||||||
|
USE_SERIAL.printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {
|
||||||
|
switch(type) {
|
||||||
|
case WStype_DISCONNECTED:
|
||||||
|
USE_SERIAL.printf("[WSc] Disconnected!\n");
|
||||||
|
break;
|
||||||
|
case WStype_CONNECTED:
|
||||||
|
USE_SERIAL.printf("[WSc] Connected to url: %s\n", payload);
|
||||||
|
|
||||||
|
// send message to server when Connected
|
||||||
|
webSocket.sendTXT("Connected");
|
||||||
|
break;
|
||||||
|
case WStype_TEXT:
|
||||||
|
USE_SERIAL.printf("[WSc] get text: %s\n", payload);
|
||||||
|
|
||||||
|
// send message to server
|
||||||
|
// webSocket.sendTXT("message here");
|
||||||
|
break;
|
||||||
|
case WStype_BIN:
|
||||||
|
USE_SERIAL.printf("[WSc] get binary length: %u\n", length);
|
||||||
|
hexdump(payload, length);
|
||||||
|
|
||||||
|
// send data to server
|
||||||
|
// webSocket.sendBIN(payload, length);
|
||||||
|
break;
|
||||||
|
case WStype_ERROR:
|
||||||
|
case WStype_FRAGMENT_TEXT_START:
|
||||||
|
case WStype_FRAGMENT_BIN_START:
|
||||||
|
case WStype_FRAGMENT:
|
||||||
|
case WStype_FRAGMENT_FIN:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
USE_SERIAL.begin(115200);
|
||||||
|
|
||||||
|
USE_SERIAL.setDebugOutput(true);
|
||||||
|
|
||||||
|
USE_SERIAL.println();
|
||||||
|
USE_SERIAL.println();
|
||||||
|
USE_SERIAL.println();
|
||||||
|
|
||||||
|
for(uint8_t t = 4; t > 0; t--) {
|
||||||
|
USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t);
|
||||||
|
USE_SERIAL.flush();
|
||||||
|
delay(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
wifiMulti.addAP("SSID", "WIFI_PASSPHRASE");
|
||||||
|
|
||||||
|
// WiFi.disconnect();
|
||||||
|
while(wifiMulti.run() != WL_CONNECTED) {
|
||||||
|
delay(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
setClock();
|
||||||
|
|
||||||
|
// server address, port and URL. This server can be flakey.
|
||||||
|
// Expected response: Request served by 0123456789abcdef
|
||||||
|
webSocket.beginSslWithBundle("echo.websocket.org", 443, "/", rootca_crt_bundle_start, "");
|
||||||
|
|
||||||
|
// event handler
|
||||||
|
webSocket.onEvent(webSocketEvent);
|
||||||
|
|
||||||
|
// use HTTP Basic Authorization this is optional enable if needed
|
||||||
|
// webSocket.setAuthorization("user", "Password");
|
||||||
|
|
||||||
|
// try ever 5000 again if connection has failed
|
||||||
|
webSocket.setReconnectInterval(5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
webSocket.loop();
|
||||||
|
}
|
@ -48,6 +48,9 @@ void WebSocketsClient::begin(const char * host, uint16_t port, const char * url,
|
|||||||
#if defined(HAS_SSL)
|
#if defined(HAS_SSL)
|
||||||
_fingerprint = SSL_FINGERPRINT_NULL;
|
_fingerprint = SSL_FINGERPRINT_NULL;
|
||||||
_CA_cert = NULL;
|
_CA_cert = NULL;
|
||||||
|
#ifdef ESP32
|
||||||
|
_CA_bundle = NULL;
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
_client.num = 0;
|
_client.num = 0;
|
||||||
@ -107,6 +110,7 @@ void WebSocketsClient::beginSSL(const char * host, uint16_t port, const char * u
|
|||||||
_client.isSSL = true;
|
_client.isSSL = true;
|
||||||
_fingerprint = fingerprint;
|
_fingerprint = fingerprint;
|
||||||
_CA_cert = NULL;
|
_CA_cert = NULL;
|
||||||
|
_CA_bundle = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebSocketsClient::beginSSL(String host, uint16_t port, String url, String fingerprint, String protocol) {
|
void WebSocketsClient::beginSSL(String host, uint16_t port, String url, String fingerprint, String protocol) {
|
||||||
@ -118,7 +122,16 @@ void WebSocketsClient::beginSslWithCA(const char * host, uint16_t port, const ch
|
|||||||
_client.isSSL = true;
|
_client.isSSL = true;
|
||||||
_fingerprint = SSL_FINGERPRINT_NULL;
|
_fingerprint = SSL_FINGERPRINT_NULL;
|
||||||
_CA_cert = CA_cert;
|
_CA_cert = CA_cert;
|
||||||
|
_CA_bundle = NULL;
|
||||||
}
|
}
|
||||||
|
void WebSocketsClient::beginSslWithBundle(const char * host, uint16_t port, const char * url, const uint8_t * CA_bundle, const char * protocol) {
|
||||||
|
begin(host, port, url, protocol);
|
||||||
|
_client.isSSL = true;
|
||||||
|
_fingerprint = SSL_FINGERPRINT_NULL;
|
||||||
|
_CA_cert = NULL;
|
||||||
|
_CA_bundle = CA_bundle;
|
||||||
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
void WebSocketsClient::beginSSL(const char * host, uint16_t port, const char * url, const uint8_t * fingerprint, const char * protocol) {
|
void WebSocketsClient::beginSSL(const char * host, uint16_t port, const char * url, const uint8_t * fingerprint, const char * protocol) {
|
||||||
begin(host, port, url, protocol);
|
begin(host, port, url, protocol);
|
||||||
@ -231,6 +244,11 @@ void WebSocketsClient::loop(void) {
|
|||||||
#else
|
#else
|
||||||
#error setCACert not implemented
|
#error setCACert not implemented
|
||||||
#endif
|
#endif
|
||||||
|
#if defined(ESP32)
|
||||||
|
} else if(_CA_bundle) {
|
||||||
|
DEBUG_WEBSOCKETS("[WS-Client] setting CA bundle");
|
||||||
|
_client.ssl->setCACertBundle(_CA_bundle);
|
||||||
|
#endif
|
||||||
#if defined(ESP32)
|
#if defined(ESP32)
|
||||||
} else if(!SSL_FINGERPRINT_IS_SET) {
|
} else if(!SSL_FINGERPRINT_IS_SET) {
|
||||||
_client.ssl->setInsecure();
|
_client.ssl->setInsecure();
|
||||||
|
@ -53,6 +53,9 @@ class WebSocketsClient : protected WebSockets {
|
|||||||
void setSSLClientCertKey(const char * clientCert = NULL, const char * clientPrivateKey = NULL);
|
void setSSLClientCertKey(const char * clientCert = NULL, const char * clientPrivateKey = NULL);
|
||||||
#endif
|
#endif
|
||||||
void beginSslWithCA(const char * host, uint16_t port, const char * url = "/", const char * CA_cert = NULL, const char * protocol = "arduino");
|
void beginSslWithCA(const char * host, uint16_t port, const char * url = "/", const char * CA_cert = NULL, const char * protocol = "arduino");
|
||||||
|
#ifdef ESP32
|
||||||
|
void beginSslWithBundle(const char * host, uint16_t port, const char * url = "/", const uint8_t * CA_bundle = NULL, const char * protocol = "arduino");
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void beginSocketIO(const char * host, uint16_t port, const char * url = "/socket.io/?EIO=3", const char * protocol = "arduino");
|
void beginSocketIO(const char * host, uint16_t port, const char * url = "/socket.io/?EIO=3", const char * protocol = "arduino");
|
||||||
@ -112,6 +115,7 @@ class WebSocketsClient : protected WebSockets {
|
|||||||
#ifdef SSL_AXTLS
|
#ifdef SSL_AXTLS
|
||||||
String _fingerprint;
|
String _fingerprint;
|
||||||
const char * _CA_cert;
|
const char * _CA_cert;
|
||||||
|
const uint8_t * _CA_bundle;
|
||||||
#define SSL_FINGERPRINT_IS_SET (_fingerprint.length())
|
#define SSL_FINGERPRINT_IS_SET (_fingerprint.length())
|
||||||
#define SSL_FINGERPRINT_NULL ""
|
#define SSL_FINGERPRINT_NULL ""
|
||||||
#else
|
#else
|
||||||
|
Reference in New Issue
Block a user