-
-
Notifications
You must be signed in to change notification settings - Fork 8.2k
extmod/mbedtls: Implement DTLS HelloVerify, update test for MSG_PEEK #17441
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
extmod/mbedtls: Implement DTLS HelloVerify, update test for MSG_PEEK #17441
Conversation
While reviewing this myself I realised the server DTLS cookie support I implemented is far too naive. Have marked as draft until I have time to improve it. |
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #17441 +/- ##
==========================================
- Coverage 98.54% 98.53% -0.01%
==========================================
Files 169 169
Lines 21941 21954 +13
==========================================
+ Hits 21621 21632 +11
- Misses 320 322 +2 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Code size report:
|
1722c92
to
4f55510
Compare
This is a lot harder than I initially thought, because in MicroPython There's two possible ways forward:
Leaning towards (2) at the moment because I think the use case for DTLS client in MicroPython is much stronger than the case for needing a DTLS server, but that would mean removing functionality that was in v1.25 (albeit not fully compliant in that version). @keenanjohnson you implemented the original DTLS support in #15764, do you have any thought about this? |
extmod/modtls_mbedtls.c
Outdated
@@ -669,6 +685,51 @@ static mp_obj_t ssl_socket_make_new(mp_obj_ssl_context_t *ssl_context, mp_obj_t | |||
mbedtls_raise_error(ret); | |||
} | |||
|
|||
#if defined(MBEDTLS_SSL_DTLS_HELLO_VERIFY) && defined(MBEDTLS_SSL_SRV_C) | |||
// DTLS Server session cookie support | |||
static int ssl_cookie_write(void *ctx, unsigned char **p, unsigned char *end, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be simpler and more general to require that these functions be implemented in Python, and the user passes them through when wrapping the socket? Eg:
cookie_dict = {}
def dtls_cookie_handler(ctx, key, value):
if value is None:
# create and return new cookie
cookie = os.urandom(32)
cookie_dict[key] = cookie
return cookie
else:
# check cookie
return cookie_dict.get(key) == value
ctx.wrap_socket(..., dtls_cookie=dtls_cookie_handler)
In regards to your question @projectgus , I personally don't see a lot of use for the DTLS server on an embedded device. However, I do think that having the server implemented in micropython does make the unit testing vastly less brittle. Every time I have tied unit tests to some sort of live public service (like you suggest in option 2), I have personally regretted the choice in the long run as it tends to be brittle, people start to encounter all sorts of spurious local test failiures, etc etc. Given that our main usecase for the server is just unit tests, I feel like moving the cookie calculation to python isn't the worst as long as we caveat the potential for a DDOS in the docs or something in case anyone ever comes along and has a real use case for the server. Thanks for thinking of me and great work on the project as always. |
This is already enabled in the ESP-IDF mbedTLS config, so provide an implementation of the cookie store functions. This allows DTLS connections between two esp32 boards. The session cookie store is a very simple dictionary associated with the SSLContext. To work, the server needs to reuse the same SSLContext (but cookies are never cleaned up, so a server with a high number of clients should recycle the context periodically.) Server code still needs to handle the MBEDTLS_ERR_SSL_HELLO_VERIFY_REQUIRED error by waiting for the next UDP packet from the client. Signed-off-by: Angus Gratton <angus@redyak.com.au>
- DTLS spec recommends HelloVerify and Anti Replay protection be enabled, and these are enabled in the default mbedTLS config. Implement them here. - To help compensate for the possible increase in code size, add a MICROPY_PY_SSL_DTLS build config macro that's enabled for EXTRA and above by default. This allows bare metal mbedTLS ports to use DTLS with HelloVerify support. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton <angus@redyak.com.au>
The original version of this test had to exchange a 1 byte UDP packet before the DTLS handshake. This is no longer needed due to MSG_PEEK support. The test also doesn't work with HelloVerify enabled, as the first connection attempt always fails with an MBEDTLS_ERR_SSL_HELLO_VERIFY_REQUIRED result. Anticipate this by listening for the client twice on the server side. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton <angus@redyak.com.au>
This is an annoying regression caused by including mpconfig.h in 36922df - the mimxrt platform headers define ARRAY_SIZE and mbedtls also defines in some source files, using a different parameter name which is a warning in gcc. Technically mimxrt SDK is to blame here, but as this isn't a named warning in gcc the only way to work around it in the mimxrt port would be to disable all warnings when building this particular mbedTLS source file. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton <angus@redyak.com.au>
4f55510
to
25d8871
Compare
Thanks @dpgeorge and @keenanjohnson for your thoughts: Alternative 1I've pushed a version that uses the mbedTLS ssl_cookie implementation in C. All that the Python code needs to do is supply a client transport identifier (i.e. bytes representing IP & port) when it wraps the socket. Code size cost is less bad than I thought: Alternative 2Alternative to this, along the lines suggested by @dpgeorge above, would be to implement the cookie support in Python - but it probably still need to be a two step process in order to pass through the client's transport id: cookie_dict = {}
def dtls_cookie_handler(ctx, key, value):
encoded_cookie = binary encoding of (timestamp, HMAC(secret, key + timestamp)
if value is None:
return encoded_cookie
else:
# compare encoded_cookie matches `value` and also check the prepended timestamp isn't too old
ctx.set_dtls_cookie_handler(dtls_cookier_handler)
while True:
_, client_addr = s.recvfrom(1, socket.MSG_PEEK)
print("incoming connection")
s.connect(client_addr) # Connect back to the client
# Wrap the UDP socket in server mode.
try:
s = ctx.wrap_socket(s, server_side=1, client_id=repr(client_addr).encode()) Alternative 3There's a slightly hackier version where we pass it all through to wrap_socket() in one go, i.e.: client_id = repr(client_addr).encode()
s = ctx.wrap_socket(s, server_side=1,
cookie_handler= lambda ctx, key, value: dtls_client_handler(ctx, key, value, client_id)) ... but the necessary implementation is hacky as would need to pass the mp_obj_t pointer via the |
Summary
SSLContext
, and is transparent to Python code (provided the sameSSLContext
is reused).MSG_PEEK
receive flag added in extmod/modlwip,esp32,unix: Add support for socket recv flags argument #17312. No longer needs an additional byte to be sent at the start of each connection.TODO
Cookie = HMAC(Secret, Client-IP, Client-Parameters)
).Testing
Trade-offs and Alternatives
This work was funded through GitHub sponsors.