Thanks to visit codestin.com
Credit goes to github.com

Skip to content

extmod/mbedtls: Support Alternative Functions Implemented in Python. #15905

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

Merged
merged 6 commits into from
Oct 24, 2024

Conversation

iabdalkader
Copy link
Contributor

@iabdalkader iabdalkader commented Sep 24, 2024

Summary

This patch enables the implementation of alternative mbedtls cryptography functions, such as ECDSA sign and verify, in pure Python. Alternative functions are implemented in Python callbacks, that get invoked from
wrapper functions when needed. If the callback fails to sign using the provided key, the calling code falls back to the default mbedtls function. A common use case for this feature is with secure elements that have drivers implemented in Python. Currently, only the ECDSA alternate sign function wrapper is implemented.

Testing

I tested signing using a private EC key stored on a secure element (NXP's SE05x) and with the key stored on the filesystem (DER) both work fine.

Trade-offs and Alternatives

An alternative is adding the drivers/libraries of every secure element we wish to support, make them portable and provide port-specific code, as implemented in my previous PR: #15817

Example usage:

import tls
import se05x

def ecdsa_sign_alt(key, data):
    if key[0:5] != b"magic":
        if log_level_enabled(logging.DEBUG):
            logging.debug("ecdsa_sign_alt falling back to default sign")
        raise RuntimeError("Not supported")

    objid = int.from_bytes(key[-4:], "big")
    if log_level_enabled(logging.DEBUG):
        logging.debug(f"ecdsa_sign_alt oid: 0x{objid:02X}")

    # Sign data on SE using reference key object id.
    sig = se_dev.sign(objid, data)
    if log_level_enabled(logging.DEBUG):
        sig_hex = "".join("%02X" % b for b in sig)
        logging.debug(f"ecdsa_sign_alt sig: {sig_hex}")
    logging.info("signed using secure element")
    return sig

# Create and initialize SE05x device.
se_dev = se05x.SE05X()

# Create SSL context etc...
ctx = tls.SSLContext(ssl.PROTOCOL_TLS_CLIENT)

# Set alternate ECDSA sign function.
ctx.ecdsa_sign_alt = ecdsa_sign_alt

# etc...

Copy link

codecov bot commented Sep 24, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 98.57%. Comparing base (09ea901) to head (4c54335).
Report is 6 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master   #15905   +/-   ##
=======================================
  Coverage   98.57%   98.57%           
=======================================
  Files         164      164           
  Lines       21341    21345    +4     
=======================================
+ Hits        21036    21040    +4     
  Misses        305      305           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link

github-actions bot commented Sep 24, 2024

Code size report:

   bare-arm:    +0 +0.000% 
minimal x86:    +0 +0.000% 
   unix x64:    +0 +0.000% standard
      stm32:    +0 +0.000% PYBV10
     mimxrt:    +0 +0.000% TEENSY40
        rp2:    +0 +0.000% RPI_PICO_W
       samd:    +0 +0.000% ADAFRUIT_ITSYBITSY_M4_EXPRESS
  qemu rv32:    +0 +0.000% VIRT_RV32

@iabdalkader iabdalkader force-pushed the mbedtls_mpy_alt branch 2 times, most recently from 66ab8ff to c15b7c6 Compare September 26, 2024 10:02
@dpgeorge dpgeorge added the extmod Relates to extmod/ directory in source label Sep 26, 2024
@iabdalkader
Copy link
Contributor Author

Anything else needs to change here ?

}

// Convert the MPI private key (d) to a binary array
ret = mbedtls_mpi_write_binary(d, key, klen);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make more sense to pass this argument in to the Python function as a Python (big) integer? After all, that's what it is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure, but I think it's more efficient this way, note the bytearrays are passed by reference.

@dpgeorge
Copy link
Member

I agree that this is more general than #15817.

Did you consider putting the new function on the SSLContext object? Eg SSLContext.ecdsa_sign_callback? That way, each SSLContext could have a different alt implementation, which would be even more flexible. Eg some connections could use the default, and others could use the custom one. (I'm not sure exactly how this would be implemented, because there needs to be a pointer to the correct SSLContext, but it's worth thinking about this possibility.)

@dpgeorge dpgeorge added this to the release-1.24.0 milestone Oct 10, 2024
@iabdalkader
Copy link
Contributor Author

Did you consider putting the new function on the SSLContext object? Eg SSLContext.ecdsa_sign_callback?
I'm not sure exactly how this would be implemented, because there needs to be a pointer to the correct SSLContext, but it's worth thinking about this possibility

Yes I did, that's actually how I wanted to implement this originally, but quickly realized that there's no way to get the SSL object in mbedtls functions. It's not an issue though, because that's what the fallback is for; if another context uses regular keys, it will still function.

FWIW this is how this feature is currently used:

https://github.com/arduino/arduino-iot-cloud-py/blob/new_se_support/src/arduino_iot_cloud/ussl.py#L34

This is not merged, waiting on it to be merged upstream first.

@dpgeorge
Copy link
Member

Yes I did, that's actually how I wanted to implement this originally, but quickly realized that there's no way to get the SSL object in mbedtls functions.

I think we should try to implement it this way, as an attribute per SSLContext. It's not that difficult actually, just have a global (thread local in threaded systems) variable which points to the current SSLContext object and be sure to set that variable on entry to all C functions in modtls_mbedtls.c that will call into mbedTLS code (eg socket_read and socket_write are the main ones). Then when you need to retrieve the signing callback, access this global SSLContext variable.

I think that's a lot more flexible, and would interoperate well with other extensions like DTLS #15764 which may not want to use custom signing.

@iabdalkader
Copy link
Contributor Author

Sure, I'll get around to making all of the requested changes next week. I will retest everything and need to send a PR to micropython-lib.

@dpgeorge
Copy link
Member

I'll get around to making all of the requested changes next week

OK, great, thanks!

I think having the callback on the SSLContext will be great if you can get that working. Here are some suggestions for that:

// in py/mpconfig.h
#define MICROPY_PY_SSL_MBEDTLS_NEED_ACTIVE_CONTEXT (MICROPY_PY_SSL_ECDSA_SIGN_CALLBACK)

// in py/mpstate.h, mp_state_thread_t struct
    #if MICROPY_PY_SSL_MBEDTLS_NEED_ACTIVE_CONTEXT
    struct _mp_obj_ssl_context_t *tls_ssl_context;
    #endif

// in extmod/modtls_mbedtls.c
static inline void store_active_context(mp_obj_ssl_context_t *ssl_context) {
    #if MICROPY_PY_SSL_MBEDTLS_NEED_ACTIVE_CONTEXT
    MP_THREAD_STATE(tls_ssl_context) = ssl_context;
    #endif
}

// at start of functions that call into mbedtls
    store_active_context(ssl_context);

// could also do this at end of functions, but not necessary
    store_active_context(NULL);

@iabdalkader
Copy link
Contributor Author

iabdalkader commented Oct 16, 2024

@dpgeorge Can we add the callback attribute to ssl.py as well ? It's non-standard, but would make it much more convenient to set from code that uses ssl for portability. Alternatively, it could probably be set using something like:

ctx._context.ecdsa_sign_callback = callback

// could also do this at end of functions, but not necessary
store_active_context(NULL);

If the root pointer is not cleared, would this stop the socket from being collected after its closed ?

iabdalkader added a commit to iabdalkader/micropython-lib that referenced this pull request Oct 16, 2024
This callback gets invoked to perform ECDSA signing if enabled.
See micropython/micropython#15905

Signed-off-by: iabdalkader <[email protected]>
@dpgeorge
Copy link
Member

Can we add the callback attribute to ssl.py as well ? It's non-standard, but would make it much more convenient to set from code that uses ssl for portability.

