#include "ImportExport.h" #include "SPIFFS.h" #include "Logger.h" #include "PreferencesKeys.h" #include #include ImportExport::ImportExport(Preferences *preferences) : _preferences(preferences) { readSettings(); } void ImportExport::readSettings() { if (_preferences->getBool(preference_cred_duo_enabled, false)) { _duoEnabled = true; _duoHost = _preferences->getString(preference_cred_duo_host, ""); _duoIkey = _preferences->getString(preference_cred_duo_ikey, ""); _duoSkey = _preferences->getString(preference_cred_duo_skey, ""); _duoUser = _preferences->getString(preference_cred_duo_user, ""); if (_duoHost == "" || _duoIkey == "" || _duoSkey == "" || _duoUser == "" || !_preferences->getBool(preference_update_time, false)) { _duoEnabled = false; } else if (_preferences->getBool(preference_cred_bypass_boot_btn_enabled, false) || _preferences->getInt(preference_cred_bypass_gpio_high, -1) > -1 || _preferences->getInt(preference_cred_bypass_gpio_low, -1) > -1) { if (_preferences->getBool(preference_cred_bypass_boot_btn_enabled, false)) { _bypassGPIO = true; } _bypassGPIOHigh = _preferences->getInt(preference_cred_bypass_gpio_high, -1); _bypassGPIOLow = _preferences->getInt(preference_cred_bypass_gpio_low, -1); } } _totpKey = _preferences->getString(preference_totp_secret, ""); _totpEnabled = _totpKey.length() > 0; } bool ImportExport::getDuoEnabled() { return _duoEnabled; } bool ImportExport::getTOTPEnabled() { return _totpEnabled; } bool ImportExport::getBypassGPIOEnabled() { return _bypassGPIO; } int ImportExport::getBypassGPIOHigh() { return _bypassGPIOHigh; } int ImportExport::getBypassGPIOLow() { return _bypassGPIOLow; } bool ImportExport::startDuoAuth(char* pushType) { int64_t timeout = esp_timer_get_time() - (30 * 1000 * 1000L); if(!_duoActiveRequest || timeout > _duoRequestTS) { const char* duo_host = _duoHost.c_str(); const char* duo_ikey = _duoIkey.c_str(); const char* duo_skey = _duoSkey.c_str(); const char* duo_user = _duoUser.c_str(); DuoAuthLib duoAuth; bool duoRequestResult; duoAuth.begin(duo_host, duo_ikey, duo_skey, &timeinfo); duoAuth.setPushType(pushType); duoRequestResult = duoAuth.pushAuth((char*)duo_user, true); if(duoRequestResult == true) { _duoTransactionId = duoAuth.getAuthTxId(); _duoActiveRequest = true; _duoRequestTS = esp_timer_get_time(); Log->println("Duo MFA Auth sent"); return true; } else { Log->println("Failed Duo MFA Auth"); return false; } } return true; } void ImportExport::setDuoCheckIP(String duoCheckIP) { _duoCheckIP = duoCheckIP; } void ImportExport::setDuoCheckId(String duoCheckId) { _duoCheckId = duoCheckId; } void ImportExport::saveSessions() { if(_preferences->getBool(preference_update_time, false)) { if (!SPIFFS.begin(true)) { Log->println("SPIFFS Mount Failed"); } else { File file; file = SPIFFS.open("/duosessions.json", "w"); serializeJson(_duoSessions, file); file.close(); } } } int ImportExport::checkDuoAuth(PsychicRequest *request) { const char* duo_host = _duoHost.c_str(); const char* duo_ikey = _duoIkey.c_str(); const char* duo_skey = _duoSkey.c_str(); const char* duo_user = _duoUser.c_str(); int type = 0; if(request->hasParam("type")) { const PsychicWebParameter* p = request->getParam("type"); if(p->value() != "") { type = p->value().toInt(); } } if (request->hasParam("id")) { const PsychicWebParameter* p = request->getParam("id"); String id = p->value(); DuoAuthLib duoAuth; if(_duoActiveRequest && _duoCheckIP == request->client()->localIP().toString() && id == _duoCheckId) { duoAuth.begin(duo_host, duo_ikey, duo_skey, &timeinfo); Log->println("Checking Duo Push Status..."); duoAuth.authStatus(_duoTransactionId); if(duoAuth.pushWaiting()) { Log->println("Duo Push Waiting..."); return 2; } else { if (duoAuth.authSuccessful()) { Log->println("Successful Duo MFA Auth"); _duoActiveRequest = false; _duoTransactionId = ""; _duoCheckIP = ""; _duoCheckId = ""; if(type==0) { int64_t durationLength = 60*60*_preferences->getInt(preference_cred_session_lifetime_duo_remember, 720); if (!_sessionsOpts[request->client()->localIP().toString()]) { durationLength = _preferences->getInt(preference_cred_session_lifetime_duo, 3600); } struct timeval time; gettimeofday(&time, NULL); int64_t time_us = (int64_t)time.tv_sec * 1000000L + (int64_t)time.tv_usec; _duoSessions[id] = time_us + (durationLength*1000000L); saveSessions(); if (_preferences->getBool(preference_mfa_reconfigure, false)) { _preferences->putBool(preference_mfa_reconfigure, false); } } else { _sessionsOpts[request->client()->localIP().toString() + "approve"] = true; } return 1; } else { Log->println("Failed Duo MFA Auth"); _duoActiveRequest = false; _duoTransactionId = ""; _duoCheckIP = ""; _duoCheckId = ""; if(type==0) { if (_preferences->getBool(preference_mfa_reconfigure, false)) { _preferences->putBool(preference_cred_duo_enabled, false); _duoEnabled = false; _preferences->putBool(preference_mfa_reconfigure, false); } } else { _sessionsOpts[request->client()->localIP().toString() + "approve"] = false; } return 0; } } } } return 0; } int ImportExport::checkDuoApprove() { const char* duo_host = _duoHost.c_str(); const char* duo_ikey = _duoIkey.c_str(); const char* duo_skey = _duoSkey.c_str(); const char* duo_user = _duoUser.c_str(); DuoAuthLib duoAuth; if(_duoActiveRequest) { duoAuth.begin(duo_host, duo_ikey, duo_skey, &timeinfo); Log->println("Checking Duo Push Status..."); duoAuth.authStatus(_duoTransactionId); if(duoAuth.pushWaiting()) { Log->println("Duo Push Waiting..."); return 2; } else { if (duoAuth.authSuccessful()) { Log->println("Successful Duo MFA Auth"); _duoActiveRequest = false; _duoTransactionId = ""; _duoCheckIP = ""; _duoCheckId = ""; return 1; } else { Log->println("Failed Duo MFA Auth"); _duoActiveRequest = false; _duoTransactionId = ""; _duoCheckIP = ""; _duoCheckId = ""; return 0; } } } return 0; } bool ImportExport::checkTOTP(String* totpKey) { String key(totpKey->c_str()); if(_totpEnabled) { time_t now; time(&now); int totpTime = -60; while (totpTime <= 60) { String key2(TOTP::currentOTP(now, _totpKey, 30, 6, totpTime)->c_str()); if(key.toInt() == key2.toInt()) { Log->println("Successful TOTP MFA Auth"); return true; } totpTime += 30; } Log->println("Failed TOTP MFA Auth"); } return false; } void ImportExport::exportHttpsJson(JsonDocument &json) { if (!SPIFFS.begin(true)) { Log->println("SPIFFS Mount Failed"); } else { File file = SPIFFS.open("/http_ssl.crt"); if (!file || file.isDirectory()) { Log->println("http_ssl.crt not found"); } else { Log->println("Reading http_ssl.crt"); size_t filesize = file.size(); char cert[filesize + 1]; file.read((uint8_t *)cert, sizeof(cert)); file.close(); cert[filesize] = '\0'; json["http_ssl.crt"] = cert; } } if (!SPIFFS.begin(true)) { Log->println("SPIFFS Mount Failed"); } else { File file = SPIFFS.open("/http_ssl.key"); if (!file || file.isDirectory()) { Log->println("http_ssl.key not found"); } else { Log->println("Reading http_ssl.key"); size_t filesize = file.size(); char key[filesize + 1]; file.read((uint8_t *)key, sizeof(key)); file.close(); key[filesize] = '\0'; json["http_ssl.key"] = key; } } } void ImportExport::exportMqttsJson(JsonDocument &json) { if (!SPIFFS.begin(true)) { Log->println("SPIFFS Mount Failed"); } else { File file = SPIFFS.open("/mqtt_ssl.ca"); if (!file || file.isDirectory()) { Log->println("mqtt_ssl.ca not found"); } else { Log->println("Reading mqtt_ssl.ca"); size_t filesize = file.size(); char ca[filesize + 1]; file.read((uint8_t *)ca, sizeof(ca)); file.close(); ca[filesize] = '\0'; json["mqtt_ssl.ca"] = ca; } } if (!SPIFFS.begin(true)) { Log->println("SPIFFS Mount Failed"); } else { File file = SPIFFS.open("/mqtt_ssl.crt"); if (!file || file.isDirectory()) { Log->println("mqtt_ssl.crt not found"); } else { Log->println("Reading mqtt_ssl.crt"); size_t filesize = file.size(); char cert[filesize + 1]; file.read((uint8_t *)cert, sizeof(cert)); file.close(); cert[filesize] = '\0'; json["mqtt_ssl.crt"] = cert; } } if (!SPIFFS.begin(true)) { Log->println("SPIFFS Mount Failed"); } else { File file = SPIFFS.open("/mqtt_ssl.key"); if (!file || file.isDirectory()) { Log->println("mqtt_ssl.key not found"); } else { Log->println("Reading mqtt_ssl.key"); size_t filesize = file.size(); char key[filesize + 1]; file.read((uint8_t *)key, sizeof(key)); file.close(); key[filesize] = '\0'; json["mqtt_ssl.key"] = key; } } } void ImportExport::exportNukiHubJson(JsonDocument &json, bool redacted, bool pairing, bool nuki, bool nukiOpener) { DebugPreferences debugPreferences; const std::vector keysPrefs = debugPreferences.getPreferencesKeys(); const std::vector boolPrefs = debugPreferences.getPreferencesBoolKeys(); const std::vector redactedPrefs = debugPreferences.getPreferencesRedactedKeys(); const std::vector bytePrefs = debugPreferences.getPreferencesByteKeys(); for(const auto& key : keysPrefs) { if(strcmp(key, preference_show_secrets) == 0) { continue; } if(strcmp(key, preference_latest_version) == 0) { continue; } if(!redacted) if(std::find(redactedPrefs.begin(), redactedPrefs.end(), key) != redactedPrefs.end()) { continue; } if(!_preferences->isKey(key)) { json[key] = ""; } else if(std::find(boolPrefs.begin(), boolPrefs.end(), key) != boolPrefs.end()) { json[key] = _preferences->getBool(key) ? "1" : "0"; } else { switch(_preferences->getType(key)) { case PT_I8: json[key] = String(_preferences->getChar(key)); break; case PT_I16: json[key] = String(_preferences->getShort(key)); break; case PT_I32: json[key] = String(_preferences->getInt(key)); break; case PT_I64: json[key] = String(_preferences->getLong64(key)); break; case PT_U8: json[key] = String(_preferences->getUChar(key)); break; case PT_U16: json[key] = String(_preferences->getUShort(key)); break; case PT_U32: json[key] = String(_preferences->getUInt(key)); break; case PT_U64: json[key] = String(_preferences->getULong64(key)); break; case PT_STR: json[key] = _preferences->getString(key); break; default: json[key] = _preferences->getString(key); break; } } } if(pairing) { if(nuki) { unsigned char currentBleAddress[6]; unsigned char authorizationId[4] = {0x00}; unsigned char secretKeyK[32] = {0x00}; uint16_t storedPincode = 0000; uint32_t storedUltraPincode = 000000; bool isUltra = false; Preferences nukiBlePref; nukiBlePref.begin("NukiHub", false); nukiBlePref.getBytes("bleAddress", currentBleAddress, 6); nukiBlePref.getBytes("secretKeyK", secretKeyK, 32); nukiBlePref.getBytes("authorizationId", authorizationId, 4); nukiBlePref.getBytes("securityPinCode", &storedPincode, 2); nukiBlePref.getBytes("ultraPinCode", &storedUltraPincode, 4); isUltra = nukiBlePref.getBool("isUltra", false); nukiBlePref.end(); char text[255]; text[0] = '\0'; for(int i = 0 ; i < 6 ; i++) { size_t offset = strlen(text); sprintf(&(text[offset]), "%02x", currentBleAddress[i]); } json["bleAddressLock"] = text; memset(text, 0, sizeof(text)); text[0] = '\0'; for(int i = 0 ; i < 32 ; i++) { size_t offset = strlen(text); sprintf(&(text[offset]), "%02x", secretKeyK[i]); } json["secretKeyKLock"] = text; memset(text, 0, sizeof(text)); text[0] = '\0'; for(int i = 0 ; i < 4 ; i++) { size_t offset = strlen(text); sprintf(&(text[offset]), "%02x", authorizationId[i]); } json["authorizationIdLock"] = text; memset(text, 0, sizeof(text)); json["securityPinCodeLock"] = storedPincode; json["ultraPinCodeLock"] = storedUltraPincode; json["isUltra"] = isUltra ? "1" : "0"; } if(nukiOpener) { unsigned char currentBleAddressOpn[6]; unsigned char authorizationIdOpn[4] = {0x00}; unsigned char secretKeyKOpn[32] = {0x00}; uint16_t storedPincodeOpn = 0000; Preferences nukiBlePref; nukiBlePref.begin("NukiHubopener", false); nukiBlePref.getBytes("bleAddress", currentBleAddressOpn, 6); nukiBlePref.getBytes("secretKeyK", secretKeyKOpn, 32); nukiBlePref.getBytes("authorizationId", authorizationIdOpn, 4); nukiBlePref.getBytes("securityPinCode", &storedPincodeOpn, 2); nukiBlePref.end(); char text[255]; text[0] = '\0'; for(int i = 0 ; i < 6 ; i++) { size_t offset = strlen(text); sprintf(&(text[offset]), "%02x", currentBleAddressOpn[i]); } json["bleAddressOpener"] = text; memset(text, 0, sizeof(text)); text[0] = '\0'; for(int i = 0 ; i < 32 ; i++) { size_t offset = strlen(text); sprintf(&(text[offset]), "%02x", secretKeyKOpn[i]); } json["secretKeyKOpener"] = text; memset(text, 0, sizeof(text)); text[0] = '\0'; for(int i = 0 ; i < 4 ; i++) { size_t offset = strlen(text); sprintf(&(text[offset]), "%02x", authorizationIdOpn[i]); } json["authorizationIdOpener"] = text; memset(text, 0, sizeof(text)); json["securityPinCodeOpener"] = storedPincodeOpn; } } for(const auto& key : bytePrefs) { size_t storedLength = _preferences->getBytesLength(key); if(storedLength == 0) { continue; } uint8_t serialized[storedLength]; memset(serialized, 0, sizeof(serialized)); size_t size = _preferences->getBytes(key, serialized, sizeof(serialized)); if(size == 0) { continue; } char text[255]; text[0] = '\0'; for(int i = 0 ; i < size ; i++) { size_t offset = strlen(text); sprintf(&(text[offset]), "%02x", serialized[i]); } json[key] = text; memset(text, 0, sizeof(text)); } } JsonDocument ImportExport::importJson(JsonDocument &doc) { JsonDocument json; unsigned char currentBleAddress[6]; unsigned char authorizationId[4] = {0x00}; unsigned char secretKeyK[32] = {0x00}; unsigned char currentBleAddressOpn[6]; unsigned char authorizationIdOpn[4] = {0x00}; unsigned char secretKeyKOpn[32] = {0x00}; DebugPreferences debugPreferences; const std::vector keysPrefs = debugPreferences.getPreferencesKeys(); const std::vector boolPrefs = debugPreferences.getPreferencesBoolKeys(); const std::vector bytePrefs = debugPreferences.getPreferencesByteKeys(); const std::vector intPrefs = debugPreferences.getPreferencesIntKeys(); const std::vector uintPrefs = debugPreferences.getPreferencesUIntKeys(); const std::vector uint64Prefs = debugPreferences.getPreferencesUInt64Keys(); for(const auto& key : keysPrefs) { if(doc[key].isNull()) { continue; } if(strcmp(key, preference_show_secrets) == 0) { continue; } if(strcmp(key, preference_latest_version) == 0) { continue; } if(std::find(boolPrefs.begin(), boolPrefs.end(), key) != boolPrefs.end()) { if (doc[key].as().length() > 0) { _preferences->putBool(key, (doc[key].as() == "1" ? true : false)); json[key] = "changed"; } else { json[key] = "removed"; _preferences->remove(key); } continue; } if(std::find(intPrefs.begin(), intPrefs.end(), key) != intPrefs.end()) { if (doc[key].as().length() > 0) { json[key] = "changed"; _preferences->putInt(key, doc[key].as()); } else { json[key] = "removed"; _preferences->remove(key); } continue; } if(std::find(uintPrefs.begin(), uintPrefs.end(), key) != uintPrefs.end()) { if (doc[key].as().length() > 0) { json[key] = "changed"; _preferences->putUInt(key, doc[key].as()); } else { json[key] = "removed"; _preferences->remove(key); } continue; } if(std::find(uint64Prefs.begin(), uint64Prefs.end(), key) != uint64Prefs.end()) { if (doc[key].as().length() > 0) { json[key] = "changed"; _preferences->putULong64(key, doc[key].as()); } else { json[key] = "removed"; _preferences->remove(key); } continue; } if (doc[key].as().length() > 0) { json[key] = "changed"; _preferences->putString(key, doc[key].as()); } else { json[key] = "removed"; _preferences->remove(key); } } for(const auto& key : bytePrefs) { if(!doc[key].isNull() && doc[key].is()) { String value = doc[key].as(); unsigned char tmpchar[32]; for(int i=0; iputBytes(key, (byte*)(&tmpchar), (value.length() / 2)); memset(tmpchar, 0, sizeof(tmpchar)); } } Preferences nukiBlePref; nukiBlePref.begin("NukiHub", false); if(!doc["bleAddressLock"].isNull()) { if (doc["bleAddressLock"].as().length() == 12) { String value = doc["bleAddressLock"].as(); for(int i=0; i().length() == 64) { String value = doc["secretKeyKLock"].as(); for(int i=0; i().length() == 8) { String value = doc["authorizationIdLock"].as(); for(int i=0; i().length() >0) { json["isUltra"] = "changed"; nukiBlePref.putBool("isUltra", (doc["isUltra"].as() == "1" ? true : false)); } } if(!doc["securityPinCodeLock"].isNull()) { if(doc["securityPinCodeLock"].as().length() > 0) { json["securityPinCodeLock"] = "changed"; nukiBlePref.putBytes("securityPinCode", (byte*)(doc["securityPinCodeLock"].as()), 2); //_nuki->setPin(doc["securityPinCodeLock"].as()); } else { json["securityPinCodeLock"] = "removed"; unsigned char pincode[2] = {0x00}; nukiBlePref.putBytes("securityPinCode", pincode, 2); //_nuki->setPin(0xffff); } } if(!doc["ultraPinCodeLock"].isNull()) { if(doc["ultraPinCodeLock"].as().length() > 0) { json["ultraPinCodeLock"] = "changed"; nukiBlePref.putBytes("ultraPinCode", (byte*)(doc["ultraPinCodeLock"].as()), 4); //_nuki->setUltraPin(doc["ultraPinCodeLock"].as()); _preferences->putInt(preference_lock_gemini_pin, doc["ultraPinCodeLock"].as()); } else { json["ultraPinCodeLock"] = "removed"; unsigned char ultraPincode[4] = {0x00}; nukiBlePref.putBytes("ultraPinCode", ultraPincode, 4); _preferences->putInt(preference_lock_gemini_pin, 0); } } nukiBlePref.end(); nukiBlePref.begin("NukiHubopener", false); if(!doc["bleAddressOpener"].isNull()) { if (doc["bleAddressOpener"].as().length() == 12) { String value = doc["bleAddressOpener"].as(); for(int i=0; i().length() == 64) { String value = doc["secretKeyKOpener"].as(); for(int i=0; i().length() == 8) { String value = doc["authorizationIdOpener"].as(); for(int i=0; i().length() > 0) { json["securityPinCodeOpener"] = "changed"; nukiBlePref.putBytes("securityPinCode", (byte*)(doc["securityPinCodeOpener"].as()), 2); //_nukiOpener->setPin(doc["securityPinCodeOpener"].as()); } else { json["securityPinCodeOpener"] = "removed"; unsigned char pincode[2] = {0x00}; nukiBlePref.putBytes("securityPinCode", pincode, 2); //_nukiOpener->setPin(0xffff); } } nukiBlePref.end(); if(!doc["mqtt_ssl.ca"].isNull()) { if (!SPIFFS.begin(true)) { Log->println("SPIFFS Mount Failed"); json["mqtt_ssl.ca"] = "error"; } else { if(doc["mqtt_ssl.ca"].as().length() > 0) { File file = SPIFFS.open("/mqtt_ssl.ca", FILE_WRITE); if (!file) { Log->println("Failed to open /mqtt_ssl.ca for writing"); json["mqtt_ssl.ca"] = "error"; } else { if (!file.print(doc["mqtt_ssl.ca"].as())) { Log->println("Failed to write /mqtt_ssl.ca"); json["mqtt_ssl.crt"] = "error"; } else { json["mqtt_ssl.ca"] = "changed"; } file.close(); } } else { if (!SPIFFS.remove("/mqtt_ssl.ca")) { Log->println("Failed to delete /mqtt_ssl.ca"); json["mqtt_ssl.crt"] = "error"; } else { json["mqtt_ssl.ca"] = "removed"; } } } } if(!doc["mqtt_ssl.crt"].isNull()) { if (!SPIFFS.begin(true)) { Log->println("SPIFFS Mount Failed"); json["mqtt_ssl.crt"] = "error"; } else { if(doc["mqtt_ssl.crt"].as().length() > 0) { File file = SPIFFS.open("/mqtt_ssl.crt", FILE_WRITE); if (!file) { Log->println("Failed to open /mqtt_ssl.crt for writing"); json["mqtt_ssl.crt"] = "error"; } else { if (!file.print(doc["mqtt_ssl.crt"].as())) { Log->println("Failed to write /mqtt_ssl.crt"); json["mqtt_ssl.crt"] = "error"; } else { json["mqtt_ssl.crt"] = "changed"; } file.close(); } } else { if (!SPIFFS.remove("/mqtt_ssl.crt")) { Log->println("Failed to delete /mqtt_ssl.crt"); json["mqtt_ssl.crt"] = "error"; } else { json["mqtt_ssl.crt"] = "removed"; } } } } if(!doc["mqtt_ssl.key"].isNull()) { if (!SPIFFS.begin(true)) { Log->println("SPIFFS Mount Failed"); json["mqtt_ssl.key"] = "error"; } else { if(doc["mqtt_ssl.key"].as().length() > 0) { File file = SPIFFS.open("/mqtt_ssl.key", FILE_WRITE); if (!file) { Log->println("Failed to open /mqtt_ssl.key for writing"); json["mqtt_ssl.key"] = "error"; } else { if (!file.print(doc["mqtt_ssl.key"].as())) { Log->println("Failed to write /mqtt_ssl.key"); json["mqtt_ssl.key"] = "error"; } else { json["mqtt_ssl.key"] = "changed"; } file.close(); } } else { if (!SPIFFS.remove("/mqtt_ssl.key")) { Log->println("Failed to delete /mqtt_ssl.key"); } else { json["mqtt_ssl.key"] = "removed"; } } } } if(!doc["http_ssl.crt"].isNull()) { if (!SPIFFS.begin(true)) { Log->println("SPIFFS Mount Failed"); json["http_ssl.crt"] = "error"; } else { if(doc["http_ssl.crt"].as().length() > 0) { File file = SPIFFS.open("/http_ssl.crt", FILE_WRITE); if (!file) { Log->println("Failed to open /http_ssl.crt for writing"); json["http_ssl.crt"] = "error"; } else { if (!file.print(doc["http_ssl.crt"].as())) { Log->println("Failed to write /http_ssl.crt"); json["http_ssl.crt"] = "error"; } else { json["http_ssl.crt"] = "changed"; } file.close(); } } else { if (!SPIFFS.remove("/http_ssl.crt")) { Log->println("Failed to delete /http_ssl.crt"); json["http_ssl.crt"] = "error"; } else { json["http_ssl.crt"] = "removed"; } } } } if(!doc["http_ssl.key"].isNull()) { if (!SPIFFS.begin(true)) { Log->println("SPIFFS Mount Failed"); json["http_ssl.key"] = "error"; } else { if(doc["http_ssl.key"].as().length() > 0) { File file = SPIFFS.open("/http_ssl.key", FILE_WRITE); if (!file) { Log->println("Failed to open /http_ssl.key for writing"); json["http_ssl.key"] = "error"; } else { if (!file.print(doc["http_ssl.key"].as())) { Log->println("Failed to write /http_ssl.key"); json["http_ssl.key"] = "error"; } else { json["http_ssl.key"] = "changed"; } file.close(); } } else { if (!SPIFFS.remove("/http_ssl.key")) { Log->println("Failed to delete /http_ssl.key"); json["http_ssl.key"] = "error"; } else { json["http_ssl.key"] = "removed"; } } } } return json; }