diff --git a/partitions.csv b/partitions.csv index 6f27eb7..1b64460 100644 --- a/partitions.csv +++ b/partitions.csv @@ -1 +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, spiffs, data, spiffs, 0x3D0000, 0x20000, coredump, data, coredump,0x3F0000, 0x10000, \ No newline at end of file +# 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, 0x130000, spiffs, data, spiffs, 0x3B0000, 0x40000, coredump, data, coredump,0x3F0000, 0x10000, \ No newline at end of file diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 323e589..6e3503f 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -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 diff --git a/src/Config.h b/src/Config.h index 1f7057d..edbc838 100644 --- a/src/Config.h +++ b/src/Config.h @@ -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" diff --git a/src/WebCfgServer.cpp b/src/WebCfgServer.cpp index 70256f0..ac05eb0 100644 --- a/src/WebCfgServer.cpp +++ b/src/WebCfgServer.cpp @@ -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 diff --git a/src/main.cpp b/src/main.cpp index 4bfb9af..0c01222 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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(); diff --git a/src/util/SSLCert.cpp b/src/util/SSLCert.cpp index df99128..f7d1f49 100644 --- a/src/util/SSLCert.cpp +++ b/src/util/SSLCert.cpp @@ -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; } diff --git a/src/util/SSLCert.hpp b/src/util/SSLCert.hpp index 8860d56..954cef8 100644 --- a/src/util/SSLCert.hpp +++ b/src/util/SSLCert.hpp @@ -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 #include #include +#include +#include #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_ */ diff --git a/updater/partitions.csv b/updater/partitions.csv index 0f6f14e..b024b85 100644 --- a/updater/partitions.csv +++ b/updater/partitions.csv @@ -1 +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, \ No newline at end of file +# 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, 0x130000, \ No newline at end of file