We are trying hard to keep ssl compatible with CPython, so we should not add this callback there.

I'm not sure what you mean by "portability" here? It won't be portable to CPython, and any existing MicroPython code needs to update to firmware that supports this callback, and that firmware will have the new tls module.

@dpgeorge
Copy link
Member

If the root pointer is not cleared, would this stop the socket from being collected after its closed ?

It will stop the SSLContext from being collected, but I think the socket will still be freed because it won't have a reference (I don't think).

@iabdalkader
Copy link
Contributor Author

are trying hard to keep ssl compatible with CPython, so we should not add this callback there.

Ah I sent a PR but will remove it.

I'm not sure what you mean by "portability" here? It won't be portable to CPython, and any existing MicroPython code needs to update to firmware that supports this callback, and that firmware will have the new tls module

I usually just import the ssl module on CPython and MicroPython, not tls. To access this attribute from tls I would have to switch to it or use ctx._context.ecdsa_sign_callback = callback ? The latter is fine.

It will stop the SSLContext from being collected

I've updated the code, it now stores the context before certain functions, read, write, wrap_socket, and clears it on stream close. Will that be enough ?

}

size_t len = 0;
MBEDTLS_ASN1_CHK_ADD(len, mbedtls_asn1_write_mpi(&p, buf, &s));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probably needs to be MBEDTLS_ASN1_CHK_CLEANUP_ADD? Also the ones below.

Also, could this function copy directly into sig, instead of using temporary buf? So start p = sig + sig_size, and buf would be sig. Then it should not underflow this buffer. At the end it can do memmove to shift the data down to the start of sig.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probably needs to be MBEDTLS_ASN1_CHK_CLEANUP_ADD?

Yes this makes sense because in this function there's a cleanup label.

Also, could this function copy directly into sig,

Here I'm just following the reference implementation, see ecdsa_signature_to_asn1. I'm a bit reluctant to change this code. It's not any less efficient than the default implementation and the buffer is stack based so no dynamic memory.

@iabdalkader iabdalkader force-pushed the mbedtls_mpy_alt branch 2 times, most recently from d6d5d3b to 0e7f984 Compare October 18, 2024 07:50
@iabdalkader
Copy link
Contributor Author

I wanted to document this new attribute, but I couldn't find any docs for the new tls module to begin with.

@dpgeorge
Copy link
Member

This will eventually need docs... but we somehow forgot to document the tls module when that was added, and doing that is out of scope for this PR. So docs for this alternative signing callback can be added when tls itself is documented.

Also would be good to add some tests for this new functionality, that can be run with the unix coverage build. That's also very difficult, it requires at least having a loopback socket and creating a dummy TLS connection. For now, testing on hardware is good enough.

This is necessary for mbedTLS callbacks that do not carry any user state,
so those callbacks can be customised per SSL context.

Signed-off-by: iabdalkader <[email protected]>
This commit enables the implementation of alternative mbedTLS cryptography
functions, such as ECDSA sign and verify, in pure Python.  Alternative
functions are implemented in Python callbacks, that get invoked from
wrapper functions when needed.  The callback can return None to fall back
to the default mbedTLS function.

A common use case for this feature is with secure elements that have
drivers implemented in Python.  Currently, only the ECDSA alternate sign
function wrapper is implemented.

Tested signing with a private EC key stored on an NXP SE05x secure element.

Signed-off-by: iabdalkader <[email protected]>
Changes:
- Add ISO7816, APDU and SE05x package.
- Add support for Opta Expansion protocol.

Signed-off-by: iabdalkader <[email protected]>
@dpgeorge dpgeorge merged commit 4c54335 into micropython:master Oct 24, 2024
62 checks passed
@iabdalkader iabdalkader deleted the mbedtls_mpy_alt branch October 24, 2024 14:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
extmod Relates to extmod/ directory in source
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants