SPIFFS and self-sign fixes

This commit is contained in:
iranl
2025-02-18 15:27:03 +01:00
parent 21d5491e50
commit 3fa605b2b0
8 changed files with 211 additions and 196 deletions

View File

@@ -1 +1 @@
# Espressif ESP32 Partition Table
# Espressif ESP32 Partition Table
1 # Espressif ESP32 Partition Table # Name Type SubType Offset Size Flags nvs data nvs 0x9000 0x5000 otadata data ota 0xe000 0x2000 app0 app ota_0 0x10000 0x270000 app1 app ota_1 0x280000 0x150000 0x130000 spiffs data spiffs 0x3D0000 0x3B0000 0x20000 0x40000 coredump data coredump 0x3F0000 0x10000

View File

@@ -3,6 +3,7 @@ CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE=y
CONFIG_SPIFFS_GC_MAX_RUNS=512
# ARDUINO
CONFIG_AUTOSTART_ARDUINO=y

View File

@@ -5,7 +5,7 @@
#define NUKI_HUB_VERSION "9.09"
#define NUKI_HUB_VERSION_INT (uint32_t)909
#define NUKI_HUB_BUILD "unknownbuildnr"
#define NUKI_HUB_DATE "2025-02-16"
#define NUKI_HUB_DATE "2025-02-18"
#define GITHUB_LATEST_RELEASE_URL (char*)"https://github.com/technyon/nuki_hub/releases/latest"
#define GITHUB_OTA_MANIFEST_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/manifest.json"

View File

@@ -5948,6 +5948,11 @@ esp_err_t WebCfgServer::buildInfoHtml(PsychicRequest *request, PsychicResponse*
response.print(uxTaskGetStackHighWaterMark(networkTaskHandle));
response.print("\nNuki task stack high watermark: ");
response.print(uxTaskGetStackHighWaterMark(nukiTaskHandle));
SPIFFS.begin(true);
response.print("\n\n------------ SPIFFS ------------");
response.printf("\nSPIFFS Total Bytes: %u", SPIFFS.totalBytes());
response.printf("\nSPIFFS Used Bytes: %u", SPIFFS.usedBytes());
response.printf("\nSPIFFS Free Bytes: %u", SPIFFS.totalBytes() - SPIFFS.usedBytes());
response.print("\n\n------------ GENERAL SETTINGS ------------");
response.print("\nNetwork task stack size: ");
response.print(_preferences->getInt(preference_task_size_network, NETWORK_TASK_SIZE));
@@ -7036,8 +7041,6 @@ void WebCfgServer::createSSLCertificate()
"20250101000000",
"20350101000000"
);
bool crtSuccess = false;
bool keySuccess = false;
if (createCertResult == 0) {
if (!SPIFFS.begin(true)) {
@@ -7051,14 +7054,10 @@ void WebCfgServer::createSSLCertificate()
}
else
{
if (!file.write((byte *)cert->getCertData(), cert->getCertLength()))
if (!file.print(cert->getCertPEM()))
{
Log->println("Failed to write /http_ssl.crt");
}
else
{
crtSuccess = true;
}
file.close();
}
@@ -7068,18 +7067,19 @@ void WebCfgServer::createSSLCertificate()
}
else
{
if (!file2.write((byte *)cert->getPKData(), cert->getPKLength()))
if (!file2.print(cert->getKeyPEM()))
{
Log->println("Failed to write /http_ssl.key");
}
else
{
keySuccess = true;
}
file2.close();
}
}
}
else
{
Log->print("SSL Self sign failed: ");
Log->println(createCertResult);
}
}
#endif

View File

