Adding TLS for weblogs - Initial commit
This commit is contained in:
parent
6c292a1065
commit
d572d3f41f
|
|
@ -2,6 +2,13 @@
|
|||
|
||||
HomeSpan includes a variety of message logs with different levels of verbosity, as well built-in methods to create your own log messages and web logs.
|
||||
|
||||
1. [HomeSpan Log Message](homespan-log-messages)
|
||||
1. [User-Defined Log Messages](user-defined-log-messages)
|
||||
1. [Web Logging](web-logging)
|
||||
1. [Custom Style Sheets (CSS)](custom-style-sheets-css)
|
||||
1. [Adding User-Defined Data and/or Custom HTML](adding-user-defined-data-and-or-custom-html)
|
||||
1. [Enabling TLS Web Logs](enabling-tls-web-logs)
|
||||
|
||||
## HomeSpan Log Messages
|
||||
|
||||
HomeSpan log messages are typically output directly to the Arduino Serial Monitor with three possible levels of verbosity:
|
||||
|
|
@ -104,6 +111,15 @@ To embed this custom HTML text in the Web Log, call `homeSpan.setWebLogCallback(
|
|||
|
||||
Note that *r* is being passed as a reference and already includes all the HTML text the Web Log has produced to set up the page and create the initial table. You should therefore *extend* this String using `+=`. Do not simple assign this variable to a new String with `=` or you will overwrite all the HTML already produced!
|
||||
|
||||
### Enabling TLS Web Logs
|
||||
When Web Logs are enabled, HomeSpan shares the same web server instance to serve HomeKit controllers and web logs. Web logs are accessed via HTTP only, providing no security. If sensitive information is expected to be logged, web logs should be accessed via HTTPS, using Transport Security Layer (TLS).
|
||||
|
||||
To enable TLS with default configuration, call `enableTLS()` prior to calling `homeSpan.begin()`. HomeSpan will then attempt to generate an ECDSA keypair and self-signed certificate using NIST P-256 curve. Since the RSA algorithm requires larger keys, the use of Elliptic Curve Cryptography (ECC) is preferred as it uses smaller key sizes while providing the same security strength. RSA keypairs are certificates are not currently supported.
|
||||
|
||||
A new EC keypair and ECDSA certificate will be created after each boot unless developers want to use their own, using `enableTLS(port, ec_private_key_pem, ec_server_cert_pem)`. This ensures that no HomeSpan device ends up with the same keypair which would be considered a security vulnerability. HomeSpan developers should generate their own keypair and certificate, PEM-encoded, and pass their values to `enableTLS(port, ec_private_key_pem, ec_server_cert_pem)`. **Never log the private key.**
|
||||
|
||||
When TLS is configured and web logs are accessed via the non-HTTPS port, HomeSpan will issue an HTTP 301 (Permanently Moved) to the HTTPS port, which defaults to 443.
|
||||
|
||||
---
|
||||
|
||||
[↩️](../README.md) Back to the Welcome page
|
||||
|
|
|
|||
|
|
@ -200,6 +200,17 @@ The following **optional** `homeSpan` methods enable additional features and pro
|
|||
* returns the version of a HomeSpan sketch, as set using `void setSketchVersion(const char *sVer)`, or "n/a" if not set
|
||||
* can by called from anywhere in a sketch
|
||||
|
||||
* `Span& enableTLS()`
|
||||
* Enable TLS (HTTPS) for weblogs, instead of unsecure (non-TLS / HTTP) connections. If weblogs have been enabled for a specific URI, end users will be redirected to the HTTPS port.
|
||||
* Defaults to using port 443 (default HTTPS port)
|
||||
* New ECDSA certificate generated at each reboot
|
||||
* `Span& enableTLS(u_int16_t port = 443, const char *ec_private_key_pem = NULL, const char *ec_server_cert_pem = NULL)`
|
||||
* Enable TLS (HTTPS) for weblogs, instead of unsecure (non-TLS / HTTP) connections. If weblogs have been enabled for a specific URI, end users will be redirected to the HTTPS port.
|
||||
* **port** Port for HTTPS connections. Defaults to 443 (default HTTPS port)
|
||||
* **ec_private_key_pem** ECC private key, in PEM format, to use for HTTPS. NOTE: RSA keys are not supported given their larger key size for similar security strength. A 256-bit ECC key provides similar security strength as an RSA 4096-bit key. When NULL, HomeSpan will attempt to generate its own ECC keypair at EACH REBOOT.
|
||||
* **ec_server_cert_pem** Matching EC certificate for the primate key. Keypair validation is not done. When NULL, HomeSpan will attempt to generate its own ECDSA with NIST P-256 certificate at EACH REBOOT.
|
||||
|
||||
|
||||
* `Span& enableWebLog(uint16_t maxEntries, const char *timeServerURL, const char *timeZone, const char *logURL)`
|
||||
* enables a rolling Web Log that displays the most recent *maxEntries* entries created by the user with the `WEBLOG()` macro. Parameters, and their default values if unspecified, are as follows:
|
||||
* *maxEntries* - maximum number of (most recent) entries to save. If unspecified, defaults to 0, in which case the Web Log will only display status without any log entries
|
||||
|
|
|
|||
50
src/HAP.cpp
50
src/HAP.cpp
|
|
@ -305,7 +305,43 @@ void HAPClient::processRequest(){
|
|||
}
|
||||
|
||||
if(homeSpan.webLog.isEnabled && !strncmp(body,homeSpan.webLog.statusURL.c_str(),homeSpan.webLog.statusURL.length())){ // GET STATUS - AN OPTIONAL, NON-HAP-R2 FEATURE
|
||||
|
||||
// if TLS server is not configured, business as usual, else redirect to it
|
||||
if (!homeSpan.webLog.tls_server) {
|
||||
getStatusURL();
|
||||
|
||||
} else {
|
||||
char *line = strtok(body, "\n");
|
||||
char *host = NULL;
|
||||
while (line != NULL && host == NULL) {
|
||||
if ((host = strstr(line, "Host:")) != NULL) {
|
||||
host += 5; // Trim "Host:"
|
||||
// Left-trim any white spaces the browser may put between "Host:" and the actual host
|
||||
while(host[0] == ' ') host++;
|
||||
// Trim the port number, if specified, as we have to redirect to a different port
|
||||
for(int x=0; x<strlen(host); x++) {
|
||||
switch(host[x]) {
|
||||
case ':':
|
||||
case '\r':
|
||||
case '\n':
|
||||
host[x] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
line = strtok(NULL, "\n");
|
||||
}
|
||||
|
||||
// Send back an HTTP 301 redirect to the TLS server host (and port, if different than 443)
|
||||
String resp = "HTTP/1.1 301 Moved Permanently\n";
|
||||
resp += "Location: https://" + (host != NULL ? host : client.localIP().toString());
|
||||
resp += homeSpan.webLog.tls_server_conf.port_secure != 443 ? ":" + String(homeSpan.webLog.tls_server_conf.port_secure) : "";
|
||||
resp += String(homeSpan.webLog.statusURL.c_str() + 4) + "\n"; // statusURL contains "GET /status", so trim first 4 chars
|
||||
LOG2("-------- HTTP Redirect -------\n%s\n---------------------\n", resp.c_str());
|
||||
client.print(resp);
|
||||
client.stop();
|
||||
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1189,7 +1225,7 @@ int HAPClient::putPrepareURL(char *json){
|
|||
|
||||
//////////////////////////////////////
|
||||
|
||||
int HAPClient::getStatusURL(){
|
||||
int HAPClient::getStatusURL(httpd_req_t *req){
|
||||
|
||||
char clocktime[33];
|
||||
|
||||
|
|
@ -1210,7 +1246,8 @@ int HAPClient::getStatusURL(){
|
|||
|
||||
sprintf(uptime,"%d:%02d:%02d:%02d",days,hours,mins,secs);
|
||||
|
||||
String response="HTTP/1.1 200 OK\r\nContent-type: text/html; charset=utf-8\r\n\r\n";
|
||||
// if req is set, HTTP response code and content type is set by httpd_resp_* APIs
|
||||
String response=(req?"":"HTTP/1.1 200 OK\r\nContent-type: text/html; charset=utf-8\r\n\r\n");
|
||||
|
||||
response+="<html><head><title>" + String(homeSpan.displayName) + "</title>\n";
|
||||
response+="<style>body {background-color:lightblue;} th, td {padding-right: 10px; padding-left: 10px; border:1px solid black;}" + homeSpan.webLog.css + "</style></head>\n";
|
||||
|
|
@ -1312,7 +1349,11 @@ int HAPClient::getStatusURL(){
|
|||
|
||||
if(response.length()>1024){ // if response grows too big, transmit chunk and reset
|
||||
LOG2(response);
|
||||
if (req) {
|
||||
httpd_resp_send_chunk(req, response.c_str(), HTTPD_RESP_USE_STRLEN);
|
||||
} else {
|
||||
client.print(response);
|
||||
}
|
||||
delay(1); // slight pause seems to be required
|
||||
response.clear();
|
||||
}
|
||||
|
|
@ -1324,7 +1365,12 @@ int HAPClient::getStatusURL(){
|
|||
|
||||
LOG2(response);
|
||||
LOG2("\n");
|
||||
if (req) {
|
||||
httpd_resp_send_chunk(req, response.c_str(), HTTPD_RESP_USE_STRLEN);
|
||||
httpd_resp_send_chunk(req, NULL, 0); // Required to complete the response.
|
||||
} else {
|
||||
client.print(response);
|
||||
}
|
||||
LOG2("------------ SENT! --------------\n");
|
||||
|
||||
delay(1);
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ struct HAPClient {
|
|||
int getCharacteristicsURL(char *urlBuf); // GET /characteristics (HAP Section 6.7.4)
|
||||
int putCharacteristicsURL(char *json); // PUT /characteristics (HAP Section 6.7.2)
|
||||
int putPrepareURL(char *json); // PUT /prepare (HAP Section 6.7.2.4)
|
||||
int getStatusURL(); // GET / status (an optional, non-HAP feature)
|
||||
int getStatusURL(httpd_req_t *req = NULL); // GET / status (an optional, non-HAP feature)
|
||||
|
||||
void tlvRespond(); // respond to client with HTTP OK header and all defined TLV data records (those with length>0)
|
||||
void sendEncrypted(char *body, uint8_t *dataBuf, int dataLen); // send client complete ChaCha20-Poly1305 encrypted HTTP mesage comprising a null-terminated 'body' and 'dataBuf' with 'dataLen' bytes
|
||||
|
|
|
|||
324
src/HomeSpan.cpp
324
src/HomeSpan.cpp
|
|
@ -155,6 +155,18 @@ void Span::begin(Category catID, const char *displayName, const char *hostNameBa
|
|||
esp_ota_mark_app_invalid_rollback_and_reboot();
|
||||
}
|
||||
|
||||
// If a private key has been set we can assume that TLS server is configured. TLS web server should be
|
||||
// started/stopped when Wifi connects / disconnects
|
||||
if (webLog.tls_server_conf.prvtkey_len > 0) {
|
||||
WiFiEventId_t wifi_event_connect = WiFi.onEvent([](WiFiEvent_t event, WiFiEventInfo_t info){
|
||||
homeSpan.startWeblogTLS();
|
||||
}, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_GOT_IP);
|
||||
|
||||
WiFiEventId_t wifi_event_disconnect = WiFi.onEvent([](WiFiEvent_t event, WiFiEventInfo_t info){
|
||||
homeSpan.stopWeblogTLS();
|
||||
}, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_LOST_IP);
|
||||
}
|
||||
|
||||
} // begin
|
||||
|
||||
///////////////////////////////
|
||||
|
|
@ -1308,6 +1320,76 @@ boolean Span::deleteAccessory(uint32_t n){
|
|||
|
||||
///////////////////////////////
|
||||
|
||||
Span& Span::enableTLS(u_int16_t port,
|
||||
const char *ec_private_key_pem,
|
||||
const char *ec_server_cert_pem) {
|
||||
webLog.tls_server_conf = HTTPD_SSL_CONFIG_DEFAULT();
|
||||
webLog.tls_server_conf.port_secure = port;
|
||||
|
||||
if (ec_private_key_pem != NULL && ec_server_cert_pem != NULL) {
|
||||
LOG2("Using custom certificate for TLS");
|
||||
webLog.tls_server_conf.prvtkey_pem = (const uint8_t *) ec_private_key_pem;
|
||||
webLog.tls_server_conf.prvtkey_len = strlen((char *) ec_private_key_pem) + 1;
|
||||
webLog.tls_server_conf.cacert_pem = (const uint8_t *) ec_server_cert_pem;
|
||||
webLog.tls_server_conf.cacert_len = strlen((char *) ec_server_cert_pem) + 1;
|
||||
|
||||
} else {
|
||||
// Since version of mbedTLS can't do SAN, gotta use hostname
|
||||
String subjectDN = "CN=";
|
||||
subjectDN += (homeSpan.hostNameBase && strlen(homeSpan.hostNameBase) > 0 ? homeSpan.hostNameBase : "HomeSpan hostname base not set");
|
||||
if (homeSpan.hostNameSuffix && strlen(homeSpan.hostNameSuffix) > 0) {
|
||||
subjectDN += ".";
|
||||
subjectDN += homeSpan.hostNameSuffix;
|
||||
}
|
||||
unsigned char private_key_pem[256]; // This will have to be increased if using RSA keys or EC keys bigger than P-256
|
||||
unsigned char server_cert_pem[512]; // This will have to be increased if using RSA keys or EC keys bigger than P-256
|
||||
memset(private_key_pem, 0, sizeof(private_key_pem));
|
||||
memset(server_cert_pem, 0, sizeof(server_cert_pem));
|
||||
|
||||
if (webLog.generate_certificate(subjectDN.c_str(), private_key_pem, sizeof(private_key_pem), server_cert_pem, sizeof(server_cert_pem))) {
|
||||
LOG2("New certificate generated, lets use it");
|
||||
|
||||
// TODO: Is this considered a leak if we actually have to keep the private key around
|
||||
webLog.tls_server_conf.prvtkey_pem = (uint8_t *) strdup((char *)private_key_pem);
|
||||
webLog.tls_server_conf.prvtkey_len = strlen((char *)private_key_pem) + 1;
|
||||
|
||||
// TODO: Is this considered a leak if we actually have to keep the certificate around
|
||||
webLog.tls_server_conf.cacert_pem = (uint8_t *) strdup((char *)server_cert_pem);
|
||||
webLog.tls_server_conf.cacert_len = strlen((char *)server_cert_pem) + 1;
|
||||
|
||||
LOG2("key size: %d, cert size: %d\n", webLog.tls_server_conf.prvtkey_len, webLog.tls_server_conf.cacert_len);
|
||||
|
||||
} else {
|
||||
LOG0("*** Error generating new HomeSpan certificate. See serial logs for more details.");
|
||||
}
|
||||
}
|
||||
return(*this);
|
||||
}
|
||||
|
||||
Span& Span::startWeblogTLS(){
|
||||
// If the private key is set, lets assume enableTLS() was called
|
||||
if (webLog.tls_server_conf.prvtkey_len > 0) {
|
||||
webLog.startTLS();
|
||||
} else {
|
||||
WEBLOG("TLS not configured. Call homeSpan.enableTLS() first");
|
||||
}
|
||||
|
||||
return(*this);
|
||||
}
|
||||
|
||||
Span& Span::stopWeblogTLS(){
|
||||
// If the private key is set, lets assume enableTLS() was called
|
||||
if (webLog.tls_server_conf.prvtkey_len > 0) {
|
||||
webLog.stopTLS();
|
||||
} else {
|
||||
WEBLOG("TLS not configured. Call homeSpan.enableTLS() first");
|
||||
}
|
||||
|
||||
return(*this);
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
|
||||
SpanCharacteristic *Span::find(uint32_t aid, int iid){
|
||||
|
||||
int index=-1;
|
||||
|
|
@ -2181,6 +2263,248 @@ void SpanWebLog::initTime(void *args){
|
|||
|
||||
}
|
||||
|
||||
bool SpanWebLog::generate_certificate(const char *subjectDN,
|
||||
unsigned char *private_key, size_t private_key_len,
|
||||
unsigned char *certificate, size_t certificate_len)
|
||||
{
|
||||
int ret = 0;
|
||||
const char *pers = "HomeSpan unique personalization string";
|
||||
|
||||
mbedtls_pk_context key; // Private key creation object
|
||||
mbedtls_x509write_cert crt; // Certificate creation object
|
||||
mbedtls_entropy_context entropy; // Entropy object
|
||||
mbedtls_ctr_drbg_context ctr_drbg; // DRBG, for random number generation
|
||||
struct mbedtls_mpi serial_mpi; // Used to store the serial number of the new certificate
|
||||
|
||||
char strerr[256]; // Used to convert error code to str
|
||||
|
||||
// TODO - This method here needs to extra love.
|
||||
// 1. The entire generate_certificate function will require around 4800 bytes, following
|
||||
// some initial tests using uxTaskGetStackHighWaterMark().
|
||||
// Need to figure out the best way to fail this method if there is not enough stack available, and warn
|
||||
// the user to call 'SET_LOOP_TASK_STACK_SIZE( 16*1024 );' to prevent a stack overflow.
|
||||
// 2. Look at the other TODO below
|
||||
// 2.1 Dynamic dates instead of hardcoded dates when calling mbedtls_x509write_crt_set_validity()
|
||||
// 2.2 Is this considered a leak if we strdup without never freeing, since we have to keep the buffer around forever?
|
||||
// 3. Once the items above have been addressed, tidy up the logs
|
||||
|
||||
WEBLOG("Must find a way to check if stack size has been set to 16k, else this will crash");
|
||||
|
||||
LOG2("About to generate an EC key and certificate - stack high watermark:%d\n", uxTaskGetStackHighWaterMark(NULL));
|
||||
|
||||
mbedtls_mpi_init(&serial_mpi);
|
||||
mbedtls_pk_init(&key);
|
||||
mbedtls_x509write_crt_init(&crt);
|
||||
mbedtls_ctr_drbg_init(&ctr_drbg);
|
||||
memset(strerr, 0, sizeof(strerr));
|
||||
|
||||
// Initializing entropy and DRBG
|
||||
mbedtls_entropy_init(&entropy);
|
||||
if ((ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy,
|
||||
(const unsigned char *) pers,
|
||||
strlen(pers))) != 0) {
|
||||
mbedtls_strerror(ret, strerr, sizeof(strerr));
|
||||
LOG0("Failed to seed DRBG ! mbedtls_ctr_drbg_seed returned -0x%04x - %s\n", (unsigned int) -ret, strerr);
|
||||
goto err;
|
||||
}
|
||||
|
||||
// Private key generation
|
||||
LOG2("Generating SECP256R1 (NIST P-256) private key..\n");
|
||||
if ((ret = mbedtls_pk_setup(&key,
|
||||
mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY))) != 0) {
|
||||
mbedtls_strerror(ret, strerr, sizeof(strerr));
|
||||
LOG0("Failed creating pkey object! mbedtls_pk_setup returned -0x%04x - %s\n", (unsigned int) -ret, strerr);
|
||||
goto err;
|
||||
}
|
||||
if((ret = mbedtls_ecp_gen_key(mbedtls_ecp_curve_info_from_grp_id(MBEDTLS_ECP_DP_SECP256R1)->grp_id,
|
||||
mbedtls_pk_ec(key),
|
||||
mbedtls_ctr_drbg_random, &ctr_drbg)) != 0) {
|
||||
mbedtls_strerror(ret, strerr, sizeof(strerr));
|
||||
LOG0("Failed generating private key! mbedtls_ecp_gen_key returned -0x%04x - %s\n", (unsigned int) -ret, strerr);
|
||||
goto err;
|
||||
}
|
||||
LOG2("Private key generated - stack high watermark:%d\n", uxTaskGetStackHighWaterMark(NULL));
|
||||
|
||||
|
||||
// Convert pkey to PEM
|
||||
// NOTE: This will crash if SET_LOOP_TASK_STACK_SIZE( 16*1024 ) is not set
|
||||
// This method alone will require ~4350 bytes
|
||||
if ((ret = mbedtls_pk_write_key_pem(&key, private_key, private_key_len)) != 0) {
|
||||
mbedtls_strerror(ret, strerr, sizeof(strerr));
|
||||
LOG0("Failed to create PEM-encoded private key! mbedtls_pk_write_key_pem returned -0x%04x - %s\n", (unsigned int) -ret, strerr);
|
||||
goto err;
|
||||
}
|
||||
|
||||
LOG2("Key generated - stack high watermark:%d\n", uxTaskGetStackHighWaterMark(NULL));
|
||||
|
||||
// Now that the private key is generated, lets create a self-signed certificate with it
|
||||
mbedtls_x509write_crt_set_version(&crt, MBEDTLS_X509_CRT_VERSION_3); // v3 required to have extensions
|
||||
mbedtls_x509write_crt_set_md_alg(&crt, MBEDTLS_MD_SHA256); // SHA256 is ok for now
|
||||
|
||||
// For a self-signed, issuer key = subject key
|
||||
mbedtls_x509write_crt_set_subject_key(&crt, &key);
|
||||
mbedtls_x509write_crt_set_issuer_key(&crt, &key);
|
||||
|
||||
// Same goes for the SubjectDN and IssuerDN, both should be the same for a self-signed certificate
|
||||
if ((ret = mbedtls_x509write_crt_set_subject_name(&crt, subjectDN)) != 0) {
|
||||
mbedtls_strerror(ret, strerr, sizeof(strerr));
|
||||
LOG0("Failed to set subjectDN! mbedtls_x509write_crt_set_subject_name returned -0x%04x - %s\n", (unsigned int) -ret, strerr);
|
||||
goto err;
|
||||
}
|
||||
|
||||
// Setting issuer name
|
||||
if ((ret = mbedtls_x509write_crt_set_issuer_name(&crt, subjectDN)) != 0) {
|
||||
mbedtls_strerror(ret, strerr, sizeof(strerr));
|
||||
LOG0("Failed to set IssuerDN! mbedtls_x509write_crt_set_issuer_name returned -0x%04x - %s\n", (unsigned int) -ret, strerr);
|
||||
goto err;
|
||||
}
|
||||
|
||||
// Creating serial number. Hardcoded for now, not a security issue
|
||||
if ((ret = mbedtls_mpi_read_binary(&serial_mpi, (const unsigned char *) "1", 1)) != 0) {
|
||||
mbedtls_strerror(ret, strerr, sizeof(strerr));
|
||||
LOG0("Failed to create serial number! mbedtls_mpi_read_binary returned -0x%04x - %s\n", (unsigned int) -ret, strerr);
|
||||
goto err;
|
||||
}
|
||||
|
||||
// Setting serial number on the certificate object
|
||||
if ((ret = mbedtls_x509write_crt_set_serial(&crt, &serial_mpi)) != 0) {
|
||||
mbedtls_strerror(ret, strerr, sizeof(strerr));
|
||||
LOG0("Failed to set serial number! mbedtls_x509write_crt_set_serial returned -0x%04x - %s\n", (unsigned int) -ret, strerr);
|
||||
goto err;
|
||||
}
|
||||
|
||||
// Setting expiration dates for the certificate. Lets go for 2 years expiration
|
||||
// TODO: change hardcoded dates to something more dynamic like now() + 2 years
|
||||
if ((ret = mbedtls_x509write_crt_set_validity(&crt, "20231201143000", "20251201143000")) != 0) {
|
||||
mbedtls_strerror(ret, strerr, sizeof(strerr));
|
||||
LOG0("Failed to set certificate validity dates! mbedtls_x509write_crt_set_validity returned -0x%04x - %s\n", (unsigned int) -ret, strerr);
|
||||
goto err;
|
||||
}
|
||||
|
||||
// Basic constraints, to tell this is not a CA cert
|
||||
if ((ret = mbedtls_x509write_crt_set_basic_constraints(&crt, false, 0)) != 0) {
|
||||
mbedtls_strerror(ret, strerr, sizeof(strerr));
|
||||
LOG0("Failed to set BasicConstraints! x509write_crt_set_basic_constraints returned -0x%04x - %s\n", (unsigned int) -ret, strerr);
|
||||
goto err;
|
||||
}
|
||||
|
||||
// Key Usage extension should be digitalSignature for TLS server certs
|
||||
if ((ret = mbedtls_x509write_crt_set_key_usage(&crt, MBEDTLS_X509_KU_DIGITAL_SIGNATURE)) != 0) {
|
||||
mbedtls_strerror(ret, strerr, sizeof(strerr));
|
||||
LOG0("Failed setting keyUsage to digitalSignature! mbedtls_x509write_crt_set_key_usage returned -0x%04x - %s\n", (unsigned int) -ret, strerr);
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
mbedTLS 2.28 does not support Extended Key Usage. Once mbedTLS gets upgraded by Arduino-ESP,
|
||||
the code below can be added
|
||||
mbedtls_asn1_sequence *ext_key_usage = (mbedtls_asn1_sequence *) malloc(sizeof(mbedtls_asn1_sequence));
|
||||
ext_key_usage->buf.tag = MBEDTLS_ASN1_OID;
|
||||
SET_OID(ext_key_usage->buf, MBEDTLS_OID_SERVER_AUTH);
|
||||
ret = mbedtls_x509write_crt_set_ext_key_usage(&crt, ext_key_usage);
|
||||
if (ret != 0) {
|
||||
mbedtls_strerror(ret, strerr, sizeof(strerr));
|
||||
WEBLOG(" failed\n ! mbedtls_x509write_crt_set_ext_key_usage returned -0x%04x - %s",
|
||||
(unsigned int) -ret,
|
||||
strerr);
|
||||
goto err;
|
||||
}
|
||||
free(ext_key_usage);
|
||||
*/
|
||||
|
||||
/*
|
||||
Likewise, mbedTLS does not support subject Alternative Names.
|
||||
This means the subjectDN CommonName MUST be the FQDN.
|
||||
|
||||
mbedtls_x509_san_list *san_list = (mbedtls_x509_san_list *) malloc(sizeof(mbedtls_x509_san_list));
|
||||
if (san_list == NULL) {
|
||||
WEBLOG("Not enough memory for subjectAltName list");
|
||||
goto err;
|
||||
}
|
||||
san_list->next = NULL;
|
||||
san_list->node.type = MBEDTLS_X509_SAN_DNS_NAME;
|
||||
san_list->node.san.unstructured_name.p = (unsigned char *) "dsc.local";
|
||||
san_list->node.san.unstructured_name.len = strlen("dsc.local");
|
||||
|
||||
ret = mbedtls_x509write_crt_set_subject_alternative_name(&crt, san_list);
|
||||
free(san_list);
|
||||
|
||||
if (ret != 0) {
|
||||
mbedtls_strerror(ret, strerr, sizeof(strerr));
|
||||
WEBLOG(" failed\n ! mbedtls_x509write_crt_set_subject_alternative_name returned -0x%04x - %s", -ret, strerr);
|
||||
goto err;
|
||||
}
|
||||
*/
|
||||
|
||||
LOG2("Creating PEM-encoded certificate - stack high watermark:%d\n", uxTaskGetStackHighWaterMark(NULL));
|
||||
|
||||
if ((ret = mbedtls_x509write_crt_pem(&crt, certificate, certificate_len, mbedtls_ctr_drbg_random, &ctr_drbg)) < 0) {
|
||||
LOG0("Failed creating certificate! mbedtls_x509write_crt_pem returned -0x%04x - %s\n", (unsigned int) -ret, strerr);
|
||||
goto err;
|
||||
}
|
||||
|
||||
LOG1("New certificate generated:\n%s\nstack high watermark:%d\n", certificate, uxTaskGetStackHighWaterMark(NULL));
|
||||
|
||||
err:
|
||||
|
||||
mbedtls_pk_free(&key);
|
||||
mbedtls_x509write_crt_free(&crt);
|
||||
mbedtls_ctr_drbg_free(&ctr_drbg);
|
||||
mbedtls_entropy_free(&entropy);
|
||||
mbedtls_mpi_free(&serial_mpi);
|
||||
|
||||
return (ret == 0);
|
||||
}
|
||||
|
||||
/* An HTTP GET handler */
|
||||
static esp_err_t weblog_tls_handler(httpd_req_t *req)
|
||||
{
|
||||
httpd_resp_set_type(req, "text/html; charset=utf-8");
|
||||
|
||||
HAPClient client;
|
||||
|
||||
client.getStatusURL(req);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
String statusURI = "";
|
||||
httpd_uri_t weblog_tls_uri = {
|
||||
.uri = "/status",
|
||||
.method = HTTP_GET,
|
||||
.handler = weblog_tls_handler
|
||||
};
|
||||
|
||||
httpd_handle_t SpanWebLog::startTLS(u_int16_t port, const char *ec_private_key_pem, const char *ec_server_cert_pem) {
|
||||
httpd_ssl_config_t localconf = this->tls_server_conf;
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
statusURI = this->statusURL;
|
||||
statusURI.trim();
|
||||
statusURI.replace("GET ", "");
|
||||
|
||||
weblog_tls_uri.uri = statusURI.c_str();
|
||||
LOG1("Starting TLS webserver, serving [%s]\n", weblog_tls_uri.uri);
|
||||
if ((ret = httpd_ssl_start(&this->tls_server, &localconf)) != ESP_OK) {
|
||||
WEBLOG("Error starting server! %s", esp_err_to_name(ret));
|
||||
return NULL;
|
||||
|
||||
} else if ((ret = httpd_register_uri_handler(this->tls_server, &weblog_tls_uri)) != ESP_OK) {
|
||||
WEBLOG("Error registering URI handlers! %s", esp_err_to_name(ret));
|
||||
return NULL;
|
||||
}
|
||||
LOG1("TLS server started\n");
|
||||
return this->tls_server;
|
||||
|
||||
}
|
||||
|
||||
void SpanWebLog::stopTLS() {
|
||||
if (this->tls_server) {
|
||||
httpd_ssl_stop(this->tls_server);
|
||||
LOG1("TLS server stopped\n");
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
|
||||
void SpanWebLog::vLog(boolean sysMsg, const char *fmt, va_list ap){
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@
|
|||
#include <ArduinoOTA.h>
|
||||
#include <esp_now.h>
|
||||
#include <mbedtls/base64.h>
|
||||
#include <esp_https_server.h>
|
||||
|
||||
#include "extras/Blinker.h"
|
||||
#include "extras/Pixel.h"
|
||||
|
|
@ -181,6 +182,8 @@ struct SpanWebLog{ // optional web status/log data
|
|||
String statusURL; // URL of status log
|
||||
uint32_t waitTime=120000; // number of milliseconds to wait for initial connection to time server
|
||||
String css=""; // optional user-defined style sheet for web log
|
||||
httpd_handle_t tls_server = NULL; // Optional TLS web server to serve weblogs
|
||||
httpd_ssl_config_t tls_server_conf; // Optional TLS web server configuration
|
||||
|
||||
struct log_t { // log entry type
|
||||
uint64_t upTime; // number of seconds since booting
|
||||
|
|
@ -192,6 +195,37 @@ struct SpanWebLog{ // optional web status/log data
|
|||
void init(uint16_t maxEntries, const char *serv, const char *tz, const char *url);
|
||||
static void initTime(void *args);
|
||||
void vLog(boolean sysMsg, const char *fmr, va_list ap);
|
||||
|
||||
/**
|
||||
* @brief Force weblogs to be served over TLS. A separate webserver than the one used by HomeSpan will
|
||||
* be launched to serve weblogs.
|
||||
* @param port HTTPS port to use, defaults to 443
|
||||
* @param ec_private_key_pem Webserver private key, PEM-encoded. Use ECDSA keypairs only to keep memory usage low.
|
||||
* @param ec_server_cert_pem Webserver certificate, PEM-encoded. Use ECDSA signature only to keep memory usage low.
|
||||
* @return Pointer to running instance of httpd_handle_t to handle HTTPS requests. NULL on error. Turn on logging for error details.
|
||||
*/
|
||||
httpd_handle_t startTLS(u_int16_t port = 443,
|
||||
const char *ec_private_key_pem = NULL,
|
||||
const char *ec_server_cert_pem = NULL);
|
||||
|
||||
/**
|
||||
* @brief Stops the TLS webserver, if started.
|
||||
*/
|
||||
void stopTLS();
|
||||
|
||||
/**
|
||||
@brief Generates an EC keypair and creates an ECDSA certificate using NIST curve P-256.
|
||||
@param subjectDN Certificate subjectDN, in a valid DN format such as "CN=myhost.local,O=My Company".
|
||||
@param private_key Pointer to an initialized char array to store the new private key, PEM encoded
|
||||
@param private_key_len Size of private_key
|
||||
@param certificate Pointer to an initialized char array to store the new certificate, PEM encoded
|
||||
@param certificate_len Size of certificate
|
||||
@return true on successful generation, false otherwise
|
||||
*/
|
||||
bool generate_certificate(const char *subjectDN,
|
||||
unsigned char *private_key, size_t private_key_len,
|
||||
unsigned char *certificate, size_t certificate_len);
|
||||
|
||||
};
|
||||
|
||||
///////////////////////////////
|
||||
|
|
@ -403,6 +437,34 @@ class Span{
|
|||
|
||||
Span& setTimeServerTimeout(uint32_t tSec){webLog.waitTime=tSec*1000;return(*this);} // sets wait time (in seconds) for optional web log time server to connect
|
||||
|
||||
/**
|
||||
* @brief Enable TLS (HTTPS) for weblogs, instead of unsecure (non-TLS / HTTP) connections.
|
||||
* If weblogs have been enabled for a specific URI, end users will be redirected to the HTTPS port
|
||||
* @param port Port for HTTPS connections. Defaults to 443 (default HTTPS port)
|
||||
* @param ec_private_key_pem ECC private key, in PEM format, to use for HTTPS. NOTE: RSA keys are not supported
|
||||
* given their larger key size for similar security strength. A 256-bit ECC key provides similar
|
||||
* security strength as an RSA 4096-bit key. When NULL, HomeSpan will attempt to generate its own ECC
|
||||
* keypair at EACH REBOOT.
|
||||
* @param ec_server_cert_pem Matching EC certificate for the primate key. NOTE: Keypair validation is not done.
|
||||
* NOTE: When NULL, HomeSpan will attempt to generate its own ECDSA with NIST P-256 certificate at
|
||||
* EACH REBOOT.
|
||||
*/
|
||||
Span& enableTLS(u_int16_t port = 443,
|
||||
const char *ec_private_key_pem = NULL,
|
||||
const char *ec_server_cert_pem = NULL);
|
||||
|
||||
/**
|
||||
* @brief Start a HTTPS server using default configuration. homeSpan.enableTLS() has to have been called first
|
||||
* @return HomeSpan instance
|
||||
*/
|
||||
Span& startWeblogTLS();
|
||||
|
||||
/**
|
||||
* @brief Stops previously started HTTPS server.
|
||||
* @return HomeSpan instance.
|
||||
*/
|
||||
Span& stopWeblogTLS();
|
||||
|
||||
[[deprecated("Please use reserveSocketConnections(n) method instead.")]]
|
||||
void setMaxConnections(uint8_t n){requestedMaxCon=n;} // sets maximum number of simultaneous HAP connections
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue