SSL/TLS Implementation for ESPAsyncWebServer - #121
Conversation
|
Thanks! We’ll have a look as soon as we can! |
|
Since this is the AsyncTCP repo and not the WebServer one, please make the example use only AsyncTCP. WebServer example should be submitted to the WebServer repo, along with the changes necessary there. Another thing for the example is to add a file called There are slight changes in mbedtls in the upcoming Arduino for ESP32 v4. They come from the updated mbedtls in ESP-IDF v6. Changes here should be compatible with both current Arduino (v3) and the upcoming one (v4). Would be great if the example uses an actual certificate and key that can be used to directly build and test it (by CI and users) |
There was a problem hiding this comment.
Pull request overview
Adds optional mbedTLS-based client/server TLS support to AsyncTCP for ESPAsyncWebServer HTTPS usage.
Changes:
- Adds TLS contexts, handshakes, encrypted I/O, and certificate handling.
- Exposes secure AsyncClient/AsyncServer APIs.
- Adds an HTTPS example and bumps the header version.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 17 comments.
Show a summary per file
| File | Description |
|---|---|
src/AsyncTCPVersion.h |
Updates the reported minor version. |
src/AsyncTCPTLS.h |
Declares the TLS context API. |
src/AsyncTCPTLS.cpp |
Implements mbedTLS over lwIP. |
src/AsyncTCP.h |
Exposes secure client/server interfaces. |
src/AsyncTCP.cpp |
Integrates TLS into connection and I/O flows. |
examples/AsyncWebServerSSL/AsyncWebServerSSL.ino |
Demonstrates an HTTPS web server. |
Suppressed comments (2)
src/AsyncTCP.cpp:1190
- Returning
ERR_ABRTdoes not abort anything here:_connected()is invoked byhandle_async_event()and its return value is discarded atAsyncTCP.cpp:324. This and the two setup-failure returns above can leave a live PCB/failing TLS context after callbacks run. Explicitly callabort()or_close()on every failure path and avoid manually firing the disconnect callback twice.
}
if (_discard_cb) {
_discard_cb(_discard_cb_arg, this);
}
return ERR_ABRT;
src/AsyncTCP.cpp:1279
- A handshake error received through this path only invokes callbacks and then returns
ERR_OK; it never closes or aborts the still-live connection. Clients without a disconnect callback remain stuck and subsequent polls retry the failed TLS state. Match the poll failure path by closing the client (letting_close()issue the disconnect callback).
async_tcp_log_e("SSL handshake failed in _recv: %d", ret);
if (_error_cb) {
_error_cb(_error_cb_arg, this, -60);
}
if (_discard_cb) {
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
mathieucarbou
left a comment
There was a problem hiding this comment.
Thanks a lof for having started this effort!
This is something that a few users were asking for, even if for a webserver on a MCU, support for SSL is quite a niche since any self-signed cert will cause issues on most clients / OS now. So this reduces valid use cases to ssl com with client cert, or use cases where the MCU would be exposed with a cert that is signed from a valid authority (internal company CA or valid internet authority).
So I am sure a lot of people will find it useful.
This reverts commit f5c390f.
There are use cases for https (oauth2, MFA/Passkeys ...etc) work arounds TLS trust can be with domain to local pointers - example https://192.168.4.1.sslip.io |
willmmiles
left a comment
There was a problem hiding this comment.
I'm sorry to say that the changes to the core logic are grossly incorrect, suggesting a poor understanding of LwIP and the purposes of a generic TCP client library. I haven't reviewed the SSL changes in detail yet, because to be frank, this makes me think I'd be wasting my time reviewing AI slop instead of well considered code.
From an architectual standpoint, what is the rationale for integrating the SSL layer deeply in to the AsyncClient class as opposed to constructing a wrapper class? The deep integration adds a lot of complexity and penalizes all clients whether they use the feature or not.
| // Drain callback: ACKs and drops any late data arriving after close is initiated. | ||
| // Without this, LwIP sends RST when data arrives on a PCB with tcp_recv=NULL. | ||
| static err_t _tcp_drain_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) { | ||
| (void)arg; (void)err; | ||
| if (p) { | ||
| tcp_recved(pcb, p->tot_len); | ||
| pbuf_free(p); | ||
| } else { | ||
| // NULL pbuf = remote closed — safe to ignore, close already in progress | ||
| } | ||
| return ERR_OK; | ||
| } | ||
|
|
There was a problem hiding this comment.
NACK on this. Sending RST on unacked data is the correct behaviour for a TCP library. It is not for us to enforce this -- If a client wants to ensure the incoming data is drained before closing, it's the client's responsibility to drain the buffer before closing.
| // Flush pending output before close — gives tcp_close the best chance | ||
| // of succeeding. Without this, queued data forces tcp_close to fail, | ||
| // and the tcp_shutdown fallback sends FIN with unACKed data → RST. | ||
| tcp_output(pcb); |
There was a problem hiding this comment.
This is also not correct or necessary. tcp_output() is called internally by tcp_close() if necessary.
| tcp_abort(pcb); | ||
| // tcp_close fails when unsent data remains (e.g. HTTP response not yet ACKed). | ||
| // Send FIN gracefully instead of RST to avoid NS_ERROR_NET_RESET. | ||
| tcp_shutdown(pcb, 0, 1); |
There was a problem hiding this comment.
No, this is also incorrect. tcp_close behaves correctly when unacked data is present. Please remove this change.
You are right things got a bit messy with this pull request. Copilot got very strict, in the process to greenlight it. Probably right about an "SSL wrapper class" instead of deep intergration. Cancel this pull request, will do more harware testing. For now will keep a fork. |
No description provided.