@@ -185,6 +185,43 @@ uint8_t checkPartition()
}
}
void listDir(fs::FS &fs, const char *dirname, uint8_t levels) {
Serial.printf("Listing directory: %s\r\n", dirname);
File root = fs.open(dirname);
if (!root) {
Serial.println("- failed to open directory");
return;
}
if (!root.isDirectory()) {
Serial.println(" - not a directory");
return;
}
File file = root.openNextFile();
while (file) {
if (file.isDirectory()) {
Serial.print(" DIR : ");
Serial.println(file.name());
if (levels) {
listDir(fs, file.path(), levels - 1);
}
} else {
Serial.print(" FILE: ");
Serial.print(file.name());
Serial.print("\tSIZE: ");
Serial.println(file.size());
}
if (file.size() > (int)(SPIFFS.totalBytes() * 0.4))
{
SPIFFS.remove((String)"/" + file.name());
}
file = root.openNextFile();
}
}
void cbSyncTime(struct timeval *tv) {
Log->println("NTP time synced");
timeSynced = true;
@@ -635,6 +672,11 @@ void setup()
{
logCoreDump();
}
if (SPIFFS.begin(true))
{
listDir(SPIFFS, "/", 1);
}
uint8_t partitionType = checkPartition();

View File

@@ -1,10 +1,34 @@
/*
MIT License
Copyright (c) 2017 Frank Hessel
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "SSLCert.hpp"
SSLCert::SSLCert(unsigned char * certData, uint16_t certLength, unsigned char * pkData, uint16_t pkLength):
SSLCert::SSLCert(uint16_t certLength, uint16_t pkLength, String keyPEM, String certPEM):
_certLength(certLength),
_certData(certData),
_pkLength(pkLength),
_pkData(pkData) {
_keyPEM(keyPEM),
_certPEM(certPEM) {
}
@@ -12,7 +36,6 @@ SSLCert::~SSLCert() {
// TODO Auto-generated destructor stub
}
uint16_t SSLCert::getCertLength() {
return _certLength;
}
@@ -21,39 +44,88 @@ uint16_t SSLCert::getPKLength() {
return _pkLength;
}
unsigned char * SSLCert::getCertData() {
return _certData;
String SSLCert::getKeyPEM() {
return _keyPEM;
}
unsigned char * SSLCert::getPKData() {
return _pkData;
String SSLCert::getCertPEM() {
return _certPEM;
}
void SSLCert::setPK(unsigned char * pkData, uint16_t length) {
_pkData = pkData;
_pkLength = length;
void SSLCert::setPK(String keyPEM) {
_keyPEM = keyPEM;
_pkLength = keyPEM.length();
}
void SSLCert::setCert(unsigned char * certData, uint16_t length) {
_certData = certData;
_certLength = length;
void SSLCert::setCert(String certPEM) {
_certPEM = certPEM;
_certLength = certPEM.length();
}
void SSLCert::clear() {
for(uint16_t i = 0; i < _certLength; i++) _certData[i]=0;
delete _certData;
_certLength = 0;
for(uint16_t i = 0; i < _pkLength; i++) _pkData[i] = 0;
delete _pkData;
_pkLength = 0;
_keyPEM = "";
_certPEM = "";
}
/**
* Returns the CN value from a DN, or "" if it cannot be found
*/
static std::string get_cn(std::string dn) {
size_t cnStart = dn.find("CN=");
if (cnStart == std::string::npos) {
return "";
}
cnStart += 3;
size_t cnStop = dn.find(",", cnStart);
if (cnStop == std::string::npos) {
cnStop = dn.length();
}
return dn.substr(cnStart, cnStop - cnStart);
}
/**
* Sets the DN as subjectAltName extension in the certificate
*/
static int add_subject_alt_name(mbedtls_x509write_cert *crt, std::string &cn) {
size_t bufsize = cn.length() + 8; // some additional space for tags and length fields
uint8_t buf[bufsize];
uint8_t *p = &buf[bufsize - 1];
uint8_t *start = buf;
int length = 0;
int ret; // used by MBEDTLS macro
// The ASN structure that we will construct as parameter for write_crt_set_extension is as follows:
// | 0x30 = Sequence | length | 0x82 = dNSName, context-specific | length | cn0 | cn1 | cn2 | cn3 | .. | cnn |
// ↑ : ↑ `-------------v------------------´:
// | : `-------------------´ :
// | `----------v------------------------------------------------------------------´
// `---------------´
// Let's encrypt has useful infos: https://letsencrypt.org/docs/a-warm-welcome-to-asn1-and-der/#choice-and-any-encoding
MBEDTLS_ASN1_CHK_ADD(length,
mbedtls_asn1_write_raw_buffer(&p, start, (uint8_t*)cn.c_str(), cn.length()));
MBEDTLS_ASN1_CHK_ADD(length,
mbedtls_asn1_write_len(&p, start, length));
MBEDTLS_ASN1_CHK_ADD(length,
mbedtls_asn1_write_tag(&p, start, MBEDTLS_ASN1_CONTEXT_SPECIFIC | 0x02)); // 0x02 = dNSName
MBEDTLS_ASN1_CHK_ADD(length,
mbedtls_asn1_write_len(&p, start, length));
MBEDTLS_ASN1_CHK_ADD(length,
mbedtls_asn1_write_tag(&p, start, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE ));
return mbedtls_x509write_crt_set_extension( crt,
MBEDTLS_OID_SUBJECT_ALT_NAME, MBEDTLS_OID_SIZE(MBEDTLS_OID_SUBJECT_ALT_NAME),
0, // not critical
p, length);
}
/**
* Function to create the key for a self-signed certificate.
*
*
* Writes private key as DER in certCtx
*
*
* Based on programs/pkey/gen_key.c
*/
static int gen_key(SSLCert &certCtx, SSLKeySize keySize) {
@@ -84,7 +156,7 @@ static int gen_key(SSLCert &certCtx, SSLKeySize keySize) {
return HTTPS_SERVER_ERROR_KEYGEN_SETUP_PK;
}
// Actual key generation
// Actual key generation
int resPkGen = mbedtls_rsa_gen_key(
mbedtls_pk_rsa( key ),
mbedtls_ctr_drbg_random,
@@ -112,30 +184,20 @@ static int gen_key(SSLCert &certCtx, SSLKeySize keySize) {
memset(output_buf, 0, 4096);
// Write the key to the temporary buffer and determine its length
int resPkWrite = mbedtls_pk_write_key_der( &key, output_buf, 4096 );
int resPkWrite = mbedtls_pk_write_key_pem( &key, output_buf, 4096 );
if (resPkWrite < 0) {
delete[] output_buf;
mbedtls_pk_free( &key );
return HTTPS_SERVER_ERROR_KEY_WRITE_PK;
}
size_t pkLength = resPkWrite;
unsigned char *pkOffset = output_buf + sizeof(unsigned char) * 4096 - pkLength;
// Copy the key into a new, fitting space on the heap
unsigned char * output_pk = new unsigned char[pkLength];
if (output_pk == NULL) {
delete[] output_buf;
mbedtls_pk_free( &key );
return HTTPS_SERVER_ERROR_KEY_WRITE_PK;
}
memcpy(output_pk, pkOffset, pkLength);
// Clean up the temporary buffer and clear the key context
delete[] output_buf;
mbedtls_pk_free( &key );
// Set the private key in the context
certCtx.setPK(output_pk, pkLength);
certCtx.setPK((char*)output_buf);
delete[] output_buf;
return 0;
}
@@ -180,12 +242,12 @@ static int parse_serial_decimal_format(unsigned char *obuf, size_t obufmax,
/**
* Function to generate the X509 certificate data for a private key
*
*
* Writes certificate in certCtx
*
* Based on programs/x509/cert_write.c
*/
static int cert_write(SSLCert &certCtx, std::string dn, std::string validityFrom, std::string validityTo) {
int funcRes = 0;
int stepRes = 0;
@@ -198,14 +260,18 @@ static int cert_write(SSLCert &certCtx, std::string dn, std::string validityFrom
unsigned char *certOffset;
unsigned char * output_buffer;
size_t certLength;
const char *defaultSerial = "1";
unsigned char serial[MBEDTLS_X509_RFC5280_MAX_SERIAL_LEN];
const char *serial = "peer";
size_t serial_len;
// Make a C-friendly version of the distinguished name
char dn_cstr[dn.length()+1];
strcpy(dn_cstr, dn.c_str());
std::string cn = get_cn(dn);
if (cn == "") {
return HTTPS_SERVER_ERROR_CERTGEN_CN;
}
// Initialize the entropy source
mbedtls_entropy_init( &entropy );
@@ -218,7 +284,8 @@ static int cert_write(SSLCert &certCtx, std::string dn, std::string validityFrom
}
mbedtls_pk_init( &key );
stepRes = mbedtls_pk_parse_key( &key, certCtx.getPKData(), certCtx.getPKLength(), NULL, 0, mbedtls_ctr_drbg_random, &ctr_drbg);
stepRes = mbedtls_pk_parse_key( &key, (const unsigned char *)certCtx.getKeyPEM().c_str(), certCtx.getPKLength() + 1, NULL, 0, mbedtls_ctr_drbg_random, &ctr_drbg);
if (stepRes != 0) {
funcRes = HTTPS_SERVER_ERROR_CERTGEN_READKEY;
goto error_after_key;
@@ -260,15 +327,14 @@ static int cert_write(SSLCert &certCtx, std::string dn, std::string validityFrom
goto error_after_cert;
}
// Initialize the serial number
stepRes = parse_serial_decimal_format(serial, sizeof(serial), defaultSerial, &serial_len);
stepRes = add_subject_alt_name( &crt, cn );
if (stepRes != 0) {
funcRes = HTTPS_SERVER_ERROR_CERTGEN_SERIAL;
goto error_after_cert_serial;
funcRes = HTTPS_SERVER_ERROR_CERTGEN_NAME;
goto error_after_cert;
}
stepRes = mbedtls_x509write_crt_set_serial_raw( &crt, serial, sizeof(serial) );
// Initialize the serial number
stepRes = mbedtls_x509write_crt_set_serial_raw( &crt, (unsigned char *)serial, strlen(serial) );
if (stepRes != 0) {
funcRes = HTTPS_SERVER_ERROR_CERTGEN_SERIAL;
goto error_after_cert_serial;
@@ -282,26 +348,14 @@ static int cert_write(SSLCert &certCtx, std::string dn, std::string validityFrom
}
// Write the actual certificate
stepRes = mbedtls_x509write_crt_der(&crt, primary_buffer, 4096, mbedtls_ctr_drbg_random, &ctr_drbg );
stepRes = mbedtls_x509write_crt_pem(&crt, primary_buffer, 4096, mbedtls_ctr_drbg_random, &ctr_drbg );
if (stepRes < 0) {
funcRes = HTTPS_SERVER_ERROR_CERTGEN_WRITE;
goto error_after_primary_buffer;
}
// Create a matching buffer
certLength = stepRes;
certOffset = primary_buffer + sizeof(unsigned char) * 4096 - certLength;
// Copy the cert into a new, fitting space on the heap
output_buffer = new unsigned char[certLength];
if (output_buffer == NULL) {
funcRes = HTTPS_SERVER_ERROR_CERTGEN_OUT_OF_MEM;
goto error_after_primary_buffer;
}
memcpy(output_buffer, certOffset, certLength);
// Configure the cert in the context
certCtx.setCert(output_buffer, certLength);
certCtx.setCert((char*)primary_buffer);
// Run through the cleanup process
error_after_primary_buffer:
@@ -334,7 +388,7 @@ int createSelfSignedCert(SSLCert &certCtx, SSLKeySize keySize, std::string dn, s
int certRes = cert_write(certCtx, dn, validFrom, validUntil);
if (certRes != 0) {
// Cert writing failed, reset the pk and return failure code
certCtx.setPK(NULL, 0);
certCtx.setPK("");
return certRes;
}

View File

@@ -1,3 +1,27 @@
/*
MIT License
Copyright (c) 2017 Frank Hessel
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef SRC_SSLCERT_HPP_
#define SRC_SSLCERT_HPP_
@@ -11,6 +35,8 @@
#include <mbedtls/x509.h>
#include <mbedtls/x509_crt.h>
#include <mbedtls/x509_csr.h>
#include <mbedtls/asn1write.h>
#include <mbedtls/oid.h>
#define HTTPS_SERVER_ERROR_KEYGEN 0x0F
#define HTTPS_SERVER_ERROR_KEYGEN_RNG 0x02
@@ -26,146 +52,38 @@
#define HTTPS_SERVER_ERROR_CERTGEN_NAME 0x17
#define HTTPS_SERVER_ERROR_CERTGEN_SERIAL 0x18
#define HTTPS_SERVER_ERROR_CERTGEN_VALIDITY 0x19
#define HTTPS_SERVER_ERROR_CERTGEN_CN 0x1a
/**
* \brief Certificate and private key that can be passed to the HTTPSServer.
*
* **Converting PEM to DER Files**
*
* Certificate:
* ```bash
* openssl x509 -inform PEM -outform DER -in myCert.crt -out cert.der
* ```
*
* Private Key:
* ```bash
* openssl rsa -inform PEM -outform DER -in myCert.key -out key.der
* ```
*
* **Converting DER File to C Header**
*
* ```bash
* echo "#ifndef KEY_H_" > ./key.h
* echo "#define KEY_H_" >> ./key.h
* xxd -i key.der >> ./key.h
* echo "#endif" >> ./key.h
* ```
*/
class SSLCert {
public:
/**
* \brief Creates a new SSLCert.
*
* The certificate and key data may be NULL (default values) if the certificate is meant
* to be passed to createSelfSignedCert().
*
* Otherwise, the data must reside in a memory location that is not deleted until the server
* using the certificate is stopped.
*
* \param[in] certData The certificate data to use (DER format)
* \param[in] certLength The length of the certificate data
* \param[in] pkData The private key data to use (DER format)
* \param[in] pkLength The length of the private key
*/
SSLCert(
unsigned char * certData = NULL,
uint16_t certLength = 0,
unsigned char * pkData = NULL,
uint16_t pkLength = 0
uint16_t pkLength = 0,
String keyPEM = "",
String certPEM = ""
);
virtual ~SSLCert();
/**
* \brief Returns the length of the certificate in byte
*/
uint16_t getCertLength();
/**
* \brief Returns the length of the private key in byte
*/
uint16_t getPKLength();
/**
* \brief Returns the certificate data
*/
unsigned char * getCertData();
/**
* \brief Returns the private key data
*/
unsigned char * getPKData();
/**
* \brief Sets the private key in DER format
*
* The data has to reside in a place in memory that is not deleted as long as the
* server is running.
*
* See SSLCert() for some information on how to generate DER data.
*
* \param[in] _pkData The data of the private key
* \param[in] length The length of the private key
*/
void setPK(unsigned char * _pkData, uint16_t length);
/**
* \brief Sets the certificate data in DER format
*
* The data has to reside in a place in memory that is not deleted as long as the
* server is running.
*
* See SSLCert for some information on how to generate DER data.
*
* \param[in] _certData The data of the certificate
* \param[in] length The length of the certificate
*/
void setCert(unsigned char * _certData, uint16_t length);
/**
* \brief Clears the key buffers and deletes them.
*/
String getCertPEM();
String getKeyPEM();
void setPK(String _keyPEM);
void setCert(String _certPEM);
void clear();
private:
uint16_t _certLength;
unsigned char * _certData;
uint16_t _pkLength;
unsigned char * _pkData;
String _keyPEM;
String _certPEM;
};
/**
* \brief Defines the key size for key generation
*
* Not available if the `HTTPS_DISABLE_SELFSIGNING` compiler flag is set
*/
enum SSLKeySize {
/** \brief RSA key with 1024 bit */
KEYSIZE_1024 = 1024,
/** \brief RSA key with 2048 bit */
KEYSIZE_2048 = 2048,
/** \brief RSA key with 4096 bit */
KEYSIZE_4096 = 4096
};
/**
* \brief Creates a self-signed certificate on the ESP32
*
* This function creates a new self-signed certificate for the given hostname on the heap.
* Make sure to clear() it before you delete it.
*
* The distinguished name (dn) parameter has to follow the x509 specifications. An example
* would be:
* CN=myesp.local,O=acme,C=US
*
* The strings validFrom and validUntil have to be formatted like this:
* "20190101000000", "20300101000000"
*
* This will take some time, so you should probably write the certificate data to non-volatile
* storage when you are done.
*
* Setting the `HTTPS_DISABLE_SELFSIGNING` compiler flag will remove this function from the library
*/
int createSelfSignedCert(SSLCert &certCtx, SSLKeySize keySize, std::string dn, std::string validFrom = "20190101000000", std::string validUntil = "20300101000000");
#endif /* SRC_SSLCERT_HPP_ */

View File

@@ -1 +1 @@
# Espressif ESP32 Partition Table
# Espressif ESP32 Partition Table
1 # Espressif ESP32 Partition Table # Name Type SubType Offset Size Flags nvs data nvs 0x9000 0x5000 otadata data ota 0xe000 0x2000 app0 app ota_0 0x10000 0x150000 0x130000