Skip to content

Instantly share code, notes, and snippets.

@gershnik
Last active April 17, 2024 20:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gershnik/072d112071d0d61adc495d6f36947d45 to your computer and use it in GitHub Desktop.
Save gershnik/072d112071d0d61adc495d6f36947d45 to your computer and use it in GitHub Desktop.
How to create an HTTPS server on Arduino Nano RP2040
// 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