Last active
April 17, 2024 20:25
-
-
Save gershnik/072d112071d0d61adc495d6f36947d45 to your computer and use it in GitHub Desktop.
How to create an HTTPS server on Arduino Nano RP2040
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (c) 2024, Eugene Gershnik | |
// All rights reserved. | |
// | |
// Redistribution and use in source and binary forms, with or without | |
// modification, are permitted provided that the following conditions are met: | |
// | |
// 1. Redistributions of source code must retain the above copyright notice, this | |
// list of conditions and the following disclaimer. | |
// | |
// 2. Redistributions in binary form must reproduce the above copyright notice, | |
// this list of conditions and the following disclaimer in the documentation | |
// and/or other materials provided with the distribution. | |
// | |
// 3. Neither the name of the copyright holder nor the names of its | |
// contributors may be used to endorse or promote products derived from | |
// this software without specific prior written permission. | |
// | |
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
/****** !!! PLEASE READ !!! ****** | |
The code in this Gist is obsolete. | |
Please use the following examples instead: | |
https://github.com/gershnik/BetterWiFiNINA/blob/mainline/examples/WiFiMbedTLSServer | |
https://github.com/gershnik/BetterWiFiNINA/blob/mainline/examples/WiFiBearSSLServer | |
and see the accompanied article at | |
https://gershnik.github.io/2024/04/17/fast-https-arduino-nano-rp2040.html | |
Original code is below | |
*******************************/ | |
// See https://gershnik.github.io/2024/04/03/https-arduino-nano-rp2040.html | |
// for a detailed description of how this code works | |
//BetterWiFiNINA library | |
//Obtain at https://github.com/gershnik/BetterWiFiNINA | |
#include <BetterWiFiNINA.h> | |
//RP2040 ring oscillator | |
#include <hardware/structs/rosc.h> | |
//Mbed TLS | |
#include <mbedtls/entropy.h> | |
#include <mbedtls/ssl.h> | |
#include <mbedtls/ctr_drbg.h> | |
#include <mbedtls/x509.h> | |
#include <mbedtls/ssl.h> | |
#include <mbedtls/ssl_cache.h> | |
#include <mbedtls/net.h> | |
const char g_ssid[] = "...your WiFi SSID here..."; | |
const char g_pass[] = "...your WiFi password here..."; | |
//Your certificate and private key. | |
//NOTE: both must be NULL terminated strings | |
//even though the functions that loads them take | |
//size as an argument | |
const char g_certificate[] = | |
R"(-----BEGIN CERTIFICATE----- | |
...your certificate here... | |
-----END CERTIFICATE-----)"; | |
const char g_privateKey[] = | |
R"(-----BEGIN PRIVATE KEY----- | |
...your private key here... | |
-----END PRIVATE KEY-----)"; | |
//Our NVRAM :) | |
uint8_t g_nvRandomSeed[MBEDTLS_ENTROPY_BLOCK_SIZE]; | |
//Mbed TLS entroy and random number generator objects | |
mbedtls_entropy_context g_entropy; | |
mbedtls_ctr_drbg_context g_ctr_drbg; | |
//SSL config, server certificate and private key | |
mbedtls_ssl_config g_ssl_conf; | |
mbedtls_x509_crt g_srvcert; | |
mbedtls_pk_context g_pkey; | |
//Server socket | |
WiFiSocket g_sslServerSocket; | |
REDIRECT_STDOUT_TO(Serial) | |
//Mbed TLS callbacks to access "NVRAM" | |
extern "C" { | |
int mbedtls_platform_std_nv_seed_read(uint8_t * buf, size_t buf_len) { | |
size_t toRead = std::min(buf_len, sizeof(g_nvRandomSeed)); | |
memcpy(buf, g_nvRandomSeed, toRead); | |
return toRead; | |
} | |
int mbedtls_platform_std_nv_seed_write(uint8_t * buf, size_t buf_len) { | |
size_t toWrite = std::min(buf_len, sizeof(g_nvRandomSeed)); | |
memcpy(g_nvRandomSeed, buf, toWrite); | |
return toWrite; | |
} | |
} | |
//From: https://www.i-programmer.info/programming/148-hardware/17030-master-the-pico-wifi-random-numbers.html | |
uint8_t getRandomByte() { | |
uint32_t random = 0; | |
uint32_t bit = 0; | |
for (int k = 0; k < 8; k++) { | |
while (true) { | |
bit = rosc_hw->randombit; | |
sleep_us(10); | |
if (bit != rosc_hw->randombit) | |
break; | |
} | |
random = (random << 1) | bit; | |
sleep_us(10); | |
} | |
return uint8_t(random); | |
} | |
void setup() { | |
// check for the WiFi module: | |
if (WiFi.status() == WL_NO_MODULE) { | |
while (true) | |
printf("Communication with WiFi module failed!\n"); | |
// don't continue | |
} | |
String fv = WiFi.firmwareVersion(); | |
if (fv < WIFI_FIRMWARE_LATEST_VERSION) { | |
printf("Please upgrade the firmware\n"); | |
} | |
// attempt to connect to WiFi network: | |
int status = WL_IDLE_STATUS; | |
while (status != WL_CONNECTED) { | |
printf("Attempting to connect to SSID: %s\n", g_ssid); | |
// Connect to WPA/WPA2 network. Change this line if using open or WEP network: | |
status = WiFi.begin(g_ssid, g_pass); | |
// wait 10 seconds for connection: | |
delay(10000); | |
} | |
for(auto & b: g_nvRandomSeed) | |
b = getRandomByte(); | |
mbedtls_entropy_init(&g_entropy); | |
mbedtls_ctr_drbg_init(&g_ctr_drbg); | |
if (auto ret = mbedtls_ctr_drbg_seed(&g_ctr_drbg, mbedtls_entropy_func, &g_entropy, | |
nullptr, 0); ret != 0) { | |
while(true) | |
printf("Random generator init failed: mbedtls_ctr_drbg_seed returned %x\n", ret); | |
} | |
mbedtls_ssl_config_init(&g_ssl_conf); | |
mbedtls_x509_crt_init(&g_srvcert); | |
mbedtls_pk_init(&g_pkey); | |
//load the certificate | |
if (auto ret = mbedtls_x509_crt_parse(&g_srvcert, (const uint8_t *)g_certificate, | |
sizeof(g_certificate)); ret != 0) { | |
while(true) | |
printf("mbedtls_x509_crt_parse failed with %x\n", ret); | |
} | |
//load the private key | |
if (auto ret = mbedtls_pk_parse_key(&g_pkey, (const uint8_t *)g_privateKey, | |
sizeof(g_privateKey), nullptr, 0); ret != 0) { | |
while(true) | |
printf("mbedtls_pk_parse_key failed with %x\n", ret); | |
} | |
//set up SSL config params | |
if (auto ret = mbedtls_ssl_config_defaults(&g_ssl_conf, | |
MBEDTLS_SSL_IS_SERVER, | |
MBEDTLS_SSL_TRANSPORT_STREAM, | |
MBEDTLS_SSL_PRESET_DEFAULT); ret != 0) { | |
while(true) | |
printf("mbedtls_ssl_config_defaults failed with %x\n", ret); | |
} | |
//connect our RNG to SSL config | |
mbedtls_ssl_conf_rng(&g_ssl_conf, mbedtls_ctr_drbg_random, &g_ctr_drbg); | |
//load certificate data into SSL config | |
mbedtls_ssl_conf_ca_chain(&g_ssl_conf, g_srvcert.next, nullptr); | |
if (auto ret = mbedtls_ssl_conf_own_cert(&g_ssl_conf, &g_srvcert, &g_pkey); ret != 0) { | |
while(true) | |
printf("mbedtls_ssl_conf_own_cert failed with %x\n", ret); | |
} | |
//Create a socket | |
g_sslServerSocket = WiFiSocket(WiFiSocket::Type::Stream, WiFiSocket::Protocol::TCP); | |
if (!g_sslServerSocket) { | |
while(true) | |
printf("Creating server socket failed: error %d\n", WiFiSocket::lastError()); | |
} | |
//Bind to port 443 | |
if (!g_sslServerSocket.bind(443)) { | |
while(true) | |
printf("Binding server socket failed: error %d\n", WiFiSocket::lastError()); | |
} | |
//And start listening | |
if (!g_sslServerSocket.listen(5)) { | |
while(true) | |
printf("Listen on server socket failed: error %d\n", WiFiSocket::lastError()); | |
} | |
} | |
int sendTLSCallback(void * ctx, const uint8_t * buf, size_t len) { | |
auto socket = static_cast<WiFiSocket *>(ctx); | |
auto sent = socket->send(buf, len); | |
if (sent < 0) { | |
auto err = WiFiSocket::lastError(); | |
if (err == EWOULDBLOCK) | |
return MBEDTLS_ERR_SSL_WANT_WRITE; | |
printf("writing to socket failed with error: %d\n", err); | |
if (err == ECONNRESET) | |
return MBEDTLS_ERR_NET_CONN_RESET; | |
return MBEDTLS_ERR_NET_SEND_FAILED; | |
} | |
return sent; | |
} | |
int recvTLSCallback(void * ctx, uint8_t * buf, size_t len) { | |
auto socket = static_cast<WiFiSocket *>(ctx); | |
auto read = socket->recv(buf, len); | |
if (read < 0) { | |
auto err = WiFiSocket::lastError(); | |
if (err == EWOULDBLOCK) | |
return MBEDTLS_ERR_SSL_WANT_READ; | |
printf("reading from socket failed with error: %d\n", err); | |
if (err == ECONNRESET) | |
return MBEDTLS_ERR_NET_CONN_RESET; | |
return MBEDTLS_ERR_NET_RECV_FAILED; | |
} | |
return read; | |
} | |
//a simple RAII wrapper over mbedtls_ssl_context | |
struct SSLSessionContext : mbedtls_ssl_context { | |
SSLSessionContext() { mbedtls_ssl_init(this); } | |
~SSLSessionContext() { mbedtls_ssl_free(this); } | |
SSLSessionContext(const SSLSessionContext &) = delete; | |
SSLSessionContext & operator=(const SSLSessionContext &) = delete; | |
}; | |
void loop() { | |
IPAddress addr; | |
uint16_t port; | |
auto sslSessionSocket = g_sslServerSocket.accept(addr, port); | |
if (!sslSessionSocket) { | |
printf("Accept on server socket failed: error %d\n", WiFiSocket::lastError()); | |
delay(100); | |
return; | |
} | |
//set the session socket to non-blocking | |
if (!sslSessionSocket.setNonBlocking(true)) { | |
printf("Setting socket to non-blocking failed: error %d\n", WiFiSocket::lastError()); | |
delay(100); | |
return; | |
} | |
//declare and initialize SSL session context | |
SSLSessionContext sslSessionContext; | |
//connect session context to BIO | |
mbedtls_ssl_set_bio(&sslSessionContext, &sslSessionSocket, sendTLSCallback, recvTLSCallback, nullptr); | |
//and to the SSL config we set up before | |
if (auto ret = mbedtls_ssl_setup(&sslSessionContext, &g_ssl_conf); ret != 0) { | |
printf("mbedtls_ssl_setup returned %x\n", ret); | |
delay(100); | |
return; | |
} | |
//perform handshake | |
while (true) { | |
auto res = mbedtls_ssl_handshake(&sslSessionContext); | |
if (res >= 0) | |
break; | |
if (res != MBEDTLS_ERR_SSL_WANT_READ && res != MBEDTLS_ERR_SSL_WANT_WRITE) { | |
printf("TLS handshake error: %x\n", res); | |
delay(100); | |
return; | |
} | |
} | |
uint8_t buffer[256]; | |
bool currentLineIsBlank = true; | |
bool doneReading = false; | |
//read until \n\r\n | |
while(!doneReading) { | |
auto read = mbedtls_ssl_read(&sslSessionContext, buffer, sizeof(buffer)); | |
if (read < 0) { | |
if (read == MBEDTLS_ERR_SSL_WANT_READ) | |
continue; | |
printf("Read error: %x\n", read); | |
delay(100); | |
return; | |
} | |
for(int i = 0; i != read; ++i) { | |
auto c = buffer[i]; | |
if (c == '\n' && currentLineIsBlank) { | |
doneReading = true; | |
break; | |
} | |
if (c == '\n') { | |
currentLineIsBlank = true; | |
} else if (c != '\r') { | |
currentLineIsBlank = false; | |
} | |
} | |
} | |
//write response | |
static const char response[] = | |
"HTTP/1.1 200 OK\r\n" | |
"Content-Type: text/html\r\n" | |
"Connection: close\r\n" | |
"\r\n" | |
"<!DOCTYPE HTML>\n" | |
"<html><body>Hello World!</html>"; | |
size_t written = 0; | |
while(written != sizeof(response)) { | |
auto sent = mbedtls_ssl_write(&sslSessionContext, | |
(const uint8_t *)response + written, | |
sizeof(response) - written); | |
if (sent < 0) { | |
if (sent == MBEDTLS_ERR_SSL_WANT_WRITE) | |
continue; | |
printf("Write error: %x\n", sent); | |
delay(100); | |
return; | |
} | |
written += sent; | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment