-
-
Notifications
You must be signed in to change notification settings - Fork 8.2k
extmod/modssl_mbedtls: Add cert verification callback. #12259
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
extmod/modssl_mbedtls: Add cert verification callback. #12259
Conversation
Code size report:
|
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #12259 +/- ##
==========================================
- Coverage 98.36% 98.36% -0.01%
==========================================
Files 159 159
Lines 21093 21069 -24
==========================================
- Hits 20748 20724 -24
Misses 345 345 ☔ View full report in Codecov by Sentry. |
6f58e67
to
73af61e
Compare
28eeb68
to
8bcb476
Compare
This seems like a pretty nice feature to have. But... it's not CPython compatible. We are really trying to keep these modules implementing a subset of CPython behaviour, and this is quite a difference to CPython. Does CPython provide any way to react to an incoming connection with a callback? To break with CPython, the way forward would be to follow the ideas of the So we could define a new module, eg Actually, in the case of TLS support, it's a pretty advanced feature that most users don't want to worry about. Instead a user would see a higher-level interface like Note that all the hard work we have done on the |
Yes, cpython does not have such a feature: python/cpython#75425 (Maybe as a quick sidenote why I want this feature: I want to have a TLS-Client (around mqtt) that verifies the server certificate, but instead of supplying a CA certificate, I want to pin the certificate and verify its hash in the callback. This usecase does not feel so niche to me.) Thinking about the code as it exists a bit more critically in terms of compatibility, the current
I understand why all those differences are there, and don't find them unreasonable, but still they are differences from the cpython behavior. The change from this PR is arguably very compatible (in the sense, that nearly all cpython programs that worked previously will still work with this change), even more than many of the other noted differences, as adding an additional optional KV-parameter and property can be easily ignored. Abstracting the cpython compatible subset-interface would mean:
Replacing the current micropython |
That was my idea when I started porting |
So, I am looking at the tests, cpython, the differences between mbedtls and axtls and starting see more and more cpython/micropython differences. I also noted the linked package in micropython-lib before. So I am starting to wonder how to balance these goals against each other:
Looking a bit at the state of compatibility, here is how I would "sort" features: cpython compatible
micropython specific
micropython legacy compat
As next steps I would suggest the following:
After enough time has passed for the deprecation, i.e. code that uses What do you think of that plan? If it sounds good to you, I'd start implementing these changes. |
7552a80
to
37fc0b9
Compare
I went ahead and implemented what I suggested. Compatibility with the current micropython-api is maintained through a python-implemented module. I've moved get/ciphers to the internal API, as that is pretty new and hopefully not already relied upon too much. |
Thanks @felixdoerre for updating the PR, it looks reasonable although I need to review it in depth.
I think we should stay away from I think As for the |
37fc0b9
to
27baf04
Compare
6b5750b
to
ec9ffff
Compare
@@ -91,24 +92,6 @@ STATIC mp_obj_t ssl_socket_make_new(mp_obj_ssl_context_t *ssl_context, mp_obj_t | |||
/******************************************************************************/ | |||
// Helper functions. | |||
|
|||
STATIC mp_obj_t read_file(mp_obj_t self_in) { |
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.
@felixdoerre why would you delete this? I thought this was better (i.e. less code size/memory) than reading the files in ssl.py
module? Have you compared both implementations?
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.
I didn't until now, because for me the comparison was "have it implement in c" vs "don't have it in c", as the goal is to only have the tls module and eventually make ssl.py
optional.
However I've just now tested it and it seems that the implementation in python is indeed smaller (and handle an error in f.read
correctly, by closing the file):
text data bss dec hex filename
718248 75912 7056 801216 c39c0 build-standard/micropython (as implemented in this PR)
718568 75944 7056 801568 c3b20 build-standard/micropython (with the file read logic moved back to C)
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.
Then I guess it makes sense to move it to ssl.py
, thanks for testing it 👍🏼
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.
Good to know that it's smaller in Python!
@felixdoerre Could you add a test to |
2d0794e
to
b1b548f
Compare
Link to corresponding |
docs/library/ssl.rst
Outdated
@@ -13,7 +13,7 @@ facilities for network sockets, both client-side and server-side. | |||
Functions | |||
--------- | |||
|
|||
.. function:: ssl.wrap_socket(sock, server_side=False, keyfile=None, certfile=None, cert_reqs=CERT_NONE, cadata=None, server_hostname=None, do_handshake=True) | |||
.. function:: ssl.wrap_socket(sock, server_side=False, key=None, cert=None, cert_reqs=CERT_NONE, cadata=None, server_hostname=None, do_handshake=True) |
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.
I will fix this in a separate commit. The docs obviously don't match the code.
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.
Done in ac8e7f7
If possible, can you please swap the order of commits around and do the renaming of |
128168c
to
f9db748
Compare
Thanks for the review. I've incorporated all changes, and changed the order of commits. |
extmod/modtls_mbedtls.c
Outdated
if (o->ssl_context->handler == mp_const_none) { | ||
return 0; | ||
} | ||
return mp_obj_get_int(mp_call_function_2(o->ssl_context->handler, mp_obj_new_bytes(crt->raw.p, crt->raw.len), MP_OBJ_NEW_SMALL_INT(depth))); |
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.
I suggest using mp_obj_new_bytearray_by_ref(ctr->raw.len, crt->raw.p)
(instead of mp_obj_new_bytes
). Using a bytearray by reference will save RAM by not creating a copy of the data.
The downside of using bytearray by reference is that the object (memory it points to) becomes invalid after the callback. But, this is an advanced feature and we can document the behaviour. We need to save memory wherever possible.
Also, is it worth passing through the *flags
argument?? I'm not sure what this does though.
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.
The flags argument is an in-out parameter that allows the inspection and modification of the "default" verification errors: https://github.com/Mbed-TLS/mbedtls/blob/32c28cebb41a25bb4e18d3a530b59c0e5929a324/include/mbedtls/x509.h#L91
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.
So I thought its more hassle and confusion to pass that through, than it would actually be worth.
I'm not sure why the AppVeyor CI is failing. Something to do with |
7dd847a
to
abfdf73
Compare
extmod/modtls_mbedtls.c
Outdated
if (o->handler == mp_const_none) { | ||
return 0; | ||
} | ||
return mp_obj_get_int(mp_call_function_2(o->handler, mp_obj_new_bytes(crt->raw.p, crt->raw.len), MP_OBJ_NEW_SMALL_INT(depth))); |
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.
What about changing the mp_obj_new_bytes()
to mp_obj_new_bytearray_by_ref()
?
abfdf73
to
72125e2
Compare
extmod/modtls_mbedtls.c
Outdated
if (o->handler == mp_const_none) { | ||
return 0; | ||
} | ||
return mp_obj_get_int(mp_call_function_2(o->handler, mp_obj_new_bytearray_by_ref(crt->raw.len, crt->raw.p), MP_OBJ_NEW_SMALL_INT(depth))); |
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.
Actually, thinking about this some more, it's probable even better to use a memoryview
, because then it can be made read-only.
This is how it's done for bluetooth.BLE
, in the IRQ callback handler.
Eg:
mp_obj_array_t cert;
mp_obj_memoryview_init(&cert, 'B', 0, crt->raw.len, crt->raw.len);
return mp_obj_get_int(mp_call_function_2(o->handler, MP_OBJ_NEW_FROM_PTR(&cert), MP_OBJ_NEW_SMALL_INT(depth)));
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.
Thanks for updating with this suggestion. Looks good now!
72125e2
to
951f713
Compare
951f713
to
f23e4b9
Compare
Signed-off-by: Damien George <damien@micropython.org>
Signed-off-by: Damien George <damien@micropython.org>
To match the mbedtls implementation. Signed-off-by: Felix Dörre <felix@dogcraft.de>
The current `ssl` module has quite a few differences to the CPython implementation. This change moves the MicroPython variant to a new `tls` module and provides a wrapper module for `ssl` (in micropython-lib). Users who only rely on implemented comparible behavior can continue to use `ssl`, while users that rely on non-compatible behavior should switch to `tls`. Then we can make the facade in `ssl` more strictly adhere to CPython. Signed-off-by: Felix Dörre <felix@dogcraft.de>
This is a useful alternative to .getpeercert() when the certificate is not stored to reduce RAM usage. Signed-off-by: Felix Dörre <felix@dogcraft.de>
bac56d5
to
aaba1d8
Compare
The Windows CI is now passing (it needed an explicit |
Now merged! Thanks a lot for your efforts on this @felixdoerre . |
the update of mbedtls has activated MBEDTLS_SSL_KEEP_PEER_CERTIFICATE. This results in the old getpeercert function not working anymore. On top, storing the certificate for the whole duration of the connection is wasting RAM.
This PR enables access to the certificate through a callback so it does not have to be stored if it is not needed, an can be inspected and analyzed in python while the connection is established, optionally aborting the connection.
fixes #5835